Compare commits

...

128 Commits

Author SHA1 Message Date
sledgehammer999
4cb386af35 Bump to 4.4.1 2022-02-15 17:14:35 +02:00
sledgehammer999
14ab1b015c Update Changelog 2022-02-15 17:12:38 +02:00
sledgehammer999
0a4971c994 Partially revert e93c360db6
QShareDataPointer causes a crash upon start on 32bit Qt5 Windows.
This is a temporary fix in order to release v4.4.1.
2022-02-15 17:08:39 +02:00
sledgehammer999
a75ae21434 Sync translations from Transifex and run lupdate 2022-02-15 17:04:49 +02:00
Vladimir Golovnev (Glassez)
01eed5dae9 Try to recover missing categories 2022-02-15 16:36:33 +02:00
Chocobo1
e73397c750 Remove hack for outdated IE 6 browser
The `mask()` isn't valid in CSS.
2022-02-14 13:37:05 +08:00
sledgehammer999
869d079507 Migrate proxy settings
Q_ENUM_NS(ProxyType) was introduced in 4.4.0.
Before that wrapping QMetaEnum used the int value itself for loading/storing.

PR #16030.
Closes #15994.
2022-02-11 16:09:16 +03:00
Prince Gupta
71174edf72 Optimize completed files handling
PR #16329.

Co-authored-by: Vladimir Golovnev (Glassez) <glassez@yandex.ru>
2022-02-11 16:09:16 +03:00
thalieht
b3d46ecb78 Add Select All/None buttons in new torrent dialog 2022-02-01 08:07:03 +03:00
thalieht
80035a2520 Fix "Free space on disk" in new torrent dialog
Always initialize it.
2022-02-01 08:07:03 +03:00
Chocobo1
6790335239 Fix crash when shutting down and clicked on system tray icon
Disconnect all signals of system tray icon when shutting down.

Closes #16324.
PR #16328.
2022-02-01 12:53:33 +08:00
Vladimir Golovnev
48ff494dca Open correct directory when clicked on Browse button
PR #16252.
2022-01-28 08:27:07 +03:00
Vladimir Golovnev
c5b361ce74 Change torrent moving state when it is cancelled
PR #16267.
2022-01-28 08:27:07 +03:00
thalieht
397b7b9407 Add tooltip to Automatic Torrent Management context menu action
PR #16241
2022-01-27 07:42:23 +03:00
thalieht
6e0c1e2147 Add confirmation for enabling Auto TMM from context menu
PR #16241
2022-01-27 07:42:23 +03:00
Vladimir Golovnev
e93c360db6 Store hybrid torrents using legacy filenames
* Make Digest32 implicitly shared class
* Store hybrid torrents using legacy filenames

PR #16237.
2022-01-25 08:22:35 +03:00
thalieht
270e2023cd Fix wrong closing brace position
Regression from 0086bf8958.
PR #16172.
2022-01-22 08:17:07 +03:00
Vladimir Golovnev
5ac858213b Don't start separate event loop for QFileDialog
It conflicts with QMenu on Qt6 that causes the crash.

PR #16158.
2022-01-22 08:17:07 +03:00
Vladimir Golovnev
f0ee6aba29 Correctly handle received metadata
It did not work correctly, since it assumed that 'lt::torrent_plugin' is created at an earlier stage and is able to track all changes in the torrent state, but in reality it turned out that it was created after the torrent moved to the `downloading_metadata` state, so we had to additionally handle it in the constructor.

PR #16121.
2022-01-17 09:41:21 +03:00
Vladimir Golovnev
fa418087c4 Handle missing torrent alerts
PR #16085.
2022-01-17 09:41:21 +03:00
thalieht
8493e1ad64 Restore all settings to the torrent list's context menu
Set location
Category
Sequential download
Download first/Last pieces first
Automatic Torrent Management

PR #16016.
2022-01-16 12:06:46 +08:00
thalieht
fe90fcef5b Update the torrent's download path field when changing category
In torrent options dialog while in Automatic Management Mode.
PR #16026.
2022-01-16 12:06:46 +08:00
Vladimir Golovnev
210fd80167 Correctly concatenate paths
PR #16086.
2022-01-14 15:17:17 +03:00
Vladimir Golovnev (Glassez)
0a1e864f74 Correctly handle XML parsing errors 2022-01-14 10:20:42 +03:00
Chocobo1
7adccab687 Add Qt6 version to INSTALL file 2022-01-14 14:43:55 +08:00
Chocobo1
67e536d869 Update default value of "Type of service for peers"
Upstream change:
3d701c7380
PR #16036.
2022-01-14 14:43:55 +08:00
Vladimir Golovnev (Glassez)
86e8d848f6 Move torrent immediately when "save path" is changed 2022-01-12 08:39:23 +03:00
Vladimir Golovnev (Glassez)
88114b4588 Don't try to move storage into its current location 2022-01-12 08:39:23 +03:00
Vladimir Golovnev (Glassez)
e468f004f4 Correctly track the root folder name change 2022-01-12 08:39:23 +03:00
Vladimir Golovnev (Glassez)
4cfccc54ea Correctly handle Auto TMM in Torrent Files Watcher 2022-01-12 08:39:23 +03:00
Vladimir Golovnev (Glassez)
5ffa7e4752 Keep "torrent info" alive while generate .torrent file 2022-01-12 08:39:23 +03:00
Chocobo1
d7fd576293 WebAPI: fix wrong key used for categories
Regression from 1c0f8b4289.
Closes #15969.
2022-01-11 11:54:15 +08:00
Chocobo1
83b34053a1 Move new line character out of translation string
PR #15948.
2022-01-08 23:43:32 +08:00
sledgehammer999
b9164adb7a Bump to 4.4.0 2022-01-06 20:41:17 +02:00
sledgehammer999
8397b118b7 Update Changelog 2022-01-06 20:37:49 +02:00
sledgehammer999
74dc000ac1 Sync translations from Transifex and run lupdate 2022-01-06 20:35:45 +02:00
sledgehammer999
9b61991523 Merge pull request #15926 from sledgehammer999/fix_migration
Correct the order of the migrated settings' mappings
2022-01-06 14:46:48 +02:00
sledgehammer999
702c79a92f Don't delete old config keys yet
It will allow users to go back to previous versions without
losing their settings.
2022-01-05 01:01:10 +02:00
sledgehammer999
a27822b557 Correct the order of the migrated settings' mappings 2022-01-04 16:48:17 +02:00
sledgehammer999
bdcb00a3b2 Merge pull request #15923 from sledgehammer999/misc_fixes
Misc fixes before v4.4.0
2022-01-04 16:40:37 +02:00
sledgehammer999
ac5a485651 Disambiguate the data type 2022-01-04 01:39:01 +02:00
sledgehammer999
e8c65388eb Bump copyright year 2022-01-04 01:39:00 +02:00
sledgehammer999
f2cbb61d49 Sync translations from Transifex and run lupdate 2022-01-04 00:39:43 +02:00
sledgehammer999
0a1c61d9d3 Merge pull request #15922 from glassez/fix-paths
Correctly concatenate path components
2022-01-04 00:17:54 +02:00
Chocobo1
01a0fff4c2 Add missing field initial value
Suppresses the following warning:
qBittorrent/src/base/bittorrent/categoryoptions.cpp: In static member function ‘static BitTorrent::CategoryOptions BitTorrent::CategoryOptions::fromJSON(const QJsonObject&)’:
qBittorrent/src/base/bittorrent/categoryoptions.cpp:44:59: warning: missing initializer for member ‘BitTorrent::CategoryOptions::DownloadPathOption::path’ [-Wmissing-field-initializers]
   44 |         options.downloadPath = {downloadPathValue.toBool()};
2022-01-03 23:42:48 +02:00
Chocobo1
bf9516d164 Simplify code
This version saves an `if` conditional.
2022-01-03 23:42:48 +02:00
Vladimir Golovnev (Glassez)
fdbf8cb0ee Correctly concatenate path components 2022-01-03 19:44:47 +03:00
Prince Gupta
7e8a176751 Support folder based UI Themes
Support folder based Themes in UIThemeManager.
Add option to select config.json as them file.

PR #15888.
2022-01-03 09:11:12 +03:00
Vladimir Golovnev
61504ae3b1 Merge pull request #15793 from glassez/save-path
Redesign "Incomplete folder" feature
2022-01-02 22:25:00 +03:00
Chocobo1
dd76525372 Merge pull request #15911 from Chocobo1/pair
Replace Qt functions with std counterparts
2022-01-02 13:34:31 +08:00
Vladimir Golovnev (Glassez)
1c0f8b4289 Redesign "Incomplete folder" feature
Change "Incomplete/temp folder" term with "download folder".
Allow to set "download folder" per torrent (in manual mode) and per category (in automatic mode).
2022-01-01 20:58:49 +03:00
Chocobo1
63043b4927 Replace min, max, clamp functions with std counterparts 2022-01-01 15:22:07 +08:00
Chocobo1
3ea4c66d41 Replace QPair with std::pair 2022-01-01 15:22:07 +08:00
Chocobo1
781d7fbf1a Merge pull request #15884 from Chocobo1/migrate
Migrate "setting key mappings" to upgrade code
2021-12-25 14:05:52 +08:00
Chocobo1
e7ebbffbfd Set appropriate migration version number for new installations 2021-12-24 14:50:06 +08:00
Chocobo1
39f054eef6 Migrate "setting key mappings" to upgrade code 2021-12-24 14:50:06 +08:00
Chocobo1
7a620c794d Fix garbage message when reporting error
PR #15883.
2021-12-24 12:28:22 +08:00
Chocobo1
cc13f3e10d GHA CI: Don't upload built artifacts for dynamic linking builds
As those won't work on testers system unless they install *all* and
same version of the dependent libraries too.

PR #15870.
2021-12-21 12:26:50 +08:00
Vladimir Golovnev (Glassez)
b0e41abf5a Allow to set placeholder for FileSystemPathEdit 2021-12-20 08:56:33 +03:00
Vladimir Golovnev
5347897b7d Merge pull request #15852 from glassez/torrent-info
Improve torrent content handling
2021-12-20 08:54:46 +03:00
Vladimir Golovnev (Glassez)
6f8fae9a7b Apply selected layout to displayed torrent content 2021-12-19 16:16:16 +03:00
Vladimir Golovnev (Glassez)
62b50d1475 Make TorrentInfo immutable 2021-12-19 16:16:16 +03:00
Vladimir Golovnev
2fb0c86f1e Add "Show torrent options" double-click action
PR #15853.
Closes #15837.
2021-12-19 09:01:20 +03:00
Chocobo1
aedd997604 Don't expire connection when there are data in buffer
For writing, this ensures expire handler won't be executed in a small
time window, that is after `m_socket->write()` and before
`QIODevice::bytesWritten()` signal.
For reading, this let the socket to have the chance to process the
received data instead of dropping it.

PR #15849.
2021-12-18 12:28:30 +08:00
Chocobo1
aa3da942cb Use correct URL scheme when https is enabled
Closes #15844.
PR #15847.
2021-12-17 11:49:11 +08:00
Chocobo1
87e1a14a4b Merge pull request #15831 from Chocobo1/server
Improvements for WebAPI server
2021-12-16 13:38:05 +08:00
Chocobo1
00f6bb7c82 Merge pull request #15829 from Chocobo1/trayIcon
Simplify tray icon related code
2021-12-15 12:51:37 +08:00
Chocobo1
cca93c2be2 Show GUI lock icon after system tray icon is initialized 2021-12-14 14:41:17 +08:00
Chocobo1
ad9d0608d4 Avoid needless string-bytes conversion
This saves a few microseconds.
2021-12-14 13:52:34 +08:00
Chocobo1
3c5688c6f6 Reserve enough buffer space according to response content size 2021-12-14 13:52:34 +08:00
Chocobo1
ece92a886a Restart idle timer on sending network response 2021-12-14 13:52:33 +08:00
Chocobo1
85777ea491 Simplify tray icon related code 2021-12-13 15:56:20 +08:00
Chocobo1
b8a84dbd83 Disable system tray icon menu when app is exiting 2021-12-13 15:56:16 +08:00
Chocobo1
35c31906b7 GHA CI: don't let lupdate scan boost library
As it produces superflous warnings.
2021-12-13 15:08:36 +08:00
Chocobo1
1fa940876f Remove redundant UI cleanups
Just exiting the application will handle all of them automatically.
2021-12-13 15:08:25 +08:00
Chocobo1
c652123145 Merge pull request #15811 from Chocobo1/configVersioning
Introduce versioning on main configuration file
2021-12-12 12:53:28 +08:00
Chocobo1
1c52fff1cc Unify value loading paths
The idea is to try load every intermediate value from the base case and
then convert them to their respective type.
2021-12-11 01:45:49 +08:00
Chocobo1
261f08b90e Sort WebUI language selection values 2021-12-11 01:45:49 +08:00
Chocobo1
2d48581570 Move main window setting to its own section 2021-12-11 01:45:49 +08:00
Chocobo1
b8a7ecfe69 Introduce versioning on main configuration file 2021-12-11 01:45:49 +08:00
Chocobo1
cbc2de6b85 Use proper method for checking value existence 2021-12-09 15:57:01 +08:00
xavier2k6
9d2bb67834 GHA CI: Update libtorrent version(s)
PR #15819.
2021-12-09 12:32:52 +08:00
Vladimir Golovnev
3d7ff9765a Make meaning of "torrent root path" consistent
PR #15816.
2021-12-09 06:12:47 +03:00
Chocobo1
28f2def21f Remove redundant layer of QVariant in Preferences class
PR #15812.
2021-12-07 12:17:37 +08:00
Chocobo1
0ee303789a GHA CI: include translation file generation in test
PR #15814.
2021-12-07 12:17:15 +08:00
Chocobo1
6ccc92020c Disable "add peers" menu items instead of hiding it
Menu item in disabled state can show tool tip to help user understand
why it is unavailable.
Related issue: #15785.
PR #15787.
2021-12-06 13:54:38 +08:00
Chocobo1
e3fe66d3ec Store enum type in settings directly
Affected settings will be migrated to new keys so nothing should break.

PR #15800.
2021-12-06 13:53:52 +08:00
OctopusET
ab5605d54b Use proper string for Korean language
PR #15799.
2021-12-01 12:06:05 +08:00
Chocobo1
a7a90613c2 Merge pull request #15796 from Chocobo1/clazy
Fix defects found by clazy
2021-11-30 12:02:33 +08:00
Chocobo1
19d95ebd10 Add comment for qHash implementation requirements
As clazy report false-positive on this.
2021-11-29 01:28:49 +08:00
Chocobo1
0e1849346b Avoid iterating over a temporary variable 2021-11-29 00:31:03 +08:00
Chocobo1
0f34e3bed9 Don't use deprecated Q_ENUMS
See: https://doc.qt.io/qt-5/qobject-obsolete.html#Q_ENUMS
2021-11-29 00:31:03 +08:00
Chocobo1
c8b66b25e8 Avoid potential container detachment
Suppress clazy warning:
warning: Don't call QList::operator[]() on temporary [-Wclazy-detaching-temporary]
2021-11-29 00:31:03 +08:00
Chocobo1
e6f07a6fe4 Use implicit copy-constructor generated by compiler
This also suppresses the following clang warning:
warning: definition of implicit copy assignment operator for 'Version<unsigned short, 2>' is deprecated because it has a user-declared copy constructor [-Wdeprecated-copy]
2021-11-29 00:30:17 +08:00
Chocobo1
51469f8fa2 Store Qt6 table header states under a different key
Follow up 22abbc1d41.
PR #15774.
2021-11-23 11:02:07 +08:00
Chocobo1
d78b2a569f Fix handling when Content-Length field is absent
Closes #15754.
PR #15757.
2021-11-21 11:48:49 +08:00
Chocobo1
ec6c970775 Merge pull request #15762 from Chocobo1/artifact
GHA CI: Use prebuilt Qt library
2021-11-21 11:47:50 +08:00
Chocobo1
67c45efff7 GHA CI: Use prebuilt Qt library 2021-11-20 14:33:12 +08:00
Chocobo1
a54772bf35 Appveyor CI: Upload built artifacts 2021-11-20 14:33:12 +08:00
Chocobo1
166be2a94d Merge pull request #15749 from Chocobo1/ci
GHA CI: Simplify commands
2021-11-19 12:31:16 +08:00
Chocobo1
7150d05399 GHA CI: Simplify commands 2021-11-18 00:20:58 +08:00
Chocobo1
36a6e22f27 Appveyor CI: Setup build environment directly
vcvars64.bat is just a link to vcvarsall.bat with parameter `x64`.
2021-11-18 00:20:26 +08:00
Chocobo1
dc13eaed1f Revert "Use percentage notation for alpha-values in CSS"
This reverts commit 864dca1b67.
Upstream change: https://github.com/stylelint/stylelint-config-standard/pull/212
PR #15745.
2021-11-18 00:19:27 +08:00
xavier2k6
001bd60d36 CI: Update AppVeyor image to Visual Studio 2022
PR #15727.
2021-11-16 19:02:07 +03:00
Chocobo1
b063042988 Apply download priority immediately in torrent content view
Apply the new priority after picking it via drop-down menu.

Fixes #14667, #15238.
PR #15739.

Co-authored-by: a-sum-duma <68896601+a-sum-duma@users.noreply.github.com>
Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2021-11-16 13:50:53 +08:00
a-sum-duma
fa1d49add5 Properly check if file priority changes
Current item priority is compared against new checkbox state. I believe the intention was to check if the priority changes before performing further actions. This PR fixes the issue - compare priority against new value that is about to be set rather then the checkbox state.

PR #15740.
2021-11-16 13:50:11 +08:00
Chocobo1
b45248bf99 Merge pull request #15452 from thalieht/autoTMM
Move some options from transfer list's context menu into "Torrent options" dialog
2021-11-10 12:39:59 +08:00
Chocobo1
dfe862dcd5 Merge pull request #15717 from Chocobo1/irc
WebUI: Remove IRC in about page
2021-11-10 12:28:53 +08:00
sledgehammer999
d4ddeaa917 Sync Changelog entries between branches 2021-11-09 19:07:23 +02:00
Chocobo1
13a49866a7 WebUI: Revise about page
Follow GUI more closely.
2021-11-09 13:17:55 +08:00
Chocobo1
7e2aea92b0 WebUI: Remove IRC in about page
This follows the GUI change in 65a30bab3f.
2021-11-09 13:17:41 +08:00
Chocobo1
7db51b2f8d Add IRC link
https://en.wikipedia.org/wiki/Internet_Relay_Chat#URI_scheme

And also show the angle brackets in rendered markdown.
2021-11-09 12:12:49 +08:00
Chocobo1
ae1b963e0f Merge pull request #15682 from Chocobo1/qt6
Store Qt6 table header states under a different key
2021-11-09 11:23:04 +08:00
a-sum-duma
b29b7e0185 Add more download options to torrent search result right-click menu
PR #15654.
2021-11-09 06:11:47 +03:00
Chocobo1
71270260bf Reformat code 2021-11-08 13:34:21 +08:00
Chocobo1
22abbc1d41 Store Qt6 table header states under a different key 2021-11-08 13:34:21 +08:00
Chocobo1
32698fe0be Migrate away from low-level SettingsStorage class
Also add `QFlags<T>` support to `SettingsStorage`.
PR #15693.
2021-11-08 13:23:33 +08:00
thalieht
16f8d6a936 Allow deselecting radio buttons in "Torrent options" for mixed torrents 2021-11-06 12:47:53 +02:00
thalieht
046d6f3bc1 Move a few torrent context menu actions into "Torrent options" dialog
Automatic torrent management
Save path
Category
Download in sequential order
Download first and last pieces first

closes #15447, closes #14064
2021-11-06 12:47:53 +02:00
Chocobo1
e33c4086b9 GHA CI: Revise artifact folder layout
Now qbittorrent binaries will be placed in its own folder and cmake
related artifacts will be in another.

PR #15683.
2021-11-06 11:11:47 +08:00
Andrei Stepanov
51d754a53e Optimize PNG images losslessly with FileOptimizer
PR #15662.
2021-11-05 12:59:25 +08:00
Chocobo1
49976bcd83 Merge pull request #15648 from Chocobo1/lockfile
Create lock file in config folder instead of temp folder
2021-11-03 12:15:22 +08:00
Losiki
f991d2bdb4 Update Update Simplified Chinese translation
PR #15653.
2021-11-02 17:54:18 +03:00
xavier2k6
e6ff23885e Sync flag icons with upstream
Upstream commit e0577caf317aa721b62c5a4788b13572cc163252 (Release v4.1.4)
PR #15657.
2021-11-02 11:10:50 +08:00
Chocobo1
7aa859a442 Don't use deprecated statfs64() on macOS (#15661)
Co-authored-by: Nick Korotysh <kolchaprogrammer@list.ru>
Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2021-11-02 10:41:09 +08:00
Chocobo1
180deb867a Use char array directly
This eliminates the possibility of reassigning the pointer to another
address.
2021-11-01 14:08:49 +08:00
Chocobo1
a5c531f0a4 Create lock file in config folder instead of temp folder
Some linux distros seem to alter TMPDIR environment variable and
therefore hamper qbt ability to find the lock files. So use config
folder instead of TMPDIR folder to create/locate the lock files.
Note that this change will also make qbt become one instance per-user
instead of one instance per-system.

Closes #15646.
2021-11-01 14:08:49 +08:00
a-sum-duma
5dd70b88d3 Fix torrent content sorting
Fix improper sorting of the list of files contained by a torrent.
Always load all torrent content data so that the files list can be sorted properly.
Load torrent content only when needed. Don't load the list of files contained by a torrent if the list widget is not visible.

PR #15604.
2021-11-01 11:45:48 +08:00
552 changed files with 124107 additions and 86594 deletions

View File

@@ -3,7 +3,7 @@ version: '{branch}-{build}'
# Do not build on tags (GitHub only)
skip_tags: true
image: Visual Studio 2019
image: Visual Studio 2022
branches:
except: # blacklist
@@ -42,7 +42,7 @@ install:
before_build:
# setup env
- CALL "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"
- CALL "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
- SET PATH=%PATH%;C:\Qt\5.15.2\msvc2019_64\bin;%CACHE_DIR%\jom
# setup project
- COPY /Y "%CACHE_DIR%\conf.pri" "%REPO_DIR%"
@@ -56,4 +56,38 @@ build_script:
- qmake qbittorrent.pro && cd src && qmake src.pro
- jom -j2 -f Makefile.Release
after_build:
- cd "%REPO_DIR%"
- MKDIR upload
- COPY dist\windows\qt.conf upload
- COPY src\release\qbittorrent.exe upload
- COPY src\release\qbittorrent.pdb upload
- COPY "%CACHE_DIR%\base\bin\libcrypto-1_1-x64.dll" upload
- COPY "%CACHE_DIR%\base\bin\libssl-1_1-x64.dll" upload
- COPY "%CACHE_DIR%\base\lib\torrent-rasterbar.dll" upload
- COPY "%CACHE_DIR%\base\lib\zlib1.dll" upload
- COPY C:\Qt\5.15.2\msvc2019_64\bin\Qt5Core.dll upload
- COPY C:\Qt\5.15.2\msvc2019_64\bin\Qt5Gui.dll upload
- COPY C:\Qt\5.15.2\msvc2019_64\bin\Qt5Network.dll upload
- COPY C:\Qt\5.15.2\msvc2019_64\bin\Qt5Sql.dll upload
- COPY C:\Qt\5.15.2\msvc2019_64\bin\Qt5Svg.dll upload
- COPY C:\Qt\5.15.2\msvc2019_64\bin\Qt5Widgets.dll upload
- COPY C:\Qt\5.15.2\msvc2019_64\bin\Qt5WinExtras.dll upload
- COPY C:\Qt\5.15.2\msvc2019_64\bin\Qt5Xml.dll upload
- MKDIR upload\plugins\iconengines
- COPY C:\Qt\5.15.2\msvc2019_64\plugins\iconengines\qsvgicon.dll upload\plugins\iconengines
- MKDIR upload\plugins\imageformats
- COPY C:\Qt\5.15.2\msvc2019_64\plugins\imageformats\qico.dll upload\plugins\imageformats
- COPY C:\Qt\5.15.2\msvc2019_64\plugins\imageformats\qsvg.dll upload\plugins\imageformats
- MKDIR upload\plugins\platforms
- COPY C:\Qt\5.15.2\msvc2019_64\plugins\platforms\qwindows.dll upload\plugins\platforms
- MKDIR upload\plugins\sqldrivers
- COPY C:\Qt\5.15.2\msvc2019_64\plugins\sqldrivers\qsqlite.dll upload\plugins\sqldrivers
- MKDIR upload\plugins\styles
- COPY C:\Qt\5.15.2\msvc2019_64\plugins\styles\qwindowsvistastyle.dll upload\plugins\styles
test: off
artifacts:
- path: upload
name: qBittorrent-Appveyor_Windows-x64

View File

@@ -9,11 +9,11 @@ jobs:
strategy:
fail-fast: false
matrix:
libt_version: ["2.0.4", "1.2.14"]
libt_version: ["2.0.5", "1.2.15"]
qbt_gui: ["GUI=ON", "GUI=OFF"]
qt_version: ["5.15.2", "6.2.0"]
exclude:
- libt_version: "1.2.14"
- libt_version: "1.2.15"
qt_version: "6.2.0"
env:
@@ -42,14 +42,18 @@ jobs:
- name: Install libtorrent
run: |
git clone --branch v${{ matrix.libt_version }} --depth 1 https://github.com/arvidn/libtorrent.git
git clone \
--branch v${{ matrix.libt_version }} \
--depth 1 \
--recurse-submodules \
https://github.com/arvidn/libtorrent.git
cd libtorrent
git submodule update --init --recursive
cmake \
-B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_STANDARD=17 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-Ddeprecated-functions=OFF \
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}"
cmake --build build
@@ -58,12 +62,13 @@ jobs:
- name: Build qBittorrent (Qt5)
if: ${{ startsWith(matrix.qt_version, 5) }}
run: |
lupdate -extensions c,cpp,h,hpp,ui ./
cmake \
-B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}" \
-DQt5_DIR="$Qt5_DIR" \
-DVERBOSE_CONFIGURE=ON \
-D${{ matrix.qbt_gui }}
cmake --build build
@@ -71,21 +76,29 @@ jobs:
- name: Build qBittorrent (Qt6)
if: ${{ startsWith(matrix.qt_version, 6) }}
run: |
lupdate -extensions c,cpp,h,hpp,ui ./
cmake \
-B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}" \
-DQT6=ON \
-DQt6_DIR="$Qt6_DIR" \
-DVERBOSE_CONFIGURE=ON \
-D${{ matrix.qbt_gui }}
cmake --build build
- name: Prepare build artifacts
run: |
mkdir upload
mv build/qbittorrent*.app upload
mkdir upload/cmake
cp build/compile_commands.json upload/cmake
mkdir upload/cmake/libtorrent
cp libtorrent/build/compile_commands.json upload/cmake/libtorrent
- name: Upload build artifacts
uses: actions/upload-artifact@v2
with:
name: qBittorrent-CI_macOS_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
path: |
build/qbittorrent.app
build/qbittorrent-nox.app
path: upload

View File

@@ -9,11 +9,11 @@ jobs:
strategy:
fail-fast: false
matrix:
libt_version: ["2.0.4", "1.2.14"]
libt_version: ["2.0.5", "1.2.15"]
qbt_gui: ["GUI=ON", "GUI=OFF"]
qt_version: ["5.15.2", "6.2.0"]
exclude:
- libt_version: "1.2.14"
- libt_version: "1.2.15"
qt_version: "6.2.0"
steps:
@@ -41,59 +41,62 @@ jobs:
- name: Install libtorrent
run: |
git clone --branch v${{ matrix.libt_version }} --depth 1 https://github.com/arvidn/libtorrent.git
git clone \
--branch v${{ matrix.libt_version }} \
--depth 1 \
--recurse-submodules \
https://github.com/arvidn/libtorrent.git
cd libtorrent
git submodule update --init --recursive
cmake \
-B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-Ddeprecated-functions=OFF \
--graphviz=cmake-build-dir/target_graph.dot
-Ddeprecated-functions=OFF
cmake --build build
sudo cmake --install build
- name: Build qBittorrent (Qt5)
if: ${{ startsWith(matrix.qt_version, 5) }}
run: |
lupdate -extensions c,cpp,h,hpp,ui ./
cmake \
-B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DQt5_DIR="$Qt5_DIR" \
-D${{ matrix.qbt_gui }} \
-DCMAKE_INSTALL_PREFIX="/usr" \
-DVERBOSE_CONFIGURE=ON \
--graphviz=build/target_graph.dot
-D${{ matrix.qbt_gui }}
cmake --build build
sudo cmake --install build
DESTDIR="qbittorrent" cmake --install build
- name: Build qBittorrent (Qt6)
if: ${{ startsWith(matrix.qt_version, 6) }}
run: |
lupdate -extensions c,cpp,h,hpp,ui ./
cmake \
-B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DQt6_DIR="$Qt6_DIR" \
-DCMAKE_INSTALL_PREFIX="/usr" \
-DQT6=ON \
-D${{ matrix.qbt_gui }} \
-DVERBOSE_CONFIGURE=ON \
--graphviz=build/target_graph.dot
-D${{ matrix.qbt_gui }}
cmake --build build
sudo cmake --install build
DESTDIR="qbittorrent" cmake --install build
- name: Prepare build artifacts
run: |
mkdir upload
mkdir upload/cmake
cp build/compile_commands.json upload/cmake
mkdir upload/cmake/libtorrent
cp libtorrent/build/compile_commands.json upload/cmake/libtorrent
- name: Upload build artifacts
uses: actions/upload-artifact@v2
with:
name: qBittorrent-CI_ubuntu-20.04-x64_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
path: |
build/compile_commands.json
build/install_manifest.txt
build/target_graph.dot
build/qbittorrent
build/qbittorrent-nox
libtorrent/cmake-build-dir/compile_commands.json
libtorrent/cmake-build-dir/target_graph.dot
name: build-info_ubuntu-20.04-x64_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
path: upload

View File

@@ -7,12 +7,12 @@ jobs:
runs-on: windows-latest
strategy:
matrix:
libt_version: ["v2.0.4", "v1.2.14"]
fail-fast: false
matrix:
libt_version: ["2.0.5", "1.2.15"]
env:
boost_path: "${{ github.workspace }}/boost"
boost_path: "${{ github.workspace }}/../boost"
libtorrent_path: "${{ github.workspace }}/libtorrent"
steps:
@@ -35,9 +35,9 @@ jobs:
doNotUpdateVcpkg: true # the preinstalled vcpkg is updated regularly
setupOnly: true
# tell vcpkg to only build Release variants of the dependencies
- name: Configure vcpkg triplet overlay
- name: Install dependencies from vcpkg
run: |
# tell vcpkg to only build Release variants of the dependencies
New-Item `
-Path "${{ github.workspace }}" `
-Name "triplets_overlay" `
@@ -48,16 +48,9 @@ jobs:
Add-Content `
"${{ github.workspace }}/triplets_overlay/x64-windows-static-release.cmake" `
-Value "set(VCPKG_BUILD_TYPE release)"
# clear buildtrees after each package installation to reduce disk space requirements
- name: Install dependencies
run: |
# clear buildtrees after each package installation to reduce disk space requirements
$packages = `
"openssl:x64-windows-static-release",
"qt5-base:x64-windows-static-release",
"qt5-svg:x64-windows-static-release",
"qt5-tools:x64-windows-static-release",
"qt5-winextras:x64-windows-static-release",
"zlib:x64-windows-static-release"
${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg.exe upgrade `
--overlay-triplets="${{ github.workspace }}/triplets_overlay" `
@@ -76,15 +69,24 @@ jobs:
7z x "${{ runner.temp }}/boost.7z" -o"${{ github.workspace }}/.."
move "${{ github.workspace }}/../boost_*" "${{ env.boost_path }}"
- name: Install Qt
uses: jurplel/install-qt-action@v2
with:
version: "5.15.2"
- name: Install libtorrent
run: |
git clone --branch ${{ matrix.libt_version }} --depth 1 https://github.com/arvidn/libtorrent.git
git clone `
--branch v${{ matrix.libt_version }} `
--depth 1 `
--recurse-submodules `
https://github.com/arvidn/libtorrent.git
cd libtorrent
git submodule update --init --recursive
cmake `
-B build `
-G "Ninja" `
-DCMAKE_BUILD_TYPE=RelWithDebInfo `
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON `
-DCMAKE_INSTALL_PREFIX="${{ env.libtorrent_path }}" `
-DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" `
-DBOOST_ROOT="${{ env.boost_path }}" `
@@ -97,6 +99,7 @@ jobs:
- name: Build qBittorrent
run: |
lupdate -extensions c,cpp,h,hpp,ui .
cmake `
-B build `
-G "Ninja" `
@@ -117,9 +120,32 @@ jobs:
copy build/qbittorrent.exe upload
copy build/qbittorrent.pdb upload
copy dist/windows/qt.conf upload
# runtimes
copy "${{ env.Qt5_DIR }}/bin/Qt5Core.dll" upload
copy "${{ env.Qt5_DIR }}/bin/Qt5Gui.dll" upload
copy "${{ env.Qt5_DIR }}/bin/Qt5Network.dll" upload
copy "${{ env.Qt5_DIR }}/bin/Qt5Sql.dll" upload
copy "${{ env.Qt5_DIR }}/bin/Qt5Svg.dll" upload
copy "${{ env.Qt5_DIR }}/bin/Qt5Widgets.dll" upload
copy "${{ env.Qt5_DIR }}/bin/Qt5WinExtras.dll" upload
copy "${{ env.Qt5_DIR }}/bin/Qt5Xml.dll" upload
mkdir upload/plugins/iconengines
copy "${{ env.Qt5_DIR }}/plugins/iconengines/qsvgicon.dll" upload/plugins/iconengines
mkdir upload/plugins/imageformats
copy "${{ env.Qt5_DIR }}/plugins/imageformats/qico.dll" upload/plugins/imageformats
copy "${{ env.Qt5_DIR }}/plugins/imageformats/qsvg.dll" upload/plugins/imageformats
mkdir upload/plugins/platforms
copy "${{ env.Qt5_DIR }}/plugins/platforms/qwindows.dll" upload/plugins/platforms
mkdir upload/plugins/sqldrivers
copy "${{ env.Qt5_DIR }}/plugins/sqldrivers/qsqlite.dll" upload/plugins/sqldrivers
mkdir upload/plugins/styles
copy "${{ env.Qt5_DIR }}/plugins/styles/qwindowsvistastyle.dll" upload/plugins/styles
# cmake additionals
mkdir upload/cmake
copy build/compile_commands.json upload/cmake
copy build/target_graph.dot upload/cmake
mkdir upload/cmake/libtorrent
copy libtorrent/build/compile_commands.json upload/cmake/libtorrent
- name: Upload build artifacts
uses: actions/upload-artifact@v2

View File

@@ -15,21 +15,28 @@ jobs:
- name: Install dependencies
run: |
sudo add-apt-repository ppa:beineri/opt-qt-5.15.2-focal
sudo apt update
sudo apt install \
build-essential cmake ninja-build pkg-config \
libboost-dev libssl-dev qt515base qt515svg qt515tools zlib1g-dev
libboost-dev libssl-dev zlib1g-dev
- name: Install Qt
uses: jurplel/install-qt-action@v2
with:
version: "5.15.2"
- name: Install libtorrent
run: |
git clone --branch v2.0.4 --depth 1 https://github.com/arvidn/libtorrent.git
git clone \
--branch "v2.0.5" \
--depth 1 \
--recurse-submodules \
https://github.com/arvidn/libtorrent.git
cd libtorrent
git submodule update --init --recursive
cmake \
-B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-Ddeprecated-functions=OFF
cmake --build build
sudo cmake --install build
@@ -49,7 +56,7 @@ jobs:
cmake \
-B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DGUI=ON \
-DVERBOSE_CONFIGURE=ON
export PATH="$(pwd)/coverity_tool/bin:$PATH"

365
Changelog
View File

@@ -1,6 +1,246 @@
Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.4.0
- FEATURE: Add support for creating v2 torrents(requires libtorrent 2.0.x) (Chocobo1)
Tue Feb 15 2022 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.4.1
- FEATURE: Restore all torrent settings to the torrent's main context menu (thalieht)
- FEATURE: Add confirmation for enabling Auto TMM from context menu (thalieht)
- FEATURE: Add tooltip to Automatic Torrent Management context menu action (thalieht)
- FEATURE: Add Select All/None buttons in new torrent dialog (thalieht)
- BUGFIX: Keep "torrent info" alive while generate .torrent file (glassez)
- BUGFIX: Correctly handle Auto TMM in Torrent Files Watcher (glassez)
- BUGFIX: Correctly track the root folder name change (glassez)
- BUGFIX: Various fixes to the moving torrent code (glassez)
- BUGFIX: Update the torrent's download path field when changing category (thalieht)
- BUGFIX: Correctly handle received metadata (glassez)
- BUGFIX: Store hybrid torrents using legacy filenames (glassez)
- BUGFIX: Open correct directory when clicked on Browse button (glassez)
- BUGFIX: Fix crash when shutting down and clicing on system tray icon (Chocobo1)
- BUGFIX: Fix "Free space on disk" in new torrent dialog (thalieht)
- BUGFIX: Optimize completed files handling (Prince Gupta)
- BUGFIX: Migrate proxy settings (sledgehammer999)
- BUGFIX: Try to recover missing categories (glassez)
- WEBUI: WebAPI: fix wrong key used for categories (Chocobo1)
- WEBUI: Remove hack for outdated IE 6 browser (Chocobo1)
- RSS: Correctly handle XML parsing errors (glassez)
Thu Jan 06 2022 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.4.0
- FEATURE: Support for v2 torrents along with libtorrent 2.0.x support (glassez, Chocobo1)
- FEATURE: Support for Qt6 (glassez)
- FEATURE: Expose libtorrent hashing_threads settings (Anton Bershanskiy)
- FEATURE: Add "Notification timeout" option (kevtechxx)
- FEATURE: Add `connection_speed` to advanced settings (Chocobo1)
- FEATURE: Announce to all trackers if IP changed (#15001) (zhuangzi926)
- FEATURE: Add tooltip for various columns (Chocobo1)
- FEATURE: Add context menu to toggle content tab columns (#15164) (AbeniMatteo)
- FEATURE: Add filter "Checking" to side panel (#15166) (AbeniMatteo)
- FEATURE: Add "Forced metadata downloading" state (#15185) (AbeniMatteo)
- FEATURE: Remember last viewed page in Options dialog (#15230) (Chocobo1)
- FEATURE: Add tooltip to listening port spinbox (Chocobo1)
- FEATURE: Add "Skip hash check" option for watched folders (glassez)
- FEATURE: Add "Show torrent options" double-click action (glassez)
- FEATURE: Allow setting temp folder per torrent/catergory (glassez)
- FEATURE: Support folder based UI Themes (Prince Gupta)
- BUGFIX: Save "resume data" once file priority is changed (glassez)
- BUGFIX: Show priority menu at top level if there is no other in Add New Torrent dialog (FozzeY)
- BUGFIX: Capitalize "peer flags" descriptions (Chocobo1)
- BUGFIX: Reorder peer flags (Chocobo1)
- BUGFIX: Show "last activity" value under all circumstances (Chocobo1)
- BUGFIX: Elide text from the right for all columns' header (smigii)
- BUGFIX: Fix startup with different profiles (jagannatharjun)
- BUGFIX: Move a few torrent context menu actions into "Torrent options" dialog (thalieht)
- BUGFIX: Allow deselecting radio buttons in "Torrent options" for mixed torrents (thalieht)
- BUGFIX: Apply file priority changes correctly (a-sum-duma, Chocobo1)
- BUGFIX: Use proper string for Korean language (OctopusET)
- BUGFIX: Disable "add peers" menu items instead of hiding it (Chocobo1)
- BUGFIX: Disable system tray icon menu when app is exiting (Chocobo1)
- BUGFIX: Show GUI lock icon after system tray icon is initialized (Chocobo1)
- BUGFIX: Apply selected layout to displayed torrent content in "Add New Torrent" dialog (glassez)
- WEBUI: Add reverse proxy source IP resolution (#15047) (HiFiPhile)
- WEBUI: Support navigating UI tables with arrow keys (Thomas Piccirello)
- WEBUI: Support expanding/collapsing UI folders with arrow keys (Thomas Piccirello)
- WEBUI: Support sorting UI tables via touch (#15205) (Tom Piccirello)
- WEBUI: Add pieces progress bar to General tab (Jesse Smick)
- WEBUI: Update authors page (Chocobo1)
- WEBUI: Set icon sizes attribute (Daniel Aleksandersen)
- WEBUI: Add meta application name (Daniel Aleksandersen)
- WEBUI: Sort WebUI language selection values (Chocobo1)
- WEBUI: Use correct URL scheme in user prompt when HTTPS is enabled (Chocobo1)
- RSS: Stick Unread row to top in RSS feed list (Prince Gupta)
- RSS: Correctly use fallback icons for RSS feed in GUI (jagannatharjun)
- SEARCH: Add context menu for tabs in search widget (#14926) (Anton)
- SEARCH: Add more download options to torrent search result right-click menu (a-sum-duma)
- WINDOWS: Add windows-clang support (#15115) (Biswapriyo Nath)
- WINDOWS: Update python installer URL for Windows (xavier2k6)
- WINDOWS: NSIS: Update Simplified Chinese translation (Losiki)
- LINUX: Prolong wait time for shutdown for qbittorrent-nox (Chocobo1)
- LINUX: Install vector program icon (Chocobo1)
- LINUX: Add detection for OpenBSD, Haiku in configure script (Chocobo1)
- MACOS: Update Mac icons for Big Sur (17jiangz1)
- EXPERIMENTAL: Setting to store/load fastresume/torrent files in an SQLite database (glassez)
- OTHER: Many internal code refactorings and bug fixing by many people
Sun Oct 31 2021 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.9
- BUGFIX: Fix "no action" option on torrent double click (Jose M. Abuin)
- BUGFIX: Fix broken behavior of "priority by shown file order" (Chocobo1)
- WEBUI: Fix WebUI crash when tracker URL is invalid (Chocobo1)
- WEBUI: Revert "WebUI: group trackers by hostname" (Chocobo1)
- WINDOWS: Remove Windows Vista support from manifest (xavier2k6)
- WINDOWS: NSIS: Update Korean, Indonesian and Traditional Chinese translation (JungHee Lee, Faisal Al-Munawar Fathur Rahman, SiderealArt)
Sun Aug 29 2021 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.8
- BUGFIX: Delay processing of watched folders (#15282) (glassez)
- BUGFIX: Use the same icon for selecting folders/files (Chocobo1)
- BUGFIX: Use default upper limits for ddns entries (Chocobo1)
- WEBUI: Expose SSRF mitigation (#15247) (Sylvain Finot)
- WEBUI: Update webui libraries (Chocobo1)
- WEBUI: Group trackers by hostname (#15264) (Mengyang Li)
- WEBUI: Improve "last activity" calculation in WebAPI (#15339) (Chocobo1)
- WINDOWS: NSIS: Add Polish translation (#15262) (Matthaiks)
Tue Aug 03 2021 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.7
- BUGFIX: Don't forget to start Watched folders timer (glassez)
- BUGFIX: Don't close tags menu when toggling items (tgregerson)
- BUGFIX: Don't overwrite tracker message (glassez)
- BUGFIX: Bump file pool size (#14966) (An0n)
- BUGFIX: Properly create "clean path" for watched folder (glassez)
- WEBUI: Disconnect comment links (Daniel Aleksandersen)
- WINDOWS: NSIS: Update Danish translation (scootergrisen)
Sat Jun 26 2021 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.6
- FEATURE: New languages: Mongolian, Persian, Thai
- BUGFIX: Provide correct error description in "upload mode" (glassez)
- BUGFIX: Allow adding torrents with relative save path (glassez)
- BUGFIX: Fix main window turns blank after restoring from tray (#15031) (Chocobo1)
- BUGFIX: Remove the lockfile on exit (#14997) (brvphoenix)
- BUGFIX: Improve "Watched folders" feature (glassez)
- BUGFIX: Keep sub-sorting order (#15074) (Dmitry Khlestkov)
- BUGFIX: Properly add torrent with new tags (glassez)
- WINDOWS: NSIS: Update Japanese, Turkish, Hungarian, Swedish translation (maboroshin, Burak Yavuz, xkrstudio, nonew-star)
Sun May 02 2021 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.5
- BUGFIX: Move cursor to the end when autofilling URL/hash in "Download from URLs" dialog (Chocobo1)
- BUGFIX: Sort invalid QDateTime values after valid values (Chocobo1)
- BUGFIX: Fix tabChangesFocus attribute in "Edit trackers" dialog (Christoph Rackwitz)
- BUGFIX: Update DynDNS register url (zhuangzi926)
- BUGFIX: Handle "not enough disk space" error more graciously (glassez)
- BUGFIX: Correctly draw progress background with stylesheet (jagannatharjun)
- WEBUI: Fix magnet url from the search facility (Chocobo1)
- WEBUI: Revise folder monitoring functions (Chocobo1)
- WEBUI: Fix magnet url from the browser (brvphoenix)
- WEBUI: Allow to specify file indexes in torrents/files API (glassez)
- WINDOWS: NSIS: Allow more strings to translated (bovirus, Chocobo1)
- WINDOWS: NSIS: Update Italian, German, Estonian, Russian, PortugueseBR translations (bovirus, Henry Water, PriitUring, Долматов Алексей, Felipe)
- LINUX: Fix D-Bus Notification `desktop-entry` field (Chocobo1)
- MACOS: Don't use executable name as CFBundleName value (Nick Korotysh)
- OTHER: Lower Qt requirement to 5.11 (sledgehammer999)
- OTHER: Clarify that the license is GPLv2+ (sledgehammer999)
Wed Mar 24 2021 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.4.1
- BUGFIX: Correctly draw progress bar (glassez)
- WEBUI: Fix javascript code which broke the UI (Chocobo1)
Tue Mar 23 2021 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.4
- FEATURE: Add ability to prioritize selected items by shown file order (Chocobo1)
- FEATURE: Allow tab to escape the text box in "Edit trackers" dialog (Christoph Rackwitz)
- FEATURE: Support sub-sorting in Transferlist (jagannatharjun)
- FEATURE: Expose ToS setting from libtorrent (Chocobo1)
- FEATURE: Improve tracker entries handling (glassez)
- BUGFIX: Drop extension from generated content folder name (glassez)
- BUGFIX: Change qBittorrent Updater window title (xavier2k6)
- BUGFIX: Validate HTTPS Tracker Certificate by default (an0n666)
- BUGFIX: Don't let "program update" dialog steal focus (Chocobo1)
- BUGFIX: Disable expand on double click in TorrentContentTreeView (jagannatharjun)
- BUGFIX: Add hyperlink to Transifex on translator list (Si Yong Kim)
- BUGFIX: Enlarge "speed limit" icon slightly (Chocobo1)
- BUGFIX: Don't prevent system sleep due to errored torrents (dyumin)
- BUGFIX: Use stable sorting in transfer list (Chocobo1)
- BUGFIX: Allow "missing files" torrents to save more resume data (glassez)
- BUGFIX: Restart "missing files" torrents after changing location (glassez)
- BUGFIX: Show proper string when torrent availability is not available (Chocobo1)
- BUGFIX: Apply "Hide zero/infinity values" to "Time Active", "Down/Up Limit" and ETA columns (Chocobo1)
- BUGFIX: Fix potential out-of-bounds access (Chocobo1)
- BUGFIX: Make SpeedPlotView averager time aware (jagannatharjun)
- BUGFIX: Add a 3-Hour graph (jagannatharjun)
- BUGFIX: Add an option to disable icons in menus (always disabled on MacOS) (Michał Kopeć)
- BUGFIX: Improve detection of filename extension of audio/video files (Chocobo1)
- BUGFIX: Various drawing improvements of progress bar (Chocobo1)
- BUGFIX: Properly stop torrent creation if aborted (Chocobo1)
- BUGFIX: Replace external program parameters in one step (Chocobo1)
- BUGFIX: Improve "save resume data" handling (glassez)
- BUGFIX: Fix bad IPv6 address format for outgoingInterfaces (treysis)
- WEBUI: Properly decode strings (brvphoenix)
- WEBUI: Accept "share limits" when adding torrent using WebAPI (glassez)
- WEBUI: Add seeding time to the active time column (thalieht)
- WEBUI: Fix incorrect seeding time string in General tab (thalieht)
- WEBUI: Allow >100 days in WebUI function "friendlyDuration" (thalieht)
- WEBUI: Avoid decoding strings repeatedly (brvphoenix)
- RSS: Add category button on AutomatedRSSDownloader on GUI (Si Yong Kim)
- WINDOWS: NSIS: Update Czech translation (slrslr)
- WINDOWS: NSIS: Update Portuguese BR translation (Alex)
- WINDOWS: NSIS: Add Estonian translation (PriitUring)
- WINDOWS: Allow change-case-only file renaming (glassez)
- LINUX: Systemd: wait for mounting of local filesystems (Juraj Oršulić)
- OTHER: Raise minimum libtorrent version to 1.2.12 (glassez)
- OTHER: Raise minimum Qt version to 5.12 (glassez)
Tue Jan 19 2021 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.3
- FEATURE: New languages: Azerbaijani, Estonian
- BUGFIX: Unify global speed dialogs for normal/alternative speeds (thalieht)
- BUGFIX: Increase maximum global speed limits ~2 GiB/s (thalieht)
- BUGFIX: Save fastresume when setting torrent speed limits (thalieht)
- BUGFIX: Group several torrent options into one dialog (thalieht)
- BUGFIX: Capitalize locale names (Chocobo1)
- BUGFIX: Improve content file/folder names handling (glassez)
- BUGFIX: Drop notification about move storage finished or failed (glassez)
- BUGFIX: Reload "missing files" torrent instead of re-checking (glassez)
- BUGFIX: Remember dialog sizes (Chocobo1)
- BUGFIX: Improve detection of file extension string (Chocobo1)
- WEBUI: Don't call non-existent elements (glassez)
- WEBUI: Update "Keep top-level folder" in WebUI options (thalieht)
- MACOS: QMake: Raise minimal macOS target version to 10.14 (glassez)
- LINUX: Use legacy 'data' directory only as a fallback (lbilli)
- OTHER: Bump project requirement to C++17 (Chocobo1)
Sun Dec 27 2020 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.2
- FEATURE: Allow to add root folder to torrent content (glassez)
- FEATURE: "HTTPS tracker validation" option is available on all platforms with latest libtorrent (Chocobo1)
- FEATURE: Option for supporting internationalized domain names (IDNs) (Chocobo1)
- BUGFIX: Fix broken sorting on some columns (Chocobo1)
- BUGFIX: Fix availability per file value (Chocobo1)
- BUGFIX: Fix status of torrents without metadata (sledgehammer999)
- BUGFIX: Don't try to remove folders for a torrent without metadata (sledgehammer999)
- BUGFIX: Lift upper limit of "Max concurrent HTTP announces" option (Chocobo1)
- BUGFIX: Add links to libtorrent documentation (Chocobo1)
- BUGFIX: Move "embedded tracker" options to qbt section (Chocobo1)
- BUGFIX: Properly handle "Append extension" option changing (glassez)
- BUGFIX: Correctly save paused torrent state (glassez)
- BUGFIX: Fix bug of "move storage job" can be performed multiple times (glassez)
- WEBUI: Add ability to use 'shift+delete' to delete torrents (Chocobo1)
- WEBUI: Allow to attach tags while adding torrents (Jesse Chan)
- WEBUI: Bump version to 2.6.2 (Jesse Chan)
- WEBUI: Remove unnecessary restriction on input length (Chocobo1)
- WINDOWS: NSIS: Update Russian translation (Andrei Stepanov)
- WINDOWS: NSIS: Update Italian translation (Alessandro Simonelli)
- OTHER: Drop support for building with libtorrent < 1.2.11 (Vladimir Golovnev)
Wed Nov 25 2020 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.1
- FEATURE: Allow progress bar styling from custom themes (jagannatharjun)
- FEATURE: Allow adding torrents using "Paste" key sequence (Chocobo1)
- FEATURE: Add Latgalian translation (sledgehammer999)
- BUGFIX: Prevent resume data to be saved for removed torrent (glassez)
- BUGFIX: Clarify connection protocol choice label (FranciscoPombal)
- BUGFIX: Fix crash when clicked outside the table of torrent content view (jagannatharjun)
- BUGFIX: Don't resume "paused" torrents when put into "checking" state by libtorrent (glassez)
- BUGFIX: Fix torrent state calculation (glassez)
- BUGFIX: Align integer data to right in torrent content view (jagannatharjun)
- WEBUI: Place WebUI RSS description in sandboxed iframe (Sepro)
- WEBUI: Avoid settings being reset via WebAPI (Chocobo1)
- WEBUI: Fix toggling advanced option in WebUI (thalieht)
- WEBUI: Expose contentPath in WebAPI torrents/info (FranciscoPombal)
- WEBUI: Fix the issue that IPv6 address can't be banned (brvphoenix)
- RSS: Fix confusion in date format description (Thomas De Rocker)
- WINDOWS: Update dutch.nsi (Thomas De Rocker)
- LINUX: Update .desktop file translations (sledgehammer999)
Thu Oct 22 2020 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.0.1
- WINDOWS: NSIS: Update Italian translation (bovirus)
Sun Oct 18 2020 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.0
- FEATURE: Many UI elements colors are themeable now (jagannatharjun)
@@ -55,6 +295,127 @@ Sun Oct 18 2020 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.0
- OTHER: Support for libtorrent 1.1.x is dropped (Chocobo1)
- OTHER: Many code cleanups and improvements (FranciscoPombal, Chocobo1, glassez)
Sat Apr 25 2020 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.2.5
- BUGFIX: Fix crash when torrent is deleted on limit reached (glassez)
- BUGFIX: Register datatype properly (Chocobo1)
- WEBUI: Add ability to send custom HTTP headers (Chocobo1)
- WEBUI: Expand RSS related API (Sepro)
- WINDOWS: Installer: Update german translation (schnurlos)
Wed Apr 22 2020 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.2.4
- BUGFIX: Fix sub-sorting of Transfer list (glassez)
- BUGFIX: Fix wrong logic that disables "prevent sleeping" timer (Chocobo1)
- BUGFIX: Set disk cache size for older libtorrent versions (NotTsunami)
- BUGFIX: Sort locale language list (Chocobo1)
- BUGFIX: Remove white outline around mascot.png (adem)
- BUGFIX: Various fixes in configuring the chosen network interface and not leaking the IP (Raif Atef, an0n666)
- BUGFIX: Save "resume data" when torrent storage is moved (glassez)
- BUGFIX: Avoid holding encoded resume data in memory (Chocobo1)
- BUGFIX: Fix date format for "Last seen complete" (Chocobo1)
- BUGFIX: Remove deprecated strict super seeding mode from advanced settings (an0n666)
- BUGFIX: Change default stop_tracker_timeout settings (an0n666)
- BUGFIX: Convert the Log widget to use custom View/Model (jagannatharjun)
- BUGFIX: Change default upload slot choking limits (an0n666)
- BUGFIX: Don't uncheck Authentication checkbox when changing proxy type (thalieht)
- BUGFIX: Reduce ambiguity for selecting tray icons (Chocobo1)
- WEBUI: Fix unable to add multiple peers in WebUI (Sepro)
- WEBUI: Fix UPnP lease duration get/set (NotTsunami)
- SEARCH: Detect python3 executable on Windows (József Sallai)
Wed Apr 01 2020 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.2.3
- FEATURE: Add logging for SOCKS5 proxy errors (Chocobo1)
- FEATURE: Add UPnP lease duration advanced option (NotTsunami)
- BUGFIX: Allow to translate error messages (Chocobo1)
- BUGFIX: Don't round scaling factor (Nick Korotysh)
- BUGFIX: Save log file in UTF-8 encoding (Chocobo1)
- BUGFIX: Avoid log file excessive flushing (Chocobo1)
- BUGFIX: Fix regression when fastresume contains network path (Tester798)
- BUGFIX: Fix broken UNC paths in fastresumes on Windows (sledgehammer999)
- BUGFIX: Prevent multiple instances for the same app config (glassez)
- BUGFIX: Fix unexpected torrent resume after app restart with libtorrent 1.1.x (glassez)
- WEBUI: Add alt and title tags for WebUI footer (LameLemon)
- WINDOWS: Installer: Update Finnish translation (Roope Jukkara)
- WINDOWS: Installer: Update Japanese translation (maboroshin)
- WINDOWS: Installer: Update Turkish translation (Burak Yavuz)
- WINDOWS: Installer: Update Russian translation (Andrei Stepanov)
Tue Mar 24 2020 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.2.2
- FEATURE: Allow transfer list text color changes through QSS (Prince Gupta)
- FEATURE: Option to show console when external program is run (sledgehammer999)
- FEATURE: Rename Country column to "Country / Region" (Thomas Piccirello)
- FEATURE: Change the defaults of some settings (FranciscoPombal)
- FEATURE: Refactored Transfer List code to allow theming. As a sideffect the row height has more padding. (glassez)
- FEATURE: Allow double-click in preview dialog (thalieht)
- FEATURE: Expose stop_tracker_timeout in advanced settings (an0n666)
- FEATURE: Add piece_extent_affinity to AdvancedSettings (FranciscoPombal)
- FEATURE: Reorganize UI theme selection (Prince Gupta)
- FEATURE: Show any multiple connections from the same IP in peer list (thalieht)
- FEATURE: Add stalled filters to GUI and Web API/UI (FranciscoPombal)
- FEATURE: Use IP geolocation database by DB-IP instead of MaxMind (sledgehammer999)
- FEATURE: Allow to save downloaded metadata as torrent file (glassez)
- FEATURE: Allow single app instance per configuration (glassez)
- PERFORMANCE: Move multiple torrents one by one (glassez)
- BUGFIX: Disable Torrent Queue by default for new users (an0n666)
- BUGFIX: Update free disk space label on Category change in Auto Mode (Medvedishce)
- BUGFIX: Save resume data after recheck (glassez)
- BUGFIX: Tracker is errored only if all local endpoints fail (sledgehammer999)
- BUGFIX: Change placement of stop tracker timeout setting (An0n)
- BUGFIX: Redesign torrent startup handling (glassez)
- BUGFIX: Show "∞" instead of " -1" in Preferences (Sakib-Abrar)
- BUGFIX: Improve code efficiency for reverse resolution of peers (Chocobo1)
- BUGFIX: Handle HTTP redirection to magnet URI (glassez)
- BUGFIX: Various fixes for portable mode (Tester798)
- BUGFIX: Include resume folder path in exception message (Chocobo1)
- BUGFIX: Change placeholder text in torrent list's filter (djt3)
- BUGFIX: Improvements in the embedded tracker to be more spec compliant (FranciscoPombal)
- BUGFIX: Improve the options tooltips (NotTsunami)
- BUGFIX: Check if file exists in seed mode (an0n666)
- BUGFIX: Delegate GUI scaling work to Qt (Nick Korotysh)
- BUGFIX: Fix crash when renaming torrent contents (Chocobo1)
- BUGFIX: Fix total connected peers count calculation (FranciscoPombal)
- BUGFIX: Allow other keypresses in LogListWidget (NotTsunami)
- BUGFIX: Disable Auto TMM when not using default savepath from monitored folder (thalieht)
- WEBUI: Fix first row renaming in files tab (Denis)
- WEBUI: Use SVG image for WebUI favicon (Nick Korotysh)
- WEBUI: Inherit text color for filter list elements (Nick Korotysh)
- WEBUI: Expose WebUI ban counter to users (Chocobo1)
- WEBUI: Expose WebUI ban duration to users (Chocobo1)
- WEBUI: Implement "Secure" flag for session cookie (FranciscoPombal)
- WEBUI: Remove unused/deprecated option (FranciscoPombal)
- WEBUI: Prevent excessive sync requests (FranciscoPombal)
- WEBUI: Fix populating statistics window (FranciscoPombal)
- WEBUI: Fix matching uncategorized torrents (FranciscoPombal)
- WEBUI: Always allow whitespace in category names (FranciscoPombal)
- SEARCH: Bump python version for new installation (Chocobo1)
- SEARCH: Fix missing string (Chocobo1)
- SEARCH: Drop python2 support (Chocobo1)
- WINDOWS: Installer: Option to start qBittorrent on Windows start up (An0n)
- WINDOWS: Installer: Improve Czech translation (slrslr)
- WINDOWS: Installer: Update French translation (zywo)
- WINDOWS: Installer: Update German translation (schnurlos)
- WINDOWS: Installer: Update Japanese translation (maboroshin)
- WINDOWS: Path length limitation is removed on Windows 10 1607 onwards (an0n666)
Wed Dec 18 2019 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.2.1
- FEATURE: Enable portable mode if "profile" directory exists (Tester798)
- FEATURE: Enable "Apply rate limit to peers on LAN" option by default (Chocobo1)
- BUGFIX: Sync translations from Transifex and run lupdate (sledgehammer999)
- BUGFIX: Don't unnecessarily delete OS files in folders (sledgehammer999)
- BUGFIX: Use the incomplete folder where appropriate (sledgehammer999)
- BUGFIX: Align Properties tab bar correctly on window resize (Prince Gupta)
- BUGFIX: Rework the listening IP/interface selection code (sledgehammer999)
- BUGFIX: Fix inconsistent icon for deleting torrent (Chocobo1)
- BUGFIX: Show torrent error message in transfer list (Chocobo1)
- BUGFIX: Fix stuck in wrong torrent state (Chocobo1)
- BUGFIX: Expand single-item folders in torrent content (warren)
- WEBUI: Bump Web API version (sledgehammer999)
- WEBUI: Add ability to rename torrent files from the WebUI (Thomas Piccirello)
- WEBUI: Mention lack of HTTPS in WebUI magnet link warning (nl6720)
- WEBUI: Fix HTML elements size in search tab (Chocobo1)
- SEARCH: Fix incorrect translation displayed after language change (Chocobo1)
- SEARCH: Fix missing translations in search plugins dialog (Chocobo1)
- WINDOWS: Update russian translation of the installer (Andrei Stepanov)
Tue Dec 03 2019 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.2.0
- FEATURE: Libtorrent 1.2.x series are supported now (glassez)
- FEATURE: Add OpenSSL version to GUI and stackdump (Chocobo1)

View File

@@ -11,7 +11,7 @@ qBittorrent - A BitTorrent client in C++ / Qt
- OpenSSL >= 1.1.1
- Qt 5.15.2 - 5.x
- Qt 5.15.2 - 5.x || 6.2.0 - 6.x
- zlib >= 1.2.11

View File

@@ -46,7 +46,7 @@ Please report any bug (or feature request) to:
http://bugs.qbittorrent.org
Official IRC channel:
`#qbittorrent on irc.libera.chat`
[#qbittorrent on irc.libera.chat](ircs://irc.libera.chat:6697/qbittorrent)
------------------------------------------
sledgehammer999 <sledgehammer999@qbittorrent.org>
sledgehammer999 \<sledgehammer999@qbittorrent.org\>

View File

@@ -25,6 +25,12 @@ macro(qbt_common_config)
$<$<NOT:$<CONFIG:Debug>>:QT_NO_DEBUG_OUTPUT>
)
if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
target_compile_definitions(qbt_common_cfg INTERFACE
_DARWIN_FEATURE_64_BIT_INODE
)
endif()
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
target_compile_definitions(qbt_common_cfg INTERFACE
NTDDI_VERSION=0x06010000

20
configure vendored
View File

@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.71 for qbittorrent v4.4.0alpha.
# Generated by GNU Autoconf 2.71 for qbittorrent v4.4.1.
#
# Report bugs to <bugs.qbittorrent.org>.
#
@@ -611,8 +611,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='qbittorrent'
PACKAGE_TARNAME='qbittorrent'
PACKAGE_VERSION='v4.4.0alpha'
PACKAGE_STRING='qbittorrent v4.4.0alpha'
PACKAGE_VERSION='v4.4.1'
PACKAGE_STRING='qbittorrent v4.4.1'
PACKAGE_BUGREPORT='bugs.qbittorrent.org'
PACKAGE_URL='https://www.qbittorrent.org/'
@@ -1329,7 +1329,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
\`configure' configures qbittorrent v4.4.0alpha to adapt to many kinds of systems.
\`configure' configures qbittorrent v4.4.1 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1400,7 +1400,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of qbittorrent v4.4.0alpha:";;
short | recursive ) echo "Configuration of qbittorrent v4.4.1:";;
esac
cat <<\_ACEOF
@@ -1533,7 +1533,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
qbittorrent configure v4.4.0alpha
qbittorrent configure v4.4.1
generated by GNU Autoconf 2.71
Copyright (C) 2021 Free Software Foundation, Inc.
@@ -1648,7 +1648,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by qbittorrent $as_me v4.4.0alpha, which was
It was created by qbittorrent $as_me v4.4.1, which was
generated by GNU Autoconf 2.71. Invocation command line was
$ $0$ac_configure_args_raw
@@ -4779,7 +4779,7 @@ fi
# Define the identity of the package.
PACKAGE='qbittorrent'
VERSION='v4.4.0alpha'
VERSION='v4.4.1'
printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
@@ -7254,7 +7254,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.4.0alpha, which was
This file was extended by qbittorrent $as_me v4.4.1, which was
generated by GNU Autoconf 2.71. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -7314,7 +7314,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
qbittorrent config.status v4.4.0alpha
qbittorrent config.status v4.4.1
configured by $0, generated by GNU Autoconf 2.71,
with options \\"\$ac_cs_config\\"

View File

@@ -1,5 +1,4 @@
AC_INIT([qbittorrent], [v4.4.0alpha], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
AC_INIT([qbittorrent], [v4.4.1], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_MACRO_DIR([m4])
: ${CFLAGS=""}

4
dist/mac/Info.plist vendored
View File

@@ -55,7 +55,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>4.4.0</string>
<string>4.4.1</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
@@ -67,7 +67,7 @@
<key>NSAppleScriptEnabled</key>
<string>YES</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2006-2021 The qBittorrent project</string>
<string>Copyright © 2006-2022 The qBittorrent project</string>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 775 B

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 775 B

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -74,6 +74,6 @@
<url type="translate">https://github.com/qbittorrent/qBittorrent/wiki/How-to-translate-qBittorrent</url>
<content_rating type="oars-1.1"/>
<releases>
<release version="4.4.0" date="2020-10-18"/>
<release version="4.4.1" date="2022-02-15"/>
</releases>
</component>

View File

@@ -16,15 +16,12 @@ Keywords=bittorrent;torrent;magnet;download;p2p;
# Translations
Comment[oc]=Telecargar e partejar de fichièrs amb BitTorrent
GenericName[oc]=Client BitTorrent
Name[oc]=qBittorrent
Comment[af]=Aflaai en deel lêers oor BitTorrent
GenericName[af]=BitTorrent kliënt
Name[af]=qBittorrent
Comment[ar]=نزّل وشارك الملفات عبر كيوبت‎تورنت
GenericName[ar]=عميل بت‎تورنت
Name[ar]=كيوبت‎تورنت
Name[ar]=qBittorrent
Comment[be]=Спампоўванне і раздача файлаў праз пратакол BitTorrent
GenericName[be]=Кліент BitTorrent
Name[be]=qBittorrent
@@ -33,7 +30,10 @@ GenericName[bg]=BitTorrent клиент
Name[bg]=qBittorrent
Comment[bn]=বিটটরেন্টে ফাইল ডাউনলোড এবং শেয়ার করুন
GenericName[bn]=বিটটরেন্ট ক্লায়েন্ট
Name[bn]=কিউবি্টটরেন্ট
Name[bn]=qBittorrent
Comment[zh]=通过 BitTorrent 下载和分享文件
GenericName[zh]=BitTorrent 客户端
Name[zh]=qBittorrent
Comment[bs]=Preuzmi i dijeli datoteke preko BitTorrent-a
GenericName[bs]=BitTorrent klijent
Name[bs]=qBittorrent
@@ -66,11 +66,11 @@ GenericName[eu]=BitTorrent bezeroa
Name[eu]=qBittorrent
Comment[fa]=دانلود و به اشتراک گذاری فایل های بوسیله بیت تورنت
GenericName[fa]=بیت تورنت نسخه کلاینت
Name[fa]=کیو بیت تورنت
Name[fa]=qBittorrent
Comment[fi]=Lataa ja jaa tiedostoja BitTorrentia käyttäen
GenericName[fi]=BitTorrent-asiakasohjelma
Name[fi]=qBittorrent
Comment[fr]=Letölteni és megosztani a dokumentumokat Bittorrenttel
Comment[fr]=Télécharger et partager des fichiers sur BitTorrent
GenericName[fr]=Client BitTorrent
Name[fr]=qBittorrent
Comment[gl]=Descargar e compartir ficheiros co protocolo BitTorrent
@@ -78,7 +78,7 @@ GenericName[gl]=Cliente BitTorrent
Name[gl]=qBittorrent
Comment[gu]=બિટ્ટોરેંટ પર ફાઈલો ડાઉનલોડ અને શેર કરો
GenericName[gu]=બિટ્ટોરેંટ ક્લાયન્ટ
Name[gu]=ક્યૂ-બિટ્ટોરેંટ
Name[gu]=qBittorrent
Comment[he]=הורד ושתף קבצים על גבי ביטורנט
GenericName[he]=לקוח ביטורנט
Name[he]=qBittorrent
@@ -109,24 +109,18 @@ Name[ka]=qBittorrent
Comment[ko]=BitTorrent를 통해 파일 다운로드 및 공유
GenericName[ko]=BitTorrent 클라이언트
Name[ko]=qBittorrent
Comment[zh]=通过 BitTorrent 下载和分享文件
GenericName[zh]=BitTorrent 客户端
Name[zh]=qBittorrent
Comment[lt]=Atsisiųskite bei dalinkitės failais BitTorrent tinkle
GenericName[lt]=BitTorrent klientas
Name[lt]=qBittorrent
Comment[mk]=Превземајте и споделувајте фајлови преку BitTorrent
GenericName[mk]=BitTorrent клиент
Name[mk]=qBittorrent
Comment[en_AU]=Download and share files over BitTorrent
GenericName[en_AU]=BitTorrent client
Name[en_AU]=qBittorrent
Comment[my]=တောရန့်ဖြင့်ဖိုင်များဒေါင်းလုဒ်ဆွဲရန်နှင့်မျှဝေရန်
GenericName[my]=တောရန့်စီမံခန့်ခွဲသည့်အရာ
Name[my]=qBittorrent
Comment[nb]=Last ned og del filer over BitTorrent
GenericName[nb]=BitTorrent-klient
Name[nb]=qBittorrent
Comment[nqo]=ߞߐߕߐ߯ߘߐ ߟߎ߬ ߟߊߖߌ߰ ߞߊ߬ ߓߊ߲߫ ߞߵߊ߬ߟߎ߬ ߘߐߕߟߊ߫ ߓߌߙߏߙߍ߲ߕ ߞߊ߲߬
GenericName[nqo]=ߓߌߙߏߙߍ߲ߕ ߕߣߐ߬ߓߐ߬ߟߊ
Name[nqo]=ߞߎ߳ߓߌߕߏߙߍ߲ߕ
Comment[nl]=Bestanden downloaden en delen via BitTorrent
GenericName[nl]=BitTorrent-client
Name[nl]=qBittorrent
@@ -151,6 +145,7 @@ Name[sk]=qBittorrent
Comment[sl]=Prenesite in delite datoteke preko BitTorrenta
GenericName[sl]=BitTorrent odjemalec
Name[sl]=qBittorrent
Name[sq]=qBittorrent
Comment[sr]=Преузимајте и делите фајлове преко BitTorrent протокола
GenericName[sr]=BitTorrent-клијент
Name[sr]=qBittorrent
@@ -160,6 +155,52 @@ Name[sr@latin]=qBittorrent
Comment[sv]=Hämta och dela filer över BitTorrent
GenericName[sv]=BitTorrent-klient
Name[sv]=qBittorrent
Comment[ta]=BitTorrent வழியாக கோப்புகளை பதிவிறக்க மற்றும் பகிர
GenericName[ta]=BitTorrent வாடிக்கையாளர்
Name[ta]=qBittorrent
Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్స్ దిగుమతి చేసుకోండి , పంచుకోండి
GenericName[te]=క్యు బిట్ టొరెంట్ క్లయింట్
Name[te]=qBittorrent
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ผ่าน BitTorrent
GenericName[th]=ไคลเอนต์ BitTorrent
Name[th]=qBittorrent
Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın
GenericName[tr]=BitTorrent istemcisi
Name[tr]=qBittorrent
Comment[ur]=BitTorrent پر فائلوں کو ڈاؤن لوڈ کریں اور اشتراک کریں
GenericName[ur]=قیو بٹ ٹورنٹ کلائنٹ
Name[ur]=qBittorrent
Comment[uk]=Завантажуйте та поширюйте файли через BitTorrent
GenericName[uk]=BitTorrent-клієнт
Name[uk]=qBittorrent
Comment[vi]=Tải về và chia sẻ tệp qua BitTorrent
GenericName[vi]=Máy khách BitTorrent
Name[vi]=qBittorrent
Comment[zh_HK]=經由BitTorrent下載並分享檔案
GenericName[zh_HK]=BitTorrent用戶端
Name[zh_HK]=qBittorrent
Comment[zh_TW]=經由 BitTorrent 下載並分享檔案
GenericName[zh_TW]=BitTorrent 客戶端
Name[zh_TW]=qBittorrent
Comment[eo]=Elŝutu kaj kunhavigu dosierojn per BitTorrent
GenericName[eo]=BitTorrent-kliento
Name[eo]=qBittorrent
Comment[kk]=BitTorrent арқылы файл жүктеу және бөлісу
GenericName[kk]=BitTorrent клиенті
Name[kk]=qBittorrent
Comment[en_AU]=Download and share files over BitTorrent
GenericName[en_AU]=BitTorrent client
Name[en_AU]=qBittorrent
Name[rm]=qBittorrent
Name[jv]=qBittorrent
Comment[oc]=Telecargar e partejar de fichièrs amb BitTorrent
GenericName[oc]=Client BitTorrent
Name[oc]=qBittorrent
Name[ug]=qBittorrent
Name[yi]=qBittorrent
Comment[nqo]=ߞߐߕߐ߯ߘߐ ߟߎ߬ ߟߊߖߌ߰ ߞߊ߬ ߓߊ߲߫ ߞߵߊ߬ߟߎ߬ ߘߐߕߟߊ߫ ߓߌߙߏߙߍ߲ߕ ߞߊ߲߬
GenericName[nqo]=ߓߌߙߏߙߍ߲ߕ ߕߣߐ߬ߓߐ߬ߟߊ
Name[nqo]=qBittorrent
Comment[uz@Latn]=BitTorrent orqali fayllarni yuklab olish va baham korish
GenericName[uz@Latn]=BitTorrent mijozi
Name[uz@Latn]=qBittorrent
@@ -168,55 +209,23 @@ GenericName[ltg]=BitTorrent klients
Name[ltg]=qBittorrent
Comment[hi_IN]=BitTorrent द्वारा फाइल डाउनलोड व सहभाजन
GenericName[hi_IN]=Bittorrent साधन
Name[hi_IN]=क्यूबिटटाॅरेंट
Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın
GenericName[tr]=BitTorrent istemcisi
Name[tr]=qBittorrent
Comment[ur]=BitTorrent پر فائلوں کو ڈاؤن لوڈ کریں اور اشتراک کریں
GenericName[ur]=قیو بٹ ٹورنٹ کلائنٹ
Name[ur]=قیو بٹ ٹورنٹ
Comment[uk]=Завантажуйте та поширюйте файли через BitTorrent
GenericName[uk]=BitTorrent-клієнт
Name[uk]=qBittorrent
Comment[vi]=Tải về và chia sẻ tệp qua BitTorrent
GenericName[vi]=Máy khách BitTorrent
Name[vi]=qBittorrent
Name[hi_IN]=qBittorrent
Comment[az@latin]=Faylları BitTorrent vasitəsilə göndərin və paylaşın
GenericName[az@latin]=BitTorrent client
Name[az@latin]=qBittorrent
Comment[zh_HK]=經由BitTorrent下載並分享檔案
GenericName[zh_HK]=BitTorrent用戶端
Name[zh_HK]=qBittorrent
Comment[zh_TW]=經由 BitTorrent 下載並分享檔案
GenericName[zh_TW]=BitTorrent 客戶端
Name[zh_TW]=qBittorrent
Comment[lv_LV]=Lejupielādēt un koplietot failus ar BitTorrent
GenericName[lv_LV]=BitTorrent klients
Name[lv_LV]=qBittorrent
Comment[kk]=BitTorrent арқылы файл жүктеу және бөлісу
GenericName[kk]=BitTorrent клиенті
Name[kk]=qBittorrent
Comment[ms_MY]=Muat turun dan kongsi fail melalui BitTorrent
GenericName[ms_MY]=Klien BitTorrent
Name[ms_MY]=qBittorrent
Comment[eo]=Elŝutu kaj kunhavigu dosierojn per BitTorrent
GenericName[eo]=BitTorrent-kliento
Name[eo]=qBittorrent
Comment[mn_MN]=BitTorrent-оор файлуудаа тат, түгээ
GenericName[mn_MN]=BitTorrent татагч
Name[mn_MN]=qBittorrent
Comment[ta]=BitTorrent வழியாக கோப்புகளை பதிவிறக்க மற்றும் பகிர
GenericName[ta]=BitTorrent வாடிக்கையாளர்
Name[ta]=qBittorrent
Comment[ne_NP]=फाइलहरू डाउनलोड गर्नुहोस् र BitTorrent मा साझा गर्नुहोस्
GenericName[ne_NP]=BitTorrent क्लाइन्ट
Name[ne_NP]=qBittorrent
Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్స్ దిగుమతి చేసుకోండి , పంచుకోండి
GenericName[te]=క్యు బిట్ టొరెంట్ క్లయింట్
Name[te]=క్యు బిట్ టొరెంట్
Comment[pt_PT]=Transferir e partilhar ficheiros por BitTorrent
GenericName[pt_PT]=Cliente BitTorrent
Name[pt_PT]=qBittorrent
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ผ่าน BitTorrent
GenericName[th]=ไคลเอนต์ BitTorrent
Name[th]=qBittorrent
Name[si_LK]=qBittorrent

View File

@@ -1,37 +1,37 @@
;Installer strings
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
LangString inst_qbt_req ${LANG_SIMPCHINESE} "qBittorrent (必要)"
LangString inst_qbt_req ${LANG_SIMPCHINESE} "qBittorrent 主程序 (必要)"
;LangString inst_dekstop ${LANG_ENGLISH} "Create Desktop Shortcut"
LangString inst_dekstop ${LANG_SIMPCHINESE} "创建桌面快捷方式"
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
LangString inst_startmenu ${LANG_SIMPCHINESE} "创建开始菜单快捷方式"
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
LangString inst_startup ${LANG_SIMPCHINESE} "在Windows上启动qBittorrent进行启动"
LangString inst_startup ${LANG_SIMPCHINESE} "qBittorrent 开机自启动"
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
LangString inst_torrent ${LANG_SIMPCHINESE} "用 qBittorrent 打开.torrent文件"
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
LangString inst_magnet ${LANG_SIMPCHINESE} "用 qBittorrent 打开磁力链接"
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
LangString inst_firewall ${LANG_SIMPCHINESE} "添加Windows防火墙规则"
LangString inst_firewall ${LANG_SIMPCHINESE} "添加 Windows 防火墙规则"
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
LangString inst_pathlimit ${LANG_SIMPCHINESE} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
LangString inst_pathlimit ${LANG_SIMPCHINESE} "解除 Windows 的 PATH 长度限制 (解除 MAX_PATH 为 260 的限制, 需要 Windows 10 1607 或更新版本)"
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
LangString inst_firewallinfo ${LANG_SIMPCHINESE} "正在添加Windows防火墙规则"
LangString inst_firewallinfo ${LANG_SIMPCHINESE} "正在添加 Windows 防火墙规则"
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
LangString inst_warning ${LANG_SIMPCHINESE} "qBittorrent 正在运行。 安装前请关闭应用程序"
LangString inst_warning ${LANG_SIMPCHINESE} "qBittorrent 正在运行。 安装前请关闭"
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
LangString inst_uninstall_question ${LANG_SIMPCHINESE} "检测到以前的安装。 它将被卸载但不删除用户设置"
LangString inst_uninstall_question ${LANG_SIMPCHINESE} "当前版本会被卸载。 用户设置和种子会被完整保留"
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
LangString inst_unist ${LANG_SIMPCHINESE} "卸载以前的版本。"
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
LangString launch_qbt ${LANG_SIMPCHINESE} "启动 qBittorrent."
LangString launch_qbt ${LANG_SIMPCHINESE} "启动 qBittorrent"
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
LangString inst_requires_64bit ${LANG_SIMPCHINESE} "此安装程序只能在64位的Windows上工作"
LangString inst_requires_64bit ${LANG_SIMPCHINESE} "此安装程序仅支持 64 位 Windows 系统"
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
LangString inst_requires_win7 ${LANG_SIMPCHINESE} "This qBittorrent version requires at least Windows 7."
LangString inst_requires_win7 ${LANG_SIMPCHINESE} "这个版本的 qBittorrent 仅支持 Windows 7 及更新的系统。"
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
LangString inst_uninstall_link_description ${LANG_SIMPCHINESE} "Uninstall qBittorrent"
LangString inst_uninstall_link_description ${LANG_SIMPCHINESE} "卸载 qBittorrent"
;------------------------------------
;Uninstaller strings
@@ -41,20 +41,20 @@ LangString remove_files ${LANG_SIMPCHINESE} "删除文件"
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
LangString remove_shortcuts ${LANG_SIMPCHINESE} "删除快捷方式"
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
LangString remove_associations ${LANG_SIMPCHINESE} "除文件关联"
LangString remove_associations ${LANG_SIMPCHINESE} "除文件关联"
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
LangString remove_registry ${LANG_SIMPCHINESE} "删除注册表键"
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
LangString remove_conf ${LANG_SIMPCHINESE} "删除配置文件"
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
LangString remove_firewall ${LANG_SIMPCHINESE} "删除Windows防火墙规则"
LangString remove_firewall ${LANG_SIMPCHINESE} "删除 Windows 防火墙规则"
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
LangString remove_firewallinfo ${LANG_SIMPCHINESE} "正在删除Windows防火墙规则"
LangString remove_firewallinfo ${LANG_SIMPCHINESE} "正在删除 Windows 防火墙规则"
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
LangString remove_cache ${LANG_SIMPCHINESE} "删除种子和缓存数据"
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
LangString uninst_warning ${LANG_SIMPCHINESE} "qBittorrent 正在运行。 卸载前请关闭程序。"
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
LangString uninst_tor_warn ${LANG_SIMPCHINESE} "不删除 .torrent 关联。 关联的是:"
LangString uninst_tor_warn ${LANG_SIMPCHINESE} "未解除与 .torrent 关联。 它已与另一程序关联:"
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
LangString uninst_mag_warn ${LANG_SIMPCHINESE} "不删除磁力关联。 关联的是:"
LangString uninst_mag_warn ${LANG_SIMPCHINESE} "未解除与 磁力链接 的关联。 它已与另一程序关联:"

View File

@@ -28,7 +28,7 @@ XPStyle on
!define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path
; Program specific
!define PROG_VERSION "4.4.0"
!define PROG_VERSION "4.4.1"
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
@@ -51,7 +51,7 @@ XPStyle on
;Installer Version Information
VIAddVersionKey "ProductName" "qBittorrent"
VIAddVersionKey "CompanyName" "The qBittorrent project"
VIAddVersionKey "LegalCopyright" "Copyright ©2006-2021 The qBittorrent project"
VIAddVersionKey "LegalCopyright" "Copyright ©2006-2022 The qBittorrent project"
VIAddVersionKey "FileDescription" "qBittorrent - A Bittorrent Client"
VIAddVersionKey "FileVersion" "${PROG_VERSION}"

View File

@@ -9,6 +9,8 @@ else {
QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.14
DEFINES += _DARWIN_FEATURE_64_BIT_INODE
LIBS += -framework Carbon -framework IOKit -framework AppKit
QT_LANG_PATH = ../dist/qt-translations

View File

@@ -100,22 +100,10 @@
namespace
{
#define SETTINGS_KEY(name) "Application/" name
// FileLogger properties keys
#define FILELOGGER_SETTINGS_KEY(name) QStringLiteral(SETTINGS_KEY("FileLogger/") name)
const QString KEY_FILELOGGER_ENABLED = FILELOGGER_SETTINGS_KEY("Enabled");
const QString KEY_FILELOGGER_PATH = FILELOGGER_SETTINGS_KEY("Path");
const QString KEY_FILELOGGER_BACKUP = FILELOGGER_SETTINGS_KEY("Backup");
const QString KEY_FILELOGGER_DELETEOLD = FILELOGGER_SETTINGS_KEY("DeleteOld");
const QString KEY_FILELOGGER_MAXSIZEBYTES = FILELOGGER_SETTINGS_KEY("MaxSizeBytes");
const QString KEY_FILELOGGER_AGE = FILELOGGER_SETTINGS_KEY("Age");
const QString KEY_FILELOGGER_AGETYPE = FILELOGGER_SETTINGS_KEY("AgeType");
// just a shortcut
inline SettingsStorage *settings() { return SettingsStorage::instance(); }
#define FILELOGGER_SETTINGS_KEY(name) (SETTINGS_KEY("FileLogger/") name)
const QString LOG_FOLDER = QStringLiteral("logs");
const QChar PARAMS_SEPARATOR = '|';
const QChar PARAMS_SEPARATOR = QLatin1Char('|');
const QString DEFAULT_PORTABLE_MODE_PROFILE_DIR = QStringLiteral("profile");
@@ -133,6 +121,13 @@ Application::Application(int &argc, char **argv)
, m_running(false)
, m_shutdownAct(ShutdownDialogAction::Exit)
, m_commandLineArgs(parseCommandLine(this->arguments()))
, m_storeFileLoggerEnabled(FILELOGGER_SETTINGS_KEY("Enabled"))
, m_storeFileLoggerBackup(FILELOGGER_SETTINGS_KEY("Backup"))
, m_storeFileLoggerDeleteOld(FILELOGGER_SETTINGS_KEY("DeleteOld"))
, m_storeFileLoggerMaxSize(FILELOGGER_SETTINGS_KEY("MaxSizeBytes"))
, m_storeFileLoggerAge(FILELOGGER_SETTINGS_KEY("Age"))
, m_storeFileLoggerAgeType(FILELOGGER_SETTINGS_KEY("AgeType"))
, m_storeFileLoggerPath(FILELOGGER_SETTINGS_KEY("Path"))
{
qRegisterMetaType<Log::Msg>("Log::Msg");
qRegisterMetaType<Log::Peer>("Log::Peer");
@@ -154,17 +149,11 @@ Application::Application(int &argc, char **argv)
const QString profileDir = portableModeEnabled
? QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(DEFAULT_PORTABLE_MODE_PROFILE_DIR)
: m_commandLineArgs.profileDir;
#ifdef Q_OS_WIN
const QString instanceId = (profileDir + (m_commandLineArgs.configurationName.isEmpty() ? QString {} : ('/' + m_commandLineArgs.configurationName))).toLower();
#else
const QString instanceId = profileDir + (m_commandLineArgs.configurationName.isEmpty() ? QString {} : ('/' + m_commandLineArgs.configurationName));
#endif
const QString appId = QLatin1String("qBittorrent-") + Utils::Misc::getUserIDString() + '@' + instanceId;
m_instanceManager = new ApplicationInstanceManager {appId, this};
Profile::initInstance(profileDir, m_commandLineArgs.configurationName,
(m_commandLineArgs.relativeFastresumePaths || portableModeEnabled));
m_instanceManager = new ApplicationInstanceManager {Profile::instance()->location(SpecialFolder::Config), this};
Logger::initInstance();
SettingsStorage::initInstance();
Preferences::initInstance();
@@ -217,7 +206,7 @@ const QBtCommandLineParameters &Application::commandLineArgs() const
bool Application::isFileLoggerEnabled() const
{
return settings()->loadValue(KEY_FILELOGGER_ENABLED, true);
return m_storeFileLoggerEnabled.get(true);
}
void Application::setFileLoggerEnabled(const bool value)
@@ -226,49 +215,48 @@ void Application::setFileLoggerEnabled(const bool value)
m_fileLogger = new FileLogger(fileLoggerPath(), isFileLoggerBackup(), fileLoggerMaxSize(), isFileLoggerDeleteOld(), fileLoggerAge(), static_cast<FileLogger::FileLogAgeType>(fileLoggerAgeType()));
else if (!value)
delete m_fileLogger;
settings()->storeValue(KEY_FILELOGGER_ENABLED, value);
m_storeFileLoggerEnabled = value;
}
QString Application::fileLoggerPath() const
{
return settings()->loadValue(KEY_FILELOGGER_PATH
, QString {specialFolderLocation(SpecialFolder::Data) + LOG_FOLDER});
return m_storeFileLoggerPath.get(QDir(specialFolderLocation(SpecialFolder::Data)).absoluteFilePath(LOG_FOLDER));
}
void Application::setFileLoggerPath(const QString &path)
{
if (m_fileLogger)
m_fileLogger->changePath(path);
settings()->storeValue(KEY_FILELOGGER_PATH, path);
m_storeFileLoggerPath = path;
}
bool Application::isFileLoggerBackup() const
{
return settings()->loadValue(KEY_FILELOGGER_BACKUP, true);
return m_storeFileLoggerBackup.get(true);
}
void Application::setFileLoggerBackup(const bool value)
{
if (m_fileLogger)
m_fileLogger->setBackup(value);
settings()->storeValue(KEY_FILELOGGER_BACKUP, value);
m_storeFileLoggerBackup = value;
}
bool Application::isFileLoggerDeleteOld() const
{
return settings()->loadValue(KEY_FILELOGGER_DELETEOLD, true);
return m_storeFileLoggerDeleteOld.get(true);
}
void Application::setFileLoggerDeleteOld(const bool value)
{
if (value && m_fileLogger)
m_fileLogger->deleteOld(fileLoggerAge(), static_cast<FileLogger::FileLogAgeType>(fileLoggerAgeType()));
settings()->storeValue(KEY_FILELOGGER_DELETEOLD, value);
m_storeFileLoggerDeleteOld = value;
}
int Application::fileLoggerMaxSize() const
{
const int val = settings()->loadValue(KEY_FILELOGGER_MAXSIZEBYTES, DEFAULT_FILELOG_SIZE);
const int val = m_storeFileLoggerMaxSize.get(DEFAULT_FILELOG_SIZE);
return std::min(std::max(val, MIN_FILELOG_SIZE), MAX_FILELOG_SIZE);
}
@@ -277,29 +265,29 @@ void Application::setFileLoggerMaxSize(const int bytes)
const int clampedValue = std::min(std::max(bytes, MIN_FILELOG_SIZE), MAX_FILELOG_SIZE);
if (m_fileLogger)
m_fileLogger->setMaxSize(clampedValue);
settings()->storeValue(KEY_FILELOGGER_MAXSIZEBYTES, clampedValue);
m_storeFileLoggerMaxSize = clampedValue;
}
int Application::fileLoggerAge() const
{
const int val = settings()->loadValue(KEY_FILELOGGER_AGE, 1);
const int val = m_storeFileLoggerAge.get(1);
return std::min(std::max(val, 1), 365);
}
void Application::setFileLoggerAge(const int value)
{
settings()->storeValue(KEY_FILELOGGER_AGE, std::min(std::max(value, 1), 365));
m_storeFileLoggerAge = std::min(std::max(value, 1), 365);
}
int Application::fileLoggerAgeType() const
{
const int val = settings()->loadValue(KEY_FILELOGGER_AGETYPE, 1);
const int val = m_storeFileLoggerAgeType.get(1);
return ((val < 0) || (val > 2)) ? 1 : val;
}
void Application::setFileLoggerAgeType(const int value)
{
settings()->storeValue(KEY_FILELOGGER_AGETYPE, ((value < 0) || (value > 2)) ? 1 : value);
m_storeFileLoggerAgeType = ((value < 0) || (value > 2)) ? 1 : value;
}
void Application::processMessage(const QString &message)
@@ -657,12 +645,13 @@ int Application::exec(const QStringList &params)
#ifdef DISABLE_GUI
#ifndef DISABLE_WEBUI
Preferences *const pref = Preferences::instance();
// Display some information to the user
const Preferences *pref = Preferences::instance();
const auto scheme = QString::fromLatin1(pref->isWebUiHttpsEnabled() ? "https" : "http");
const auto url = QString::fromLatin1("%1://localhost:%2\n").arg(scheme, QString::number(pref->getWebUiPort()));
const QString mesg = QString::fromLatin1("\n******** %1 ********\n").arg(tr("Information"))
+ tr("To control qBittorrent, access the Web UI at %1")
.arg(QString("http://localhost:") + QString::number(pref->getWebUiPort())) + '\n';
printf("%s", qUtf8Printable(mesg));
+ tr("To control qBittorrent, access the WebUI at: %1").arg(url);
printf("%s\n", qUtf8Printable(mesg));
if (pref->getWebUIPassword() == "ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ==")
{

View File

@@ -47,6 +47,7 @@ class QSessionManager;
using BaseApplication = QCoreApplication;
#endif // DISABLE_GUI
#include "base/settingvalue.h"
#include "base/types.h"
#include "cmdoptions.h"
@@ -120,6 +121,11 @@ private slots:
#endif
private:
void initializeTranslation();
void processParams(const QStringList &params);
void runExternalProgram(const BitTorrent::Torrent *torrent) const;
void sendNotificationEmail(const BitTorrent::Torrent *torrent);
ApplicationInstanceManager *m_instanceManager = nullptr;
bool m_running;
ShutdownDialogAction m_shutdownAct;
@@ -140,8 +146,11 @@ private:
QTranslator m_translator;
QStringList m_paramsQueue;
void initializeTranslation();
void processParams(const QStringList &params);
void runExternalProgram(const BitTorrent::Torrent *torrent) const;
void sendNotificationEmail(const BitTorrent::Torrent *torrent);
SettingValue<bool> m_storeFileLoggerEnabled;
SettingValue<bool> m_storeFileLoggerBackup;
SettingValue<bool> m_storeFileLoggerDeleteOld;
SettingValue<int> m_storeFileLoggerMaxSize;
SettingValue<int> m_storeFileLoggerAge;
SettingValue<int> m_storeFileLoggerAgeType;
SettingValue<QString> m_storeFileLoggerPath;
};

View File

@@ -28,24 +28,27 @@
#include "applicationinstancemanager.h"
#include <QtGlobal>
#ifdef Q_OS_WIN
#include <windows.h>
#endif
#include <QDebug>
#include <QSharedMemory>
#endif
#include "qtlocalpeer/qtlocalpeer.h"
ApplicationInstanceManager::ApplicationInstanceManager(const QString &appId, QObject *parent)
ApplicationInstanceManager::ApplicationInstanceManager(const QString &instancePath, QObject *parent)
: QObject {parent}
, m_peer {new QtLocalPeer {this, appId}}
, m_peer {new QtLocalPeer {instancePath, this}}
, m_isFirstInstance {!m_peer->isClient()}
{
connect(m_peer, &QtLocalPeer::messageReceived, this, &ApplicationInstanceManager::messageReceived);
#ifdef Q_OS_WIN
auto sharedMem = new QSharedMemory {appId + QLatin1String {"-shared-memory-key"}, this};
const QString sharedMemoryKey = instancePath + QLatin1String {"/shared-memory"};
auto sharedMem = new QSharedMemory {sharedMemoryKey, this};
if (m_isFirstInstance)
{
// First instance creates shared memory and store PID
@@ -79,8 +82,3 @@ bool ApplicationInstanceManager::sendMessage(const QString &message, const int t
{
return m_peer->sendMessage(message, timeout);
}
QString ApplicationInstanceManager::appId() const
{
return m_peer->applicationId();
}

View File

@@ -32,16 +32,15 @@
class QtLocalPeer;
class ApplicationInstanceManager : public QObject
class ApplicationInstanceManager final : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(ApplicationInstanceManager)
public:
explicit ApplicationInstanceManager(const QString &appId, QObject *parent = nullptr);
explicit ApplicationInstanceManager(const QString &instancePath, QObject *parent = nullptr);
bool isFirstInstance() const;
QString appId() const;
public slots:
bool sendMessage(const QString &message, int timeout = 5000);

View File

@@ -188,7 +188,6 @@ int main(int argc, char *argv[])
#ifndef DISABLE_GUI
if (!userAgreesWithLegalNotice())
return EXIT_SUCCESS;
#elif defined(Q_OS_WIN)
if (_isatty(_fileno(stdin))
&& _isatty(_fileno(stdout))
@@ -201,6 +200,8 @@ int main(int argc, char *argv[])
&& !userAgreesWithLegalNotice())
return EXIT_SUCCESS;
#endif
setCurrentMigrationVersion();
}
// Check if qBittorrent is already running for this user

View File

@@ -68,20 +68,16 @@
#include "qtlocalpeer.h"
#if defined(Q_OS_UNIX)
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <QtGlobal>
#if defined(Q_OS_WIN)
#include <Windows.h>
#endif
#include <QCoreApplication>
#include <QDataStream>
#include <QDir>
#include <QFileInfo>
#include <QLocalServer>
#include <QLocalSocket>
#include <QRegularExpression>
#include "base/utils/misc.h"
namespace QtLP_Private
{
@@ -94,75 +90,49 @@ namespace QtLP_Private
#endif
}
const char* QtLocalPeer::ack = "ack";
const char ACK[] = "ack";
QtLocalPeer::QtLocalPeer(QObject *parent, const QString &appId)
QtLocalPeer::QtLocalPeer(const QString &path, QObject *parent)
: QObject(parent)
, id(appId)
, m_socketName(path + QLatin1String("/ipc-socket"))
, m_server(new QLocalServer(this))
{
QString prefix = id;
if (id.isEmpty())
{
id = QCoreApplication::applicationFilePath();
#if defined(Q_OS_WIN)
id = id.toLower();
#endif
prefix = id.section(QLatin1Char('/'), -1);
}
prefix.remove(QRegularExpression("[^a-zA-Z]"));
prefix.truncate(6);
m_server->setSocketOptions(QLocalServer::UserAccessOption);
QByteArray idc = id.toUtf8();
quint16 idNum = qChecksum(idc.constData(), idc.size());
socketName = QLatin1String("qtsingleapp-") + prefix
+ QLatin1Char('-') + QString::number(idNum, 16);
#if defined(Q_OS_WIN)
DWORD sessionId = 0;
::ProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
socketName += (QLatin1Char('-') + QString::number(sessionId, 16));
#else
socketName += (QLatin1Char('-') + QString::number(::getuid(), 16));
#endif
server = new QLocalServer(this);
server->setSocketOptions(QLocalServer::UserAccessOption);
QString lockName = QDir(QDir::tempPath()).absolutePath()
+ QLatin1Char('/') + socketName
+ QLatin1String("-lockfile");
lockFile.setFileName(lockName);
lockFile.open(QIODevice::ReadWrite);
m_lockFile.setFileName(path + QLatin1String("/lockfile"));
m_lockFile.open(QIODevice::ReadWrite);
}
QtLocalPeer::~QtLocalPeer()
{
if (!isClient())
{
lockFile.unlock();
lockFile.remove();
m_lockFile.unlock();
m_lockFile.remove();
}
}
bool QtLocalPeer::isClient()
{
if (lockFile.isLocked())
if (m_lockFile.isLocked())
return false;
if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false))
if (!m_lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false))
return true;
bool res = server->listen(socketName);
bool res = m_server->listen(m_socketName);
#if defined(Q_OS_UNIX)
// ### Workaround
if (!res && server->serverError() == QAbstractSocket::AddressInUseError)
if (!res && m_server->serverError() == QAbstractSocket::AddressInUseError)
{
QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName);
res = server->listen(socketName);
QFile::remove(m_socketName);
res = m_server->listen(m_socketName);
}
#endif
if (!res)
qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString()));
connect(server, &QLocalServer::newConnection, this, &QtLocalPeer::receiveConnection);
qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qUtf8Printable(m_server->errorString()));
connect(m_server, &QLocalServer::newConnection, this, &QtLocalPeer::receiveConnection);
return false;
}
@@ -176,7 +146,7 @@ bool QtLocalPeer::sendMessage(const QString &message, const int timeout)
for(int i = 0; i < 2; i++)
{
// Try twice, in case the other instance is just starting up
socket.connectToServer(socketName);
socket.connectToServer(m_socketName);
connOk = socket.waitForConnected(timeout/2);
if (connOk || i)
break;
@@ -199,19 +169,14 @@ bool QtLocalPeer::sendMessage(const QString &message, const int timeout)
{
res &= socket.waitForReadyRead(timeout); // wait for ack
if (res)
res &= (socket.read(qstrlen(ack)) == ack);
res &= (socket.read(qstrlen(ACK)) == ACK);
}
return res;
}
QString QtLocalPeer::applicationId() const
{
return id;
}
void QtLocalPeer::receiveConnection()
{
QLocalSocket* socket = server->nextPendingConnection();
QLocalSocket *socket = m_server->nextPendingConnection();
if (!socket)
return;
@@ -255,7 +220,7 @@ void QtLocalPeer::receiveConnection()
return;
}
QString message(QString::fromUtf8(uMsg));
socket->write(ack, qstrlen(ack));
socket->write(ACK, qstrlen(ACK));
socket->waitForBytesWritten(1000);
socket->waitForDisconnected(1000); // make sure client reads ack
delete socket;

View File

@@ -68,34 +68,32 @@
#pragma once
#include <QString>
#include "qtlockedfile.h"
class QLocalServer;
class QtLocalPeer : public QObject
class QtLocalPeer final : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(QtLocalPeer)
public:
QtLocalPeer(QObject *parent = nullptr, const QString &appId = QString());
QtLocalPeer(const QString &path, QObject *parent = nullptr);
~QtLocalPeer() override;
bool isClient();
bool sendMessage(const QString &message, int timeout);
QString applicationId() const;
signals:
void messageReceived(const QString &message);
protected slots:
private slots:
void receiveConnection();
protected:
QString id;
QString socketName;
QLocalServer *server = nullptr;
QtLP_Private::QtLockedFile lockFile;
private:
static const char* ack;
QString m_socketName;
QLocalServer *m_server = nullptr;
QtLP_Private::QtLockedFile m_lockFile;
};

View File

@@ -108,11 +108,7 @@
\sa QFile::QFile()
*/
QtLockedFile::QtLockedFile()
: QFile()
{
m_lock_mode = NoLock;
}
QtLockedFile::QtLockedFile() = default;
/*!
Constructs an unlocked QtLockedFile object with file \a name. This
@@ -124,7 +120,6 @@ QtLockedFile::QtLockedFile()
QtLockedFile::QtLockedFile(const QString &name)
: QFile(name)
{
m_lock_mode = NoLock;
}
/*!
@@ -142,7 +137,8 @@ QtLockedFile::QtLockedFile(const QString &name)
*/
bool QtLockedFile::open(const OpenMode mode)
{
if (mode & QIODevice::Truncate) {
if (mode & QIODevice::Truncate)
{
qWarning("QtLockedFile::open(): Truncate mode not allowed.");
return false;
}
@@ -157,7 +153,7 @@ bool QtLockedFile::open(const OpenMode mode)
*/
bool QtLockedFile::isLocked() const
{
return m_lock_mode != NoLock;
return m_lockMode != NoLock;
}
/*!
@@ -168,7 +164,7 @@ bool QtLockedFile::isLocked() const
*/
QtLockedFile::LockMode QtLockedFile::lockMode() const
{
return m_lock_mode;
return m_lockMode;
}
/*!

View File

@@ -71,6 +71,7 @@
#include <QFile>
#ifdef Q_OS_WIN
#include <QString>
#include <QVector>
#endif
@@ -100,14 +101,14 @@ namespace QtLP_Private
private:
#ifdef Q_OS_WIN
Qt::HANDLE getMutexHandle(int idx, bool doCreate);
bool waitMutex(Qt::HANDLE mutex, bool doBlock);
bool waitMutex(Qt::HANDLE mutex, bool doBlock) const;
Qt::HANDLE wmutex = nullptr;
Qt::HANDLE rmutex = nullptr;
QVector<Qt::HANDLE> rmutexes;
QString mutexname;
Qt::HANDLE m_writeMutex = nullptr;
Qt::HANDLE m_readMutex = nullptr;
QVector<Qt::HANDLE> m_readMutexes;
QString m_mutexName;
#endif
LockMode m_lock_mode;
LockMode m_lockMode = NoLock;
};
}

View File

@@ -73,9 +73,10 @@
#include <string.h>
#include <unistd.h>
bool QtLockedFile::lock(LockMode mode, bool block)
bool QtLockedFile::lock(const LockMode mode, const bool block)
{
if (!isOpen()) {
if (!isOpen())
{
qWarning("QtLockedFile::lock(): file is not opened");
return false;
}
@@ -83,10 +84,10 @@ bool QtLockedFile::lock(LockMode mode, bool block)
if (mode == NoLock)
return unlock();
if (mode == m_lock_mode)
if (mode == m_lockMode)
return true;
if (m_lock_mode != NoLock)
if (m_lockMode != NoLock)
unlock();
struct flock fl;
@@ -97,19 +98,21 @@ bool QtLockedFile::lock(LockMode mode, bool block)
int cmd = block ? F_SETLKW : F_SETLK;
int ret = fcntl(handle(), cmd, &fl);
if (ret == -1) {
if (ret == -1)
{
if (errno != EINTR && errno != EAGAIN)
qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
return false;
}
m_lock_mode = mode;
m_lockMode = mode;
return true;
}
bool QtLockedFile::unlock()
{
if (!isOpen()) {
if (!isOpen())
{
qWarning("QtLockedFile::unlock(): file is not opened");
return false;
}
@@ -124,12 +127,13 @@ bool QtLockedFile::unlock()
fl.l_type = F_UNLCK;
int ret = fcntl(handle(), F_SETLKW, &fl);
if (ret == -1) {
if (ret == -1)
{
qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
return false;
}
m_lock_mode = NoLock;
m_lockMode = NoLock;
return true;
}

View File

@@ -70,64 +70,73 @@
#include <QFileInfo>
#define MUTEX_PREFIX "QtLockedFile mutex "
#include "base/global.h"
// Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS
#define MAX_READERS MAXIMUM_WAIT_OBJECTS
const int MAX_READERS = MAXIMUM_WAIT_OBJECTS;
#define QT_WA(unicode, ansi) unicode
Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate)
Qt::HANDLE QtLockedFile::getMutexHandle(const int idx, const bool doCreate)
{
if (mutexname.isEmpty()) {
if (m_mutexName.isEmpty())
{
QFileInfo fi(*this);
mutexname = QString::fromLatin1(MUTEX_PREFIX)
+ fi.absoluteFilePath().toLower();
m_mutexName = QString::fromLatin1("QtLockedFile mutex ") + fi.absoluteFilePath().toLower();
}
QString mname(mutexname);
QString mname = m_mutexName;
if (idx >= 0)
mname += QString::number(idx);
Qt::HANDLE mutex;
if (doCreate) {
QT_WA( { mutex = CreateMutexW(NULL, FALSE, reinterpret_cast<const TCHAR *>(mname.utf16())); },
{ mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); } );
if (!mutex) {
if (doCreate)
{
const Qt::HANDLE mutex = ::CreateMutexW(NULL, FALSE, reinterpret_cast<const TCHAR *>(mname.utf16()));
if (!mutex)
{
qErrnoWarning("QtLockedFile::lock(): CreateMutex failed");
return 0;
return nullptr;
}
return mutex;
}
else {
QT_WA( { mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, reinterpret_cast<const TCHAR *>(mname.utf16())); },
{ mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); } );
if (!mutex) {
else
{
const Qt::HANDLE mutex = ::OpenMutexW((SYNCHRONIZE | MUTEX_MODIFY_STATE), FALSE, reinterpret_cast<const TCHAR *>(mname.utf16()));
if (!mutex)
{
if (GetLastError() != ERROR_FILE_NOT_FOUND)
qErrnoWarning("QtLockedFile::lock(): OpenMutex failed");
return 0;
return nullptr;
}
return mutex;
}
return mutex;
return nullptr;
}
bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock)
bool QtLockedFile::waitMutex(const Qt::HANDLE mutex, const bool doBlock) const
{
Q_ASSERT(mutex);
DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0);
switch (res) {
const DWORD res = ::WaitForSingleObject(mutex, (doBlock ? INFINITE : 0));
switch (res)
{
case WAIT_OBJECT_0:
case WAIT_ABANDONED:
return true;
break;
case WAIT_TIMEOUT:
break;
return false;
default:
qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed");
break;
}
return false;
}
bool QtLockedFile::lock(LockMode mode, bool block)
bool QtLockedFile::lock(const LockMode mode, const bool block)
{
if (!isOpen()) {
if (!isOpen())
{
qWarning("QtLockedFile::lock(): file is not opened");
return false;
}
@@ -135,72 +144,85 @@ bool QtLockedFile::lock(LockMode mode, bool block)
if (mode == NoLock)
return unlock();
if (mode == m_lock_mode)
if (mode == m_lockMode)
return true;
if (m_lock_mode != NoLock)
if (m_lockMode != NoLock)
unlock();
if (!wmutex && !(wmutex = getMutexHandle(-1, true)))
if (!m_writeMutex && !(m_writeMutex = getMutexHandle(-1, true)))
return false;
if (!waitMutex(wmutex, block))
if (!waitMutex(m_writeMutex, block))
return false;
if (mode == ReadLock) {
if (mode == ReadLock)
{
int idx = 0;
for (; idx < MAX_READERS; idx++) {
rmutex = getMutexHandle(idx, false);
if (!rmutex || waitMutex(rmutex, false))
for (; idx < MAX_READERS; ++idx)
{
m_readMutex = getMutexHandle(idx, false);
if (!m_readMutex || waitMutex(m_readMutex, false))
break;
CloseHandle(rmutex);
::CloseHandle(m_readMutex);
}
bool ok = true;
if (idx >= MAX_READERS) {
if (idx >= MAX_READERS)
{
qWarning("QtLockedFile::lock(): too many readers");
rmutex = 0;
m_readMutex = nullptr;
ok = false;
}
else if (!rmutex) {
rmutex = getMutexHandle(idx, true);
if (!rmutex || !waitMutex(rmutex, false))
else if (!m_readMutex)
{
m_readMutex = getMutexHandle(idx, true);
if (!m_readMutex || !waitMutex(m_readMutex, false))
ok = false;
}
if (!ok && rmutex) {
CloseHandle(rmutex);
rmutex = 0;
if (!ok && m_readMutex)
{
::CloseHandle(m_readMutex);
m_readMutex = nullptr;
}
ReleaseMutex(wmutex);
::ReleaseMutex(m_writeMutex);
if (!ok)
return false;
}
else {
Q_ASSERT(rmutexes.isEmpty());
for (int i = 0; i < MAX_READERS; i++) {
Qt::HANDLE mutex = getMutexHandle(i, false);
else
{
Q_ASSERT(m_readMutexes.isEmpty());
for (int i = 0; i < MAX_READERS; ++i)
{
const Qt::HANDLE mutex = getMutexHandle(i, false);
if (mutex)
rmutexes.append(mutex);
m_readMutexes.append(mutex);
}
if (rmutexes.size()) {
DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(),
TRUE, block ? INFINITE : 0);
if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) {
if (m_readMutexes.size())
{
const DWORD res = ::WaitForMultipleObjects(m_readMutexes.size(), m_readMutexes.constData(),
TRUE, (block ? INFINITE : 0));
if ((res != WAIT_OBJECT_0) && (res != WAIT_ABANDONED))
{
if (res != WAIT_TIMEOUT)
qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed");
m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky
m_lockMode = WriteLock; // trick unlock() to clean up - semiyucky
unlock();
return false;
}
}
}
m_lock_mode = mode;
m_lockMode = mode;
return true;
}
bool QtLockedFile::unlock()
{
if (!isOpen()) {
if (!isOpen())
{
qWarning("QtLockedFile::unlock(): file is not opened");
return false;
}
@@ -208,21 +230,24 @@ bool QtLockedFile::unlock()
if (!isLocked())
return true;
if (m_lock_mode == ReadLock) {
ReleaseMutex(rmutex);
CloseHandle(rmutex);
rmutex = 0;
if (m_lockMode == ReadLock)
{
::ReleaseMutex(m_readMutex);
::CloseHandle(m_readMutex);
m_readMutex = nullptr;
}
else {
foreach(Qt::HANDLE mutex, rmutexes) {
ReleaseMutex(mutex);
CloseHandle(mutex);
else
{
for (const Qt::HANDLE &mutex : asConst(m_readMutexes))
{
::ReleaseMutex(mutex);
::CloseHandle(mutex);
}
rmutexes.clear();
ReleaseMutex(wmutex);
m_readMutexes.clear();
::ReleaseMutex(m_writeMutex);
}
m_lock_mode = QtLockedFile::NoLock;
m_lockMode = QtLockedFile::NoLock;
return true;
}
@@ -230,6 +255,6 @@ QtLockedFile::~QtLockedFile()
{
if (isOpen())
unlock();
if (wmutex)
CloseHandle(wmutex);
if (m_writeMutex)
::CloseHandle(m_writeMutex);
}

View File

@@ -29,18 +29,23 @@
#include "upgrade.h"
#include <QMetaEnum>
#include <QVector>
#include "base/bittorrent/torrentcontentlayout.h"
#include "base/logger.h"
#include "base/net/proxyconfigurationmanager.h"
#include "base/preferences.h"
#include "base/profile.h"
#include "base/settingsstorage.h"
#include "base/settingvalue.h"
#include "base/utils/fs.h"
#include "base/utils/io.h"
#include "base/utils/string.h"
namespace
{
const int MIGRATION_VERSION = 3;
const char MIGRATION_VERSION_KEY[] = "Meta/MigrationVersion";
void exportWebUIHttpsFiles()
{
const auto migrate = [](const QString &oldKey, const QString &newKey, const QString &savePath)
@@ -70,10 +75,10 @@ namespace
const QString configPath {specialFolderLocation(SpecialFolder::Config)};
migrate(QLatin1String("Preferences/WebUI/HTTPS/Certificate")
, QLatin1String("Preferences/WebUI/HTTPS/CertificatePath")
, Utils::Fs::toNativePath(configPath + QLatin1String("WebUICertificate.crt")));
, Utils::Fs::toNativePath(configPath + QLatin1String("/WebUICertificate.crt")));
migrate(QLatin1String("Preferences/WebUI/HTTPS/Key")
, QLatin1String("Preferences/WebUI/HTTPS/KeyPath")
, Utils::Fs::toNativePath(configPath + QLatin1String("WebUIPrivateKey.pem")));
, Utils::Fs::toNativePath(configPath + QLatin1String("/WebUIPrivateKey.pem")));
}
void upgradeTorrentContentLayout()
@@ -110,16 +115,293 @@ namespace
settingsStorage->removeValue(oldKey);
}
}
void upgradeSchedulerDaysSettings()
{
auto *settingsStorage = SettingsStorage::instance();
const auto key = QString::fromLatin1("Preferences/Scheduler/days");
const auto value = settingsStorage->loadValue<QString>(key);
bool ok = false;
const auto number = value.toInt(&ok);
if (ok)
{
switch (number)
{
case 0:
settingsStorage->storeValue(key, Scheduler::Days::EveryDay);
break;
case 1:
settingsStorage->storeValue(key, Scheduler::Days::Weekday);
break;
case 2:
settingsStorage->storeValue(key, Scheduler::Days::Weekend);
break;
case 3:
settingsStorage->storeValue(key, Scheduler::Days::Monday);
break;
case 4:
settingsStorage->storeValue(key, Scheduler::Days::Tuesday);
break;
case 5:
settingsStorage->storeValue(key, Scheduler::Days::Wednesday);
break;
case 6:
settingsStorage->storeValue(key, Scheduler::Days::Thursday);
break;
case 7:
settingsStorage->storeValue(key, Scheduler::Days::Friday);
break;
case 8:
settingsStorage->storeValue(key, Scheduler::Days::Saturday);
break;
case 9:
settingsStorage->storeValue(key, Scheduler::Days::Sunday);
break;
default:
LogMsg(QObject::tr("Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
.arg(key, QString::number(number)), Log::WARNING);
settingsStorage->removeValue(key);
break;
}
}
}
void upgradeDNSServiceSettings()
{
auto *settingsStorage = SettingsStorage::instance();
const auto key = QString::fromLatin1("Preferences/DynDNS/Service");
const auto value = settingsStorage->loadValue<QString>(key);
bool ok = false;
const auto number = value.toInt(&ok);
if (ok)
{
switch (number)
{
case -1:
settingsStorage->storeValue(key, DNS::Service::None);
break;
case 0:
settingsStorage->storeValue(key, DNS::Service::DynDNS);
break;
case 1:
settingsStorage->storeValue(key, DNS::Service::NoIP);
break;
default:
LogMsg(QObject::tr("Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
.arg(key, QString::number(number)), Log::WARNING);
settingsStorage->removeValue(key);
break;
}
}
}
void upgradeTrayIconStyleSettings()
{
auto *settingsStorage = SettingsStorage::instance();
const auto key = QString::fromLatin1("Preferences/Advanced/TrayIconStyle");
const auto value = settingsStorage->loadValue<QString>(key);
bool ok = false;
const auto number = value.toInt(&ok);
if (ok)
{
switch (number)
{
case 0:
settingsStorage->storeValue(key, TrayIcon::Style::Normal);
break;
case 1:
settingsStorage->storeValue(key, TrayIcon::Style::MonoDark);
break;
case 2:
settingsStorage->storeValue(key, TrayIcon::Style::MonoLight);
break;
default:
LogMsg(QObject::tr("Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
.arg(key, QString::number(number)), Log::WARNING);
settingsStorage->removeValue(key);
break;
}
}
}
void migrateSettingKeys()
{
struct KeyMapping
{
QString newKey;
QString oldKey;
};
const KeyMapping mappings[] =
{
{"AddNewTorrentDialog/Enabled", "Preferences/Downloads/NewAdditionDialog"},
{"AddNewTorrentDialog/Expanded", "AddNewTorrentDialog/expanded"},
{"AddNewTorrentDialog/Position", "AddNewTorrentDialog/y"},
{"AddNewTorrentDialog/SavePathHistory", "TorrentAdditionDlg/save_path_history"},
{"AddNewTorrentDialog/TopLevel", "Preferences/Downloads/NewAdditionDialogFront"},
{"AddNewTorrentDialog/TreeHeaderState", "AddNewTorrentDialog/qt5/treeHeaderState"},
{"AddNewTorrentDialog/Width", "AddNewTorrentDialog/width"},
{"BitTorrent/Session/AddExtensionToIncompleteFiles", "Preferences/Downloads/UseIncompleteExtension"},
{"BitTorrent/Session/AdditionalTrackers", "Preferences/Bittorrent/TrackersList"},
{"BitTorrent/Session/AddTorrentPaused", "Preferences/Downloads/StartInPause"},
{"BitTorrent/Session/AddTrackersEnabled", "Preferences/Bittorrent/AddTrackers"},
{"BitTorrent/Session/AlternativeGlobalDLSpeedLimit", "Preferences/Connection/GlobalDLLimitAlt"},
{"BitTorrent/Session/AlternativeGlobalUPSpeedLimit", "Preferences/Connection/GlobalUPLimitAlt"},
{"BitTorrent/Session/AnnounceIP", "Preferences/Connection/InetAddress"},
{"BitTorrent/Session/AnnounceToAllTrackers", "Preferences/Advanced/AnnounceToAllTrackers"},
{"BitTorrent/Session/AnonymousModeEnabled", "Preferences/Advanced/AnonymousMode"},
{"BitTorrent/Session/BandwidthSchedulerEnabled", "Preferences/Scheduler/Enabled"},
{"BitTorrent/Session/DefaultSavePath", "Preferences/Downloads/SavePath"},
{"BitTorrent/Session/DHTEnabled", "Preferences/Bittorrent/DHT"},
{"BitTorrent/Session/DiskCacheSize", "Preferences/Downloads/DiskWriteCacheSize"},
{"BitTorrent/Session/DiskCacheTTL", "Preferences/Downloads/DiskWriteCacheTTL"},
{"BitTorrent/Session/Encryption", "Preferences/Bittorrent/Encryption"},
{"BitTorrent/Session/FinishedTorrentExportDirectory", "Preferences/Downloads/FinishedTorrentExportDir"},
{"BitTorrent/Session/ForceProxy", "Preferences/Connection/ProxyForce"},
{"BitTorrent/Session/GlobalDLSpeedLimit", "Preferences/Connection/GlobalDLLimit"},
{"BitTorrent/Session/GlobalMaxRatio", "Preferences/Bittorrent/MaxRatio"},
{"BitTorrent/Session/GlobalUPSpeedLimit", "Preferences/Connection/GlobalUPLimit"},
{"BitTorrent/Session/IgnoreLimitsOnLAN", "Preferences/Advanced/IgnoreLimitsLAN"},
{"BitTorrent/Session/IgnoreSlowTorrentsForQueueing", "Preferences/Queueing/IgnoreSlowTorrents"},
{"BitTorrent/Session/IncludeOverheadInLimits", "Preferences/Advanced/IncludeOverhead"},
{"BitTorrent/Session/Interface", "Preferences/Connection/Interface"},
{"BitTorrent/Session/InterfaceAddress", "Preferences/Connection/InterfaceAddress"},
{"BitTorrent/Session/InterfaceName", "Preferences/Connection/InterfaceName"},
{"BitTorrent/Session/IPFilter", "Preferences/IPFilter/File"},
{"BitTorrent/Session/IPFilteringEnabled", "Preferences/IPFilter/Enabled"},
{"BitTorrent/Session/LSDEnabled", "Preferences/Bittorrent/LSD"},
{"BitTorrent/Session/MaxActiveDownloads", "Preferences/Queueing/MaxActiveDownloads"},
{"BitTorrent/Session/MaxActiveTorrents", "Preferences/Queueing/MaxActiveTorrents"},
{"BitTorrent/Session/MaxActiveUploads", "Preferences/Queueing/MaxActiveUploads"},
{"BitTorrent/Session/MaxConnections", "Preferences/Bittorrent/MaxConnecs"},
{"BitTorrent/Session/MaxConnectionsPerTorrent", "Preferences/Bittorrent/MaxConnecsPerTorrent"},
{"BitTorrent/Session/MaxHalfOpenConnections", "Preferences/Connection/MaxHalfOpenConnec"},
{"BitTorrent/Session/MaxRatioAction", "Preferences/Bittorrent/MaxRatioAction"},
{"BitTorrent/Session/MaxUploads", "Preferences/Bittorrent/MaxUploads"},
{"BitTorrent/Session/MaxUploadsPerTorrent", "Preferences/Bittorrent/MaxUploadsPerTorrent"},
{"BitTorrent/Session/OutgoingPortsMax", "Preferences/Advanced/OutgoingPortsMax"},
{"BitTorrent/Session/OutgoingPortsMin", "Preferences/Advanced/OutgoingPortsMin"},
{"BitTorrent/Session/PeXEnabled", "Preferences/Bittorrent/PeX"},
{"BitTorrent/Session/Port", "Preferences/Connection/PortRangeMin"},
{"BitTorrent/Session/Preallocation", "Preferences/Downloads/PreAllocation"},
{"BitTorrent/Session/ProxyPeerConnections", "Preferences/Connection/ProxyPeerConnections"},
{"BitTorrent/Session/QueueingSystemEnabled", "Preferences/Queueing/QueueingEnabled"},
{"BitTorrent/Session/RefreshInterval", "Preferences/General/RefreshInterval"},
{"BitTorrent/Session/SaveResumeDataInterval", "Preferences/Downloads/SaveResumeDataInterval"},
{"BitTorrent/Session/SuperSeedingEnabled", "Preferences/Advanced/SuperSeeding"},
{"BitTorrent/Session/TempPath", "Preferences/Downloads/TempPath"},
{"BitTorrent/Session/TempPathEnabled", "Preferences/Downloads/TempPathEnabled"},
{"BitTorrent/Session/TorrentExportDirectory", "Preferences/Downloads/TorrentExportDir"},
{"BitTorrent/Session/TrackerFilteringEnabled", "Preferences/IPFilter/FilterTracker"},
{"BitTorrent/Session/UseAlternativeGlobalSpeedLimit", "Preferences/Connection/alt_speeds_on"},
{"BitTorrent/Session/UseOSCache", "Preferences/Advanced/osCache"},
{"BitTorrent/Session/UseRandomPort", "Preferences/General/UseRandomPort"},
{"BitTorrent/Session/uTPEnabled", "Preferences/Bittorrent/uTP"},
{"BitTorrent/Session/uTPRateLimited", "Preferences/Bittorrent/uTP_rate_limited"},
{"BitTorrent/TrackerEnabled", "Preferences/Advanced/trackerEnabled"},
{"Network/PortForwardingEnabled", "Preferences/Connection/UPnP"},
{"Network/Proxy/Authentication", "Preferences/Connection/Proxy/Authentication"},
{"Network/Proxy/IP", "Preferences/Connection/Proxy/IP"},
{"Network/Proxy/OnlyForTorrents", "Preferences/Connection/ProxyOnlyForTorrents"},
{"Network/Proxy/Password", "Preferences/Connection/Proxy/Password"},
{"Network/Proxy/Port", "Preferences/Connection/Proxy/Port"},
{"Network/Proxy/Type", "Preferences/Connection/ProxyType"},
{"Network/Proxy/Username", "Preferences/Connection/Proxy/Username"},
{"State/BannedIPs", "Preferences/IPFilter/BannedIPs"}
};
auto *settingsStorage = SettingsStorage::instance();
for (const KeyMapping &mapping : mappings)
{
if (settingsStorage->hasKey(mapping.oldKey))
{
const auto value = settingsStorage->loadValue<QVariant>(mapping.oldKey);
settingsStorage->storeValue(mapping.newKey, value);
// TODO: Remove oldKey after ~v4.4.3 and bump migration version
}
}
}
void migrateProxySettingsEnum()
{
auto *settingsStorage = SettingsStorage::instance();
const auto key = QString::fromLatin1("Network/Proxy/Type");
const auto value = settingsStorage->loadValue<QString>(key);
bool ok = false;
const auto number = value.toInt(&ok);
if (ok)
{
switch (number)
{
case 0:
settingsStorage->storeValue(key, Net::ProxyType::None);
break;
case 1:
settingsStorage->storeValue(key, Net::ProxyType::HTTP);
break;
case 2:
settingsStorage->storeValue(key, Net::ProxyType::SOCKS5);
break;
case 3:
settingsStorage->storeValue(key, Net::ProxyType::HTTP_PW);
break;
case 4:
settingsStorage->storeValue(key, Net::ProxyType::SOCKS5_PW);
break;
case 5:
settingsStorage->storeValue(key, Net::ProxyType::SOCKS4);
break;
default:
LogMsg(QObject::tr("Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
.arg(key, QString::number(number)), Log::WARNING);
settingsStorage->removeValue(key);
break;
}
}
}
}
bool upgrade(const bool /*ask*/)
{
exportWebUIHttpsFiles();
upgradeTorrentContentLayout();
upgradeListenPortSettings();
CachedSettingValue<int> version {MIGRATION_VERSION_KEY, 0};
if (version != MIGRATION_VERSION)
{
if (version < 1)
{
exportWebUIHttpsFiles();
upgradeTorrentContentLayout();
upgradeListenPortSettings();
upgradeSchedulerDaysSettings();
upgradeDNSServiceSettings();
upgradeTrayIconStyleSettings();
}
if (version < 2)
migrateSettingKeys();
if (version < 3)
migrateProxySettingsEnum();
version = MIGRATION_VERSION;
}
return true;
}
void setCurrentMigrationVersion()
{
SettingsStorage::instance()->storeValue(QLatin1String(MIGRATION_VERSION_KEY), MIGRATION_VERSION);
}
void handleChangedDefaults(const DefaultPreferencesMode mode)
{
struct DefaultValue
@@ -129,15 +411,18 @@ void handleChangedDefaults(const DefaultPreferencesMode mode)
QVariant current;
};
const QVector<DefaultValue> changedDefaults
const DefaultValue changedDefaults[] =
{
{QLatin1String {"BitTorrent/Session/QueueingSystemEnabled"}, true, false}
};
SettingsStorage *settingsStorage {SettingsStorage::instance()};
for (auto it = changedDefaults.cbegin(); it != changedDefaults.cend(); ++it)
auto *settingsStorage = SettingsStorage::instance();
for (const DefaultValue &value : changedDefaults)
{
if (settingsStorage->loadValue<QVariant>(it->name).isNull())
settingsStorage->storeValue(it->name, (mode == DefaultPreferencesMode::Legacy ? it->legacy : it->current));
if (!settingsStorage->hasKey(value.name))
{
settingsStorage->storeValue(value.name
, (mode == DefaultPreferencesMode::Legacy ? value.legacy : value.current));
}
}
}

View File

@@ -36,3 +36,4 @@ enum class DefaultPreferencesMode
void handleChangedDefaults(DefaultPreferencesMode mode);
bool upgrade(bool ask = true);
void setCurrentMigrationVersion();

View File

@@ -8,6 +8,7 @@ add_library(qbt_base STATIC
bittorrent/bandwidthscheduler.h
bittorrent/bencoderesumedatastorage.h
bittorrent/cachestatus.h
bittorrent/categoryoptions.h
bittorrent/common.h
bittorrent/customstorage.h
bittorrent/dbresumedatastorage.h
@@ -100,6 +101,7 @@ add_library(qbt_base STATIC
bittorrent/abstractfilestorage.cpp
bittorrent/bandwidthscheduler.cpp
bittorrent/bencoderesumedatastorage.cpp
bittorrent/categoryoptions.cpp
bittorrent/customstorage.cpp
bittorrent/dbresumedatastorage.cpp
bittorrent/downloadpriority.cpp
@@ -116,6 +118,7 @@ add_library(qbt_base STATIC
bittorrent/speedmonitor.cpp
bittorrent/statistics.cpp
bittorrent/torrent.cpp
bittorrent/torrentcontentlayout.cpp
bittorrent/torrentcreatorthread.cpp
bittorrent/torrentimpl.cpp
bittorrent/torrentinfo.cpp

View File

@@ -7,6 +7,7 @@ HEADERS += \
$$PWD/bittorrent/bandwidthscheduler.h \
$$PWD/bittorrent/bencoderesumedatastorage.h \
$$PWD/bittorrent/cachestatus.h \
$$PWD/bittorrent/categoryoptions.h \
$$PWD/bittorrent/common.h \
$$PWD/bittorrent/customstorage.h \
$$PWD/bittorrent/downloadpriority.h \
@@ -100,6 +101,7 @@ SOURCES += \
$$PWD/bittorrent/abstractfilestorage.cpp \
$$PWD/bittorrent/bandwidthscheduler.cpp \
$$PWD/bittorrent/bencoderesumedatastorage.cpp \
$$PWD/bittorrent/categoryoptions.cpp \
$$PWD/bittorrent/customstorage.cpp \
$$PWD/bittorrent/dbresumedatastorage.cpp \
$$PWD/bittorrent/downloadpriority.cpp \
@@ -116,6 +118,7 @@ SOURCES += \
$$PWD/bittorrent/speedmonitor.cpp \
$$PWD/bittorrent/statistics.cpp \
$$PWD/bittorrent/torrent.cpp \
$$PWD/bittorrent/torrentcontentlayout.cpp \
$$PWD/bittorrent/torrentcreatorthread.cpp \
$$PWD/bittorrent/torrentimpl.cpp \
$$PWD/bittorrent/torrentinfo.cpp \

View File

@@ -48,11 +48,13 @@ namespace BitTorrent
QString category;
TagSet tags;
QString savePath;
bool disableTempPath = false; // e.g. for imported torrents
std::optional<bool> useDownloadPath;
QString downloadPath;
bool sequential = false;
bool firstLastPiecePriority = false;
bool addForced = false;
std::optional<bool> addPaused;
QStringList filePaths; // used if TorrentInfo is set
QVector<DownloadPriority> filePriorities; // used if TorrentInfo is set
bool skipChecking = false;
std::optional<BitTorrent::TorrentContentLayout> contentLayout;

View File

@@ -61,7 +61,7 @@ bool BandwidthScheduler::isTimeForAlternative() const
QTime start = pref->getSchedulerStartTime();
QTime end = pref->getSchedulerEndTime();
const QTime now = QTime::currentTime();
const int schedulerDays = pref->getSchedulerDays();
const Scheduler::Days schedulerDays = pref->getSchedulerDays();
const int day = QDate::currentDate().dayOfWeek();
bool alternative = false;
@@ -75,20 +75,34 @@ bool BandwidthScheduler::isTimeForAlternative() const
{
switch (schedulerDays)
{
case EVERY_DAY:
case Scheduler::Days::EveryDay:
alternative = !alternative;
break;
case WEEK_ENDS:
case Scheduler::Days::Monday:
case Scheduler::Days::Tuesday:
case Scheduler::Days::Wednesday:
case Scheduler::Days::Thursday:
case Scheduler::Days::Friday:
case Scheduler::Days::Saturday:
case Scheduler::Days::Sunday:
{
const int offset = static_cast<int>(Scheduler::Days::Monday) - 1;
const int dayOfWeek = static_cast<int>(schedulerDays) - offset;
if (day == dayOfWeek)
alternative = !alternative;
}
break;
case Scheduler::Days::Weekday:
if ((day >= 1) && (day <= 5))
alternative = !alternative;
break;
case Scheduler::Days::Weekend:
if ((day == 6) || (day == 7))
alternative = !alternative;
break;
case WEEK_DAYS:
if ((day != 6) && (day != 7))
alternative = !alternative;
break;
default:
if (day == (schedulerDays - 2))
alternative = !alternative;
Q_ASSERT(false);
break;
}
}

View File

@@ -173,12 +173,19 @@ std::optional<BitTorrent::LoadTorrentParams> BitTorrent::BencodeResumeDataStorag
torrentParams.restored = true;
torrentParams.category = fromLTString(root.dict_find_string_value("qBt-category"));
torrentParams.name = fromLTString(root.dict_find_string_value("qBt-name"));
torrentParams.savePath = Profile::instance()->fromPortablePath(
Utils::Fs::toUniformPath(fromLTString(root.dict_find_string_value("qBt-savePath"))));
torrentParams.hasSeedStatus = root.dict_find_int_value("qBt-seedStatus");
torrentParams.firstLastPiecePriority = root.dict_find_int_value("qBt-firstLastPiecePriority");
torrentParams.seedingTimeLimit = root.dict_find_int_value("qBt-seedingTimeLimit", Torrent::USE_GLOBAL_SEEDING_TIME);
torrentParams.savePath = Profile::instance()->fromPortablePath(
Utils::Fs::toUniformPath(fromLTString(root.dict_find_string_value("qBt-savePath"))));
torrentParams.useAutoTMM = torrentParams.savePath.isEmpty();
if (!torrentParams.useAutoTMM)
{
torrentParams.downloadPath = Profile::instance()->fromPortablePath(
Utils::Fs::toUniformPath(fromLTString(root.dict_find_string_value("qBt-downloadPath"))));
}
// TODO: The following code is deprecated. Replace with the commented one after several releases in 4.4.x.
// === BEGIN DEPRECATED CODE === //
const lt::bdecode_node contentLayoutNode = root.dict_find("qBt-contentLayout");
@@ -352,7 +359,6 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co
}
}
data["qBt-savePath"] = Profile::instance()->toPortablePath(resumeData.savePath).toStdString();
data["qBt-ratioLimit"] = static_cast<int>(resumeData.ratioLimit * 1000);
data["qBt-seedingTimeLimit"] = resumeData.seedingTimeLimit;
data["qBt-category"] = resumeData.category.toStdString();
@@ -362,6 +368,12 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co
data["qBt-contentLayout"] = Utils::String::fromEnum(resumeData.contentLayout).toStdString();
data["qBt-firstLastPiecePriority"] = resumeData.firstLastPiecePriority;
if (!resumeData.useAutoTMM)
{
data["qBt-savePath"] = Profile::instance()->toPortablePath(resumeData.savePath).toStdString();
data["qBt-downloadPath"] = Profile::instance()->toPortablePath(resumeData.downloadPath).toStdString();
}
const QString resumeFilepath = m_resumeDataDir.absoluteFilePath(QString::fromLatin1("%1.fastresume").arg(id.toString()));
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(resumeFilepath, data);
if (!result)

View File

@@ -0,0 +1,78 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#include "categoryoptions.h"
#include <QJsonObject>
#include <QJsonValue>
const QString OPTION_SAVEPATH {QStringLiteral("save_path")};
const QString OPTION_DOWNLOADPATH {QStringLiteral("download_path")};
BitTorrent::CategoryOptions BitTorrent::CategoryOptions::fromJSON(const QJsonObject &jsonObj)
{
CategoryOptions options;
options.savePath = jsonObj.value(OPTION_SAVEPATH).toString();
const QJsonValue downloadPathValue = jsonObj.value(OPTION_DOWNLOADPATH);
if (downloadPathValue.isBool())
options.downloadPath = {downloadPathValue.toBool(), {}};
else if (downloadPathValue.isString())
options.downloadPath = {true, downloadPathValue.toString()};
return options;
}
QJsonObject BitTorrent::CategoryOptions::toJSON() const
{
QJsonValue downloadPathValue = QJsonValue::Undefined;
if (downloadPath)
{
if (downloadPath->enabled)
downloadPathValue = downloadPath->path;
else
downloadPathValue = false;
}
return {
{OPTION_SAVEPATH, savePath},
{OPTION_DOWNLOADPATH, downloadPathValue}
};
}
bool BitTorrent::operator==(const BitTorrent::CategoryOptions::DownloadPathOption &left, const BitTorrent::CategoryOptions::DownloadPathOption &right)
{
return ((left.enabled == right.enabled)
&& (left.path == right.path));
}
bool BitTorrent::operator==(const BitTorrent::CategoryOptions &left, const BitTorrent::CategoryOptions &right)
{
return ((left.savePath == right.savePath)
&& (left.downloadPath == right.downloadPath));
}

View File

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

View File

@@ -28,6 +28,8 @@
#include "dbresumedatastorage.h"
#include <utility>
#include <libtorrent/bdecode.hpp>
#include <libtorrent/bencode.hpp>
#include <libtorrent/entry.hpp>
@@ -57,11 +59,13 @@ namespace
{
const char DB_CONNECTION_NAME[] = "ResumeDataStorage";
const int DB_VERSION = 1;
const int DB_VERSION = 2;
const char DB_TABLE_META[] = "meta";
const char DB_TABLE_TORRENTS[] = "torrents";
const char META_VERSION[] = "version";
struct Column
{
QString name;
@@ -80,6 +84,7 @@ namespace
const Column DB_COLUMN_CATEGORY = makeColumn("category");
const Column DB_COLUMN_TAGS = makeColumn("tags");
const Column DB_COLUMN_TARGET_SAVE_PATH = makeColumn("target_save_path");
const Column DB_COLUMN_DOWNLOAD_PATH = makeColumn("download_path");
const Column DB_COLUMN_CONTENT_LAYOUT = makeColumn("content_layout");
const Column DB_COLUMN_RATIO_LIMIT = makeColumn("ratio_limit");
const Column DB_COLUMN_SEEDING_TIME_LIMIT = makeColumn("seeding_time_limit");
@@ -109,42 +114,50 @@ namespace
return QString::fromLatin1("CREATE TABLE %1 (%2)").arg(quoted(tableName), items.join(QLatin1Char(',')));
}
QString makeInsertStatement(const QString &tableName, const QVector<Column> &columns)
std::pair<QString, QString> joinColumns(const QVector<Column> &columns)
{
QStringList names;
names.reserve(columns.size());
QStringList values;
values.reserve(columns.size());
int namesSize = columns.size();
int valuesSize = columns.size();
for (const Column &column : columns)
{
names.append(quoted(column.name));
values.append(column.placeholder);
namesSize += column.name.size() + 2;
valuesSize += column.placeholder.size();
}
const QString jointNames = names.join(QLatin1Char(','));
const QString jointValues = values.join(QLatin1Char(','));
QString names;
names.reserve(namesSize);
QString values;
values.reserve(valuesSize);
for (const Column &column : columns)
{
names.append(quoted(column.name) + QLatin1Char(','));
values.append(column.placeholder + QLatin1Char(','));
}
names.chop(1);
values.chop(1);
return std::make_pair(names, values);
}
QString makeInsertStatement(const QString &tableName, const QVector<Column> &columns)
{
const auto [names, values] = joinColumns(columns);
return QString::fromLatin1("INSERT INTO %1 (%2) VALUES (%3)")
.arg(quoted(tableName), jointNames, jointValues);
.arg(quoted(tableName), names, values);
}
QString makeUpdateStatement(const QString &tableName, const QVector<Column> &columns)
{
const auto [names, values] = joinColumns(columns);
return QString::fromLatin1("UPDATE %1 SET (%2) = (%3)")
.arg(quoted(tableName), names, values);
}
QString makeOnConflictUpdateStatement(const Column &constraint, const QVector<Column> &columns)
{
QStringList names;
names.reserve(columns.size());
QStringList values;
values.reserve(columns.size());
for (const Column &column : columns)
{
names.append(quoted(column.name));
values.append(column.placeholder);
}
const QString jointNames = names.join(QLatin1Char(','));
const QString jointValues = values.join(QLatin1Char(','));
const auto [names, values] = joinColumns(columns);
return QString::fromLatin1(" ON CONFLICT (%1) DO UPDATE SET (%2) = (%3)")
.arg(quoted(constraint.name), jointNames, jointValues);
.arg(quoted(constraint.name), names, values);
}
QString makeColumnDefinition(const Column &column, const char *definition)
@@ -187,7 +200,15 @@ BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const QString &dbPath, QObj
throw RuntimeError(db.lastError().text());
if (needCreateDB)
{
createDB();
}
else
{
const int dbVersion = currentDBVersion();
if (dbVersion == 1)
updateDBFromVersion1();
}
m_asyncWorker = new Worker(dbPath, QLatin1String("ResumeDataStorageWorker"));
m_asyncWorker->moveToThread(m_ioThread);
@@ -276,8 +297,6 @@ std::optional<BitTorrent::LoadTorrentParams> BitTorrent::DBResumeDataStorage::lo
const QStringList tagList = tagsData.split(QLatin1Char(','));
resumeData.tags.insert(tagList.cbegin(), tagList.cend());
}
resumeData.savePath = Profile::instance()->fromPortablePath(
Utils::Fs::toUniformPath(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
resumeData.hasSeedStatus = query.value(DB_COLUMN_HAS_SEED_STATUS.name).toBool();
resumeData.firstLastPiecePriority = query.value(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.name).toBool();
resumeData.ratioLimit = query.value(DB_COLUMN_RATIO_LIMIT.name).toInt() / 1000.0;
@@ -288,6 +307,15 @@ std::optional<BitTorrent::LoadTorrentParams> BitTorrent::DBResumeDataStorage::lo
query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged);
resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
resumeData.savePath = Profile::instance()->fromPortablePath(
Utils::Fs::toUniformPath(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
resumeData.useAutoTMM = resumeData.savePath.isEmpty();
if (!resumeData.useAutoTMM)
{
resumeData.downloadPath = Profile::instance()->fromPortablePath(
Utils::Fs::toUniformPath(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
}
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray();
const QByteArray allData = ((bencodedMetadata.isEmpty() || bencodedResumeData.isEmpty())
@@ -297,6 +325,9 @@ std::optional<BitTorrent::LoadTorrentParams> BitTorrent::DBResumeDataStorage::lo
lt::error_code ec;
const lt::bdecode_node root = lt::bdecode(allData, ec);
resumeData.downloadPath = Profile::instance()->fromPortablePath(
Utils::Fs::toUniformPath(fromLTString(root.dict_find_string_value("qBt-downloadPath"))));
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
p = lt::read_resume_data(root, ec);
@@ -329,6 +360,33 @@ void BitTorrent::DBResumeDataStorage::storeQueue(const QVector<TorrentID> &queue
});
}
int BitTorrent::DBResumeDataStorage::currentDBVersion() const
{
const auto selectDBVersionStatement = QString::fromLatin1("SELECT %1 FROM %2 WHERE %3 = %4;")
.arg(quoted(DB_COLUMN_VALUE.name), quoted(DB_TABLE_META), quoted(DB_COLUMN_NAME.name), DB_COLUMN_NAME.placeholder);
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
QSqlQuery query {db};
if (!query.prepare(selectDBVersionStatement))
throw RuntimeError(query.lastError().text());
query.bindValue(DB_COLUMN_NAME.placeholder, QString::fromLatin1(META_VERSION));
if (!query.exec())
throw RuntimeError(query.lastError().text());
if (!query.next())
throw RuntimeError(tr("Database is corrupted."));
bool ok;
const int dbVersion = query.value(0).toInt(&ok);
if (!ok)
throw RuntimeError(tr("Database is corrupted."));
return dbVersion;
}
void BitTorrent::DBResumeDataStorage::createDB() const
{
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
@@ -353,7 +411,7 @@ void BitTorrent::DBResumeDataStorage::createDB() const
if (!query.prepare(insertMetaVersionQuery))
throw RuntimeError(query.lastError().text());
query.bindValue(DB_COLUMN_NAME.placeholder, QString::fromLatin1("version"));
query.bindValue(DB_COLUMN_NAME.placeholder, QString::fromLatin1(META_VERSION));
query.bindValue(DB_COLUMN_VALUE.placeholder, DB_VERSION);
if (!query.exec())
@@ -391,6 +449,42 @@ void BitTorrent::DBResumeDataStorage::createDB() const
}
}
void BitTorrent::DBResumeDataStorage::updateDBFromVersion1() const
{
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
if (!db.transaction())
throw RuntimeError(db.lastError().text());
QSqlQuery query {db};
try
{
const auto alterTableTorrentsQuery = QString::fromLatin1("ALTER TABLE %1 ADD %2")
.arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, "TEXT"));
if (!query.exec(alterTableTorrentsQuery))
throw RuntimeError(query.lastError().text());
const QString updateMetaVersionQuery = makeUpdateStatement(DB_TABLE_META, {DB_COLUMN_NAME, DB_COLUMN_VALUE});
if (!query.prepare(updateMetaVersionQuery))
throw RuntimeError(query.lastError().text());
query.bindValue(DB_COLUMN_NAME.placeholder, QString::fromLatin1(META_VERSION));
query.bindValue(DB_COLUMN_VALUE.placeholder, DB_VERSION);
if (!query.exec())
throw RuntimeError(query.lastError().text());
if (!db.commit())
throw RuntimeError(db.lastError().text());
}
catch (const RuntimeError &)
{
db.rollback();
throw;
}
}
BitTorrent::DBResumeDataStorage::Worker::Worker(const QString &dbPath, const QString &dbConnectionName)
: m_path {dbPath}
, m_connectionName {dbConnectionName}
@@ -499,7 +593,6 @@ void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const L
query.bindValue(DB_COLUMN_CATEGORY.placeholder, resumeData.category);
query.bindValue(DB_COLUMN_TAGS.placeholder, (resumeData.tags.isEmpty()
? QVariant(QVariant::String) : resumeData.tags.join(QLatin1String(","))));
query.bindValue(DB_COLUMN_TARGET_SAVE_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.savePath));
query.bindValue(DB_COLUMN_CONTENT_LAYOUT.placeholder, Utils::String::fromEnum(resumeData.contentLayout));
query.bindValue(DB_COLUMN_RATIO_LIMIT.placeholder, static_cast<int>(resumeData.ratioLimit * 1000));
query.bindValue(DB_COLUMN_SEEDING_TIME_LIMIT.placeholder, resumeData.seedingTimeLimit);
@@ -507,6 +600,13 @@ void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const L
query.bindValue(DB_COLUMN_HAS_SEED_STATUS.placeholder, resumeData.hasSeedStatus);
query.bindValue(DB_COLUMN_OPERATING_MODE.placeholder, Utils::String::fromEnum(resumeData.operatingMode));
query.bindValue(DB_COLUMN_STOPPED.placeholder, resumeData.stopped);
if (!resumeData.useAutoTMM)
{
query.bindValue(DB_COLUMN_TARGET_SAVE_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.savePath));
query.bindValue(DB_COLUMN_DOWNLOAD_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.downloadPath));
}
query.bindValue(DB_COLUMN_RESUMEDATA.placeholder, bencodedResumeData);
if (!bencodedMetadata.isEmpty())
query.bindValue(DB_COLUMN_METADATA.placeholder, bencodedMetadata);

View File

@@ -50,7 +50,9 @@ namespace BitTorrent
void storeQueue(const QVector<TorrentID> &queue) const override;
private:
int currentDBVersion() const;
void createDB() const;
void updateDBFromVersion1() const;
QThread *m_ioThread = nullptr;

View File

@@ -34,7 +34,7 @@
#include "base/bittorrent/infohash.h"
void FileSearcher::search(const BitTorrent::TorrentID &id, const QStringList &originalFileNames
, const QString &completeSavePath, const QString &incompleteSavePath)
, const QString &savePath, const QString &downloadPath)
{
const auto findInDir = [](const QString &dirPath, QStringList &fileNames) -> bool
{
@@ -56,14 +56,14 @@ void FileSearcher::search(const BitTorrent::TorrentID &id, const QStringList &or
return found;
};
QString savePath = completeSavePath;
QString usedPath = savePath;
QStringList adjustedFileNames = originalFileNames;
const bool found = findInDir(savePath, adjustedFileNames);
if (!found && !incompleteSavePath.isEmpty())
const bool found = findInDir(usedPath, adjustedFileNames);
if (!found && !downloadPath.isEmpty())
{
savePath = incompleteSavePath;
findInDir(savePath, adjustedFileNames);
usedPath = downloadPath;
findInDir(usedPath, adjustedFileNames);
}
emit searchFinished(id, savePath, adjustedFileNames);
emit searchFinished(id, usedPath, adjustedFileNames);
}

View File

@@ -45,7 +45,7 @@ public:
public slots:
void search(const BitTorrent::TorrentID &id, const QStringList &originalFileNames
, const QString &completeSavePath, const QString &incompleteSavePath);
, const QString &savePath, const QString &downloadPath);
signals:
void searchFinished(const BitTorrent::TorrentID &id, const QString &savePath, const QStringList &fileNames);

View File

@@ -36,6 +36,13 @@ BitTorrent::InfoHash::InfoHash(const WrappedType &nativeHash)
{
}
#ifdef QBT_USES_LIBTORRENT2
BitTorrent::InfoHash::InfoHash(const SHA1Hash &v1, const SHA256Hash &v2)
: InfoHash {WrappedType(v1, v2)}
{
}
#endif
bool BitTorrent::InfoHash::isValid() const
{
return m_valid;

View File

@@ -64,8 +64,10 @@ namespace BitTorrent
#endif
InfoHash() = default;
InfoHash(const InfoHash &other) = default;
InfoHash(const WrappedType &nativeHash);
#ifdef QBT_USES_LIBTORRENT2
InfoHash(const SHA1Hash &v1, const SHA256Hash &v2);
#endif
bool isValid() const;
SHA1Hash v1() const;
@@ -86,3 +88,6 @@ namespace BitTorrent
}
Q_DECLARE_METATYPE(BitTorrent::TorrentID)
// We can declare it as Q_MOVABLE_TYPE to improve performance
// since base type uses QSharedDataPointer as the only member
Q_DECLARE_TYPEINFO(BitTorrent::TorrentID, Q_MOVABLE_TYPE);

View File

@@ -46,8 +46,10 @@ namespace BitTorrent
QString category;
TagSet tags;
QString savePath;
QString downloadPath;
TorrentContentLayout contentLayout = TorrentContentLayout::Original;
TorrentOperatingMode operatingMode = TorrentOperatingMode::AutoManaged;
bool useAutoTMM = false;
bool firstLastPiecePriority = false;
bool hasSeedStatus = false;
bool stopped = false;

View File

@@ -34,6 +34,9 @@
#include <QHash>
// From https://doc.qt.io/qt-6/qhash.html#the-hashing-function:
// A hashing function for a key type K may be provided in two different ways.
// The first way is by having an overload of qHash() in K's namespace.
namespace libtorrent
{
namespace aux

View File

@@ -41,6 +41,7 @@ namespace
NativeTorrentExtension::NativeTorrentExtension(const lt::torrent_handle &torrentHandle)
: m_torrentHandle {torrentHandle}
{
on_state(m_torrentHandle.status({}).state);
}
bool NativeTorrentExtension::on_pause()
@@ -56,7 +57,10 @@ bool NativeTorrentExtension::on_pause()
void NativeTorrentExtension::on_state(const lt::torrent_status::state_t state)
{
if (m_state == lt::torrent_status::downloading_metadata)
m_torrentHandle.set_flags(lt::torrent_flags::stop_when_ready);
{
m_torrentHandle.unset_flags(lt::torrent_flags::auto_managed);
m_torrentHandle.pause();
}
m_state = state;
}

View File

@@ -33,16 +33,13 @@
#include <QDebug>
#include "base/logger.h"
#include "base/settingsstorage.h"
const QString KEY_ENABLED = QStringLiteral("Network/PortForwardingEnabled");
PortForwarderImpl::PortForwarderImpl(lt::session *provider, QObject *parent)
: Net::PortForwarder {parent}
, m_active {SettingsStorage::instance()->loadValue(KEY_ENABLED, true)}
, m_storeActive {"Network/PortForwardingEnabled", true}
, m_provider {provider}
{
if (m_active)
if (isEnabled())
start();
}
@@ -53,20 +50,19 @@ PortForwarderImpl::~PortForwarderImpl()
bool PortForwarderImpl::isEnabled() const
{
return m_active;
return m_storeActive;
}
void PortForwarderImpl::setEnabled(const bool enabled)
{
if (m_active != enabled)
if (m_storeActive != enabled)
{
if (enabled)
start();
else
stop();
m_active = enabled;
SettingsStorage::instance()->storeValue(KEY_ENABLED, enabled);
m_storeActive = enabled;
}
}

View File

@@ -36,6 +36,7 @@
#include <QHash>
#include "base/net/portforwarder.h"
#include "base/settingvalue.h"
class PortForwarderImpl final : public Net::PortForwarder
{
@@ -56,7 +57,7 @@ private:
void start();
void stop();
bool m_active;
CachedSettingValue<bool> m_storeActive;
lt::session *m_provider;
QHash<quint16, std::vector<lt::port_mapping_t>> m_mappedPorts;
};

File diff suppressed because it is too large Load Diff

View File

@@ -47,6 +47,7 @@
#include "base/types.h"
#include "addtorrentparams.h"
#include "cachestatus.h"
#include "categoryoptions.h"
#include "sessionstatus.h"
#include "torrentinfo.h"
#include "trackerentry.h"
@@ -211,22 +212,23 @@ namespace BitTorrent
static void freeInstance();
static Session *instance();
QString defaultSavePath() const;
void setDefaultSavePath(QString path);
QString tempPath() const;
void setTempPath(QString path);
bool isTempPathEnabled() const;
void setTempPathEnabled(bool enabled);
QString torrentTempPath(const TorrentInfo &torrentInfo) const;
QString savePath() const;
void setSavePath(const QString &path);
QString downloadPath() const;
void setDownloadPath(const QString &path);
bool isDownloadPathEnabled() const;
void setDownloadPathEnabled(bool enabled);
static bool isValidCategoryName(const QString &name);
// returns category itself and all top level categories
static QStringList expandCategory(const QString &category);
QStringMap categories() const;
QStringList categories() const;
CategoryOptions categoryOptions(const QString &categoryName) const;
QString categorySavePath(const QString &categoryName) const;
bool addCategory(const QString &name, const QString &savePath = "");
bool editCategory(const QString &name, const QString &savePath);
QString categoryDownloadPath(const QString &categoryName) const;
bool addCategory(const QString &name, const CategoryOptions &options = {});
bool editCategory(const QString &name, const CategoryOptions &options);
bool removeCategory(const QString &name);
bool isSubcategoriesEnabled() const;
void setSubcategoriesEnabled(bool value);
@@ -499,7 +501,8 @@ namespace BitTorrent
bool addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newPath, MoveStorageMode mode);
void findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath) const;
void findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath
, const QString &downloadPath, const QStringList &filePaths = {}) const;
signals:
void allTorrentsFinished();
@@ -642,6 +645,10 @@ namespace BitTorrent
void moveTorrentStorage(const MoveStorageJob &job) const;
void handleMoveTorrentStorageJobFinished();
void loadCategories();
void storeCategories() const;
void upgradeCategories();
// BitTorrent
lt::session *m_nativeSession = nullptr;
@@ -729,13 +736,12 @@ namespace BitTorrent
CachedSettingValue<bool> m_isProxyPeerConnectionsEnabled;
CachedSettingValue<ChokingAlgorithm> m_chokingAlgorithm;
CachedSettingValue<SeedChokingAlgorithm> m_seedChokingAlgorithm;
CachedSettingValue<QVariantMap> m_storedCategories;
CachedSettingValue<QStringList> m_storedTags;
CachedSettingValue<int> m_maxRatioAction;
CachedSettingValue<QString> m_defaultSavePath;
CachedSettingValue<QString> m_tempPath;
CachedSettingValue<QString> m_savePath;
CachedSettingValue<QString> m_downloadPath;
CachedSettingValue<bool> m_isSubcategoriesEnabled;
CachedSettingValue<bool> m_isTempPathEnabled;
CachedSettingValue<bool> m_isDownloadPathEnabled;
CachedSettingValue<bool> m_isAutoTMMDisabledByDefault;
CachedSettingValue<bool> m_isDisableAutoTMMWhenCategoryChanged;
CachedSettingValue<bool> m_isDisableAutoTMMWhenDefaultSavePathChanged;
@@ -780,7 +786,7 @@ namespace BitTorrent
QHash<QString, AddTorrentParams> m_downloadedTorrents;
QHash<TorrentID, RemovingTorrentData> m_removingTorrents;
QSet<TorrentID> m_needSaveResumeDataTorrents;
QStringMap m_categories;
QMap<QString, CategoryOptions> m_categories;
QSet<QString> m_tags;
// I/O errored torrents
@@ -800,6 +806,8 @@ namespace BitTorrent
QString m_lastExternalIP;
bool m_needUpgradeDownloadPath = false;
static Session *m_instance;
};
}

View File

@@ -125,12 +125,11 @@ namespace BitTorrent
virtual qlonglong wastedSize() const = 0;
virtual QString currentTracker() const = 0;
// 1. savePath() - the path where all the files and subfolders of torrent are stored (as always).
// 2. rootPath() - absolute path of torrent file tree (save path + first item from 1st torrent file path).
// 1. savePath() - the path where all the files and subfolders of torrent are stored.
// 1.1 downloadPath() - the path where all the files and subfolders of torrent are stored until torrent has finished downloading.
// 2. rootPath() - absolute path of torrent file tree (first common subfolder of torrent files); empty string if torrent has no root folder.
// 3. contentPath() - absolute path of torrent content (root path for multifile torrents, absolute file path for singlefile torrents).
//
// These methods have 'actual' parameter (defaults to false) which allow to get actual or final path variant.
//
// Examples.
// Suppose we have three torrent with following structures and save path `/home/user/torrents`:
//
@@ -166,16 +165,17 @@ namespace BitTorrent
// | A | /home/user/torrents/torrentA | /home/user/torrents/torrentA |
// | A*| <empty> | /home/user/torrents |
// | B | /home/user/torrents/torrentB | /home/user/torrents/torrentB/subdir1/file1 |
// | C | /home/user/torrents/file1 | /home/user/torrents/file1 |
virtual QString savePath(bool actual = false) const = 0;
virtual QString rootPath(bool actual = false) const = 0;
virtual QString contentPath(bool actual = false) const = 0;
virtual bool useTempPath() const = 0;
// | C | <empty> | /home/user/torrents/file1 |
virtual bool isAutoTMMEnabled() const = 0;
virtual void setAutoTMMEnabled(bool enabled) = 0;
virtual QString savePath() const = 0;
virtual void setSavePath(const QString &savePath) = 0;
virtual QString downloadPath() const = 0;
virtual void setDownloadPath(const QString &downloadPath) = 0;
virtual QString actualStorageLocation() const = 0;
virtual QString rootPath() const = 0;
virtual QString contentPath() const = 0;
virtual QString category() const = 0;
virtual bool belongsToCategory(const QString &category) const = 0;
virtual bool setCategory(const QString &category) = 0;
@@ -273,7 +273,6 @@ namespace BitTorrent
virtual void setFirstLastPiecePriority(bool enabled) = 0;
virtual void pause() = 0;
virtual void resume(TorrentOperatingMode mode = TorrentOperatingMode::AutoManaged) = 0;
virtual void move(QString path) = 0;
virtual void forceReannounce(int index = -1) = 0;
virtual void forceDHTAnnounce() = 0;
virtual void forceRecheck() = 0;

View File

@@ -0,0 +1,70 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2020-2021 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#include "torrentcontentlayout.h"
#include "base/utils/fs.h"
namespace
{
QString removeExtension(const QString &fileName)
{
const QString extension = Utils::Fs::fileExtension(fileName);
return extension.isEmpty()
? fileName
: fileName.chopped(extension.size() + 1);
}
}
BitTorrent::TorrentContentLayout BitTorrent::detectContentLayout(const QStringList &filePaths)
{
const QString rootFolder = Utils::Fs::findRootFolder(filePaths);
return (rootFolder.isEmpty()
? TorrentContentLayout::NoSubfolder
: TorrentContentLayout::Subfolder);
}
void BitTorrent::applyContentLayout(QStringList &filePaths, const BitTorrent::TorrentContentLayout contentLayout, const QString &rootFolder)
{
Q_ASSERT(!filePaths.isEmpty());
switch (contentLayout)
{
case TorrentContentLayout::Subfolder:
if (Utils::Fs::findRootFolder(filePaths).isEmpty())
Utils::Fs::addRootFolder(filePaths, !rootFolder.isEmpty() ? rootFolder : removeExtension(filePaths.at(0)));
break;
case TorrentContentLayout::NoSubfolder:
Utils::Fs::stripRootFolder(filePaths);
break;
default:
break;
}
}

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2020 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2020-2021 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
@@ -48,4 +48,7 @@ namespace BitTorrent
Q_ENUM_NS(TorrentContentLayout)
}
TorrentContentLayout detectContentLayout(const QStringList &filePaths);
void applyContentLayout(QStringList &filePaths, TorrentContentLayout contentLayout, const QString &rootFolder = {});
}

View File

@@ -33,10 +33,6 @@
#include <memory>
#include <type_traits>
#ifdef Q_OS_WIN
#include <Windows.h>
#endif
#include <libtorrent/address.hpp>
#include <libtorrent/alert_types.hpp>
#include <libtorrent/magnet_uri.hpp>
@@ -256,7 +252,8 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession
, m_infoHash(m_nativeHandle.info_hash())
#endif
, m_name(params.name)
, m_savePath(Utils::Fs::toNativePath(params.savePath))
, m_savePath(params.savePath)
, m_downloadPath(params.downloadPath)
, m_category(params.category)
, m_tags(params.tags)
, m_ratioLimit(params.ratioLimit)
@@ -265,18 +262,30 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession
, m_contentLayout(params.contentLayout)
, m_hasSeedStatus(params.hasSeedStatus)
, m_hasFirstLastPiecePriority(params.firstLastPiecePriority)
, m_useAutoTMM(params.savePath.isEmpty())
, m_useAutoTMM(params.useAutoTMM)
, m_isStopped(params.stopped)
, m_ltAddTorrentParams(params.ltAddTorrentParams)
{
if (m_useAutoTMM)
m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category));
if (m_ltAddTorrentParams.ti)
{
// Initialize it only if torrent is added with metadata.
// Otherwise it should be initialized in "Metadata received" handler.
m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
m_torrentInfo = TorrentInfo(*m_ltAddTorrentParams.ti);
Q_ASSERT(m_filePaths.isEmpty());
Q_ASSERT(m_indexMap.isEmpty());
const int filesCount = m_torrentInfo.filesCount();
m_filePaths.reserve(filesCount);
m_indexMap.reserve(filesCount);
const std::shared_ptr<const lt::torrent_info> currentInfo = m_nativeHandle.torrent_file();
const lt::file_storage &fileStorage = currentInfo->files();
for (int i = 0; i < filesCount; ++i)
{
const lt::file_index_t nativeIndex = m_torrentInfo.nativeIndexes().at(i);
m_indexMap[nativeIndex] = i;
const QString filePath = Utils::Fs::toUniformPath(QString::fromStdString(fileStorage.file_path(nativeIndex)));
m_filePaths.append(filePath);
}
}
initializeStatus(m_nativeStatus, m_ltAddTorrentParams);
@@ -285,7 +294,7 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession
if (hasMetadata())
applyFirstLastPiecePriority(m_hasFirstLastPiecePriority);
// TODO: Remove the following upgrade code in v.4.4
// TODO: Remove the following upgrade code in v4.4
// == BEGIN UPGRADE CODE ==
const QString spath = actualStorageLocation();
for (int i = 0; i < filesCount(); ++i)
@@ -388,39 +397,73 @@ QString TorrentImpl::currentTracker() const
return QString::fromStdString(m_nativeStatus.current_tracker);
}
QString TorrentImpl::savePath(bool actual) const
QString TorrentImpl::savePath() const
{
if (actual)
return Utils::Fs::toUniformPath(actualStorageLocation());
else
return Utils::Fs::toUniformPath(m_savePath);
return isAutoTMMEnabled() ? m_session->categorySavePath(category()) : m_savePath;
}
QString TorrentImpl::rootPath(bool actual) const
void TorrentImpl::setSavePath(const QString &path)
{
Q_ASSERT(!isAutoTMMEnabled());
const QString resolvedPath = (QDir::isAbsolutePath(path) ? path : Utils::Fs::resolvePath(path, m_session->savePath()));
if (resolvedPath == savePath())
return;
m_savePath = resolvedPath;
m_session->handleTorrentNeedSaveResumeData(this);
const bool isFinished = isSeed() || m_hasSeedStatus;
if (isFinished || downloadPath().isEmpty())
moveStorage(savePath(), MoveStorageMode::KeepExistingFiles);
}
QString TorrentImpl::downloadPath() const
{
return isAutoTMMEnabled() ? m_session->categoryDownloadPath(category()) : m_downloadPath;
}
void TorrentImpl::setDownloadPath(const QString &path)
{
Q_ASSERT(!isAutoTMMEnabled());
const QString resolvedPath = ((path.isEmpty() || QDir::isAbsolutePath(path))
? path : Utils::Fs::resolvePath(path, m_session->downloadPath()));
if (resolvedPath == m_downloadPath)
return;
m_downloadPath = resolvedPath;
m_session->handleTorrentNeedSaveResumeData(this);
const bool isFinished = isSeed() || m_hasSeedStatus;
if (!isFinished)
moveStorage((m_downloadPath.isEmpty() ? savePath() : m_downloadPath), MoveStorageMode::KeepExistingFiles);
}
QString TorrentImpl::rootPath() const
{
if (!hasMetadata())
return {};
const QString firstFilePath = filePath(0);
const int slashIndex = firstFilePath.indexOf('/');
if (slashIndex >= 0)
return QDir(savePath(actual)).absoluteFilePath(firstFilePath.left(slashIndex));
else
return QDir(savePath(actual)).absoluteFilePath(firstFilePath);
const QString relativeRootPath = Utils::Fs::findRootFolder(filePaths());
if (relativeRootPath.isEmpty())
return {};
return QDir(actualStorageLocation()).absoluteFilePath(relativeRootPath);
}
QString TorrentImpl::contentPath(const bool actual) const
QString TorrentImpl::contentPath() const
{
if (!hasMetadata())
return {};
if (filesCount() == 1)
return QDir(savePath(actual)).absoluteFilePath(filePath(0));
return QDir(actualStorageLocation()).absoluteFilePath(filePath(0));
if (m_torrentInfo.hasRootFolder())
return rootPath(actual);
return savePath(actual);
const QString rootPath = this->rootPath();
return (rootPath.isEmpty() ? actualStorageLocation() : rootPath);
}
bool TorrentImpl::isAutoTMMEnabled() const
@@ -430,19 +473,25 @@ bool TorrentImpl::isAutoTMMEnabled() const
void TorrentImpl::setAutoTMMEnabled(bool enabled)
{
if (m_useAutoTMM == enabled) return;
if (m_useAutoTMM == enabled)
return;
m_useAutoTMM = enabled;
if (!m_useAutoTMM)
{
m_savePath = m_session->categorySavePath(category());
m_downloadPath = m_session->categoryDownloadPath(category());
}
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentSavingModeChanged(this);
if (m_useAutoTMM)
move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite);
adjustStorageLocation();
}
QString TorrentImpl::actualStorageLocation() const
{
return QString::fromStdString(m_nativeStatus.save_path);
return Utils::Fs::toUniformPath(QString::fromStdString(m_nativeStatus.save_path));
}
void TorrentImpl::setAutoManaged(const bool enable)
@@ -750,7 +799,7 @@ int TorrentImpl::seedingTimeLimit() const
QString TorrentImpl::filePath(const int index) const
{
return m_torrentInfo.filePath(index);
return m_filePaths.at(index);
}
qlonglong TorrentImpl::fileSize(const int index) const
@@ -760,7 +809,7 @@ qlonglong TorrentImpl::fileSize(const int index) const
QStringList TorrentImpl::filePaths() const
{
return m_torrentInfo.filePaths();
return m_filePaths;
}
// Return a list of absolute paths corresponding
@@ -769,7 +818,7 @@ QStringList TorrentImpl::absoluteFilePaths() const
{
if (!hasMetadata()) return {};
const QDir saveDir {savePath(true)};
const QDir saveDir {actualStorageLocation()};
QStringList res;
res.reserve(filesCount());
for (int i = 0; i < filesCount(); ++i)
@@ -986,7 +1035,7 @@ int TorrentImpl::queuePosition() const
QString TorrentImpl::error() const
{
if (m_nativeStatus.errc)
return QString::fromStdString(m_nativeStatus.errc.message());
return QString::fromLocal8Bit(m_nativeStatus.errc.message().c_str());
if (m_nativeStatus.flags & lt::torrent_flags::upload_mode)
{
@@ -1058,7 +1107,7 @@ qlonglong TorrentImpl::eta() const
seedingTimeEta = 0;
}
return qMin(ratioEta, seedingTimeEta);
return std::min(ratioEta, seedingTimeEta);
}
if (!speedAverage.download) return MAX_ETA;
@@ -1346,7 +1395,7 @@ bool TorrentImpl::setCategory(const QString &category)
if (m_useAutoTMM)
{
if (!m_session->isDisableAutoTMMWhenCategoryChanged())
move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite);
adjustStorageLocation();
else
setAutoTMMEnabled(false);
}
@@ -1355,41 +1404,6 @@ bool TorrentImpl::setCategory(const QString &category)
return true;
}
void TorrentImpl::move(QString path)
{
if (m_useAutoTMM)
{
m_useAutoTMM = false;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentSavingModeChanged(this);
}
path = Utils::Fs::toUniformPath(path.trimmed());
if (path.isEmpty())
path = m_session->defaultSavePath();
if (!path.endsWith('/'))
path += '/';
move_impl(path, MoveStorageMode::KeepExistingFiles);
}
void TorrentImpl::move_impl(QString path, const MoveStorageMode mode)
{
if (path == savePath()) return;
path = Utils::Fs::toNativePath(path);
if (!useTempPath())
{
moveStorage(path, mode);
}
else
{
m_savePath = path;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentSavePathChanged(this);
}
}
void TorrentImpl::forceReannounce(const int index)
{
m_nativeHandle.force_reannounce(0, index);
@@ -1468,7 +1482,7 @@ void TorrentImpl::applyFirstLastPiecePriority(const bool enabled, const QVector<
// Determine the priority to set
const DownloadPriority newPrio = enabled ? DownloadPriority::Maximum : filePrio;
const auto piecePrio = static_cast<lt::download_priority_t>(static_cast<int>(newPrio));
const TorrentInfo::PieceRange extremities = info().filePieces(index);
const TorrentInfo::PieceRange extremities = m_torrentInfo.filePieces(index);
// worst case: AVI index = 1% of total file size (at the end of the file)
const int nNumPieces = std::ceil(fileSize(index) * 0.01 / pieceLength());
@@ -1489,14 +1503,24 @@ void TorrentImpl::fileSearchFinished(const QString &savePath, const QStringList
void TorrentImpl::endReceivedMetadataHandling(const QString &savePath, const QStringList &fileNames)
{
Q_ASSERT(m_filePaths.isEmpty());
Q_ASSERT(m_indexMap.isEmpty());
lt::add_torrent_params &p = m_ltAddTorrentParams;
const TorrentInfo torrentInfo {m_nativeHandle.torrent_file()};
const auto nativeIndexes = torrentInfo.nativeIndexes();
const std::shared_ptr<lt::torrent_info> metadata = std::const_pointer_cast<lt::torrent_info>(m_nativeHandle.torrent_file());
m_torrentInfo = TorrentInfo(*metadata);
m_filePaths = fileNames;
m_indexMap.reserve(filesCount());
const auto nativeIndexes = m_torrentInfo.nativeIndexes();
for (int i = 0; i < fileNames.size(); ++i)
p.renamed_files[nativeIndexes[i]] = fileNames[i].toStdString();
{
const auto nativeIndex = nativeIndexes.at(i);
m_indexMap[nativeIndex] = i;
p.renamed_files[nativeIndex] = fileNames[i].toStdString();
}
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
p.ti = torrentInfo.nativeInfo();
p.ti = metadata;
const int internalFilesCount = p.ti->files().num_files(); // including .pad files
// Use qBittorrent default priority rather than libtorrent's (4)
@@ -1543,8 +1567,6 @@ void TorrentImpl::reload()
m_nativeHandle = m_nativeSession->add_torrent(p);
m_nativeHandle.queue_position_set(queuePos);
m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
}
void TorrentImpl::pause()
@@ -1606,7 +1628,7 @@ void TorrentImpl::resume(const TorrentOperatingMode mode)
void TorrentImpl::moveStorage(const QString &newPath, const MoveStorageMode mode)
{
if (m_session->addMoveTorrentStorageJob(this, newPath, mode))
if (m_session->addMoveTorrentStorageJob(this, Utils::Fs::toNativePath(newPath), mode))
{
m_storageIsMoving = true;
updateStatus();
@@ -1615,12 +1637,8 @@ void TorrentImpl::moveStorage(const QString &newPath, const MoveStorageMode mode
void TorrentImpl::renameFile(const int index, const QString &path)
{
#ifndef QBT_USES_LIBTORRENT2
const QString oldPath = filePath(index);
m_oldPath[index].push_back(oldPath);
#endif
++m_renameCount;
m_nativeHandle.rename_file(m_torrentInfo.nativeIndexes()[index], Utils::Fs::toNativePath(path).toStdString());
m_nativeHandle.rename_file(m_torrentInfo.nativeIndexes().at(index), Utils::Fs::toNativePath(path).toStdString());
}
void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
@@ -1628,18 +1646,18 @@ void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
updateStatus(nativeStatus);
}
void TorrentImpl::handleDownloadPathChanged()
{
adjustStorageLocation();
}
void TorrentImpl::handleMoveStorageJobFinished(const bool hasOutstandingJob)
{
m_session->handleTorrentNeedSaveResumeData(this);
m_storageIsMoving = hasOutstandingJob;
updateStatus();
const QString newPath = QString::fromStdString(m_nativeStatus.save_path);
if (!useTempPath() && (newPath != m_savePath))
{
m_savePath = newPath;
m_session->handleTorrentSavePathChanged(this);
}
m_session->handleTorrentSavePathChanged(this);
if (!m_storageIsMoving)
{
@@ -1716,7 +1734,7 @@ void TorrentImpl::handleTorrentCheckedAlert(const lt::torrent_checked_alert *p)
else if (progress() == 1.0)
m_hasSeedStatus = true;
adjustActualSavePath();
adjustStorageLocation();
manageIncompleteFiles();
}
@@ -1734,7 +1752,7 @@ void TorrentImpl::handleTorrentFinishedAlert(const lt::torrent_finished_alert *p
updateStatus();
m_hasSeedStatus = true;
adjustActualSavePath();
adjustStorageLocation();
manageIncompleteFiles();
m_session->handleTorrentNeedSaveResumeData(this);
@@ -1773,10 +1791,11 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
m_ltAddTorrentParams.have_pieces.clear();
m_ltAddTorrentParams.verified_pieces.clear();
TorrentInfo metadata = TorrentInfo {m_nativeHandle.torrent_file()};
metadata.setContentLayout(m_contentLayout);
TorrentInfo metadata = TorrentInfo(*m_nativeHandle.torrent_file());
m_session->findIncompleteFiles(metadata, m_savePath);
QStringList filePaths = metadata.filePaths();
applyContentLayout(filePaths, m_contentLayout);
m_session->findIncompleteFiles(metadata, savePath(), downloadPath(), filePaths);
}
else
{
@@ -1810,7 +1829,6 @@ void TorrentImpl::prepareResumeData(const lt::add_torrent_params &params)
LoadTorrentParams resumeData;
resumeData.name = m_name;
resumeData.category = m_category;
resumeData.savePath = m_useAutoTMM ? "" : m_savePath;
resumeData.tags = m_tags;
resumeData.contentLayout = m_contentLayout;
resumeData.ratioLimit = m_ratioLimit;
@@ -1820,6 +1838,12 @@ void TorrentImpl::prepareResumeData(const lt::add_torrent_params &params)
resumeData.stopped = m_isStopped;
resumeData.operatingMode = m_operatingMode;
resumeData.ltAddTorrentParams = m_ltAddTorrentParams;
resumeData.useAutoTMM = m_useAutoTMM;
if (!resumeData.useAutoTMM)
{
resumeData.savePath = m_savePath;
resumeData.downloadPath = m_downloadPath;
}
m_session->handleTorrentResumeDataReady(this, resumeData);
}
@@ -1849,21 +1873,17 @@ void TorrentImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_al
void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
{
const int fileIndex = m_torrentInfo.nativeIndexes().indexOf(p->index);
const int fileIndex = m_indexMap.value(p->index, -1);
Q_ASSERT(fileIndex >= 0);
// Remove empty leftover folders
// For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
// be removed if they are empty
#ifndef QBT_USES_LIBTORRENT2
const QString oldFilePath = m_oldPath[fileIndex].takeFirst();
if (m_oldPath[fileIndex].isEmpty())
m_oldPath.remove(fileIndex);
#else
const QString oldFilePath = Utils::Fs::toUniformPath(p->old_name());
#endif
const QString oldFilePath = m_filePaths.at(fileIndex);
const QString newFilePath = Utils::Fs::toUniformPath(p->new_name());
m_filePaths[fileIndex] = newFilePath;
QList<QStringView> oldPathParts = QStringView(oldFilePath).split('/', Qt::SkipEmptyParts);
oldPathParts.removeLast(); // drop file name part
QList<QStringView> newPathParts = QStringView(newFilePath).split('/', Qt::SkipEmptyParts);
@@ -1883,9 +1903,10 @@ void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
++pathIdx;
}
QDir storageDir {actualStorageLocation()};
for (int i = (oldPathParts.size() - 1); i >= pathIdx; --i)
{
QDir().rmdir(savePath() + Utils::String::join(oldPathParts, QString::fromLatin1("/")));
storageDir.rmdir(Utils::String::join(oldPathParts, QString::fromLatin1("/")));
oldPathParts.removeLast();
}
@@ -1898,18 +1919,12 @@ void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p)
{
const int fileIndex = m_torrentInfo.nativeIndexes().indexOf(p->index);
const int fileIndex = m_indexMap.value(p->index, -1);
Q_ASSERT(fileIndex >= 0);
LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
.arg(name(), filePath(fileIndex), QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING);
#ifndef QBT_USES_LIBTORRENT2
m_oldPath[fileIndex].removeFirst();
if (m_oldPath[fileIndex].isEmpty())
m_oldPath.remove(fileIndex);
#endif
--m_renameCount;
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
m_moveFinishedTriggers.takeFirst()();
@@ -1919,12 +1934,11 @@ void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert
void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
{
const int fileIndex = m_torrentInfo.nativeIndexes().indexOf(p->index);
Q_ASSERT(fileIndex >= 0);
qDebug("A file completed download in torrent \"%s\"", qUtf8Printable(name()));
if (m_session->isAppendExtensionEnabled())
{
const int fileIndex = m_indexMap.value(p->index, -1);
Q_ASSERT(fileIndex >= 0);
QString name = filePath(fileIndex);
if (name.endsWith(QB_EXT))
{
@@ -1964,15 +1978,10 @@ void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const
, Log::INFO);
}
void TorrentImpl::handleTempPathChanged()
{
adjustActualSavePath();
}
void TorrentImpl::handleCategorySavePathChanged()
void TorrentImpl::handleCategoryOptionsChanged()
{
if (m_useAutoTMM)
move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite);
adjustStorageLocation();
}
void TorrentImpl::handleAppendExtensionToggled()
@@ -2077,39 +2086,14 @@ void TorrentImpl::manageIncompleteFiles()
}
}
void TorrentImpl::adjustActualSavePath()
void TorrentImpl::adjustStorageLocation()
{
if (!isMoveInProgress())
adjustActualSavePath_impl();
else
m_moveFinishedTriggers.append([this]() { adjustActualSavePath_impl(); });
}
const QString downloadPath = this->downloadPath();
const bool isFinished = isSeed() || m_hasSeedStatus;
const QDir targetDir {((isFinished || downloadPath.isEmpty()) ? savePath() : downloadPath)};
void TorrentImpl::adjustActualSavePath_impl()
{
const bool needUseTempDir = useTempPath();
const QDir tempDir {m_session->torrentTempPath(info())};
const QDir currentDir {actualStorageLocation()};
const QDir targetDir {needUseTempDir ? tempDir : QDir {savePath()}};
if (targetDir == currentDir) return;
if (!needUseTempDir)
{
if ((currentDir == tempDir) && (currentDir != QDir {m_session->tempPath()}))
{
// torrent without root folder still has it in its temporary save path
// so its temp path isn't equal to temp path root
const QString currentDirPath = currentDir.absolutePath();
m_moveFinishedTriggers.append([currentDirPath]
{
qDebug() << "Removing torrent temp folder:" << currentDirPath;
Utils::Fs::smartRemoveEmptyFolderTree(currentDirPath);
});
}
}
moveStorage(Utils::Fs::toNativePath(targetDir.absolutePath()), MoveStorageMode::Overwrite);
if ((targetDir != QDir(actualStorageLocation())) || isMoveInProgress())
moveStorage(targetDir.absolutePath(), MoveStorageMode::Overwrite);
}
lt::torrent_handle TorrentImpl::nativeHandle() const
@@ -2122,11 +2106,6 @@ bool TorrentImpl::isMoveInProgress() const
return m_storageIsMoving;
}
bool TorrentImpl::useTempPath() const
{
return m_session->isTempPathEnabled() && !(isSeed() || m_hasSeedStatus);
}
void TorrentImpl::updateStatus()
{
updateStatus(m_nativeHandle.status());
@@ -2298,6 +2277,8 @@ void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
QVector<qreal> TorrentImpl::availableFileFractions() const
{
Q_ASSERT(hasMetadata());
const int filesCount = this->filesCount();
if (filesCount <= 0) return {};
@@ -2307,10 +2288,9 @@ QVector<qreal> TorrentImpl::availableFileFractions() const
QVector<qreal> res;
res.reserve(filesCount);
const TorrentInfo info = this->info();
for (int i = 0; i < filesCount; ++i)
{
const TorrentInfo::PieceRange filePieces = info.filePieces(i);
const TorrentInfo::PieceRange filePieces = m_torrentInfo.filePieces(i);
int availablePieces = 0;
for (const int piece : filePieces)

View File

@@ -99,14 +99,15 @@ namespace BitTorrent
qlonglong wastedSize() const override;
QString currentTracker() const override;
QString savePath(bool actual = false) const override;
QString rootPath(bool actual = false) const override;
QString contentPath(bool actual = false) const override;
bool useTempPath() const override;
bool isAutoTMMEnabled() const override;
void setAutoTMMEnabled(bool enabled) override;
QString savePath() const override;
void setSavePath(const QString &path) override;
QString downloadPath() const override;
void setDownloadPath(const QString &path) override;
QString actualStorageLocation() const override;
QString rootPath() const override;
QString contentPath() const override;
QString category() const override;
bool belongsToCategory(const QString &category) const override;
bool setCategory(const QString &category) override;
@@ -201,7 +202,6 @@ namespace BitTorrent
void setFirstLastPiecePriority(bool enabled) override;
void pause() override;
void resume(TorrentOperatingMode mode = TorrentOperatingMode::AutoManaged) override;
void move(QString path) override;
void forceReannounce(int index = -1) override;
void forceDHTAnnounce() override;
void forceRecheck() override;
@@ -232,15 +232,13 @@ namespace BitTorrent
void handleAlert(const lt::alert *a);
void handleStateUpdate(const lt::torrent_status &nativeStatus);
void handleTempPathChanged();
void handleCategorySavePathChanged();
void handleDownloadPathChanged();
void handleCategoryOptionsChanged();
void handleAppendExtensionToggled();
void saveResumeData();
void handleMoveStorageJobFinished(bool hasOutstandingJob);
void fileSearchFinished(const QString &savePath, const QStringList &fileNames);
QString actualStorageLocation() const;
private:
using EventTrigger = std::function<void ()>;
@@ -272,9 +270,7 @@ namespace BitTorrent
void setAutoManaged(bool enable);
void adjustActualSavePath();
void adjustActualSavePath_impl();
void move_impl(QString path, MoveStorageMode mode);
void adjustStorageLocation();
void moveStorage(const QString &newPath, MoveStorageMode mode);
void manageIncompleteFiles();
void applyFirstLastPiecePriority(bool enabled, const QVector<DownloadPriority> &updatedFilePrio = {});
@@ -289,6 +285,8 @@ namespace BitTorrent
lt::torrent_status m_nativeStatus;
TorrentState m_state = TorrentState::Unknown;
TorrentInfo m_torrentInfo;
QStringList m_filePaths;
QHash<lt::file_index_t, int> m_indexMap;
SpeedMonitor m_speedMonitor;
InfoHash m_infoHash;
@@ -301,18 +299,13 @@ namespace BitTorrent
MaintenanceJob m_maintenanceJob = MaintenanceJob::None;
#ifndef QBT_USES_LIBTORRENT2
// Until libtorrent provided an "old_name" field in `file_renamed_alert`
// we relied on this workaround to remove empty leftover folders
QHash<int, QVector<QString>> m_oldPath;
#endif
QHash<QString, QMap<lt::tcp::endpoint, int>> m_trackerPeerCounts;
FileErrorInfo m_lastFileError;
// Persistent data
QString m_name;
QString m_savePath;
QString m_downloadPath;
QString m_category;
TagSet m_tags;
qreal m_ratioLimit;

View File

@@ -49,38 +49,14 @@
using namespace BitTorrent;
namespace
{
QString getRootFolder(const QStringList &filePaths)
{
QString rootFolder;
for (const QString &filePath : filePaths)
{
if (QDir::isAbsolutePath(filePath)) continue;
const auto filePathElements = QStringView(filePath).split(u'/');
// if at least one file has no root folder, no common root folder exists
if (filePathElements.count() <= 1) return {};
if (rootFolder.isEmpty())
rootFolder = filePathElements.at(0).toString();
else if (rootFolder != filePathElements.at(0))
return {};
}
return rootFolder;
}
}
const int torrentInfoId = qRegisterMetaType<TorrentInfo>();
TorrentInfo::TorrentInfo(std::shared_ptr<const lt::torrent_info> nativeInfo)
: m_nativeInfo {std::const_pointer_cast<lt::torrent_info>(nativeInfo)}
TorrentInfo::TorrentInfo(const lt::torrent_info &nativeInfo)
: m_nativeInfo {std::make_shared<const lt::torrent_info>(nativeInfo)}
{
if (!m_nativeInfo)
return;
Q_ASSERT(m_nativeInfo->is_valid() && (m_nativeInfo->num_files() > 0));
const lt::file_storage &fileStorage = m_nativeInfo->files();
const lt::file_storage &fileStorage = m_nativeInfo->orig_files();
m_nativeIndexes.reserve(fileStorage.num_files());
for (const lt::file_index_t nativeIndex : fileStorage.file_range())
{
@@ -105,6 +81,11 @@ TorrentInfo &TorrentInfo::operator=(const TorrentInfo &other)
return *this;
}
bool TorrentInfo::isValid() const
{
return (m_nativeInfo != nullptr);
}
nonstd::expected<TorrentInfo, QString> TorrentInfo::load(const QByteArray &data) noexcept
{
// 2-step construction to overcome default limits of `depth_limit` & `token_limit` which are
@@ -118,11 +99,11 @@ nonstd::expected<TorrentInfo, QString> TorrentInfo::load(const QByteArray &data)
if (ec)
return nonstd::make_unexpected(QString::fromStdString(ec.message()));
TorrentInfo info {std::shared_ptr<lt::torrent_info>(new lt::torrent_info(node, ec))};
lt::torrent_info nativeInfo {node, ec};
if (ec)
return nonstd::make_unexpected(QString::fromStdString(ec.message()));
return info;
return TorrentInfo(nativeInfo);
}
nonstd::expected<TorrentInfo, QString> TorrentInfo::loadFromFile(const QString &path) noexcept
@@ -159,7 +140,7 @@ nonstd::expected<void, QString> TorrentInfo::saveToFile(const QString &path) con
try
{
const auto torrentCreator = lt::create_torrent(*nativeInfo());
const auto torrentCreator = lt::create_torrent(*m_nativeInfo);
const lt::entry torrentEntry = torrentCreator.generate();
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, torrentEntry);
if (!result)
@@ -173,11 +154,6 @@ nonstd::expected<void, QString> TorrentInfo::saveToFile(const QString &path) con
return {};
}
bool TorrentInfo::isValid() const
{
return (m_nativeInfo && m_nativeInfo->is_valid() && (m_nativeInfo->num_files() > 0));
}
InfoHash TorrentInfo::infoHash() const
{
if (!isValid()) return {};
@@ -192,6 +168,7 @@ InfoHash TorrentInfo::infoHash() const
QString TorrentInfo::name() const
{
if (!isValid()) return {};
return QString::fromStdString(m_nativeInfo->orig_files().name());
}
@@ -206,56 +183,65 @@ QDateTime TorrentInfo::creationDate() const
QString TorrentInfo::creator() const
{
if (!isValid()) return {};
return QString::fromStdString(m_nativeInfo->creator());
}
QString TorrentInfo::comment() const
{
if (!isValid()) return {};
return QString::fromStdString(m_nativeInfo->comment());
}
bool TorrentInfo::isPrivate() const
{
if (!isValid()) return false;
return m_nativeInfo->priv();
}
qlonglong TorrentInfo::totalSize() const
{
if (!isValid()) return -1;
return m_nativeInfo->total_size();
}
int TorrentInfo::filesCount() const
{
if (!isValid()) return -1;
return m_nativeIndexes.size();
}
int TorrentInfo::pieceLength() const
{
if (!isValid()) return -1;
return m_nativeInfo->piece_length();
}
int TorrentInfo::pieceLength(const int index) const
{
if (!isValid()) return -1;
return m_nativeInfo->piece_size(lt::piece_index_t {index});
}
int TorrentInfo::piecesCount() const
{
if (!isValid()) return -1;
return m_nativeInfo->num_pieces();
}
QString TorrentInfo::filePath(const int index) const
{
if (!isValid()) return {};
return Utils::Fs::toUniformPath(
QString::fromStdString(m_nativeInfo->files().file_path(m_nativeIndexes[index])));
QString::fromStdString(m_nativeInfo->orig_files().file_path(m_nativeIndexes[index])));
}
QStringList TorrentInfo::filePaths() const
@@ -268,23 +254,18 @@ QStringList TorrentInfo::filePaths() const
return list;
}
QString TorrentInfo::origFilePath(const int index) const
{
if (!isValid()) return {};
return Utils::Fs::toUniformPath(
QString::fromStdString(m_nativeInfo->orig_files().file_path(m_nativeIndexes[index])));
}
qlonglong TorrentInfo::fileSize(const int index) const
{
if (!isValid()) return -1;
return m_nativeInfo->files().file_size(m_nativeIndexes[index]);
return m_nativeInfo->orig_files().file_size(m_nativeIndexes[index]);
}
qlonglong TorrentInfo::fileOffset(const int index) const
{
if (!isValid()) return -1;
return m_nativeInfo->files().file_offset(m_nativeIndexes[index]);
return m_nativeInfo->orig_files().file_offset(m_nativeIndexes[index]);
}
QVector<TrackerEntry> TorrentInfo::trackers() const
@@ -349,8 +330,8 @@ QVector<int> TorrentInfo::fileIndicesForPiece(const int pieceIndex) const
if (!isValid() || (pieceIndex < 0) || (pieceIndex >= piecesCount()))
return {};
const std::vector<lt::file_slice> files = nativeInfo()->map_block(
lt::piece_index_t {pieceIndex}, 0, nativeInfo()->piece_size(lt::piece_index_t {pieceIndex}));
const std::vector<lt::file_slice> files = m_nativeInfo->map_block(
lt::piece_index_t {pieceIndex}, 0, m_nativeInfo->piece_size(lt::piece_index_t {pieceIndex}));
QVector<int> res;
res.reserve(static_cast<decltype(res)::size_type>(files.size()));
for (const lt::file_slice &fileSlice : files)
@@ -403,7 +384,7 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(const int fileIndex) const
return {};
}
const lt::file_storage &files = nativeInfo()->files();
const lt::file_storage &files = m_nativeInfo->orig_files();
const auto fileSize = files.file_size(m_nativeIndexes[fileIndex]);
const auto fileOffset = files.file_offset(m_nativeIndexes[fileIndex]);
@@ -415,102 +396,33 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(const int fileIndex) const
return makeInterval(beginIdx, endIdx);
}
void TorrentInfo::renameFile(const int index, const QString &newPath)
{
if (!isValid()) return;
nativeInfo()->rename_file(m_nativeIndexes[index], Utils::Fs::toNativePath(newPath).toStdString());
}
int 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
for (int i = 0; i < filesCount(); ++i)
{
if (fileName == filePath(i))
return i;
}
return -1;
}
QString TorrentInfo::rootFolder() const
TorrentContentLayout TorrentInfo::contentLayout() const
{
return getRootFolder(filePaths());
}
if (!isValid())
return TorrentContentLayout::Original;
bool TorrentInfo::hasRootFolder() const
{
return !rootFolder().isEmpty();
}
void TorrentInfo::setContentLayout(const TorrentContentLayout layout)
{
switch (layout)
{
case TorrentContentLayout::Original:
setContentLayout(defaultContentLayout());
break;
case TorrentContentLayout::Subfolder:
if (rootFolder().isEmpty())
addRootFolder();
break;
case TorrentContentLayout::NoSubfolder:
if (!rootFolder().isEmpty())
stripRootFolder();
break;
}
}
void TorrentInfo::stripRootFolder()
{
lt::file_storage files = m_nativeInfo->files();
// Solution for case of renamed root folder
const QString path = filePath(0);
const std::string newName = path.left(path.indexOf('/')).toStdString();
if (files.name() != newName)
{
files.set_name(newName);
for (const lt::file_index_t nativeIndex : files.file_range())
files.rename_file(nativeIndex, files.file_path(nativeIndex));
}
files.set_name("");
m_nativeInfo->remap_files(files);
}
void TorrentInfo::addRootFolder()
{
const QString originalName = name();
Q_ASSERT(!originalName.isEmpty());
const QString extension = Utils::Fs::fileExtension(originalName);
const QString rootFolder = extension.isEmpty()
? originalName
: originalName.chopped(extension.size() + 1);
const std::string rootPrefix = Utils::Fs::toNativePath(rootFolder + QLatin1Char {'/'}).toStdString();
lt::file_storage files = m_nativeInfo->files();
files.set_name(rootFolder.toStdString());
for (const lt::file_index_t nativeIndex : files.file_range())
files.rename_file(nativeIndex, rootPrefix + files.file_path(nativeIndex));
m_nativeInfo->remap_files(files);
}
TorrentContentLayout TorrentInfo::defaultContentLayout() const
{
QStringList origFilePaths;
origFilePaths.reserve(filesCount());
for (int i = 0; i < filesCount(); ++i)
origFilePaths << origFilePath(i);
const QString origRootFolder = getRootFolder(origFilePaths);
return (origRootFolder.isEmpty()
? TorrentContentLayout::NoSubfolder
: TorrentContentLayout::Subfolder);
return detectContentLayout(filePaths());
}
std::shared_ptr<lt::torrent_info> TorrentInfo::nativeInfo() const
{
return m_nativeInfo;
if (!isValid())
return nullptr;
return std::make_shared<lt::torrent_info>(*m_nativeInfo);
}
QVector<lt::file_index_t> TorrentInfo::nativeIndexes() const

View File

@@ -35,7 +35,6 @@
#include "base/3rdparty/expected.hpp"
#include "base/indexrange.h"
#include "abstractfilestorage.h"
#include "torrentcontentlayout.h"
class QByteArray;
@@ -48,14 +47,16 @@ namespace BitTorrent
class InfoHash;
struct TrackerEntry;
class TorrentInfo final : public AbstractFileStorage
class TorrentInfo
{
Q_DECLARE_TR_FUNCTIONS(TorrentInfo)
public:
explicit TorrentInfo(std::shared_ptr<const lt::torrent_info> nativeInfo = {});
TorrentInfo() = default;
TorrentInfo(const TorrentInfo &other);
explicit TorrentInfo(const lt::torrent_info &nativeInfo);
static nonstd::expected<TorrentInfo, QString> load(const QByteArray &data) noexcept;
static nonstd::expected<TorrentInfo, QString> loadFromFile(const QString &path) noexcept;
nonstd::expected<void, QString> saveToFile(const QString &path) const;
@@ -70,14 +71,13 @@ namespace BitTorrent
QString comment() const;
bool isPrivate() const;
qlonglong totalSize() const;
int filesCount() const override;
int filesCount() const;
int pieceLength() const;
int pieceLength(int index) const;
int piecesCount() const;
QString filePath(int index) const override;
QString filePath(int index) const;
QStringList filePaths() const;
QString origFilePath(int index) const;
qlonglong fileSize(int index) const override;
qlonglong fileSize(int index) const;
qlonglong fileOffset(int index) const;
QVector<TrackerEntry> trackers() const;
QVector<QUrl> urlSeeds() const;
@@ -92,23 +92,15 @@ namespace BitTorrent
PieceRange filePieces(const QString &file) const;
PieceRange filePieces(int fileIndex) const;
void renameFile(int index, const QString &newPath) override;
QString rootFolder() const;
bool hasRootFolder() const;
void setContentLayout(TorrentContentLayout layout);
std::shared_ptr<lt::torrent_info> nativeInfo() const;
QVector<lt::file_index_t> nativeIndexes() const;
private:
// returns file index or -1 if fileName is not found
int fileIndex(const QString &fileName) const;
void stripRootFolder();
void addRootFolder();
TorrentContentLayout defaultContentLayout() const;
TorrentContentLayout contentLayout() const;
std::shared_ptr<lt::torrent_info> m_nativeInfo;
std::shared_ptr<const lt::torrent_info> m_nativeInfo;
// internal indexes of files (payload only, excluding any .pad files)
// by which they are addressed in libtorrent

View File

@@ -41,7 +41,6 @@ public:
using UnderlyingType = lt::digest32<N>;
Digest32() = default;
Digest32(const Digest32 &other) = default;
Digest32(const UnderlyingType &nativeDigest)
: m_valid {true}

View File

@@ -45,8 +45,18 @@ Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObj
, m_requestHandler(requestHandler)
{
m_socket->setParent(this);
// reset timer when there are activity
m_idleTimer.start();
connect(m_socket, &QTcpSocket::readyRead, this, &Connection::read);
connect(m_socket, &QIODevice::readyRead, this, [this]()
{
m_idleTimer.start();
read();
});
connect(m_socket, &QIODevice::bytesWritten, this, [this]()
{
m_idleTimer.start();
});
}
Connection::~Connection()
@@ -56,7 +66,6 @@ Connection::~Connection()
void Connection::read()
{
m_idleTimer.restart();
m_receivedData.append(m_socket->readAll());
while (!m_receivedData.isEmpty())
@@ -66,7 +75,7 @@ void Connection::read()
switch (result.status)
{
case RequestParser::ParseStatus::Incomplete:
{
{
const long bufferLimit = RequestParser::MAX_CONTENT_SIZE * 1.1; // some margin for headers
if (m_receivedData.size() > bufferLimit)
{
@@ -83,7 +92,7 @@ void Connection::read()
return;
case RequestParser::ParseStatus::BadRequest:
{
{
Logger::instance()->addMessage(tr("Bad Http request, closing socket. IP: %1")
.arg(m_socket->peerAddress().toString()), Log::WARNING);
@@ -96,7 +105,7 @@ void Connection::read()
return;
case RequestParser::ParseStatus::OK:
{
{
const Environment env {m_socket->localAddress(), m_socket->localPort(), m_socket->peerAddress(), m_socket->peerPort()};
Response resp = m_requestHandler->processRequest(result.request, env);
@@ -125,7 +134,9 @@ void Connection::sendResponse(const Response &response) const
bool Connection::hasExpired(const qint64 timeout) const
{
return m_idleTimer.hasExpired(timeout);
return (m_socket->bytesAvailable() == 0)
&& (m_socket->bytesToWrite() == 0)
&& m_idleTimer.hasExpired(timeout);
}
bool Connection::isClosed() const

View File

@@ -52,11 +52,9 @@ namespace Http
bool hasExpired(qint64 timeout) const;
bool isClosed() const;
private slots:
void read();
private:
static bool acceptsGzipEncoding(QString codings);
void read();
void sendResponse(const Response &response) const;
QTcpSocket *m_socket;

View File

@@ -109,9 +109,18 @@ RequestParser::ParseResult RequestParser::doParse(const QByteArray &data)
return {ParseStatus::OK, m_request, headerLength};
if (m_request.method == HEADER_REQUEST_METHOD_POST)
{
bool ok = false;
const int contentLength = m_request.headers[HEADER_CONTENT_LENGTH].toInt(&ok);
if (!ok || (contentLength < 0))
const auto parseContentLength = [this]() -> int
{
// [rfc7230] 3.3.2. Content-Length
const QString rawValue = m_request.headers.value(HEADER_CONTENT_LENGTH);
if (rawValue.isNull()) // `HEADER_CONTENT_LENGTH` does not exist
return 0;
return Utils::String::parseInt(rawValue).value_or(-1);
};
const int contentLength = parseContentLength();
if (contentLength < 0)
{
qWarning() << Q_FUNC_INFO << "bad request: content-length invalid";
return {ParseStatus::BadRequest, Request(), 0};

View File

@@ -42,19 +42,23 @@ QByteArray Http::toByteArray(Response response)
response.headers[HEADER_DATE] = httpDate();
QByteArray buf;
buf.reserve(10 * 1024);
buf.reserve(1024 + response.content.length());
// Status Line
buf += QString("HTTP/%1 %2 %3")
.arg("1.1", // TODO: depends on request
QString::number(response.status.code),
response.status.text)
.toLatin1()
buf.append("HTTP/1.1 ") // TODO: depends on request
.append(QByteArray::number(response.status.code))
.append(' ')
.append(response.status.text.toLatin1())
.append(CRLF);
// Header Fields
for (auto i = response.headers.constBegin(); i != response.headers.constEnd(); ++i)
buf += QString::fromLatin1("%1: %2").arg(i.key(), i.value()).toLatin1().append(CRLF);
{
buf.append(i.key().toLatin1())
.append(": ")
.append(i.value().toLatin1())
.append(CRLF);
}
// the first empty line
buf += CRLF;

View File

@@ -41,7 +41,7 @@ using namespace Net;
DNSUpdater::DNSUpdater(QObject *parent)
: QObject(parent)
, m_state(OK)
, m_service(DNS::NONE)
, m_service(DNS::Service::None)
{
updateCredentials();
@@ -143,15 +143,16 @@ QString DNSUpdater::getUpdateUrl() const
// Service specific
switch (m_service)
{
case DNS::DYNDNS:
case DNS::Service::DynDNS:
url.setHost("members.dyndns.org");
break;
case DNS::NOIP:
case DNS::Service::NoIP:
url.setHost("dynupdate.no-ip.com");
break;
default:
qWarning() << "Unrecognized Dynamic DNS service!";
Q_ASSERT(0);
Q_ASSERT(false);
break;
}
url.setPath("/nic/update");
@@ -295,16 +296,17 @@ void DNSUpdater::updateCredentials()
}
}
QUrl DNSUpdater::getRegistrationUrl(const int service)
QUrl DNSUpdater::getRegistrationUrl(const DNS::Service service)
{
switch (service)
{
case DNS::DYNDNS:
case DNS::Service::DynDNS:
return {"https://account.dyn.com/entrance/"};
case DNS::NOIP:
case DNS::Service::NoIP:
return {"https://www.noip.com/remote-access"};
default:
Q_ASSERT(0);
Q_ASSERT(false);
break;
}
return {};
}

View File

@@ -48,7 +48,7 @@ namespace Net
explicit DNSUpdater(QObject *parent = nullptr);
~DNSUpdater();
static QUrl getRegistrationUrl(int service);
static QUrl getRegistrationUrl(DNS::Service service);
public slots:
void updateCredentials();

View File

@@ -89,7 +89,7 @@ void GeoIPManager::loadDatabase()
m_geoIPDatabase = nullptr;
const QString filepath = Utils::Fs::expandPathAbs(
QString::fromLatin1("%1%2/%3").arg(specialFolderLocation(SpecialFolder::Data), GEODB_FOLDER, GEODB_FILENAME));
QString::fromLatin1("%1/%2/%3").arg(specialFolderLocation(SpecialFolder::Data), GEODB_FOLDER, GEODB_FILENAME));
QString error;
m_geoIPDatabase = GeoIPDatabase::load(filepath, error);
@@ -448,7 +448,7 @@ void GeoIPManager::downloadFinished(const DownloadResult &result)
.arg(m_geoIPDatabase->type(), m_geoIPDatabase->buildEpoch().toString()),
Log::INFO);
const QString targetPath = Utils::Fs::expandPathAbs(
specialFolderLocation(SpecialFolder::Data) + GEODB_FOLDER);
QDir(specialFolderLocation(SpecialFolder::Data)).absoluteFilePath(GEODB_FOLDER));
if (!QDir(targetPath).exists())
QDir().mkpath(targetPath);

View File

@@ -28,20 +28,7 @@
#include "proxyconfigurationmanager.h"
#include "base/settingsstorage.h"
#define SETTINGS_KEY(name) QStringLiteral("Network/Proxy/" name)
const QString KEY_ONLY_FOR_TORRENTS = SETTINGS_KEY("OnlyForTorrents");
const QString KEY_TYPE = SETTINGS_KEY("Type");
const QString KEY_IP = SETTINGS_KEY("IP");
const QString KEY_PORT = SETTINGS_KEY("Port");
const QString KEY_USERNAME = SETTINGS_KEY("Username");
const QString KEY_PASSWORD = SETTINGS_KEY("Password");
namespace
{
inline SettingsStorage *settings() { return SettingsStorage::instance(); }
}
#define SETTINGS_KEY(name) ("Network/Proxy/" name)
bool Net::operator==(const ProxyConfiguration &left, const ProxyConfiguration &right)
{
@@ -62,17 +49,21 @@ using namespace Net;
ProxyConfigurationManager *ProxyConfigurationManager::m_instance = nullptr;
ProxyConfigurationManager::ProxyConfigurationManager(QObject *parent)
: QObject(parent)
: QObject {parent}
, m_storeProxyOnlyForTorrents {SETTINGS_KEY("OnlyForTorrents")}
, m_storeProxyType {SETTINGS_KEY("Type")}
, m_storeProxyIP {SETTINGS_KEY("IP")}
, m_storeProxyPort {SETTINGS_KEY("Port")}
, m_storeProxyUsername {SETTINGS_KEY("Username")}
, m_storeProxyPassword {SETTINGS_KEY("Password")}
{
m_isProxyOnlyForTorrents = settings()->loadValue(KEY_ONLY_FOR_TORRENTS, false);
m_config.type = static_cast<ProxyType>(
settings()->loadValue(KEY_TYPE, static_cast<int>(ProxyType::None)));
m_config.type = m_storeProxyType.get(ProxyType::None);
if ((m_config.type < ProxyType::None) || (m_config.type > ProxyType::SOCKS4))
m_config.type = ProxyType::None;
m_config.ip = settings()->loadValue<QString>(KEY_IP, "0.0.0.0");
m_config.port = settings()->loadValue<ushort>(KEY_PORT, 8080);
m_config.username = settings()->loadValue<QString>(KEY_USERNAME);
m_config.password = settings()->loadValue<QString>(KEY_PASSWORD);
m_config.ip = m_storeProxyIP.get(QLatin1String("0.0.0.0"));
m_config.port = m_storeProxyPort.get(8080);
m_config.username = m_storeProxyUsername;
m_config.password = m_storeProxyPassword;
configureProxy();
}
@@ -100,14 +91,14 @@ ProxyConfiguration ProxyConfigurationManager::proxyConfiguration() const
void ProxyConfigurationManager::setProxyConfiguration(const ProxyConfiguration &config)
{
if (config != m_config)
if (m_config != config)
{
m_config = config;
settings()->storeValue(KEY_TYPE, static_cast<int>(config.type));
settings()->storeValue(KEY_IP, config.ip);
settings()->storeValue(KEY_PORT, config.port);
settings()->storeValue(KEY_USERNAME, config.username);
settings()->storeValue(KEY_PASSWORD, config.password);
m_storeProxyType = config.type;
m_storeProxyIP = config.ip;
m_storeProxyPort = config.port;
m_storeProxyUsername = config.username;
m_storeProxyPassword = config.password;
configureProxy();
emit proxyConfigurationChanged();
@@ -116,16 +107,12 @@ void ProxyConfigurationManager::setProxyConfiguration(const ProxyConfiguration &
bool ProxyConfigurationManager::isProxyOnlyForTorrents() const
{
return m_isProxyOnlyForTorrents || (m_config.type == ProxyType::SOCKS4);
return m_storeProxyOnlyForTorrents || (m_config.type == ProxyType::SOCKS4);
}
void ProxyConfigurationManager::setProxyOnlyForTorrents(bool onlyForTorrents)
void ProxyConfigurationManager::setProxyOnlyForTorrents(const bool onlyForTorrents)
{
if (m_isProxyOnlyForTorrents != onlyForTorrents)
{
settings()->storeValue(KEY_ONLY_FOR_TORRENTS, onlyForTorrents);
m_isProxyOnlyForTorrents = onlyForTorrents;
}
m_storeProxyOnlyForTorrents = onlyForTorrents;
}
bool ProxyConfigurationManager::isAuthenticationRequired() const
@@ -138,7 +125,7 @@ void ProxyConfigurationManager::configureProxy()
{
// Define environment variables for urllib in search engine plugins
QString proxyStrHTTP, proxyStrSOCK;
if (!m_isProxyOnlyForTorrents)
if (!isProxyOnlyForTorrents())
{
switch (m_config.type)
{

View File

@@ -30,8 +30,12 @@
#include <QObject>
#include "base/settingvalue.h"
namespace Net
{
Q_NAMESPACE
enum class ProxyType
{
None = 0,
@@ -41,6 +45,7 @@ namespace Net
SOCKS5_PW = 4,
SOCKS4 = 5
};
Q_ENUM_NS(ProxyType)
struct ProxyConfiguration
{
@@ -53,7 +58,7 @@ namespace Net
bool operator==(const ProxyConfiguration &left, const ProxyConfiguration &right);
bool operator!=(const ProxyConfiguration &left, const ProxyConfiguration &right);
class ProxyConfigurationManager : public QObject
class ProxyConfigurationManager final : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(ProxyConfigurationManager)
@@ -81,6 +86,11 @@ namespace Net
static ProxyConfigurationManager *m_instance;
ProxyConfiguration m_config;
bool m_isProxyOnlyForTorrents;
SettingValue<bool> m_storeProxyOnlyForTorrents;
SettingValue<ProxyType> m_storeProxyType;
SettingValue<QString> m_storeProxyIP;
SettingValue<ushort> m_storeProxyPort;
SettingValue<QString> m_storeProxyUsername;
SettingValue<QString> m_storeProxyPassword;
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -29,9 +29,9 @@
#pragma once
#include <QObject>
#include <QtContainerFwd>
#include <QtGlobal>
#include <QVariant>
#include "base/utils/net.h"
@@ -40,38 +40,50 @@ class QNetworkCookie;
class QSize;
class QTime;
enum SchedulerDays
namespace Scheduler
{
EVERY_DAY,
WEEK_DAYS,
WEEK_ENDS,
MON,
TUE,
WED,
THU,
FRI,
SAT,
SUN
};
Q_NAMESPACE
namespace TrayIcon
{
enum Style
enum class Days : int
{
NORMAL = 0,
MONO_DARK,
MONO_LIGHT
EveryDay = 0,
Weekday = 1,
Weekend = 2,
Monday = 3,
Tuesday = 4,
Wednesday = 5,
Thursday = 6,
Friday = 7,
Saturday = 8,
Sunday = 9
};
Q_ENUM_NS(Days)
}
namespace DNS
{
enum Service
Q_NAMESPACE
enum class Service : int
{
DYNDNS,
NOIP,
NONE = -1
DynDNS = 0,
NoIP = 1,
None = -1
};
Q_ENUM_NS(Service)
}
namespace TrayIcon
{
Q_NAMESPACE
enum class Style : int
{
Normal = 0,
MonoDark = 1,
MonoLight = 2
};
Q_ENUM_NS(Style)
}
class Preferences : public QObject
@@ -81,9 +93,6 @@ class Preferences : public QObject
Preferences();
QVariant value(const QString &key, const QVariant &defaultValue = {}) const;
void setValue(const QString &key, const QVariant &value);
static Preferences *m_instance;
signals:
@@ -161,8 +170,8 @@ public:
void setSchedulerStartTime(const QTime &time);
QTime getSchedulerEndTime() const;
void setSchedulerEndTime(const QTime &time);
SchedulerDays getSchedulerDays() const;
void setSchedulerDays(SchedulerDays days);
Scheduler::Days getSchedulerDays() const;
void setSchedulerDays(Scheduler::Days days);
// Search
bool isSearchEnabled() const;
@@ -236,7 +245,7 @@ public:
bool isDynDNSEnabled() const;
void setDynDNSEnabled(bool enabled);
DNS::Service getDynDNSService() const;
void setDynDNSService(int service);
void setDynDNSService(DNS::Service service);
QString getDynDomainName() const;
void setDynDomainName(const QString &name);
QString getDynDNSUsername() const;
@@ -306,8 +315,8 @@ public:
bool confirmRemoveAllTags() const;
void setConfirmRemoveAllTags(bool enabled);
#ifndef Q_OS_MACOS
bool systrayIntegration() const;
void setSystrayIntegration(bool enabled);
bool systemTrayEnabled() const;
void setSystemTrayEnabled(bool enabled);
bool minimizeToTrayNotified() const;
void setMinimizeToTrayNotified(bool b);
bool minimizeToTray() const;

View File

@@ -88,8 +88,6 @@ QString Profile::location(const SpecialFolder folder) const
break;
}
if (!result.endsWith(QLatin1Char('/')))
result += QLatin1Char('/');
return result;
}

View File

@@ -88,10 +88,10 @@ QString Private::DefaultProfile::dataLocation() const
#else
// On Linux keep using the legacy directory ~/.local/share/data/ if it exists
const QString legacyDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
+ QLatin1String("/data/") + profileName() + QLatin1Char('/');
+ QLatin1String("/data/") + profileName();
const QString dataDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
+ QLatin1Char('/') + profileName() + QLatin1Char('/');
+ QLatin1Char('/') + profileName();
if (!QDir(dataDir).exists() && QDir(legacyDir).exists())
{
@@ -169,9 +169,9 @@ SettingsPtr Private::CustomProfile::applicationSettings(const QString &name) con
{
// here we force QSettings::IniFormat format always because we need it to be portable across platforms
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
constexpr const char *CONF_FILE_EXTENSION = ".ini";
const char CONF_FILE_EXTENSION[] = ".ini";
#else
constexpr const char *CONF_FILE_EXTENSION = ".conf";
const char CONF_FILE_EXTENSION[] = ".conf";
#endif
const QString settingsFileName {QDir(configLocation()).absoluteFilePath(name + QLatin1String(CONF_FILE_EXTENSION))};
return SettingsPtr(new QSettings(settingsFileName, QSettings::IniFormat));

View File

@@ -45,7 +45,6 @@
#include "../global.h"
#include "../logger.h"
#include "../profile.h"
#include "../settingsstorage.h"
#include "../utils/fs.h"
#include "rss_article.h"
#include "rss_autodownloadrule.h"
@@ -62,10 +61,6 @@ struct ProcessingJob
const QString ConfFolderName(QStringLiteral("rss"));
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
{
QVector<RSS::AutoDownloadRule> rulesFromJSON(const QByteArray &jsonData)
@@ -103,7 +98,9 @@ QString computeSmartFilterRegex(const QStringList &filters)
}
AutoDownloader::AutoDownloader()
: m_processingEnabled(SettingsStorage::instance()->loadValue(SettingsKey_ProcessingEnabled, false))
: m_storeProcessingEnabled("RSS/AutoDownloader/EnableProcessing", false)
, m_storeSmartEpisodeFilter("RSS/AutoDownloader/SmartEpisodeFilter")
, m_storeDownloadRepacks("RSS/AutoDownloader/DownloadRepacks")
, m_processingTimer(new QTimer(this))
, m_ioThread(new QThread(this))
{
@@ -111,7 +108,7 @@ AutoDownloader::AutoDownloader()
m_instance = this;
m_fileStorage = new AsyncFileStorage(
Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Config) + ConfFolderName));
Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Config) + QLatin1Char('/') + ConfFolderName));
if (!m_fileStorage)
throw RuntimeError(tr("Directory for RSS AutoDownloader data is unavailable."));
@@ -142,7 +139,7 @@ AutoDownloader::AutoDownloader()
m_processingTimer->setSingleShot(true);
connect(m_processingTimer, &QTimer::timeout, this, &AutoDownloader::process);
if (m_processingEnabled)
if (isProcessingEnabled())
startProcessing();
}
@@ -288,22 +285,19 @@ void AutoDownloader::importRulesFromLegacyFormat(const QByteArray &data)
QStringList AutoDownloader::smartEpisodeFilters() const
{
const auto filtersSetting = SettingsStorage::instance()->loadValue<QVariant>(SettingsKey_SmartEpisodeFilter);
if (filtersSetting.isNull())
const QVariant filter = m_storeSmartEpisodeFilter.get();
if (filter.isNull())
{
QStringList filters =
const QStringList defaultFilters =
{
"s(\\d+)e(\\d+)", // Format 1: s01e01
"(\\d+)x(\\d+)", // Format 2: 01x01
"(\\d{4}[.\\-]\\d{1,2}[.\\-]\\d{1,2})", // Format 3: 2017.01.01
"(\\d{1,2}[.\\-]\\d{1,2}[.\\-]\\d{4})" // Format 4: 01.01.2017
};
return filters;
return defaultFilters;
}
return filtersSetting.toStringList();
return filter.toStringList();
}
QRegularExpression AutoDownloader::smartEpisodeRegex() const
@@ -313,7 +307,7 @@ QRegularExpression AutoDownloader::smartEpisodeRegex() const
void AutoDownloader::setSmartEpisodeFilters(const QStringList &filters)
{
SettingsStorage::instance()->storeValue(SettingsKey_SmartEpisodeFilter, filters);
m_storeSmartEpisodeFilter = filters;
const QString regex = computeSmartFilterRegex(filters);
m_smartEpisodeRegex.setPattern(regex);
@@ -321,12 +315,12 @@ void AutoDownloader::setSmartEpisodeFilters(const QStringList &filters)
bool AutoDownloader::downloadRepacks() const
{
return SettingsStorage::instance()->loadValue(SettingsKey_DownloadRepacks, true);
return m_storeDownloadRepacks.get(true);
}
void AutoDownloader::setDownloadRepacks(const bool downloadRepacks)
void AutoDownloader::setDownloadRepacks(const bool enabled)
{
SettingsStorage::instance()->storeValue(SettingsKey_DownloadRepacks, downloadRepacks);
m_storeDownloadRepacks = enabled;
}
void AutoDownloader::process()
@@ -480,13 +474,13 @@ void AutoDownloader::storeDeferred()
bool AutoDownloader::isProcessingEnabled() const
{
return m_processingEnabled;
return m_storeProcessingEnabled;
}
void AutoDownloader::resetProcessingQueue()
{
m_processingQueue.clear();
if (!m_processingEnabled) return;
if (!isProcessingEnabled()) return;
for (Article *article : asConst(Session::instance()->rootFolder()->articles()))
{
@@ -503,11 +497,10 @@ void AutoDownloader::startProcessing()
void AutoDownloader::setProcessingEnabled(const bool enabled)
{
if (m_processingEnabled != enabled)
if (m_storeProcessingEnabled != enabled)
{
m_processingEnabled = enabled;
SettingsStorage::instance()->storeValue(SettingsKey_ProcessingEnabled, m_processingEnabled);
if (m_processingEnabled)
m_storeProcessingEnabled = enabled;
if (enabled)
{
startProcessing();
}
@@ -517,7 +510,7 @@ void AutoDownloader::setProcessingEnabled(const bool enabled)
disconnect(Session::instance()->rootFolder(), &Folder::newArticle, this, &AutoDownloader::handleNewArticle);
}
emit processingStateChanged(m_processingEnabled);
emit processingStateChanged(enabled);
}
}

View File

@@ -37,6 +37,7 @@
#include <QSharedPointer>
#include "base/exceptions.h"
#include "base/settingvalue.h"
class QThread;
class QTimer;
@@ -86,7 +87,7 @@ namespace RSS
QRegularExpression smartEpisodeRegex() const;
bool downloadRepacks() const;
void setDownloadRepacks(bool downloadRepacks);
void setDownloadRepacks(bool enabled);
bool hasRule(const QString &ruleName) const;
AutoDownloadRule ruleByName(const QString &ruleName) const;
@@ -131,7 +132,10 @@ namespace RSS
static QPointer<AutoDownloader> m_instance;
bool m_processingEnabled;
CachedSettingValue<bool> m_storeProcessingEnabled;
SettingValue<QVariant> m_storeSmartEpisodeFilter;
SettingValue<bool> m_storeDownloadRepacks;
QTimer *m_processingTimer;
QThread *m_ioThread;
AsyncFileStorage *m_fileStorage;

View File

@@ -31,6 +31,7 @@
#include "rss_feed.h"
#include <algorithm>
#include <utility>
#include <vector>
#include <QDir>
@@ -461,19 +462,19 @@ int Feed::updateArticles(const QList<QVariantHash> &loadedArticles)
if (newArticles.empty())
return 0;
using ArticleSortAdaptor = QPair<QDateTime, const QVariantHash *>;
using ArticleSortAdaptor = std::pair<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);
return std::make_pair(article->date(), nullptr);
});
std::transform(newArticles.begin(), newArticles.end(), std::back_inserter(sortData)
, [](const QVariantHash &article)
{
return qMakePair(article[Article::KeyDate].toDateTime(), &article);
return std::make_pair(article[Article::KeyDate].toDateTime(), &article);
});
// Sort article list in reverse chronological order

View File

@@ -591,16 +591,16 @@ void Parser::parse_impl(const QByteArray &feedData)
xml.skipCurrentElement();
}
if (!foundChannel)
{
m_result.error = tr("Invalid RSS feed.");
}
else if (xml.hasError())
if (xml.hasError())
{
m_result.error = tr("%1 (line: %2, column: %3, offset: %4).")
.arg(xml.errorString()).arg(xml.lineNumber())
.arg(xml.columnNumber()).arg(xml.characterOffset());
}
else if (!foundChannel)
{
m_result.error = tr("Invalid RSS feed.");
}
emit finished(m_result);
m_result.articles.clear(); // clear articles only

View File

@@ -53,25 +53,21 @@ const QString ConfFolderName(QStringLiteral("rss"));
const QString DataFolderName(QStringLiteral("rss/articles"));
const QString FeedsFileName(QStringLiteral("feeds.json"));
const QString SettingsKey_ProcessingEnabled(QStringLiteral("RSS/Session/EnableProcessing"));
const QString SettingsKey_RefreshInterval(QStringLiteral("RSS/Session/RefreshInterval"));
const QString SettingsKey_MaxArticlesPerFeed(QStringLiteral("RSS/Session/MaxArticlesPerFeed"));
using namespace RSS;
QPointer<Session> Session::m_instance = nullptr;
Session::Session()
: m_processingEnabled(SettingsStorage::instance()->loadValue(SettingsKey_ProcessingEnabled, false))
: m_storeProcessingEnabled("RSS/Session/EnableProcessing")
, m_storeRefreshInterval("RSS/Session/RefreshInterval", 30)
, m_storeMaxArticlesPerFeed("RSS/Session/MaxArticlesPerFeed", 50)
, m_workingThread(new QThread(this))
, m_refreshInterval(SettingsStorage::instance()->loadValue(SettingsKey_RefreshInterval, 30))
, m_maxArticlesPerFeed(SettingsStorage::instance()->loadValue(SettingsKey_MaxArticlesPerFeed, 50))
{
Q_ASSERT(!m_instance); // only one instance is allowed
m_instance = this;
m_confFileStorage = new AsyncFileStorage(
Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Config) + ConfFolderName));
Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Config) + QLatin1Char('/') + ConfFolderName));
m_confFileStorage->moveToThread(m_workingThread);
connect(m_workingThread, &QThread::finished, m_confFileStorage, &AsyncFileStorage::deleteLater);
connect(m_confFileStorage, &AsyncFileStorage::failed, [](const QString &fileName, const QString &errorString)
@@ -81,7 +77,7 @@ Session::Session()
});
m_dataFileStorage = new AsyncFileStorage(
Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + DataFolderName));
Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + QLatin1Char('/') + DataFolderName));
m_dataFileStorage->moveToThread(m_workingThread);
connect(m_workingThread, &QThread::finished, m_dataFileStorage, &AsyncFileStorage::deleteLater);
connect(m_dataFileStorage, &AsyncFileStorage::failed, [](const QString &fileName, const QString &errorString)
@@ -96,9 +92,9 @@ Session::Session()
load();
connect(&m_refreshTimer, &QTimer::timeout, this, &Session::refresh);
if (m_processingEnabled)
if (isProcessingEnabled())
{
m_refreshTimer.start(m_refreshInterval * MsecsPerMin);
m_refreshTimer.start(refreshInterval() * MsecsPerMin);
refresh();
}
@@ -166,7 +162,7 @@ nonstd::expected<void, QString> Session::addFeed(const QString &url, const QStri
const auto destFolder = result.value();
addItem(new Feed(generateUID(), url, path, this), destFolder);
store();
if (m_processingEnabled)
if (isProcessingEnabled())
feedByURL(url)->refresh();
return {};
@@ -429,18 +425,17 @@ void Session::addItem(Item *item, Folder *destFolder)
bool Session::isProcessingEnabled() const
{
return m_processingEnabled;
return m_storeProcessingEnabled;
}
void Session::setProcessingEnabled(bool enabled)
void Session::setProcessingEnabled(const bool enabled)
{
if (m_processingEnabled != enabled)
if (m_storeProcessingEnabled != enabled)
{
m_processingEnabled = enabled;
SettingsStorage::instance()->storeValue(SettingsKey_ProcessingEnabled, m_processingEnabled);
if (m_processingEnabled)
m_storeProcessingEnabled = enabled;
if (enabled)
{
m_refreshTimer.start(m_refreshInterval * MsecsPerMin);
m_refreshTimer.start(refreshInterval() * MsecsPerMin);
refresh();
}
else
@@ -448,7 +443,7 @@ void Session::setProcessingEnabled(bool enabled)
m_refreshTimer.stop();
}
emit processingStateChanged(m_processingEnabled);
emit processingStateChanged(enabled);
}
}
@@ -479,16 +474,15 @@ Feed *Session::feedByURL(const QString &url) const
int Session::refreshInterval() const
{
return m_refreshInterval;
return m_storeRefreshInterval;
}
void Session::setRefreshInterval(const int refreshInterval)
{
if (m_refreshInterval != refreshInterval)
if (m_storeRefreshInterval != refreshInterval)
{
SettingsStorage::instance()->storeValue(SettingsKey_RefreshInterval, refreshInterval);
m_refreshInterval = refreshInterval;
m_refreshTimer.start(m_refreshInterval * MsecsPerMin);
m_storeRefreshInterval = refreshInterval;
m_refreshTimer.start(m_storeRefreshInterval * MsecsPerMin);
}
}
@@ -527,15 +521,14 @@ QUuid Session::generateUID() const
int Session::maxArticlesPerFeed() const
{
return m_maxArticlesPerFeed;
return m_storeMaxArticlesPerFeed;
}
void Session::setMaxArticlesPerFeed(const int n)
{
if (m_maxArticlesPerFeed != n)
if (m_storeMaxArticlesPerFeed != n)
{
m_maxArticlesPerFeed = n;
SettingsStorage::instance()->storeValue(SettingsKey_MaxArticlesPerFeed, n);
m_storeMaxArticlesPerFeed = n;
emit maxArticlesPerFeedChanged(n);
}
}

View File

@@ -74,6 +74,7 @@
#include <QTimer>
#include "base/3rdparty/expected.hpp"
#include "base/settingvalue.h"
class QThread;
@@ -154,13 +155,13 @@ namespace RSS
static QPointer<Session> m_instance;
bool m_processingEnabled;
CachedSettingValue<bool> m_storeProcessingEnabled;
CachedSettingValue<int> m_storeRefreshInterval;
CachedSettingValue<int> m_storeMaxArticlesPerFeed;
QThread *m_workingThread;
AsyncFileStorage *m_confFileStorage;
AsyncFileStorage *m_dataFileStorage;
QTimer m_refreshTimer;
int m_refreshInterval;
int m_maxArticlesPerFeed;
QHash<QString, Item *> m_itemsByPath;
QHash<QUuid, Feed *> m_feedsByUID;
QHash<QString, Feed *> m_feedsByURL;

View File

@@ -367,7 +367,7 @@ QString SearchPluginManager::engineLocation()
static QString location;
if (location.isEmpty())
{
location = Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + "nova3");
location = Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + "/nova3");
const QDir locationDir(location);
locationDir.mkpath(locationDir.absolutePath());

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