Compare commits

...

85 Commits

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

@@ -46,7 +46,7 @@ jobs:
curl \
-L \
-o "${{ runner.temp }}/boost.tar.bz2" \
"https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_0.tar.bz2"
"https://boostorg.jfrog.io/artifactory/main/release/1.81.0/source/boost_1_81_0.tar.bz2"
tar -xf "${{ runner.temp }}/boost.tar.bz2" -C "${{ github.workspace }}/.."
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"

View File

@@ -78,7 +78,7 @@ jobs:
cmake --build build --target qbt_update_translations
cmake --build build
cmake --build build --target check
DESTDIR="qbittorrent" cmake --install build
DESTDIR="qbittorrent" cmake --install build --strip
- name: Build qBittorrent (Qt6)
if: ${{ startsWith(matrix.qt_version, 6) }}
@@ -97,7 +97,7 @@ jobs:
cmake --build build --target qbt_update_translations
cmake --build build
cmake --build build --target check
DESTDIR="qbittorrent" cmake --install build
DESTDIR="qbittorrent" cmake --install build --strip
- name: Prepare build artifacts
run: |
@@ -107,8 +107,35 @@ jobs:
mkdir upload/cmake/libtorrent
cp libtorrent/build/compile_commands.json upload/cmake/libtorrent
- name: 'AppImage: Prepare env'
run: |
sudo apt install libfuse2
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
wget https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage
chmod +x linuxdeploy-x86_64.AppImage
chmod +x linuxdeploy-plugin-qt-x86_64.AppImage
chmod +x linuxdeploy-plugin-appimage-x86_64.AppImage
- name: 'AppImage: Prepare nox'
if: matrix.qbt_gui == 'GUI=OFF'
run: |
mkdir -p qbittorrent/usr/share/icons/hicolor/scalable/apps/
mkdir -p qbittorrent/usr/share/applications/
cp dist/unix/menuicons/scalable/apps/qbittorrent.svg qbittorrent/usr/share/icons/hicolor/scalable/apps/qbittorrent.svg
cp .github/workflows/helper/appimage/org.qbittorrent.qBittorrent.desktop qbittorrent/usr/share/applications/org.qbittorrent.qBittorrent.desktop
- name: 'AppImage: Package'
run: |
./linuxdeploy-x86_64.AppImage --appdir=qbittorrent --plugin qt
rm qbittorrent/apprun-hooks/*
cp .github/workflows/helper/appimage/export_vars.sh qbittorrent/apprun-hooks/export_vars.sh
NO_APPSTREAM=1 \
OUTPUT=upload/qbittorrent-CI_Ubuntu_x86_64.AppImage \
./linuxdeploy-x86_64.AppImage --appdir=qbittorrent --output appimage
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-info_ubuntu-x64_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
name: qBittorrent-CI_Ubuntu-x64_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
path: upload

View File

@@ -67,7 +67,7 @@ jobs:
- name: Install boost
run: |
aria2c `
"https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_0.7z" `
"https://boostorg.jfrog.io/artifactory/main/release/1.81.0/source/boost_1_81_0.7z" `
-d "${{ runner.temp }}" `
-o "boost.7z"
7z x "${{ runner.temp }}/boost.7z" -o"${{ github.workspace }}/.."

View File

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

View File

@@ -0,0 +1,6 @@
[Desktop Entry]
Name=qBittorrent
Exec=qbittorrent-nox %U
Icon=qbittorrent
Type=Application
Categories=Network

View File

@@ -3,7 +3,7 @@ host = https://www.transifex.com
[qbittorrent.qbittorrent_master]
file_filter = src/lang/qbittorrent_<lang>.ts
lang_map = pt: pt_PT
lang_map = pt: pt_PT, zh: zh_CN
source_file = src/lang/qbittorrent_en.ts
source_lang = en
type = QT
@@ -19,7 +19,7 @@ mode = developer
[qbittorrent.qbittorrent_webui]
file_filter = src/webui/www/translations/webui_<lang>.ts
lang_map = pt: pt_PT
lang_map = pt: pt_PT, zh: zh_CN
source_file = src/webui/www/translations/webui_en.ts
source_lang = en
type = QT

113
Changelog
View File

@@ -1,4 +1,115 @@
Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.0
Sun Feb 12 2023 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.1
- FEATURE: Re-allow to use icons from system theme (glassez)
- BUGFIX: Fix Speed limit icon size (Nowshed H. Imran)
- BUGFIX: Revise and fix some text colors (Chocobo1, Nowshed H. Imran)
- BUGFIX: Correctly load folder based UI theme (glassez)
- BUGFIX: Fix crash due to invalid encoding of tracker URLs (glassez)
- BUGFIX: Don't drop !qB extension when renaming incomplete file (glassez)
- BUGFIX: Correctly count the number of torrents in subcategories (glassez)
- BUGFIX: Use "additional trackers" when metadata retrieving (glassez)
- BUGFIX: Apply correct tab order to Category options dialog (glassez)
- BUGFIX: Add all torrents passed via the command line (glassez)
- BUGFIX: Fix startup performance on Qt5 (glassez)
- BUGFIX: Automatic move will now overwrite existing files (aka previous behavior) (glassez)
- BUGFIX: Some fixes for loading Chinese locales (sledgehammer999)
- BUGFIX: New Pause icon color for toolbar/menu (Nowshed H. Imran, sledgehammer999)
- BUGFIX: Adjust env variable for PDB discovery (sledgehammer999)
- WEBUI: Fix missing "queued" icon (thalieht)
- WEBUI: Return paths using platform-independent separator format (glassez)
- WEBUI: Change order of accepted types of file input (Jason Carr)
- WEBUI: Add missing icons (brvphoenix)
- WEBUI: Add "Resume data storage type" option (thalieht)
- WEBUI: Make rename file dialog resizable (Torsten Schwarz)
- WEBUI: Prevent incorrect line breaking (David Xuang)
- WEBUI: Improve hotkeys (Fidel Selva)
- WEBUI: Remove suggestions while searching for torrents (Midhun V Nadh)
- WEBUI: Expose "IS PRIVATE" flag (sotiris-bos)
- WEBUI: Return name/hash/infohash_v1/infohash_v2 torrent properties (qbittorrentfan)
- WINDOWS: Correctly detect drive letter in path (glassez)
- WINDOWS: NSIS: Update Swedish, Lithuanian translations (Jonatan, Deividas)
- LINUX: Fix tray icon issues (glassez)
Sat Nov 26 2022 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.0
- FEATURE: Add `Auto resize columns` functionality (Chocobo1)
- FEATURE: Allow to use Category paths in `Manual` mode (glassez)
- FEATURE: Allow to disable Automatic mode when default "temp" path changed (glassez)
- FEATURE: Add tuning options related to performance warnings (Chocobo1)
- FEATURE: Add right click menu for status filters (An0n)
- FEATURE: Allow setting the number of maximum active checking torrents (An0n)
- FEATURE: Add option to toggle filters sidebar (AbeniMatteo)
- FEATURE: Allow to set `working set limit` on non-Windows OS (Chocobo1)
- FEATURE: Add `Export .torrent` action (Chocobo1)
- FEATURE: Add keyboard navigation keys (itlezy)
- FEATURE: Allow to use POSIX-compliant disk IO type (Coda)
- FEATURE: Add `Filter files` field in new torrent dialog (thalieht)
- FEATURE: Implement new icon/color theme (now-im, xavier2k6)
- FEATURE: Add file name filter/blacklist (mxtsdev, thalieht)
- FEATURE: Add support for custom SMTP ports (Emil M George)
- FEATURE: Split the OS cache settings into Disk IO read/write modes (summer)
- FEATURE: When duplicate torrent is added set metadata to existing one (glassez)
- FEATURE: Greatly improve startup time with many torrents (glassez, jagannatharjun)
- FEATURE: Add keyboard shortcut to Download URL dialog (Chocobo1)
- FEATURE: Add ability to run external program on torrent added (glassez)
- FEATURE: Add infohash and download path columns (tristanleboss)
- FEATURE: Allow to set torrent stop condition (glassez, thalieht)
- FEATURE: Add a `Moving` status filter (tristanleboss)
- FEATURE: Change color palettes for both dark, light themes (Chocobo1)
- FEATURE: Add a `Use proxy for hostname lookup` option (Nathan Lewis)
- FEATURE: Introduce a `change listen port` cmd option (BallsOfSpaghetti)
- FEATURE: Implement `Peer ID Client` column for `Peers` tab (Hanabishi)
- FEATURE: Add port forwarding option for embedded tracker (Chocobo1)
- BUGFIX: Store hybrid torrents using `torrent ID` as basename (glassez)
- BUGFIX: Enable Combobox editor for the `Mixed` file download priority (Aleksandr Cupacenko)
- BUGFIX: Allow shortcut folders for the Open and Save directory dialogs (Aleksandr Cupacenko)
- BUGFIX: Rename content tab `Size` column to `Total Size` (Aleksandr Cupacenko)
- BUGFIX: Fix scrolling to the lowermost visible torrent (Aleksandr Cupacenko)
- BUGFIX: Allow changing file priorities for finished torrents (An0n)
- BUGFIX: Focus save path when Manual mode is selected initially (Aleksandr Cupacenko)
- BUGFIX: Disable force reannounce when it is not possible (An0n)
- BUGFIX: Add horizontal scrolling for tracker list and torrent content (NotTsunami)
- BUGFIX: Enlarge "speed limits" icons (Chocobo1)
- BUGFIX: Change Downloaded to Times Downloaded in trackers tab (An0n)
- BUGFIX: Remove artificial max limits from `Torrent Queueing` related options (Chocobo1)
- BUGFIX: Preserve `skip hash check` when there is no metadata (glassez)
- BUGFIX: Fix DHT/PeX/LSD status when it is globally disabled (Kacper Michajłow)
- BUGFIX: Fix rate calculation when interval is too low (glassez)
- BUGFIX: Add tooltip message when system tray icon isn't available (Chocobo1)
- BUGFIX: Improve sender field in mail notifications (Dmitry Vodopyanov)
- BUGFIX: Fix "Add torrent dialog" spill-over on smaller screens (Chocobo1)
- BUGFIX: Fix peer count issue when tracker responds with zero figure (summer)
- BUGFIX: Don't merge trackers by default (glassez)
- BUGFIX: Don't inhibit system sleep/auto shutdown for torrents stuck at downloading metadata (summer)
- BUGFIX: Allow to pause a checking torrent from context menu (summer)
- BUGFIX: Allow to use subnet notation in reverse proxy list (Chocobo1)
- BUGFIX: Fine tune translations loading for Chinese locales (sledgehammer999)
- BUGFIX: Fix torrent content checkboxes not updated properly (Chocobo1)
- BUGFIX: Correctly load state of `Use another path for incomplete torrents` in Watched folders (glassez)
- BUGFIX: Add confirmation to resume/pause all (BallsOfSpaghetti)
- BUGFIX: Fix wrong count of errored trackers (Chocobo1)
- WEBUI: Allow blank lines in multipart form-data input (Aleksandr Cupacenko)
- WEBUI: Make various dialogs resizable (Chocobo1)
- WEBUI: Fix wrong v2 hash string displayed (Chocobo1)
- WEBUI: WebAPI: return correct status (Requi)
- WEBUI: Fix empty selection in language combobox (Chocobo1)
- WEBUI: Store WebUI port setting in human readable number (Chocobo1)
- WEBUI: Add support for exporting .torrent (Tom Piccirello)
- WEBUI: WebAPI: Add endpoint to set speed limit mode (glassez)
- WEBUI: Improve progress bar rendering (Mike Lei)
- WEBUI: Add transfer list refresh interval settings (summer)
- WEBUI: Use natural sort (Chocobo1)
- WEBUI: Apply i18n translation only to built-in WebUI (Chocobo1)
- WEBUI: Alert when HTTPS settings are incomplete (Chocobo1)
- WEBUI: Handle drag and drop events (Chocobo1)
- WEBUI: Fix wrong behavior for shutdown action (Chocobo1)
- WEBUI: Don't disable combobox for file priority (Chocobo1)
- RSS: Increase limit of maximum number of articles per feed (summer)
- WINDOWS: Fix `Open destination folder` delay on Windows (Andrew)
- WINDOWS: NSIS: Update Russian, Estonian, Japanese, Dutch, Portuguese BR, German and Indonesian translations (Andrei Stepanov, Priit Uring, maboroshin, Thomas De Rocker, Ícaro, schnurlos, Faisal A. F. Rahman)
- LINUX: Mark as single window app in .desktop file (Nicolas Fella)
- LINUX: Add Dockerfile (Amanuense-del-diavolo, Tom Piccirello, Chocobo1)
- LINUX: Remove option of using icons from system theme (now-im)
- MACOS: Fix wrong background color in properties widget (NotTsunami)
- OTHER: Binary distributions of qbittorrent are GPLv3+ licensed (sledgehammer999)
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)

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.5.0beta1.
# Generated by GNU Autoconf 2.71 for qbittorrent v4.5.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.5.0beta1'
PACKAGE_STRING='qbittorrent v4.5.0beta1'
PACKAGE_VERSION='v4.5.1'
PACKAGE_STRING='qbittorrent v4.5.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.5.0beta1 to adapt to many kinds of systems.
\`configure' configures qbittorrent v4.5.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.5.0beta1:";;
short | recursive ) echo "Configuration of qbittorrent v4.5.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.5.0beta1
qbittorrent configure v4.5.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.5.0beta1, which was
It was created by qbittorrent $as_me v4.5.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.5.0beta1'
VERSION='v4.5.1'
printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
@@ -7237,7 +7237,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by qbittorrent $as_me v4.5.0beta1, which was
This file was extended by qbittorrent $as_me v4.5.1, which was
generated by GNU Autoconf 2.71. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -7297,7 +7297,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
qbittorrent config.status v4.5.0beta1
qbittorrent config.status v4.5.1
configured by $0, generated by GNU Autoconf 2.71,
with options \\"\$ac_cs_config\\"

View File

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

8
dist/docker/.env vendored
View File

@@ -1,8 +0,0 @@
# refer to Readme.md for an explanation of the variables
QBT_EULA=
QBT_VERSION=devel
QBT_WEBUI_PORT=8080
QBT_CONFIG_PATH=<your_path>/config
QBT_DOWNLOADS_PATH=<your_path>/downloads

View File

@@ -1,62 +0,0 @@
# image for building
FROM alpine:latest AS builder
ARG QBT_VERSION
# alpine linux qbittorrent package: https://git.alpinelinux.org/aports/tree/community/qbittorrent/APKBUILD
RUN \
apk --update-cache add \
boost-dev \
cmake \
g++ \
libtorrent-rasterbar-dev \
ninja \
qt6-qtbase-dev \
qt6-qttools-dev
RUN \
if [ "$QBT_VERSION" = "devel" ]; then \
wget https://github.com/qbittorrent/qBittorrent/archive/refs/heads/master.zip && \
unzip master.zip && \
cd qBittorrent-master ; \
else \
wget "https://github.com/qbittorrent/qBittorrent/archive/refs/tags/release-${QBT_VERSION}.tar.gz" && \
tar -xf "release-${QBT_VERSION}.tar.gz" && \
cd "qBittorrent-release-${QBT_VERSION}" ; \
fi && \
cmake \
-B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DGUI=OFF \
-DQT6=ON \
-DSTACKTRACE=OFF && \
cmake --build build && \
cmake --install build
# image for running
FROM alpine:latest
RUN \
apk --no-cache add \
doas \
libtorrent-rasterbar \
python3 \
qt6-qtbase \
tini
RUN \
adduser \
-D \
-H \
-s /sbin/nologin \
-u 1000 \
qbtUser && \
echo "permit nopass :root" >> "/etc/doas.d/doas.conf"
COPY --from=builder /usr/local/bin/qbittorrent-nox /usr/bin/qbittorrent-nox
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/sbin/tini", "-g", "--", "/entrypoint.sh"]

101
dist/docker/Readme.md vendored
View File

@@ -1,101 +0,0 @@
# qBittorrent-nox Docker Image
This Dockerfile allows you to build a Docker Image containing qBittorrent-nox
## Prerequisites
In order to build/run this image you'll need Docker installed: https://docs.docker.com/get-docker/
If you don't need the GUI, you can just install Docker Engine: https://docs.docker.com/engine/install/
It is also recommended to install Docker Compose as it can significantly ease the process: https://docs.docker.com/compose/install/
## Building Docker Image
* If you are using Docker (not Docker Compose) then run the following commands in this folder:
```shell
export \
QBT_VERSION=devel
docker build \
--build-arg QBT_VERSION \
-t qbittorrent-nox:"$QBT_VERSION" \
.
```
* If you are using Docker Compose then you should edit `.env` file first.
You can find an explanation of the variables in the following [Parameters](#parameters) section. \
Then run the following commands in this folder:
```shell
docker compose build \
--build-arg QBT_VERSION
```
### Parameters
#### Environment variables
* `QBT_EULA` \
This environment variable defines whether you accept the end-user license agreement (EULA) of qBittorrent. \
**Put `accept` only if you understand and accepted the EULA.** You can find
the EULA [here](https://github.com/qbittorrent/qBittorrent/blob/56667e717b82c79433ecb8a5ff6cc2d7b315d773/src/app/main.cpp#L320-L323).
* `QBT_VERSION` \
This environment variable specifies the version of qBittorrent-nox to be built. \
For example, `4.4.0` is a valid entry. You can find all tagged versions [here](https://github.com/qbittorrent/qBittorrent/tags). \
Or you can put `devel` to build the latest development version.
* `QBT_WEBUI_PORT` \
This environment variable sets the port number which qBittorrent WebUI will be binded to.
#### Volumes
There are some paths involved:
* `<your_path>/config` \
Full path to a folder on your host machine which will store qBittorrent configurations.
Using relative path won't work.
* `<your_path>/downloads` \
Full path to a folder on your host machine which will store the files downloaded by qBittorrent.
Using relative path won't work.
## Running container
* Using Docker (not Docker Compose), simply run:
```shell
export \
QBT_EULA=accept \
QBT_VERSION=devel \
QBT_WEBUI_PORT=8080 \
QBT_CONFIG_PATH="/tmp/bbb/config"
QBT_DOWNLOADS_PATH="/tmp/bbb/downloads"
docker run \
-t \
--read-only \
--rm \
--tmpfs /tmp \
--name qbittorrent-nox \
-e QBT_EULA \
-e QBT_WEBUI_PORT \
-p "$QBT_WEBUI_PORT":"$QBT_WEBUI_PORT"/tcp \
-p 6881:6881/tcp \
-p 6881:6881/udp \
-v "$QBT_CONFIG_PATH":/config \
-v "$QBT_DOWNLOADS_PATH":/downloads \
qbittorrent-nox:"$QBT_VERSION"
```
* Using Docker Compose:
```shell
docker compose up
```
Then you can login at: `http://127.0.0.1:8080`
## Stopping container
* Using Docker (not Docker Compose):
```shell
docker stop -t 1800 qbittorrent-nox
```
* Using Docker Compose:
```shell
docker compose down
```

View File

@@ -1,25 +0,0 @@
version: "3.9"
services:
qbittorrent-nox:
build: .
container_name: qbittorrent-nox
environment:
- QBT_EULA=${QBT_EULA}
- QBT_VERSION=${QBT_VERSION}
- QBT_WEBUI_PORT=${QBT_WEBUI_PORT}
image: qbittorrent-nox:${QBT_VERSION}
ports:
# for bittorrent traffic
- 6881:6881/tcp
- 6881:6881/udp
# for WebUI
- ${QBT_WEBUI_PORT}:${QBT_WEBUI_PORT}/tcp
read_only: true
stop_grace_period: 30m
tmpfs:
- /tmp
tty: true
volumes:
- ${QBT_CONFIG_PATH}:/config
- ${QBT_DOWNLOADS_PATH}:/downloads

View File

@@ -1,35 +0,0 @@
#!/bin/sh
downloadsPath="/downloads"
profilePath="/config"
qbtConfigFile="$profilePath/qBittorrent/config/qBittorrent.conf"
if [ ! -f "$qbtConfigFile" ]; then
mkdir -p "$(dirname $qbtConfigFile)"
cat << EOF > "$qbtConfigFile"
[BitTorrent]
Session\DefaultSavePath=/downloads
Session\Port=6881
Session\TempPath=/downloads/temp
[LegalNotice]
Accepted=false
EOF
if [ "$QBT_EULA" = "accept" ]; then
sed -i '/^\[LegalNotice\]$/{$!{N;s|\(\[LegalNotice\]\nAccepted=\).*|\1true|}}' "$qbtConfigFile"
else
sed -i '/^\[LegalNotice\]$/{$!{N;s|\(\[LegalNotice\]\nAccepted=\).*|\1false|}}' "$qbtConfigFile"
fi
fi
# those are owned by root by default
# don't change existing files owner in `$downloadsPath`
chown qbtUser:qbtUser "$downloadsPath"
chown qbtUser:qbtUser -R "$profilePath"
doas -u qbtUser \
qbittorrent-nox \
--profile="$profilePath" \
--webui-port="$QBT_WEBUI_PORT" \
"$@"

2
dist/mac/Info.plist vendored
View File

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

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.5.0" date="2022-01-06"/>
<release version="4.5.1" date="2023-02-12"/>
</releases>
</component>

View File

@@ -98,8 +98,8 @@ Name[is]=qBittorrent
Comment[it]=Scarica e condividi file tramite BitTorrent
GenericName[it]=Client BitTorrent
Name[it]=qBittorrent
Comment[ja]=BitTorrent でファイルをダウンロードおよび共有します
GenericName[ja]=BitTorrent クライアント
Comment[ja]=BitTorrentでファイルをダウンロードおよび共有
GenericName[ja]=BitTorrentクライアント
Name[ja]=qBittorrent
Comment[ka]=გადმოტვირთეთ და გააზიარეთ ფაილები BitTorrent-ის საშუალებით
GenericName[ka]=BitTorrent კლიენტი

View File

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

View File

@@ -3,7 +3,7 @@
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
LangString inst_qbt_req ${LANG_LITHUANIAN} "qBittorrent (reikalingas)"
;LangString inst_dekstop ${LANG_ENGLISH} "Create Desktop Shortcut"
LangString inst_dekstop ${LANG_LITHUANIAN} "Sukurti darbalaukyje nuorodą"
LangString inst_dekstop ${LANG_LITHUANIAN} "Sukurti nuorodą darbalaukyje"
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
LangString inst_startmenu ${LANG_LITHUANIAN} "Sukurti Pradėti meniu nuorodą"
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
@@ -13,27 +13,27 @@ LangString inst_torrent ${LANG_LITHUANIAN} "Atidaryti .torrent failus su qBittor
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
LangString inst_magnet ${LANG_LITHUANIAN} "Atidaryti magneto nuorodas su qBittorrent"
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
LangString inst_firewall ${LANG_LITHUANIAN} "Sukurti Windows užkardos leidimą"
LangString inst_firewall ${LANG_LITHUANIAN} "Sukurti Windows interneto užkardos leidimą"
;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_LITHUANIAN} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
LangString inst_pathlimit ${LANG_LITHUANIAN} "Išjungti Windows path ilgio limitaciją (260 ženklų MAX_PATH limitacija, reikalinga versija yra Windows 10 1607 ar naujesnė)"
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
LangString inst_firewallinfo ${LANG_LITHUANIAN} "Pridedu Windows užkardos leidimą"
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
LangString inst_warning ${LANG_LITHUANIAN} "qBittorrent yra paleistas. Prašau uždaryti programą prieš įdiegiant."
LangString inst_warning ${LANG_LITHUANIAN} "qBittorrent yra paleistas. Prašome uždaryti programą prieš įdiegiant."
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
LangString inst_uninstall_question ${LANG_LITHUANIAN} "Current version will be uninstalled. User settings and torrents will remain intact."
LangString inst_uninstall_question ${LANG_LITHUANIAN} "Dabartinė versija bus pašalinta. Naudotojo nustatymai ir torrentai liks nepakeisti."
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
LangString inst_unist ${LANG_LITHUANIAN} "Šalinu ankstesnę versiją."
LangString inst_unist ${LANG_LITHUANIAN} "Šalinama ankstesnė versija."
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
LangString launch_qbt ${LANG_LITHUANIAN} "Paleisti qBittorrent."
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
LangString inst_requires_64bit ${LANG_LITHUANIAN} "This installer works only in 64-bit Windows versions."
LangString inst_requires_64bit ${LANG_LITHUANIAN} "Šis įdiegėjas veikia tik su 64 bitų Windows versija."
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
LangString inst_requires_win7 ${LANG_LITHUANIAN} "This qBittorrent version requires at least Windows 7."
LangString inst_requires_win7 ${LANG_LITHUANIAN} "Ši qBittorent versija reikalauja bent Windows 7."
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 1809."
LangString inst_requires_win10 ${LANG_LITHUANIAN} "This installer requires at least Windows 10 1809."
LangString inst_requires_win10 ${LANG_LITHUANIAN} "Šis įdiegėjas reikalauja bent Windows 10 1809."
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
LangString inst_uninstall_link_description ${LANG_LITHUANIAN} "Uninstall qBittorrent"
LangString inst_uninstall_link_description ${LANG_LITHUANIAN} "Pašalinti qBittorrent"
;------------------------------------
;Uninstaller strings
@@ -49,14 +49,14 @@ LangString remove_registry ${LANG_LITHUANIAN} "Pašalinti registro raktus"
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
LangString remove_conf ${LANG_LITHUANIAN} "Pašalinti nustatymų failus"
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
LangString remove_firewall ${LANG_LITHUANIAN} "Pašalinti Windows užkardos leidimą"
LangString remove_firewall ${LANG_LITHUANIAN} "Pašalinti Windows interneto užkardos leidimą"
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
LangString remove_firewallinfo ${LANG_LITHUANIAN} "Šalinu Windows užkardos leidimą"
LangString remove_firewallinfo ${LANG_LITHUANIAN} "Šalinamas Windows interneto užkardos leidimas"
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
LangString remove_cache ${LANG_LITHUANIAN} "Pašalinti torentus ir podėlio duomenis"
LangString remove_cache ${LANG_LITHUANIAN} "Pašalinti torentus ir talpyklos duomenis"
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
LangString uninst_warning ${LANG_LITHUANIAN} "qBittorrent yra paleistas. Prašau uždarykite programą prieš išdiegiant."
LangString uninst_warning ${LANG_LITHUANIAN} "qBittorrent yra paleistas. Prašome uždaryti programą prieš išdiegiant."
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
LangString uninst_tor_warn ${LANG_LITHUANIAN} "Negaliu pašalinti .torrent asociacijos. Ji yra susieti su:"
LangString uninst_tor_warn ${LANG_LITHUANIAN} "Neįmanoma pašalinti .torrent asociacijos. Ji yra susieta su:"
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
LangString uninst_mag_warn ${LANG_LITHUANIAN} "Negaliu pašalinti magneto asociacijos. Jis susietas su:"
LangString uninst_mag_warn ${LANG_LITHUANIAN} "Neįmanoma pašalinti magneto asociacijos. Ji yra susieta su:"

View File

@@ -27,11 +27,11 @@ LangString inst_unist ${LANG_SWEDISH} "Avinstallerar tidigare version."
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
LangString launch_qbt ${LANG_SWEDISH} "Kör qBittorrent."
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
LangString inst_requires_64bit ${LANG_SWEDISH} "Installationsprogrammet fungerar endast i 64-bitars Windows-versioner."
LangString inst_requires_64bit ${LANG_SWEDISH} "Det här installationsprogrammet fungerar endast i 64-bitars Windows-versioner."
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
LangString inst_requires_win7 ${LANG_SWEDISH} "Den här qBittorrent-versionen kräver minst Windows 7."
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 1809."
LangString inst_requires_win10 ${LANG_SWEDISH} "This installer requires at least Windows 10 1809."
LangString inst_requires_win10 ${LANG_SWEDISH} "Det här installationsprogrammet kräver minst Windows 10 1809."
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
LangString inst_uninstall_link_description ${LANG_SWEDISH} "Avinstallera qBittorrent"

View File

@@ -35,6 +35,8 @@ Section $(inst_qbt_req) ;"qBittorrent (required)"
SetOutPath "$INSTDIR\translations"
; Put files there
File /r "translations\qt*.qm"
; Restore output path because it affects `CreateShortCut`. It affects the "Start in" field.
SetOutPath $INSTDIR
; Write the installation path into the registry
WriteRegStr HKLM "Software\qBittorrent" "InstallLocation" "$INSTDIR"

View File

@@ -88,6 +88,7 @@
#include "base/version.h"
#include "applicationinstancemanager.h"
#include "filelogger.h"
#include "upgrade.h"
#ifndef DISABLE_GUI
#include "gui/addnewtorrentdialog.h"
@@ -171,6 +172,18 @@ Application::Application(int &argc, char **argv)
SettingsStorage::initInstance();
Preferences::initInstance();
const bool firstTimeUser = !Preferences::instance()->getAcceptedLegal();
if (!firstTimeUser)
{
if (!upgrade())
throw RuntimeError(u"Failed migration of old settings"_qs); // Not translatable. Translation isn't configured yet.
handleChangedDefaults(DefaultPreferencesMode::Legacy);
}
else
{
handleChangedDefaults(DefaultPreferencesMode::Current);
}
initializeTranslation();
connect(this, &QCoreApplication::aboutToQuit, this, &Application::cleanup);
@@ -658,8 +671,7 @@ Application::AddTorrentParams Application::parseParams(const QStringList &params
continue;
}
parsedParams.torrentSource = param;
break;
parsedParams.torrentSources.append(param);
}
return parsedParams;
@@ -675,10 +687,16 @@ void Application::processParams(const AddTorrentParams &params)
// should be overridden.
const bool showDialogForThisTorrent = !params.skipTorrentDialog.value_or(!AddNewTorrentDialog::isEnabled());
if (showDialogForThisTorrent)
AddNewTorrentDialog::show(params.torrentSource, params.torrentParams, m_window);
{
for (const QString &torrentSource : params.torrentSources)
AddNewTorrentDialog::show(torrentSource, params.torrentParams, m_window);
}
else
#endif
BitTorrent::Session::instance()->addTorrent(params.torrentSource, params.torrentParams);
{
for (const QString &torrentSource : params.torrentSources)
BitTorrent::Session::instance()->addTorrent(torrentSource, params.torrentParams);
}
}
int Application::exec(const QStringList &params)
@@ -706,7 +724,7 @@ try
#ifndef DISABLE_GUI
UIThemeManager::initInstance();
m_desktopIntegration = new DesktopIntegration(this);
m_desktopIntegration = new DesktopIntegration;
m_desktopIntegration->setToolTip(tr("Loading torrents..."));
#ifndef Q_OS_MACOS
auto *desktopIntegrationMenu = new QMenu;
@@ -788,12 +806,9 @@ try
});
disconnect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog);
// we must not delete menu while it is used by DesktopIntegration
auto *oldMenu = m_desktopIntegration->menu();
const MainWindow::State windowState = (!m_startupProgressDialog || (m_startupProgressDialog->windowState() & Qt::WindowMinimized))
? MainWindow::Minimized : MainWindow::Normal;
m_window = new MainWindow(this, windowState);
delete oldMenu;
delete m_startupProgressDialog;
#ifdef Q_OS_WIN
auto *pref = Preferences::instance();
@@ -882,7 +897,7 @@ void Application::createStartupProgressDialog()
m_startupProgressDialog = new QProgressDialog(tr("Loading torrents..."), tr("Exit"), 0, 100);
m_startupProgressDialog->setAttribute(Qt::WA_DeleteOnClose);
m_startupProgressDialog->setWindowFlag(Qt::WindowMinimizeButtonHint);
m_startupProgressDialog->setMinimumDuration(0); // Show dialog immediatelly by default
m_startupProgressDialog->setMinimumDuration(0); // Show dialog immediately by default
m_startupProgressDialog->setAutoReset(false);
m_startupProgressDialog->setAutoClose(false);
@@ -1201,6 +1216,7 @@ void Application::cleanup()
::ShutdownBlockReasonDestroy(reinterpret_cast<HWND>(m_window->effectiveWinId()));
#endif // Q_OS_WIN
delete m_window;
delete m_desktopIntegration;
UIThemeManager::freeInstance();
}
#endif // DISABLE_GUI

View File

@@ -148,7 +148,7 @@ private slots:
private:
struct AddTorrentParams
{
QString torrentSource;
QStringList torrentSources;
BitTorrent::AddTorrentParams torrentParams;
std::optional<bool> skipTorrentDialog;
};

View File

@@ -118,6 +118,17 @@ int main(int argc, char *argv[])
// Create Application
auto app = std::make_unique<Application>(argc, argv);
#ifdef Q_OS_WIN
// QCoreApplication::applicationDirPath() needs an Application object instantiated first
// Let's hope that there won't be a crash before this line
const char *envName = "_NT_SYMBOL_PATH";
const QString envValue = qEnvironmentVariable(envName);
if (envValue.isEmpty())
qputenv(envName, Application::applicationDirPath().toLocal8Bit());
else
qputenv(envName, u"%1;%2"_qs.arg(envValue, Application::applicationDirPath()).toLocal8Bit());
#endif
const QBtCommandLineParameters params = app->commandLineArgs();
if (!params.unknownParameter.isEmpty())
{
@@ -221,26 +232,6 @@ int main(int argc, char *argv[])
app->setAttribute(Qt::AA_DontShowIconsInMenus);
#endif
if (!firstTimeUser)
{
handleChangedDefaults(DefaultPreferencesMode::Legacy);
#ifndef DISABLE_GUI
if (!upgrade()) return EXIT_FAILURE;
#elif defined(Q_OS_WIN)
if (!upgrade(_isatty(_fileno(stdin))
&& _isatty(_fileno(stdout)))) return EXIT_FAILURE;
#else
if (!upgrade(!params.shouldDaemonize
&& isatty(fileno(stdin))
&& isatty(fileno(stdout)))) return EXIT_FAILURE;
#endif
}
else
{
handleChangedDefaults(DefaultPreferencesMode::Current);
}
#if defined(DISABLE_GUI) && !defined(Q_OS_WIN)
if (params.shouldDaemonize)
{
@@ -274,6 +265,11 @@ int main(int argc, char *argv[])
displayBadArgMessage(er.message());
return EXIT_FAILURE;
}
catch (const RuntimeError &er)
{
qDebug() << er.message();
return EXIT_FAILURE;
}
}
#if !defined(DISABLE_GUI)

View File

@@ -384,9 +384,21 @@ namespace
}
}
#endif
void migrateChineseLocale()
{
auto *settingsStorage = SettingsStorage::instance();
const auto key = u"Preferences/General/Locale"_qs;
if (settingsStorage->hasKey(key))
{
const auto locale = settingsStorage->loadValue<QString>(key);
if (locale.compare(u"zh"_qs, Qt::CaseInsensitive) == 0)
settingsStorage->storeValue(key, u"zh_CN"_qs);
}
}
}
bool upgrade(const bool /*ask*/)
bool upgrade()
{
CachedSettingValue<int> version {MIGRATION_VERSION_KEY, 0};
@@ -413,6 +425,9 @@ bool upgrade(const bool /*ask*/)
migrateMemoryPrioritySettings();
#endif
{
migrateChineseLocale();
}
version = MIGRATION_VERSION;
}

View File

@@ -35,5 +35,5 @@ enum class DefaultPreferencesMode
};
void handleChangedDefaults(DefaultPreferencesMode mode);
bool upgrade(bool ask = true);
bool upgrade();
void setCurrentMigrationVersion();

View File

@@ -101,6 +101,7 @@ add_library(qbt_base STATIC
utils/password.h
utils/random.h
utils/string.h
utils/thread.h
utils/version.h
version.h
@@ -183,6 +184,7 @@ add_library(qbt_base STATIC
utils/password.cpp
utils/random.cpp
utils/string.cpp
utils/thread.cpp
)
target_link_libraries(qbt_base

View File

@@ -101,6 +101,7 @@ HEADERS += \
$$PWD/utils/password.h \
$$PWD/utils/random.h \
$$PWD/utils/string.h \
$$PWD/utils/thread.h \
$$PWD/utils/version.h \
$$PWD/version.h
@@ -182,4 +183,5 @@ SOURCES += \
$$PWD/utils/net.cpp \
$$PWD/utils/password.cpp \
$$PWD/utils/random.cpp \
$$PWD/utils/string.cpp
$$PWD/utils/string.cpp \
$$PWD/utils/thread.cpp

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -91,7 +91,7 @@ namespace
BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const Path &path, QObject *parent)
: ResumeDataStorage(path, parent)
, m_ioThread {new QThread(this)}
, m_ioThread {new QThread}
, m_asyncWorker {new Worker(path)}
{
Q_ASSERT(path.isAbsolute());
@@ -117,17 +117,11 @@ BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const Path &path,
qDebug() << "Registered torrents count: " << m_registeredTorrents.size();
m_asyncWorker->moveToThread(m_ioThread);
connect(m_ioThread, &QThread::finished, m_asyncWorker, &QObject::deleteLater);
m_asyncWorker->moveToThread(m_ioThread.get());
connect(m_ioThread.get(), &QThread::finished, m_asyncWorker, &QObject::deleteLater);
m_ioThread->start();
}
BitTorrent::BencodeResumeDataStorage::~BencodeResumeDataStorage()
{
m_ioThread->quit();
m_ioThread->wait();
}
QVector<BitTorrent::TorrentID> BitTorrent::BencodeResumeDataStorage::registeredTorrents() const
{
return m_registeredTorrents;
@@ -210,7 +204,6 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
return nonstd::make_unexpected(tr("Cannot parse resume data: invalid format"));
LoadTorrentParams torrentParams;
torrentParams.restored = true;
torrentParams.category = fromLTString(resumeDataRoot.dict_find_string_value("qBt-category"));
torrentParams.name = fromLTString(resumeDataRoot.dict_find_string_value("qBt-name"));
torrentParams.hasSeedStatus = resumeDataRoot.dict_find_int_value("qBt-seedStatus");

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -32,6 +32,8 @@
#include <QVector>
#include "base/pathfwd.h"
#include "base/utils/thread.h"
#include "resumedatastorage.h"
class QByteArray;
@@ -46,7 +48,6 @@ namespace BitTorrent
public:
explicit BencodeResumeDataStorage(const Path &path, QObject *parent = nullptr);
~BencodeResumeDataStorage() override;
QVector<TorrentID> registeredTorrents() const override;
LoadResumeDataResult load(const TorrentID &id) const override;
@@ -60,7 +61,7 @@ namespace BitTorrent
LoadResumeDataResult loadTorrentResumeData(const QByteArray &data, const QByteArray &metadata) const;
QVector<TorrentID> m_registeredTorrents;
QThread *m_ioThread = nullptr;
Utils::Thread::UniquePtr m_ioThread;
class Worker;
Worker *m_asyncWorker = nullptr;

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2021-2022 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -196,7 +196,6 @@ namespace BitTorrent
LoadTorrentParams parseQueryResultRow(const QSqlQuery &query)
{
LoadTorrentParams resumeData;
resumeData.restored = true;
resumeData.name = query.value(DB_COLUMN_NAME.name).toString();
resumeData.category = query.value(DB_COLUMN_CATEGORY.name).toString();
const QString tagsData = query.value(DB_COLUMN_TAGS.name).toString();
@@ -257,7 +256,7 @@ namespace BitTorrent
BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject *parent)
: ResumeDataStorage(dbPath, parent)
, m_ioThread {new QThread(this)}
, m_ioThread {new QThread}
{
const bool needCreateDB = !dbPath.exists();
@@ -278,8 +277,8 @@ BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject
}
m_asyncWorker = new Worker(dbPath, u"ResumeDataStorageWorker"_qs, m_dbLock);
m_asyncWorker->moveToThread(m_ioThread);
connect(m_ioThread, &QThread::finished, m_asyncWorker, &QObject::deleteLater);
m_asyncWorker->moveToThread(m_ioThread.get());
connect(m_ioThread.get(), &QThread::finished, m_asyncWorker, &QObject::deleteLater);
m_ioThread->start();
RuntimeError *errPtr = nullptr;
@@ -303,9 +302,6 @@ BitTorrent::DBResumeDataStorage::~DBResumeDataStorage()
{
QMetaObject::invokeMethod(m_asyncWorker, &Worker::closeDatabase);
QSqlDatabase::removeDatabase(DB_CONNECTION_NAME);
m_ioThread->quit();
m_ioThread->wait();
}
QVector<BitTorrent::TorrentID> BitTorrent::DBResumeDataStorage::registeredTorrents() const

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2021-2022 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -31,6 +31,7 @@
#include <QReadWriteLock>
#include "base/pathfwd.h"
#include "base/utils/thread.h"
#include "resumedatastorage.h"
class QThread;
@@ -59,7 +60,7 @@ namespace BitTorrent
void createDB() const;
void updateDB(int fromVersion) const;
QThread *m_ioThread = nullptr;
Utils::Thread::UniquePtr m_ioThread;
class Worker;
Worker *m_asyncWorker = nullptr;

View File

@@ -58,7 +58,5 @@ namespace BitTorrent
qreal ratioLimit = Torrent::USE_GLOBAL_RATIO;
int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME;
bool restored = false; // is existing torrent job?
};
}

View File

@@ -181,6 +181,31 @@ QString PeerInfo::client() const
return QString::fromStdString(m_nativeInfo.client);
}
QString PeerInfo::peerIdClient() const
{
// when peer ID is not known yet it contains only zero bytes,
// do not create string in such case, return empty string instead
if (m_nativeInfo.pid.is_all_zeros())
return {};
QString result;
// interesting part of a typical peer ID is first 8 chars
for (int i = 0; i < 8; ++i)
{
const std::uint8_t c = m_nativeInfo.pid[i];
// ensure that the peer ID slice consists only of printable ASCII characters,
// this should filter out most of the improper IDs
if ((c < 32) || (c > 126))
return tr("Unknown");
result += QChar::fromLatin1(c);
}
return result;
}
qreal PeerInfo::progress() const
{
return m_nativeInfo.progress;

View File

@@ -78,6 +78,7 @@ namespace BitTorrent
PeerAddress address() const;
QString client() const;
QString peerIdClient() const;
qreal progress() const;
int payloadUpSpeed() const;
int payloadDownSpeed() const;

View File

@@ -30,6 +30,7 @@
#include <libtorrent/session.hpp>
#include "base/algorithm.h"
#include "base/logger.h"
PortForwarderImpl::PortForwarderImpl(lt::session *provider, QObject *parent)
@@ -63,45 +64,64 @@ void PortForwarderImpl::setEnabled(const bool enabled)
m_storeActive = enabled;
}
void PortForwarderImpl::addPort(const quint16 port)
void PortForwarderImpl::setPorts(const QString &profile, QSet<quint16> ports)
{
if (m_mappedPorts.contains(port))
return;
if (isEnabled())
m_mappedPorts.insert(port, m_provider->add_port_mapping(lt::session::tcp, port, port));
else
m_mappedPorts.insert(port, {});
}
void PortForwarderImpl::deletePort(const quint16 port)
{
const auto iter = m_mappedPorts.find(port);
if (iter == m_mappedPorts.end())
return;
if (isEnabled())
PortMapping &portMapping = m_portProfiles[profile];
Algorithm::removeIf(portMapping, [this, &ports](const quint16 port, const std::vector<lt::port_mapping_t> &handles)
{
for (const lt::port_mapping_t &portMapping : *iter)
m_provider->delete_port_mapping(portMapping);
// keep existing forwardings
const bool isAlreadyMapped = ports.remove(port);
if (isAlreadyMapped)
return false;
// remove outdated forwardings
for (const lt::port_mapping_t &handle : handles)
m_provider->delete_port_mapping(handle);
m_forwardedPorts.remove(port);
return true;
});
// add new forwardings
for (const quint16 port : ports)
{
// port already forwarded/taken by other profile, don't do anything
if (m_forwardedPorts.contains(port))
continue;
if (isEnabled())
portMapping.insert(port, m_provider->add_port_mapping(lt::session::tcp, port, port));
else
portMapping.insert(port, {});
m_forwardedPorts.insert(port);
}
m_mappedPorts.erase(iter);
if (portMapping.isEmpty())
m_portProfiles.remove(profile);
}
void PortForwarderImpl::removePorts(const QString &profile)
{
setPorts(profile, {});
}
void PortForwarderImpl::start()
{
lt::settings_pack settingsPack = m_provider->get_settings();
lt::settings_pack settingsPack;
settingsPack.set_bool(lt::settings_pack::enable_upnp, true);
settingsPack.set_bool(lt::settings_pack::enable_natpmp, true);
m_provider->apply_settings(settingsPack);
m_provider->apply_settings(std::move(settingsPack));
for (auto iter = m_mappedPorts.begin(); iter != m_mappedPorts.end(); ++iter)
for (auto profileIter = m_portProfiles.begin(); profileIter != m_portProfiles.end(); ++profileIter)
{
Q_ASSERT(iter.value().empty());
PortMapping &portMapping = profileIter.value();
for (auto iter = portMapping.begin(); iter != portMapping.end(); ++iter)
{
Q_ASSERT(iter.value().empty());
const quint16 port = iter.key();
iter.value() = m_provider->add_port_mapping(lt::session::tcp, port, port);
const quint16 port = iter.key();
iter.value() = m_provider->add_port_mapping(lt::session::tcp, port, port);
}
}
LogMsg(tr("UPnP/NAT-PMP support: ON"), Log::INFO);
@@ -109,14 +129,18 @@ void PortForwarderImpl::start()
void PortForwarderImpl::stop()
{
lt::settings_pack settingsPack = m_provider->get_settings();
lt::settings_pack settingsPack;
settingsPack.set_bool(lt::settings_pack::enable_upnp, false);
settingsPack.set_bool(lt::settings_pack::enable_natpmp, false);
m_provider->apply_settings(settingsPack);
m_provider->apply_settings(std::move(settingsPack));
// don't clear m_mappedPorts so a later `start()` call can restore the port forwarding
for (auto iter = m_mappedPorts.begin(); iter != m_mappedPorts.end(); ++iter)
iter.value().clear();
// don't clear m_portProfiles so a later `start()` call can restore the port forwardings
for (auto profileIter = m_portProfiles.begin(); profileIter != m_portProfiles.end(); ++profileIter)
{
PortMapping &portMapping = profileIter.value();
for (auto iter = portMapping.begin(); iter != portMapping.end(); ++iter)
iter.value().clear();
}
LogMsg(tr("UPnP/NAT-PMP support: OFF"), Log::INFO);
}

View File

@@ -34,6 +34,7 @@
#include <libtorrent/portmap.hpp>
#include <QHash>
#include <QSet>
#include "base/net/portforwarder.h"
#include "base/settingvalue.h"
@@ -50,8 +51,8 @@ public:
bool isEnabled() const override;
void setEnabled(bool enabled) override;
void addPort(quint16 port) override;
void deletePort(quint16 port) override;
void setPorts(const QString &profile, QSet<quint16> ports) override;
void removePorts(const QString &profile) override;
private:
void start();
@@ -59,5 +60,8 @@ private:
CachedSettingValue<bool> m_storeActive;
lt::session *const m_provider = nullptr;
QHash<quint16, std::vector<lt::port_mapping_t>> m_mappedPorts;
using PortMapping = QHash<quint16, std::vector<lt::port_mapping_t>>; // <port, handles>
QHash<QString, PortMapping> m_portProfiles;
QSet<quint16> m_forwardedPorts;
};

View File

@@ -33,6 +33,7 @@
#include <QMetaObject>
#include <QMutexLocker>
#include <QThread>
#include <QVector>
const int TORRENTIDLIST_TYPEID = qRegisterMetaType<QVector<BitTorrent::TorrentID>>();
@@ -59,11 +60,11 @@ void BitTorrent::ResumeDataStorage::loadAll() const
loadingThread->start();
}
QVector<BitTorrent::LoadedResumeData> BitTorrent::ResumeDataStorage::fetchLoadedResumeData() const
QList<BitTorrent::LoadedResumeData> BitTorrent::ResumeDataStorage::fetchLoadedResumeData() const
{
const QMutexLocker locker {&m_loadedResumeDataMutex};
const QVector<BitTorrent::LoadedResumeData> loadedResumeData = m_loadedResumeData;
const QList<BitTorrent::LoadedResumeData> loadedResumeData = m_loadedResumeData;
m_loadedResumeData.clear();
return loadedResumeData;

View File

@@ -29,9 +29,9 @@
#pragma once
#include <QtContainerFwd>
#include <QList>
#include <QMutex>
#include <QObject>
#include <QVector>
#include "base/3rdparty/expected.hpp"
#include "base/path.h"
@@ -65,7 +65,7 @@ namespace BitTorrent
virtual void storeQueue(const QVector<TorrentID> &queue) const = 0;
void loadAll() const;
QVector<LoadedResumeData> fetchLoadedResumeData() const;
QList<LoadedResumeData> fetchLoadedResumeData() const;
signals:
void loadStarted(const QVector<BitTorrent::TorrentID> &torrents);
@@ -78,7 +78,7 @@ namespace BitTorrent
virtual void doLoadAll() const = 0;
const Path m_path;
mutable QVector<LoadedResumeData> m_loadedResumeData;
mutable QList<LoadedResumeData> m_loadedResumeData;
mutable QMutex m_loadedResumeDataMutex;
};
}

View File

@@ -403,9 +403,6 @@ namespace BitTorrent
virtual Torrent *findTorrent(const InfoHash &infoHash) const = 0;
virtual QVector<Torrent *> torrents() const = 0;
virtual qsizetype torrentsCount() const = 0;
virtual bool hasActiveTorrents() const = 0;
virtual bool hasUnfinishedTorrents() const = 0;
virtual bool hasRunningSeed() const = 0;
virtual const SessionStatus &status() const = 0;
virtual const CacheStatus &cacheStatus() const = 0;
virtual bool isListening() const = 0;

View File

@@ -80,6 +80,7 @@
#include "base/logger.h"
#include "base/net/downloadmanager.h"
#include "base/net/proxyconfigurationmanager.h"
#include "base/preferences.h"
#include "base/profile.h"
#include "base/torrentfileguard.h"
#include "base/torrentfilter.h"
@@ -321,7 +322,7 @@ struct BitTorrent::SessionImpl::ResumeSessionContext final : public QObject
ResumeDataStorage *startupStorage = nullptr;
ResumeDataStorageType currentStorageType = ResumeDataStorageType::Legacy;
QVector<LoadedResumeData> loadedResumeData;
QList<LoadedResumeData> loadedResumeData;
int processingResumeDataCount = 0;
int64_t totalResumeDataCount = 0;
int64_t finishedResumeDataCount = 0;
@@ -510,7 +511,7 @@ SessionImpl::SessionImpl(QObject *parent)
, m_resumeDataStorageType(BITTORRENT_SESSION_KEY(u"ResumeDataStorageType"_qs), ResumeDataStorageType::Legacy)
, m_seedingLimitTimer {new QTimer {this}}
, m_resumeDataTimer {new QTimer {this}}
, m_ioThread {new QThread {this}}
, m_ioThread {new QThread}
, m_recentErroredTorrentsTimer {new QTimer {this}}
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
, m_networkManager {new QNetworkConfigurationManager {this}}
@@ -548,8 +549,6 @@ SessionImpl::SessionImpl(QObject *parent)
if (isExcludedFileNamesEnabled())
populateExcludedFileNamesRegExpList();
enableTracker(isTrackerEnabled());
connect(Net::ProxyConfigurationManager::instance()
, &Net::ProxyConfigurationManager::proxyConfigurationChanged
, this, &SessionImpl::configureDeferred);
@@ -563,17 +562,20 @@ SessionImpl::SessionImpl(QObject *parent)
#endif
m_fileSearcher = new FileSearcher;
m_fileSearcher->moveToThread(m_ioThread);
connect(m_ioThread, &QThread::finished, m_fileSearcher, &QObject::deleteLater);
m_fileSearcher->moveToThread(m_ioThread.get());
connect(m_ioThread.get(), &QThread::finished, m_fileSearcher, &QObject::deleteLater);
connect(m_fileSearcher, &FileSearcher::searchFinished, this, &SessionImpl::fileSearchFinished);
m_ioThread->start();
initMetrics();
loadStatistics();
// initialize PortForwarder instance
new PortForwarderImpl(m_nativeSession);
initMetrics();
loadStatistics();
// start embedded tracker
enableTracker(isTrackerEnabled());
prepareStartup();
}
@@ -594,11 +596,8 @@ SessionImpl::~SessionImpl()
// we delete lt::session
delete Net::PortForwarder::instance();
qDebug("Deleting the session");
qDebug("Deleting libtorrent session...");
delete m_nativeSession;
m_ioThread->quit();
m_ioThread->wait();
}
bool SessionImpl::isDHTEnabled() const
@@ -1055,25 +1054,25 @@ void SessionImpl::adjustLimits()
{
if (isQueueingSystemEnabled())
{
lt::settings_pack settingsPack = m_nativeSession->get_settings();
adjustLimits(settingsPack);
m_nativeSession->apply_settings(settingsPack);
lt::settings_pack settingsPack;
// Internally increase the queue limits to ensure that the magnet is started
settingsPack.set_int(lt::settings_pack::active_downloads, adjustLimit(maxActiveDownloads()));
settingsPack.set_int(lt::settings_pack::active_limit, adjustLimit(maxActiveTorrents()));
m_nativeSession->apply_settings(std::move(settingsPack));
}
}
void SessionImpl::applyBandwidthLimits()
{
lt::settings_pack settingsPack = m_nativeSession->get_settings();
applyBandwidthLimits(settingsPack);
m_nativeSession->apply_settings(settingsPack);
lt::settings_pack settingsPack;
settingsPack.set_int(lt::settings_pack::download_rate_limit, downloadSpeedLimit());
settingsPack.set_int(lt::settings_pack::upload_rate_limit, uploadSpeedLimit());
m_nativeSession->apply_settings(std::move(settingsPack));
}
void SessionImpl::configure()
{
lt::settings_pack settingsPack = m_nativeSession->get_settings();
loadLTSettings(settingsPack);
m_nativeSession->apply_settings(settingsPack);
m_nativeSession->apply_settings(loadLTSettings());
configureComponents();
m_deferredConfigureScheduled = false;
@@ -1390,7 +1389,12 @@ void SessionImpl::endStartup(ResumeSessionContext *context)
context->startupStorage->deleteLater();
if (context->currentStorageType == ResumeDataStorageType::Legacy)
Utils::Fs::removeFile(dbPath);
{
connect(context->startupStorage, &QObject::destroyed, [dbPath]
{
Utils::Fs::removeFile(dbPath);
});
}
}
context->deleteLater();
@@ -1419,10 +1423,11 @@ void SessionImpl::endStartup(ResumeSessionContext *context)
void SessionImpl::initializeNativeSession()
{
const std::string peerId = lt::generate_fingerprint(PEER_ID, QBT_VERSION_MAJOR, QBT_VERSION_MINOR, QBT_VERSION_BUGFIX, QBT_VERSION_BUILD);
lt::settings_pack pack = loadLTSettings();
lt::settings_pack pack;
const std::string peerId = lt::generate_fingerprint(PEER_ID, QBT_VERSION_MAJOR, QBT_VERSION_MINOR, QBT_VERSION_BUGFIX, QBT_VERSION_BUILD);
pack.set_str(lt::settings_pack::peer_fingerprint, peerId);
pack.set_bool(lt::settings_pack::listen_system_port_fallback, false);
pack.set_str(lt::settings_pack::user_agent, USER_AGENT.toStdString());
pack.set_bool(lt::settings_pack::use_dht_as_fallback, false);
@@ -1439,8 +1444,7 @@ void SessionImpl::initializeNativeSession()
pack.set_bool(lt::settings_pack::enable_set_file_valid_data, true);
#endif
loadLTSettings(pack);
lt::session_params sessionParams {pack, {}};
lt::session_params sessionParams {std::move(pack), {}};
#ifdef QBT_USES_LIBTORRENT2
switch (diskIOType())
{
@@ -1498,28 +1502,14 @@ void SessionImpl::processBannedIPs(lt::ip_filter &filter)
}
}
void SessionImpl::adjustLimits(lt::settings_pack &settingsPack) const
int SessionImpl::adjustLimit(const int limit) const
{
// Internally increase the queue limits to ensure that the magnet is started
const auto adjustLimit = [this](const int limit) -> int
{
if (limit <= -1)
return limit;
// check for overflow: (limit + m_extraLimit) < std::numeric_limits<int>::max()
return (m_extraLimit < (std::numeric_limits<int>::max() - limit))
? (limit + m_extraLimit)
: std::numeric_limits<int>::max();
};
settingsPack.set_int(lt::settings_pack::active_downloads, adjustLimit(maxActiveDownloads()));
settingsPack.set_int(lt::settings_pack::active_limit, adjustLimit(maxActiveTorrents()));
}
void SessionImpl::applyBandwidthLimits(lt::settings_pack &settingsPack) const
{
const bool altSpeedLimitEnabled = isAltGlobalSpeedLimitEnabled();
settingsPack.set_int(lt::settings_pack::download_rate_limit, altSpeedLimitEnabled ? altGlobalDownloadSpeedLimit() : globalDownloadSpeedLimit());
settingsPack.set_int(lt::settings_pack::upload_rate_limit, altSpeedLimitEnabled ? altGlobalUploadSpeedLimit() : globalUploadSpeedLimit());
if (limit <= -1)
return limit;
// check for overflow: (limit + m_extraLimit) < std::numeric_limits<int>::max()
return (m_extraLimit < (std::numeric_limits<int>::max() - limit))
? (limit + m_extraLimit)
: std::numeric_limits<int>::max();
}
void SessionImpl::initMetrics()
@@ -1564,8 +1554,10 @@ void SessionImpl::initMetrics()
m_metricIndices.disk.diskJobTime = findMetricIndex("disk.disk_job_time");
}
void SessionImpl::loadLTSettings(lt::settings_pack &settingsPack)
lt::settings_pack SessionImpl::loadLTSettings() const
{
lt::settings_pack settingsPack;
const lt::alert_category_t alertMask = lt::alert::error_notification
| lt::alert::file_progress_notification
| lt::alert::ip_block_notification
@@ -1583,8 +1575,10 @@ void SessionImpl::loadLTSettings(lt::settings_pack &settingsPack)
// It will not take affect until the listen_interfaces settings is updated
settingsPack.set_int(lt::settings_pack::listen_queue_size, socketBacklogSize());
configureNetworkInterfaces(settingsPack);
applyBandwidthLimits(settingsPack);
applyNetworkInterfacesSettings(settingsPack);
settingsPack.set_int(lt::settings_pack::download_rate_limit, downloadSpeedLimit());
settingsPack.set_int(lt::settings_pack::upload_rate_limit, uploadSpeedLimit());
// The most secure, rc4 only so that all streams are encrypted
settingsPack.set_int(lt::settings_pack::allowed_enc_level, lt::settings_pack::pe_rc4);
@@ -1719,7 +1713,9 @@ void SessionImpl::loadLTSettings(lt::settings_pack &settingsPack)
// Queueing System
if (isQueueingSystemEnabled())
{
adjustLimits(settingsPack);
// Internally increase the queue limits to ensure that the magnet is started
settingsPack.set_int(lt::settings_pack::active_downloads, adjustLimit(maxActiveDownloads()));
settingsPack.set_int(lt::settings_pack::active_limit, adjustLimit(maxActiveTorrents()));
settingsPack.set_int(lt::settings_pack::active_seeds, maxActiveUploads());
settingsPack.set_bool(lt::settings_pack::dont_count_slow_torrents, ignoreSlowTorrentsForQueueing());
@@ -1835,9 +1831,11 @@ void SessionImpl::loadLTSettings(lt::settings_pack &settingsPack)
settingsPack.set_int(lt::settings_pack::seed_choking_algorithm, lt::settings_pack::anti_leech);
break;
}
return settingsPack;
}
void SessionImpl::configureNetworkInterfaces(lt::settings_pack &settingsPack)
void SessionImpl::applyNetworkInterfacesSettings(lt::settings_pack &settingsPack) const
{
if (m_listenInterfaceConfigured)
return;
@@ -1980,16 +1978,27 @@ void SessionImpl::configurePeerClasses()
void SessionImpl::enableTracker(const bool enable)
{
const QString profile = u"embeddedTracker"_qs;
auto *portForwarder = Net::PortForwarder::instance();
if (enable)
{
if (!m_tracker)
m_tracker = new Tracker(this);
m_tracker->start();
const auto *pref = Preferences::instance();
if (pref->isTrackerPortForwardingEnabled())
portForwarder->setPorts(profile, {static_cast<quint16>(pref->getTrackerPort())});
else
portForwarder->removePorts(profile);
}
else
{
delete m_tracker;
portForwarder->removePorts(profile);
}
}
@@ -2181,30 +2190,6 @@ Torrent *SessionImpl::findTorrent(const InfoHash &infoHash) const
return m_torrents.value(altID);
}
bool SessionImpl::hasActiveTorrents() const
{
return std::any_of(m_torrents.begin(), m_torrents.end(), [](TorrentImpl *torrent)
{
return TorrentFilter::ActiveTorrent.match(torrent);
});
}
bool SessionImpl::hasUnfinishedTorrents() const
{
return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentImpl *torrent)
{
return (!torrent->isSeed() && !torrent->isPaused() && !torrent->isErrored() && torrent->hasMetadata());
});
}
bool SessionImpl::hasRunningSeed() const
{
return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentImpl *torrent)
{
return (torrent->isSeed() && !torrent->isPaused());
});
}
void SessionImpl::banIP(const QString &ip)
{
QStringList bannedIPs = m_bannedIPs;
@@ -2753,9 +2738,6 @@ bool SessionImpl::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &so
p.flags |= lt::torrent_flags::duplicate_is_error;
// Prevent torrent from saving initial resume data twice
p.flags &= ~lt::torrent_flags::need_save_resume;
p.added_time = std::time(nullptr);
// Limits
@@ -2805,6 +2787,19 @@ bool SessionImpl::downloadMetadata(const MagnetUri &magnetUri)
lt::add_torrent_params p = magnetUri.addTorrentParams();
if (isAddTrackersEnabled())
{
// Use "additional trackers" when metadata retrieving (this can help when the DHT nodes are few)
p.trackers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerList.size()));
p.tracker_tiers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerList.size()));
p.tracker_tiers.resize(p.trackers.size(), 0);
for (const TrackerEntry &trackerEntry : asConst(m_additionalTrackerList))
{
p.trackers.push_back(trackerEntry.url.toStdString());
p.tracker_tiers.push_back(trackerEntry.tier);
}
}
// Flags
// Preallocation mode
if (isPreallocationEnabled())
@@ -2898,10 +2893,8 @@ void SessionImpl::saveResumeData()
saveTorrentsQueue();
for (const TorrentImpl *torrent : asConst(m_torrents))
{
torrent->nativeHandle().save_resume_data(lt::torrent_handle::only_if_modified);
++m_numResumeData;
}
m_numResumeData += m_torrents.size();
QElapsedTimer timer;
timer.start();
@@ -4634,7 +4627,11 @@ void SessionImpl::handleTorrentFinished(TorrentImpl *const torrent)
}
}
if (!hasUnfinishedTorrents())
const bool hasUnfinishedTorrents = std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
{
return !(torrent->isSeed() || torrent->isPaused() || torrent->isErrored());
});
if (!hasUnfinishedTorrents)
emit allTorrentsFinished();
}
@@ -5207,9 +5204,9 @@ TorrentImpl *SessionImpl::createTorrent(const lt::torrent_handle &nativeHandle,
if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid())
m_hybridTorrentsByAltID.insert(TorrentID::fromSHA1Hash(infoHash.v1()), torrent);
if (!params.restored)
if (isRestored())
{
m_resumeDataStorage->store(torrent->id(), params);
torrent->saveResumeData(lt::torrent_handle::save_info_dict);
// The following is useless for newly added magnet
if (torrent->hasMetadata())
@@ -5225,7 +5222,7 @@ TorrentImpl *SessionImpl::createTorrent(const lt::torrent_handle &nativeHandle,
m_seedingLimitTimer->start();
}
if (params.restored)
if (!isRestored())
{
LogMsg(tr("Restored torrent. Torrent: \"%1\"").arg(torrent->name()));
}

View File

@@ -45,6 +45,7 @@
#include "base/path.h"
#include "base/settingvalue.h"
#include "base/types.h"
#include "base/utils/thread.h"
#include "addtorrentparams.h"
#include "cachestatus.h"
#include "categoryoptions.h"
@@ -375,9 +376,6 @@ namespace BitTorrent
Torrent *findTorrent(const InfoHash &infoHash) const override;
QVector<Torrent *> torrents() const override;
qsizetype torrentsCount() const override;
bool hasActiveTorrents() const override;
bool hasUnfinishedTorrents() const override;
bool hasRunningSeed() const override;
const SessionStatus &status() const override;
const CacheStatus &cacheStatus() const override;
bool isListening() const override;
@@ -474,11 +472,10 @@ namespace BitTorrent
Q_INVOKABLE void configure();
void configureComponents();
void initializeNativeSession();
void loadLTSettings(lt::settings_pack &settingsPack);
void configureNetworkInterfaces(lt::settings_pack &settingsPack);
lt::settings_pack loadLTSettings() const;
void applyNetworkInterfacesSettings(lt::settings_pack &settingsPack) const;
void configurePeerClasses();
void adjustLimits(lt::settings_pack &settingsPack) const;
void applyBandwidthLimits(lt::settings_pack &settingsPack) const;
int adjustLimit(int limit) const;
void initMetrics();
void adjustLimits();
void applyBandwidthLimits();
@@ -553,7 +550,7 @@ namespace BitTorrent
bool m_deferredConfigureScheduled = false;
bool m_IPFilteringConfigured = false;
bool m_listenInterfaceConfigured = false;
mutable bool m_listenInterfaceConfigured = false;
CachedSettingValue<bool> m_isDHTEnabled;
CachedSettingValue<bool> m_isLSDEnabled;
@@ -690,7 +687,7 @@ namespace BitTorrent
// Tracker
QPointer<Tracker> m_tracker;
QThread *m_ioThread = nullptr;
Utils::Thread::UniquePtr m_ioThread;
ResumeDataStorage *m_resumeDataStorage = nullptr;
FileSearcher *m_fileSearcher = nullptr;

View File

@@ -520,6 +520,17 @@ void TorrentImpl::setAutoManaged(const bool enable)
m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
}
Path TorrentImpl::wantedActualPath(int index, const Path &path) const
{
if (m_session->isAppendExtensionEnabled()
&& (fileSize(index) > 0) && !m_completedFiles.at(index))
{
return path + QB_EXT;
}
return path;
}
QVector<TrackerEntry> TorrentImpl::trackers() const
{
if (!m_updatedTrackerEntries.isEmpty())
@@ -690,9 +701,9 @@ bool TorrentImpl::needSaveResumeData() const
return m_nativeStatus.need_save_resume;
}
void TorrentImpl::saveResumeData()
void TorrentImpl::saveResumeData(lt::resume_data_flags_t flags)
{
m_nativeHandle.save_resume_data();
m_nativeHandle.save_resume_data(flags);
m_session->handleTorrentSaveResumeDataRequested(this);
}
@@ -1530,20 +1541,21 @@ void TorrentImpl::refreshTrackerEntries() const
const std::vector<lt::announce_entry> nativeTrackers = m_nativeHandle.trackers();
Q_ASSERT(nativeTrackers.size() == m_trackerEntries.size());
for (TrackerEntry &trackerEntry : m_trackerEntries)
for (const lt::announce_entry &announceEntry : nativeTrackers)
{
const auto updatedTrackerIter = m_updatedTrackerEntries.find(trackerEntry.url);
const auto trackerURL = QString::fromStdString(announceEntry.url);
const auto updatedTrackerIter = m_updatedTrackerEntries.find(trackerURL);
if (updatedTrackerIter == m_updatedTrackerEntries.end())
continue;
const auto nativeTrackerIter = std::find_if(nativeTrackers.cbegin(), nativeTrackers.cend()
, [trackerURL = trackerEntry.url.toStdString()](const lt::announce_entry &announceEntry)
const auto trackerIter = std::find_if(m_trackerEntries.begin(), m_trackerEntries.end()
, [&trackerURL](const TrackerEntry &trackerEntry)
{
return (announceEntry.url == trackerURL);
return (trackerEntry.url == trackerURL);
});
Q_ASSERT(nativeTrackerIter != nativeTrackers.cend());
Q_ASSERT(trackerIter != m_trackerEntries.end());
const lt::announce_entry &announceEntry = *nativeTrackerIter;
TrackerEntry &trackerEntry = *trackerIter;
#ifdef QBT_USES_LIBTORRENT2
updateTrackerEntry(trackerEntry, announceEntry, m_nativeHandle.info_hashes(), updatedTrackerIter.value());
#else
@@ -1730,15 +1742,12 @@ void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageMode mode)
void TorrentImpl::renameFile(const int index, const Path &path)
{
const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
Q_ASSERT(index >= 0);
Q_ASSERT(index < nativeIndexes.size());
if ((index < 0) || (index >= nativeIndexes.size()))
Q_ASSERT((index >= 0) && (index < filesCount()));
if (Q_UNLIKELY((index < 0) || (index >= filesCount())))
return;
++m_renameCount;
m_nativeHandle.rename_file(nativeIndexes[index], path.toString().toStdString());
const Path wantedPath = wantedActualPath(index, path);
doRenameFile(index, wantedPath);
}
void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
@@ -1990,13 +1999,12 @@ void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
// For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
// be removed if they are empty
const Path oldFilePath = m_filePaths.at(fileIndex);
const Path newFilePath {QString::fromUtf8(p->new_name())};
const Path newFilePath = Path(QString::fromUtf8(p->new_name())).removedExtension(QB_EXT);
// Check if ".!qB" extension was just added or removed
// We should compare path in a case sensitive manner even on case insensitive
// platforms since it can be renamed by only changing case of some character(s)
if ((oldFilePath.data() != newFilePath.data())
&& ((oldFilePath + QB_EXT) != newFilePath))
if (oldFilePath.data() != newFilePath.data())
{
m_filePaths[fileIndex] = newFilePath;
@@ -2048,7 +2056,7 @@ void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
if (actualPath != path)
{
qDebug("Renaming %s to %s", qUtf8Printable(actualPath.toString()), qUtf8Printable(path.toString()));
renameFile(fileIndex, path);
doRenameFile(fileIndex, path);
}
}
}
@@ -2155,7 +2163,6 @@ void TorrentImpl::manageIncompleteFiles()
{
const std::shared_ptr<const lt::torrent_info> nativeInfo = nativeTorrentInfo();
const lt::file_storage &nativeFiles = nativeInfo->files();
const bool isAppendExtensionEnabled = m_session->isAppendExtensionEnabled();
for (int i = 0; i < filesCount(); ++i)
{
@@ -2163,23 +2170,11 @@ void TorrentImpl::manageIncompleteFiles()
const auto nativeIndex = m_torrentInfo.nativeIndexes().at(i);
const Path actualPath {nativeFiles.file_path(nativeIndex)};
if (isAppendExtensionEnabled && (fileSize(i) > 0) && !m_completedFiles.at(i))
const Path wantedPath = wantedActualPath(i, path);
if (actualPath != wantedPath)
{
const Path wantedPath = path + QB_EXT;
if (actualPath != wantedPath)
{
qDebug() << "Renaming" << actualPath.toString() << "to" << wantedPath.toString();
renameFile(i, wantedPath);
}
}
else
{
if (actualPath != path)
{
qDebug() << "Renaming" << actualPath.toString() << "to" << path.toString();
renameFile(i, path);
}
qDebug() << "Renaming" << actualPath.toString() << "to" << wantedPath.toString();
doRenameFile(i, wantedPath);
}
}
}
@@ -2191,7 +2186,20 @@ void TorrentImpl::adjustStorageLocation()
const Path targetPath = ((isFinished || downloadPath.isEmpty()) ? savePath() : downloadPath);
if ((targetPath != actualStorageLocation()) || isMoveInProgress())
moveStorage(targetPath, MoveStorageMode::FailIfExist);
moveStorage(targetPath, MoveStorageMode::Overwrite);
}
void TorrentImpl::doRenameFile(int index, const Path &path)
{
const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
Q_ASSERT(index >= 0);
Q_ASSERT(index < nativeIndexes.size());
if (Q_UNLIKELY((index < 0) || (index >= nativeIndexes.size())))
return;
++m_renameCount;
m_nativeHandle.rename_file(nativeIndexes[index], path.toString().toStdString());
}
lt::torrent_handle TorrentImpl::nativeHandle() const

View File

@@ -245,7 +245,7 @@ namespace BitTorrent
void handleStateUpdate(const lt::torrent_status &nativeStatus);
void handleCategoryOptionsChanged();
void handleAppendExtensionToggled();
void saveResumeData();
void saveResumeData(lt::resume_data_flags_t flags = {});
void handleMoveStorageJobFinished(const Path &path, bool hasOutstandingJob);
void fileSearchFinished(const Path &savePath, const PathList &fileNames);
void updatePeerCount(const QString &trackerURL, const TrackerEntry::Endpoint &endpoint, int count);
@@ -281,7 +281,9 @@ namespace BitTorrent
void setAutoManaged(bool enable);
Path wantedActualPath(int index, const Path &path) const;
void adjustStorageLocation();
void doRenameFile(int index, const Path &path);
void moveStorage(const Path &newPath, MoveStorageMode mode);
void manageIncompleteFiles();
void applyFirstLastPiecePriority(bool enabled);

View File

@@ -203,12 +203,12 @@ Tracker::Tracker(QObject *parent)
bool Tracker::start()
{
const QHostAddress ip = QHostAddress::Any;
const int port = Preferences::instance()->getTrackerPort();
if (m_server->isListening())
{
if (m_server->serverPort() == port)
if (const int oldPort = m_server->serverPort()
; oldPort == port)
{
// Already listening on the right port, just return
return true;
@@ -218,9 +218,9 @@ bool Tracker::start()
m_server->close();
}
// Listen on the predefined port
// Listen on port
const QHostAddress ip = QHostAddress::Any;
const bool listenSuccess = m_server->listen(ip, port);
if (listenSuccess)
{
LogMsg(tr("Embedded Tracker: Now listening on IP: %1, port: %2")

View File

@@ -124,7 +124,7 @@ namespace
// Spoof HTTP Referer to allow adding torrent link from Torcache/KickAssTorrents
request.setRawHeader("Referer", request.url().toEncoded().data());
#ifdef QT_NO_COMPRESS
// The macro "QT_NO_COMPRESS" defined in QT will disable the zlib releated features
// The macro "QT_NO_COMPRESS" defined in QT will disable the zlib related features
// and reply data auto-decompression in QT will also be disabled. But we can support
// gzip encoding and manually decompress the reply data.
request.setRawHeader("Accept-Encoding", "gzip");

View File

@@ -29,6 +29,9 @@
#pragma once
#include <QObject>
#include <QSet>
class QString;
namespace Net
{
@@ -45,8 +48,8 @@ namespace Net
virtual bool isEnabled() const = 0;
virtual void setEnabled(bool enabled) = 0;
virtual void addPort(quint16 port) = 0;
virtual void deletePort(quint16 port) = 0;
virtual void setPorts(const QString &profile, QSet<quint16> ports) = 0;
virtual void removePorts(const QString &profile) = 0;
private:
static PortForwarder *m_instance;

View File

@@ -142,7 +142,7 @@ Path Path::rootItem() const
#ifdef Q_OS_WIN
// should be `c:/` instead of `c:`
if (m_pathStr.at(slashIndex - 1) == u':')
if ((slashIndex == 2) && hasDriveLetter(m_pathStr))
return createUnchecked(m_pathStr.left(slashIndex + 1));
#endif
return createUnchecked(m_pathStr.left(slashIndex));

View File

@@ -996,6 +996,18 @@ void Preferences::resolvePeerHostNames(const bool resolve)
setValue(u"Preferences/Connection/ResolvePeerHostNames"_qs, resolve);
}
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
bool Preferences::useSystemIcons() const
{
return value(u"Preferences/Advanced/useSystemIconTheme"_qs, false);
}
void Preferences::useSystemIcons(const bool enabled)
{
setValue(u"Preferences/Advanced/useSystemIconTheme"_qs, enabled);
}
#endif
bool Preferences::isRecursiveDownloadEnabled() const
{
return !value(u"Preferences/Advanced/DisableRecursiveDownload"_qs, false);
@@ -1164,6 +1176,16 @@ void Preferences::setTrackerPort(const int port)
setValue(u"Preferences/Advanced/trackerPort"_qs, port);
}
bool Preferences::isTrackerPortForwardingEnabled() const
{
return value(u"Preferences/Advanced/trackerPortForwarding"_qs, false);
}
void Preferences::setTrackerPortForwardingEnabled(const bool enabled)
{
setValue(u"Preferences/Advanced/trackerPortForwarding"_qs, enabled);
}
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
bool Preferences::isUpdateCheckEnabled() const
{

View File

@@ -281,6 +281,10 @@ public:
void resolvePeerCountries(bool resolve);
bool resolvePeerHostNames() const;
void resolvePeerHostNames(bool resolve);
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
bool useSystemIcons() const;
void useSystemIcons(bool enabled);
#endif
bool isRecursiveDownloadEnabled() const;
void setRecursiveDownloadEnabled(bool enable);
#ifdef Q_OS_WIN
@@ -299,6 +303,8 @@ public:
#endif
int getTrackerPort() const;
void setTrackerPort(int port);
bool isTrackerPortForwardingEnabled() const;
void setTrackerPortForwardingEnabled(bool enabled);
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
bool isUpdateCheckEnabled() const;
void setUpdateCheckEnabled(bool enabled);

View File

@@ -102,15 +102,15 @@ AutoDownloader::AutoDownloader()
, m_storeSmartEpisodeFilter(u"RSS/AutoDownloader/SmartEpisodeFilter"_qs)
, m_storeDownloadRepacks(u"RSS/AutoDownloader/DownloadRepacks"_qs)
, m_processingTimer(new QTimer(this))
, m_ioThread(new QThread(this))
, m_ioThread(new QThread)
{
Q_ASSERT(!m_instance); // only one instance is allowed
m_instance = this;
m_fileStorage = new AsyncFileStorage(specialFolderLocation(SpecialFolder::Config) / Path(CONF_FOLDER_NAME));
m_fileStorage->moveToThread(m_ioThread);
connect(m_ioThread, &QThread::finished, m_fileStorage, &AsyncFileStorage::deleteLater);
m_fileStorage->moveToThread(m_ioThread.get());
connect(m_ioThread.get(), &QThread::finished, m_fileStorage, &AsyncFileStorage::deleteLater);
connect(m_fileStorage, &AsyncFileStorage::failed, [](const Path &fileName, const QString &errorString)
{
LogMsg(tr("Couldn't save RSS AutoDownloader data in %1. Error: %2")
@@ -155,9 +155,6 @@ AutoDownloader::AutoDownloader()
AutoDownloader::~AutoDownloader()
{
store();
m_ioThread->quit();
m_ioThread->wait();
}
AutoDownloader *AutoDownloader::instance()

View File

@@ -38,6 +38,7 @@
#include "base/exceptions.h"
#include "base/settingvalue.h"
#include "base/utils/thread.h"
class QThread;
class QTimer;
@@ -137,7 +138,7 @@ namespace RSS
SettingValue<bool> m_storeDownloadRepacks;
QTimer *m_processingTimer = nullptr;
QThread *m_ioThread = nullptr;
Utils::Thread::UniquePtr m_ioThread;
AsyncFileStorage *m_fileStorage = nullptr;
QHash<QString, AutoDownloadRule> m_rules;
QList<QSharedPointer<ProcessingJob>> m_processingQueue;

View File

@@ -277,9 +277,11 @@ void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result)
void Feed::load()
{
QMetaObject::invokeMethod(m_serializer, [this]()
QMetaObject::invokeMethod(m_serializer
, [serializer = m_serializer, url = m_url
, path = (m_session->dataFileStorage()->storageDir() / m_dataFileName)]
{
m_serializer->load((m_session->dataFileStorage()->storageDir() / m_dataFileName), m_url);
serializer->load(path, url);
});
}
@@ -297,9 +299,11 @@ void Feed::store()
for (Article *article :asConst(m_articles))
articlesData.push_back(article->data());
QMetaObject::invokeMethod(m_serializer, [this, articlesData]()
QMetaObject::invokeMethod(m_serializer
, [articlesData, serializer = m_serializer
, path = (m_session->dataFileStorage()->storageDir() / m_dataFileName)]
{
m_serializer->store((m_session->dataFileStorage()->storageDir() / m_dataFileName), articlesData);
serializer->store(path, articlesData);
});
}

View File

@@ -62,14 +62,14 @@ Session::Session()
: m_storeProcessingEnabled(u"RSS/Session/EnableProcessing"_qs)
, m_storeRefreshInterval(u"RSS/Session/RefreshInterval"_qs, 30)
, m_storeMaxArticlesPerFeed(u"RSS/Session/MaxArticlesPerFeed"_qs, 50)
, m_workingThread(new QThread(this))
, m_workingThread(new QThread)
{
Q_ASSERT(!m_instance); // only one instance is allowed
m_instance = this;
m_confFileStorage = new AsyncFileStorage(specialFolderLocation(SpecialFolder::Config) / Path(CONF_FOLDER_NAME));
m_confFileStorage->moveToThread(m_workingThread);
connect(m_workingThread, &QThread::finished, m_confFileStorage, &AsyncFileStorage::deleteLater);
m_confFileStorage->moveToThread(m_workingThread.get());
connect(m_workingThread.get(), &QThread::finished, m_confFileStorage, &AsyncFileStorage::deleteLater);
connect(m_confFileStorage, &AsyncFileStorage::failed, [](const Path &fileName, const QString &errorString)
{
LogMsg(tr("Couldn't save RSS session configuration. File: \"%1\". Error: \"%2\"")
@@ -77,8 +77,8 @@ Session::Session()
});
m_dataFileStorage = new AsyncFileStorage(specialFolderLocation(SpecialFolder::Data) / Path(DATA_FOLDER_NAME));
m_dataFileStorage->moveToThread(m_workingThread);
connect(m_workingThread, &QThread::finished, m_dataFileStorage, &AsyncFileStorage::deleteLater);
m_dataFileStorage->moveToThread(m_workingThread.get());
connect(m_workingThread.get(), &QThread::finished, m_dataFileStorage, &AsyncFileStorage::deleteLater);
connect(m_dataFileStorage, &AsyncFileStorage::failed, [](const Path &fileName, const QString &errorString)
{
LogMsg(tr("Couldn't save RSS session data. File: \"%1\". Error: \"%2\"")
@@ -126,11 +126,6 @@ Session::~Session()
//store();
delete m_itemsByPath[u""_qs]; // deleting root folder
// some items may add I/O operation at destruction
// stop working thread after deleting root folder
m_workingThread->quit();
m_workingThread->wait();
qDebug() << "RSS Session deleted.";
}
@@ -487,7 +482,7 @@ void Session::setRefreshInterval(const int refreshInterval)
QThread *Session::workingThread() const
{
return m_workingThread;
return m_workingThread.get();
}
void Session::handleItemAboutToBeDestroyed(Item *item)

View File

@@ -75,6 +75,7 @@
#include "base/3rdparty/expected.hpp"
#include "base/settingvalue.h"
#include "base/utils/thread.h"
class QThread;
@@ -158,7 +159,7 @@ namespace RSS
CachedSettingValue<bool> m_storeProcessingEnabled;
CachedSettingValue<int> m_storeRefreshInterval;
CachedSettingValue<int> m_storeMaxArticlesPerFeed;
QThread *m_workingThread = nullptr;
Utils::Thread::UniquePtr m_workingThread;
AsyncFileStorage *m_confFileStorage = nullptr;
AsyncFileStorage *m_dataFileStorage = nullptr;
QTimer m_refreshTimer;

View File

@@ -254,7 +254,7 @@ TorrentFilesWatcher *TorrentFilesWatcher::instance()
TorrentFilesWatcher::TorrentFilesWatcher(QObject *parent)
: QObject {parent}
, m_ioThread {new QThread(this)}
, m_ioThread {new QThread}
{
const auto *btSession = BitTorrent::Session::instance();
if (btSession->isRestored())
@@ -267,8 +267,7 @@ TorrentFilesWatcher::TorrentFilesWatcher(QObject *parent)
TorrentFilesWatcher::~TorrentFilesWatcher()
{
m_ioThread->quit();
m_ioThread->wait();
m_ioThread.reset();
delete m_asyncWorker;
}
@@ -281,7 +280,7 @@ void TorrentFilesWatcher::initWorker()
connect(m_asyncWorker, &TorrentFilesWatcher::Worker::magnetFound, this, &TorrentFilesWatcher::onMagnetFound);
connect(m_asyncWorker, &TorrentFilesWatcher::Worker::torrentFound, this, &TorrentFilesWatcher::onTorrentFound);
m_asyncWorker->moveToThread(m_ioThread);
m_asyncWorker->moveToThread(m_ioThread.get());
m_ioThread->start();
for (auto it = m_watchedFolders.cbegin(); it != m_watchedFolders.cend(); ++it)

View File

@@ -33,6 +33,7 @@
#include "base/bittorrent/addtorrentparams.h"
#include "base/path.h"
#include "base/utils/thread.h"
class QThread;
@@ -89,7 +90,7 @@ private:
QHash<Path, WatchedFolderOptions> m_watchedFolders;
QThread *m_ioThread = nullptr;
Utils::Thread::UniquePtr m_ioThread;
class Worker;
Worker *m_asyncWorker = nullptr;

View File

@@ -53,6 +53,8 @@
#include <zlib.h>
#include <QCoreApplication>
#include <QDebug>
#include <QLocale>
#include <QMimeDatabase>
#include <QRegularExpression>
#include <QSet>
@@ -403,6 +405,94 @@ QString Utils::Misc::getUserIDString()
return uid;
}
QString Utils::Misc::languageToLocalizedString(const QString &localeStr)
{
if (localeStr.startsWith(u"eo", Qt::CaseInsensitive))
{
// QLocale doesn't work with that locale. Esperanto isn't a "real" language.
return C_LOCALE_ESPERANTO;
}
if (localeStr.startsWith(u"ltg", Qt::CaseInsensitive))
{
// QLocale doesn't work with that locale.
return C_LOCALE_LATGALIAN;
}
const QLocale locale {localeStr};
switch (locale.language())
{
case QLocale::Arabic: return C_LOCALE_ARABIC;
case QLocale::Armenian: return C_LOCALE_ARMENIAN;
case QLocale::Azerbaijani: return C_LOCALE_AZERBAIJANI;
case QLocale::Basque: return C_LOCALE_BASQUE;
case QLocale::Bulgarian: return C_LOCALE_BULGARIAN;
case QLocale::Byelorussian: return C_LOCALE_BYELORUSSIAN;
case QLocale::Catalan: return C_LOCALE_CATALAN;
case QLocale::Chinese:
switch (locale.country())
{
case QLocale::China: return C_LOCALE_CHINESE_SIMPLIFIED;
case QLocale::HongKong: return C_LOCALE_CHINESE_TRADITIONAL_HK;
default: return C_LOCALE_CHINESE_TRADITIONAL_TW;
}
case QLocale::Croatian: return C_LOCALE_CROATIAN;
case QLocale::Czech: return C_LOCALE_CZECH;
case QLocale::Danish: return C_LOCALE_DANISH;
case QLocale::Dutch: return C_LOCALE_DUTCH;
case QLocale::English:
switch (locale.country())
{
case QLocale::Australia: return C_LOCALE_ENGLISH_AUSTRALIA;
case QLocale::UnitedKingdom: return C_LOCALE_ENGLISH_UNITEDKINGDOM;
default: return C_LOCALE_ENGLISH;
}
case QLocale::Estonian: return C_LOCALE_ESTONIAN;
case QLocale::Finnish: return C_LOCALE_FINNISH;
case QLocale::French: return C_LOCALE_FRENCH;
case QLocale::Galician: return C_LOCALE_GALICIAN;
case QLocale::Georgian: return C_LOCALE_GEORGIAN;
case QLocale::German: return C_LOCALE_GERMAN;
case QLocale::Greek: return C_LOCALE_GREEK;
case QLocale::Hebrew: return C_LOCALE_HEBREW;
case QLocale::Hindi: return C_LOCALE_HINDI;
case QLocale::Hungarian: return C_LOCALE_HUNGARIAN;
case QLocale::Icelandic: return C_LOCALE_ICELANDIC;
case QLocale::Indonesian: return C_LOCALE_INDONESIAN;
case QLocale::Italian: return C_LOCALE_ITALIAN;
case QLocale::Japanese: return C_LOCALE_JAPANESE;
case QLocale::Korean: return C_LOCALE_KOREAN;
case QLocale::Latvian: return C_LOCALE_LATVIAN;
case QLocale::Lithuanian: return C_LOCALE_LITHUANIAN;
case QLocale::Malay: return C_LOCALE_MALAY;
case QLocale::Mongolian: return C_LOCALE_MONGOLIAN;
case QLocale::NorwegianBokmal: return C_LOCALE_NORWEGIAN;
case QLocale::Occitan: return C_LOCALE_OCCITAN;
case QLocale::Persian: return C_LOCALE_PERSIAN;
case QLocale::Polish: return C_LOCALE_POLISH;
case QLocale::Portuguese:
if (locale.country() == QLocale::Brazil)
return C_LOCALE_PORTUGUESE_BRAZIL;
return C_LOCALE_PORTUGUESE;
case QLocale::Romanian: return C_LOCALE_ROMANIAN;
case QLocale::Russian: return C_LOCALE_RUSSIAN;
case QLocale::Serbian: return C_LOCALE_SERBIAN;
case QLocale::Slovak: return C_LOCALE_SLOVAK;
case QLocale::Slovenian: return C_LOCALE_SLOVENIAN;
case QLocale::Spanish: return C_LOCALE_SPANISH;
case QLocale::Swedish: return C_LOCALE_SWEDISH;
case QLocale::Thai: return C_LOCALE_THAI;
case QLocale::Turkish: return C_LOCALE_TURKISH;
case QLocale::Ukrainian: return C_LOCALE_UKRAINIAN;
case QLocale::Uzbek: return C_LOCALE_UZBEK;
case QLocale::Vietnamese: return C_LOCALE_VIETNAMESE;
default:
const QString lang = QLocale::languageToString(locale.language());
qWarning() << "Unrecognized language name: " << lang;
return lang;
}
}
QString Utils::Misc::parseHtmlLinks(const QString &rawText)
{
QString result = rawText;

View File

@@ -85,6 +85,8 @@ namespace Utils::Misc
QString userFriendlyDuration(qlonglong seconds, qlonglong maxCap = -1);
QString getUserIDString();
QString languageToLocalizedString(const QString &localeStr);
#ifdef Q_OS_WIN
Path windowsSystemPath();

38
src/base/utils/thread.cpp Normal file
View File

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

43
src/base/utils/thread.h Normal file
View File

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

View File

@@ -30,9 +30,9 @@
#define QBT_VERSION_MAJOR 4
#define QBT_VERSION_MINOR 5
#define QBT_VERSION_BUGFIX 0
#define QBT_VERSION_BUGFIX 1
#define QBT_VERSION_BUILD 0
#define QBT_VERSION_STATUS "beta1" // Should be empty for stable releases!
#define QBT_VERSION_STATUS "" // Should be empty for stable releases!
#define QBT__STRINGIFY(x) #x
#define QBT_STRINGIFY(x) QBT__STRINGIFY(x)

View File

@@ -97,6 +97,7 @@ namespace
// embedded tracker
TRACKER_STATUS,
TRACKER_PORT,
TRACKER_PORT_FORWARDING,
// libtorrent section
LIBTORRENT_HEADER,
ASYNC_IO_THREADS,
@@ -292,7 +293,9 @@ void AdvancedSettings::saveAdvancedSettings() const
// Tracker
pref->setTrackerPort(m_spinBoxTrackerPort.value());
pref->setTrackerPortForwardingEnabled(m_checkBoxTrackerPortForwarding.isChecked());
session->setTrackerEnabled(m_checkBoxTrackerStatus.isChecked());
// Choking algorithm
session->setChokingAlgorithm(m_comboBoxChokingAlgorithm.currentData().value<BitTorrent::ChokingAlgorithm>());
// Seed choking algorithm
@@ -732,6 +735,9 @@ void AdvancedSettings::loadAdvancedSettings()
m_spinBoxTrackerPort.setMaximum(65535);
m_spinBoxTrackerPort.setValue(pref->getTrackerPort());
addRow(TRACKER_PORT, tr("Embedded tracker port"), &m_spinBoxTrackerPort);
// Tracker port forwarding
m_checkBoxTrackerPortForwarding.setChecked(pref->isTrackerPortForwardingEnabled());
addRow(TRACKER_PORT_FORWARDING, tr("Enable port forwarding for embedded tracker"), &m_checkBoxTrackerPortForwarding);
// Choking algorithm
m_comboBoxChokingAlgorithm.addItem(tr("Fixed slots"), QVariant::fromValue(BitTorrent::ChokingAlgorithm::FixedSlots));
m_comboBoxChokingAlgorithm.addItem(tr("Upload rate based"), QVariant::fromValue(BitTorrent::ChokingAlgorithm::RateBased));

View File

@@ -68,7 +68,7 @@ private:
m_spinBoxSavePathHistoryLength, m_spinBoxPeerTurnover, m_spinBoxPeerTurnoverCutoff, m_spinBoxPeerTurnoverInterval, m_spinBoxRequestQueueSize;
QCheckBox m_checkBoxOsCache, m_checkBoxRecheckCompleted, m_checkBoxResolveCountries, m_checkBoxResolveHosts,
m_checkBoxProgramNotifications, m_checkBoxTorrentAddedNotifications, m_checkBoxReannounceWhenAddressChanged, m_checkBoxTrackerFavicon, m_checkBoxTrackerStatus,
m_checkBoxConfirmTorrentRecheck, m_checkBoxConfirmRemoveAllTags, m_checkBoxAnnounceAllTrackers, m_checkBoxAnnounceAllTiers,
m_checkBoxTrackerPortForwarding, m_checkBoxConfirmTorrentRecheck, m_checkBoxConfirmRemoveAllTags, m_checkBoxAnnounceAllTrackers, m_checkBoxAnnounceAllTiers,
m_checkBoxMultiConnectionsPerIp, m_checkBoxValidateHTTPSTrackerCertificate, m_checkBoxSSRFMitigation, m_checkBoxBlockPeersOnPrivilegedPorts, m_checkBoxPieceExtentAffinity,
m_checkBoxSuggestMode, m_checkBoxSpeedWidgetEnabled, m_checkBoxIDNSupport;
QComboBox m_comboBoxInterface, m_comboBoxInterfaceAddress, m_comboBoxDiskIOReadMode, m_comboBoxDiskIOWriteMode, m_comboBoxUtpMixedMode, m_comboBoxChokingAlgorithm,

View File

@@ -56,7 +56,7 @@ public:
clear();
if (m_parent)
{
m_parent->m_torrentsCount -= m_torrentsCount;
m_parent->decreaseTorrentsCount(m_torrentsCount);
const QString uid = m_parent->m_children.key(this);
m_parent->m_children.remove(uid);
m_parent->m_childUids.removeOne(uid);
@@ -86,18 +86,18 @@ public:
return m_torrentsCount;
}
void increaseTorrentsCount()
void increaseTorrentsCount(const int delta = 1)
{
++m_torrentsCount;
m_torrentsCount += delta;
if (m_parent)
m_parent->increaseTorrentsCount();
m_parent->increaseTorrentsCount(delta);
}
void decreaseTorrentsCount()
void decreaseTorrentsCount(const int delta = 1)
{
--m_torrentsCount;
m_torrentsCount -= delta;
if (m_parent)
m_parent->decreaseTorrentsCount();
m_parent->decreaseTorrentsCount(delta);
}
int pos() const
@@ -139,7 +139,7 @@ public:
item->m_parent = this;
m_children[uid] = item;
m_childUids.append(uid);
m_torrentsCount += item->torrentsCount();
increaseTorrentsCount(item->torrentsCount());
}
void clear()
@@ -211,7 +211,7 @@ QVariant CategoryFilterModel::data(const QModelIndex &index, int role) const
if ((index.column() == 0) && (role == Qt::DecorationRole))
{
return UIThemeManager::instance()->getIcon(u"view-categories"_qs);
return UIThemeManager::instance()->getIcon(u"view-categories"_qs, u"inode-directory"_qs);
}
if ((index.column() == 0) && (role == Qt::DisplayRole))
@@ -408,9 +408,9 @@ void CategoryFilterModel::populate()
m_rootItem->addChild(UID_UNCATEGORIZED, new CategoryModelItem(nullptr, tr("Uncategorized"), torrentsCount));
using BitTorrent::Torrent;
for (const QString &categoryName : asConst(session->categories()))
if (m_isSubcategoriesEnabled)
{
if (m_isSubcategoriesEnabled)
for (const QString &categoryName : asConst(session->categories()))
{
CategoryModelItem *parent = m_rootItem;
for (const QString &subcat : asConst(session->expandCategory(categoryName)))
@@ -419,16 +419,19 @@ void CategoryFilterModel::populate()
if (!parent->hasChild(subcatName))
{
const int torrentsCount = std::count_if(torrents.cbegin(), torrents.cend()
, [subcat](Torrent *torrent) { return torrent->category() == subcat; });
, [subcat](Torrent *torrent) { return torrent->category() == subcat; });
new CategoryModelItem(parent, subcatName, torrentsCount);
}
parent = parent->child(subcatName);
}
}
else
}
else
{
for (const QString &categoryName : asConst(session->categories()))
{
const int torrentsCount = std::count_if(torrents.begin(), torrents.end()
, [categoryName](Torrent *torrent) { return torrent->belongsToCategory(categoryName); });
, [categoryName](Torrent *torrent) { return torrent->belongsToCategory(categoryName); });
new CategoryModelItem(m_rootItem, categoryName, torrentsCount);
}
}

View File

@@ -121,18 +121,18 @@ void CategoryFilterWidget::showMenu()
, this, &CategoryFilterWidget::addSubcategory);
}
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-rename"_qs), tr("Edit category...")
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-rename"_qs, u"document-edit"_qs), tr("Edit category...")
, this, &CategoryFilterWidget::editCategory);
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs), tr("Remove category")
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs), tr("Remove category")
, this, &CategoryFilterWidget::removeCategory);
}
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs), tr("Remove unused categories")
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs), tr("Remove unused categories")
, this, &CategoryFilterWidget::removeUnusedCategories);
menu->addSeparator();
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs), tr("Resume torrents")
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs), tr("Resume torrents")
, this, &CategoryFilterWidget::actionResumeTorrentsTriggered);
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs), tr("Pause torrents")
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs), tr("Pause torrents")
, this, &CategoryFilterWidget::actionPauseTorrentsTriggered);
menu->addAction(UIThemeManager::instance()->getIcon(u"list-remove"_qs), tr("Remove torrents")
, this, &CategoryFilterWidget::actionDeleteTorrentsTriggered);

View File

@@ -43,13 +43,13 @@ namespace Color
inline const QColor accentEmphasis = 0x1f6feb;
inline const QColor accentFg = 0x58a6ff;
inline const QColor dangerFg = 0xf85149;
inline const QColor doneFg = 0xa371f7;
inline const QColor fgMuted = 0x8b949e;
inline const QColor fgSubtle = 0x6e7681;
inline const QColor severeFg = 0xdb6d28;
inline const QColor successEmphasis = 0x238636;
inline const QColor successFg = 0x1a7f37;
inline const QColor successFg = 0x3fb950;
// Scale variables
inline const QColor scaleBlue4 = 0x388bfd;
inline const QColor scaleYellow6 = 0x845306;
}
@@ -59,13 +59,13 @@ namespace Color
inline const QColor accentEmphasis = 0x0969da;
inline const QColor accentFg = 0x0969da;
inline const QColor dangerFg = 0xcf222e;
inline const QColor doneFg = 0x8250df;
inline const QColor fgMuted = 0x57606a;
inline const QColor fgSubtle = 0x6e7781;
inline const QColor severeFg = 0xbc4c00;
inline const QColor successEmphasis = 0x2da44e;
inline const QColor successFg = 0x1a7f37;
// Scale variables
inline const QColor scaleBlue4 = 0x218bff;
inline const QColor scaleYellow6 = 0x7d4e00;
}
}

View File

@@ -99,12 +99,18 @@ DesktopIntegration::DesktopIntegration(QObject *parent)
connect(Preferences::instance(), &Preferences::changed, this, &DesktopIntegration::onPreferencesChanged);
}
DesktopIntegration::~DesktopIntegration()
{
if (m_menu)
delete m_menu;
}
bool DesktopIntegration::isActive() const
{
#ifdef Q_OS_MACOS
return true;
#else
return QSystemTrayIcon::isSystemTrayAvailable();
return m_systrayIcon && QSystemTrayIcon::isSystemTrayAvailable();
#endif
}
@@ -135,12 +141,36 @@ void DesktopIntegration::setMenu(QMenu *menu)
if (menu == m_menu)
return;
#if defined Q_OS_MACOS
if (m_menu)
delete m_menu;
m_menu = menu;
#ifdef Q_OS_MACOS
if (m_menu)
m_menu->setAsDockMenu();
#elif defined Q_OS_UNIX
const bool systemTrayEnabled = m_systrayIcon;
if (m_menu)
{
if (m_systrayIcon)
{
delete m_systrayIcon;
m_systrayIcon = nullptr;
}
delete m_menu;
}
m_menu = menu;
if (systemTrayEnabled && !m_systrayIcon)
createTrayIcon();
#else
if (m_menu)
delete m_menu;
m_menu = menu;
if (m_systrayIcon)
m_systrayIcon->setContextMenu(m_menu);
#endif

View File

@@ -49,6 +49,7 @@ class DesktopIntegration final : public QObject
public:
explicit DesktopIntegration(QObject *parent = nullptr);
~DesktopIntegration() override;
bool isActive() const;

View File

@@ -69,8 +69,8 @@ ExecutionLogWidget::ExecutionLogWidget(const Log::MsgTypes types, QWidget *paren
m_ui->tabBan->layout()->addWidget(peerView);
#ifndef Q_OS_MACOS
m_ui->tabConsole->setTabIcon(0, UIThemeManager::instance()->getIcon(u"help-contents"_qs));
m_ui->tabConsole->setTabIcon(1, UIThemeManager::instance()->getIcon(u"ip-blocked"_qs));
m_ui->tabConsole->setTabIcon(0, UIThemeManager::instance()->getIcon(u"help-contents"_qs, u"view-calendar-journal"_qs));
m_ui->tabConsole->setTabIcon(1, UIThemeManager::instance()->getIcon(u"ip-blocked"_qs, u"view-filter"_qs));
#endif
}

View File

@@ -50,10 +50,6 @@
#include <QtGlobal>
#include <QTimer>
#ifdef QBT_USES_CUSTOMDBUSNOTIFICATIONS
#include "notifications/dbusnotifier.h"
#endif
#include "base/bittorrent/session.h"
#include "base/bittorrent/sessionstatus.h"
#include "base/global.h"
@@ -134,8 +130,7 @@ MainWindow::MainWindow(IGUIApplication *app, const State initialState)
m_displaySpeedInTitle = pref->speedInTitleBar();
// Setting icons
#ifndef Q_OS_MACOS
const QIcon appLogo(UIThemeManager::instance()->getIcon(u"qbittorrent"_qs, u"qbittorrent-tray"_qs));
setWindowIcon(appLogo);
setWindowIcon(UIThemeManager::instance()->getIcon(u"qbittorrent"_qs));
#endif // Q_OS_MACOS
#if (defined(Q_OS_UNIX))
@@ -147,7 +142,7 @@ MainWindow::MainWindow(IGUIApplication *app, const State initialState)
m_ui->actionOpen->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_qs));
m_ui->actionDownloadFromURL->setIcon(UIThemeManager::instance()->getIcon(u"insert-link"_qs));
m_ui->actionSetGlobalSpeedLimits->setIcon(UIThemeManager::instance()->getIcon(u"speedometer"_qs));
m_ui->actionCreateTorrent->setIcon(UIThemeManager::instance()->getIcon(u"torrent-creator"_qs));
m_ui->actionCreateTorrent->setIcon(UIThemeManager::instance()->getIcon(u"torrent-creator"_qs, u"document-edit"_qs));
m_ui->actionAbout->setIcon(UIThemeManager::instance()->getIcon(u"help-about"_qs));
m_ui->actionStatistics->setIcon(UIThemeManager::instance()->getIcon(u"view-statistics"_qs));
m_ui->actionTopQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-top"_qs));
@@ -159,13 +154,13 @@ MainWindow::MainWindow(IGUIApplication *app, const State initialState)
m_ui->actionDonateMoney->setIcon(UIThemeManager::instance()->getIcon(u"wallet-open"_qs));
m_ui->actionExit->setIcon(UIThemeManager::instance()->getIcon(u"application-exit"_qs));
m_ui->actionLock->setIcon(UIThemeManager::instance()->getIcon(u"object-locked"_qs));
m_ui->actionOptions->setIcon(UIThemeManager::instance()->getIcon(u"configure"_qs));
m_ui->actionPause->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs));
m_ui->actionPauseAll->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs));
m_ui->actionStart->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_qs));
m_ui->actionStartAll->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_qs));
m_ui->menuAutoShutdownOnDownloadsCompletion->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_qs));
m_ui->actionManageCookies->setIcon(UIThemeManager::instance()->getIcon(u"browser-cookies"_qs));
m_ui->actionOptions->setIcon(UIThemeManager::instance()->getIcon(u"configure"_qs, u"preferences-system"_qs));
m_ui->actionPause->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs));
m_ui->actionPauseAll->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs));
m_ui->actionStart->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs));
m_ui->actionStartAll->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs));
m_ui->menuAutoShutdownOnDownloadsCompletion->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_qs, u"application-exit"_qs));
m_ui->actionManageCookies->setIcon(UIThemeManager::instance()->getIcon(u"browser-cookies"_qs, u"preferences-web-browser-cookies"_qs));
m_ui->menuLog->setIcon(UIThemeManager::instance()->getIcon(u"help-contents"_qs));
m_ui->actionCheckForUpdates->setIcon(UIThemeManager::instance()->getIcon(u"view-refresh"_qs));
@@ -464,7 +459,6 @@ MainWindow::MainWindow(IGUIApplication *app, const State initialState)
MainWindow::~MainWindow()
{
app()->desktopIntegration()->setMenu(nullptr);
delete m_ui;
}
@@ -1139,7 +1133,12 @@ void MainWindow::closeEvent(QCloseEvent *e)
}
#endif // Q_OS_MACOS
if (pref->confirmOnExit() && BitTorrent::Session::instance()->hasActiveTorrents())
const QVector<BitTorrent::Torrent *> allTorrents = BitTorrent::Session::instance()->torrents();
const bool hasActiveTorrents = std::any_of(allTorrents.cbegin(), allTorrents.cend(), [](BitTorrent::Torrent *torrent)
{
return torrent->isActive();
});
if (pref->confirmOnExit() && hasActiveTorrents)
{
if (e->spontaneous() || m_forceExit)
{
@@ -1569,7 +1568,7 @@ void MainWindow::downloadFromURLList(const QStringList &urlList)
QMenu *MainWindow::createDesktopIntegrationMenu()
{
auto *menu = new QMenu(this);
auto *menu = new QMenu;
#ifndef Q_OS_MACOS
connect(menu, &QMenu::aboutToShow, this, [this]()
@@ -1903,8 +1902,17 @@ void MainWindow::on_actionAutoShutdown_toggled(bool enabled)
void MainWindow::updatePowerManagementState()
{
const bool inhibitSuspend = (Preferences::instance()->preventFromSuspendWhenDownloading() && BitTorrent::Session::instance()->hasUnfinishedTorrents())
|| (Preferences::instance()->preventFromSuspendWhenSeeding() && BitTorrent::Session::instance()->hasRunningSeed());
const QVector<BitTorrent::Torrent *> allTorrents = BitTorrent::Session::instance()->torrents();
const bool hasUnfinishedTorrents = std::any_of(allTorrents.cbegin(), allTorrents.cend(), [](const BitTorrent::Torrent *torrent)
{
return (!torrent->isSeed() && !torrent->isPaused() && !torrent->isErrored() && torrent->hasMetadata());
});
const bool hasRunningSeed = std::any_of(allTorrents.cbegin(), allTorrents.cend(), [](const BitTorrent::Torrent *torrent)
{
return (torrent->isSeed() && !torrent->isPaused());
});
const bool inhibitSuspend = (Preferences::instance()->preventFromSuspendWhenDownloading() && hasUnfinishedTorrents)
|| (Preferences::instance()->preventFromSuspendWhenSeeding() && hasRunningSeed);
m_pwr->setActivityState(inhibitSuspend);
}

View File

@@ -68,11 +68,6 @@ namespace Ui
class MainWindow;
}
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) && defined(QT_DBUS_LIB)
#define QBT_USES_CUSTOMDBUSNOTIFICATIONS
class DBusNotifier;
#endif
class MainWindow final : public QMainWindow, public GUIApplicationComponent
{
Q_OBJECT

View File

@@ -52,8 +52,8 @@
#include "base/rss/rss_session.h"
#include "base/torrentfileguard.h"
#include "base/torrentfileswatcher.h"
#include "base/unicodestrings.h"
#include "base/utils/fs.h"
#include "base/utils/misc.h"
#include "base/utils/net.h"
#include "base/utils/password.h"
#include "base/utils/random.h"
@@ -89,81 +89,6 @@ namespace
return ret;
}
QString languageToLocalizedString(const QLocale &locale)
{
switch (locale.language())
{
case QLocale::Arabic: return C_LOCALE_ARABIC;
case QLocale::Armenian: return C_LOCALE_ARMENIAN;
case QLocale::Azerbaijani: return C_LOCALE_AZERBAIJANI;
case QLocale::Basque: return C_LOCALE_BASQUE;
case QLocale::Bulgarian: return C_LOCALE_BULGARIAN;
case QLocale::Byelorussian: return C_LOCALE_BYELORUSSIAN;
case QLocale::Catalan: return C_LOCALE_CATALAN;
case QLocale::Chinese:
switch (locale.country())
{
case QLocale::China: return C_LOCALE_CHINESE_SIMPLIFIED;
case QLocale::HongKong: return C_LOCALE_CHINESE_TRADITIONAL_HK;
default: return C_LOCALE_CHINESE_TRADITIONAL_TW;
}
case QLocale::Croatian: return C_LOCALE_CROATIAN;
case QLocale::Czech: return C_LOCALE_CZECH;
case QLocale::Danish: return C_LOCALE_DANISH;
case QLocale::Dutch: return C_LOCALE_DUTCH;
case QLocale::English:
switch (locale.country())
{
case QLocale::Australia: return C_LOCALE_ENGLISH_AUSTRALIA;
case QLocale::UnitedKingdom: return C_LOCALE_ENGLISH_UNITEDKINGDOM;
default: return C_LOCALE_ENGLISH;
}
case QLocale::Estonian: return C_LOCALE_ESTONIAN;
case QLocale::Finnish: return C_LOCALE_FINNISH;
case QLocale::French: return C_LOCALE_FRENCH;
case QLocale::Galician: return C_LOCALE_GALICIAN;
case QLocale::Georgian: return C_LOCALE_GEORGIAN;
case QLocale::German: return C_LOCALE_GERMAN;
case QLocale::Greek: return C_LOCALE_GREEK;
case QLocale::Hebrew: return C_LOCALE_HEBREW;
case QLocale::Hindi: return C_LOCALE_HINDI;
case QLocale::Hungarian: return C_LOCALE_HUNGARIAN;
case QLocale::Icelandic: return C_LOCALE_ICELANDIC;
case QLocale::Indonesian: return C_LOCALE_INDONESIAN;
case QLocale::Italian: return C_LOCALE_ITALIAN;
case QLocale::Japanese: return C_LOCALE_JAPANESE;
case QLocale::Korean: return C_LOCALE_KOREAN;
case QLocale::Latvian: return C_LOCALE_LATVIAN;
case QLocale::Lithuanian: return C_LOCALE_LITHUANIAN;
case QLocale::Malay: return C_LOCALE_MALAY;
case QLocale::Mongolian: return C_LOCALE_MONGOLIAN;
case QLocale::NorwegianBokmal: return C_LOCALE_NORWEGIAN;
case QLocale::Occitan: return C_LOCALE_OCCITAN;
case QLocale::Persian: return C_LOCALE_PERSIAN;
case QLocale::Polish: return C_LOCALE_POLISH;
case QLocale::Portuguese:
if (locale.country() == QLocale::Brazil)
return C_LOCALE_PORTUGUESE_BRAZIL;
return C_LOCALE_PORTUGUESE;
case QLocale::Romanian: return C_LOCALE_ROMANIAN;
case QLocale::Russian: return C_LOCALE_RUSSIAN;
case QLocale::Serbian: return C_LOCALE_SERBIAN;
case QLocale::Slovak: return C_LOCALE_SLOVAK;
case QLocale::Slovenian: return C_LOCALE_SLOVENIAN;
case QLocale::Spanish: return C_LOCALE_SPANISH;
case QLocale::Swedish: return C_LOCALE_SWEDISH;
case QLocale::Thai: return C_LOCALE_THAI;
case QLocale::Turkish: return C_LOCALE_TURKISH;
case QLocale::Ukrainian: return C_LOCALE_UKRAINIAN;
case QLocale::Uzbek: return C_LOCALE_UZBEK;
case QLocale::Vietnamese: return C_LOCALE_VIETNAMESE;
default:
const QString lang = QLocale::languageToString(locale.language());
qWarning() << "Unrecognized language name: " << lang;
return lang;
}
}
class WheelEventEater final : public QObject
{
public:
@@ -203,17 +128,17 @@ OptionsDialog::OptionsDialog(IGUIApplication *app, QWidget *parent)
// Main icons
m_ui->tabSelection->item(TAB_UI)->setIcon(UIThemeManager::instance()->getIcon(u"preferences-desktop"_qs));
m_ui->tabSelection->item(TAB_BITTORRENT)->setIcon(UIThemeManager::instance()->getIcon(u"preferences-bittorrent"_qs));
m_ui->tabSelection->item(TAB_CONNECTION)->setIcon(UIThemeManager::instance()->getIcon(u"network-connect"_qs));
m_ui->tabSelection->item(TAB_DOWNLOADS)->setIcon(UIThemeManager::instance()->getIcon(u"download"_qs));
m_ui->tabSelection->item(TAB_SPEED)->setIcon(UIThemeManager::instance()->getIcon(u"speedometer"_qs));
m_ui->tabSelection->item(TAB_RSS)->setIcon(UIThemeManager::instance()->getIcon(u"application-rss"_qs));
m_ui->tabSelection->item(TAB_BITTORRENT)->setIcon(UIThemeManager::instance()->getIcon(u"preferences-bittorrent"_qs, u"preferences-system-network"_qs));
m_ui->tabSelection->item(TAB_CONNECTION)->setIcon(UIThemeManager::instance()->getIcon(u"network-connect"_qs, u"network-wired"_qs));
m_ui->tabSelection->item(TAB_DOWNLOADS)->setIcon(UIThemeManager::instance()->getIcon(u"download"_qs, u"folder-download"_qs));
m_ui->tabSelection->item(TAB_SPEED)->setIcon(UIThemeManager::instance()->getIcon(u"speedometer"_qs, u"chronometer"_qs));
m_ui->tabSelection->item(TAB_RSS)->setIcon(UIThemeManager::instance()->getIcon(u"application-rss"_qs, u"application-rss+xml"_qs));
#ifdef DISABLE_WEBUI
m_ui->tabSelection->item(TAB_WEBUI)->setHidden(true);
#else
m_ui->tabSelection->item(TAB_WEBUI)->setIcon(UIThemeManager::instance()->getIcon(u"preferences-webui"_qs));
m_ui->tabSelection->item(TAB_WEBUI)->setIcon(UIThemeManager::instance()->getIcon(u"preferences-webui"_qs, u"network-server"_qs));
#endif
m_ui->tabSelection->item(TAB_ADVANCED)->setIcon(UIThemeManager::instance()->getIcon(u"preferences-advanced"_qs));
m_ui->tabSelection->item(TAB_ADVANCED)->setIcon(UIThemeManager::instance()->getIcon(u"preferences-advanced"_qs, u"preferences-other"_qs));
// set uniform size for all icons
int maxHeight = -1;
@@ -288,6 +213,11 @@ void OptionsDialog::loadBehaviorTabOptions()
m_ui->customThemeFilePath->setMode(FileSystemPathEdit::Mode::FileOpen);
m_ui->customThemeFilePath->setDialogCaption(tr("Select qBittorrent UI Theme file"));
m_ui->customThemeFilePath->setFileNameFilter(tr("qBittorrent UI Theme file (*.qbtheme config.json)"));
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
m_ui->checkUseSystemIcon->setChecked(pref->useSystemIcons());
#else
m_ui->checkUseSystemIcon->setVisible(false);
#endif
m_ui->confirmDeletion->setChecked(pref->confirmTorrentDeletion());
m_ui->checkAltRowColors->setChecked(pref->useAlternatingRowColors());
@@ -382,6 +312,9 @@ void OptionsDialog::loadBehaviorTabOptions()
connect(m_ui->comboI18n, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
connect(m_ui->checkUseSystemIcon, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
#endif
connect(m_ui->checkUseCustomTheme, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->customThemeFilePath, &FileSystemPathEdit::selectedPathChanged, this, &ThisType::enableApplyButton);
@@ -451,6 +384,9 @@ void OptionsDialog::saveBehaviorTabOptions() const
}
pref->setLocale(locale);
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
pref->useSystemIcons(m_ui->checkUseSystemIcon->isChecked());
#endif
pref->setUseCustomUITheme(m_ui->checkUseCustomTheme->isChecked());
pref->setCustomUIThemePath(m_ui->customThemeFilePath->selectedPath());
@@ -1315,25 +1251,8 @@ void OptionsDialog::initializeLanguageCombo()
const QStringList langFiles = langDir.entryList(QStringList(u"qbittorrent_*.qm"_qs), QDir::Files);
for (const QString &langFile : langFiles)
{
QString localeStr = langFile.mid(12); // remove "qbittorrent_"
localeStr.chop(3); // Remove ".qm"
QString languageName;
if (localeStr.startsWith(u"eo", Qt::CaseInsensitive))
{
// QLocale doesn't work with that locale. Esperanto isn't a "real" language.
languageName = C_LOCALE_ESPERANTO;
}
else if (localeStr.startsWith(u"ltg", Qt::CaseInsensitive))
{
// QLocale doesn't work with that locale.
languageName = C_LOCALE_LATGALIAN;
}
else
{
QLocale locale(localeStr);
languageName = languageToLocalizedString(locale);
}
m_ui->comboI18n->addItem(/*QIcon(":/icons/flags/"+country+".svg"), */ languageName, localeStr);
const QString localeStr = langFile.section(u"_"_qs, 1, -1).section(u"."_qs, 0, 0); // remove "qbittorrent_" and ".qm"
m_ui->comboI18n->addItem(/*QIcon(":/icons/flags/"+country+".svg"), */ Utils::Misc::languageToLocalizedString(localeStr), localeStr);
qDebug() << "Supported locale:" << localeStr;
}
}

View File

@@ -133,41 +133,18 @@
<string>Interface</string>
</property>
<layout class="QGridLayout" name="gridLayout_81">
<item row="3" column="0" colspan="3">
<widget class="QGroupBox" name="checkUseCustomTheme">
<property name="title">
<string>Use custom UI Theme</string>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_15">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="checkable">
<bool>true</bool>
<property name="text">
<string>Changing Interface settings requires application restart</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_18">
<item>
<widget class="QLabel" name="label_16">
<property name="text">
<string>UI Theme file:</string>
</property>
</widget>
</item>
<item>
<widget class="FileSystemPathLineEdit" name="customThemeFilePath" native="true"/>
</item>
</layout>
</widget>
</item>
<item row="1" column="2">
<spacer name="horizontalSpacer_111">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>200</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
@@ -191,15 +168,45 @@
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_15">
<property name="font">
<font>
<italic>true</italic>
</font>
<item row="1" column="2">
<spacer name="horizontalSpacer_111">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>200</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0" colspan="3">
<widget class="QGroupBox" name="checkUseCustomTheme">
<property name="title">
<string>Use custom UI Theme</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_18">
<item>
<widget class="QLabel" name="label_16">
<property name="text">
<string>UI Theme file:</string>
</property>
</widget>
</item>
<item>
<widget class="FileSystemPathLineEdit" name="customThemeFilePath" native="true"/>
</item>
</layout>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="checkUseSystemIcon">
<property name="text">
<string>Changing Interface settings requires application restart</string>
<string>Use icons from system theme</string>
</property>
</widget>
</item>

View File

@@ -89,7 +89,7 @@ PeerListWidget::PeerListWidget(PropertiesWidget *parent)
, m_properties(parent)
{
// Load settings
loadSettings();
const bool columnLoaded = loadSettings();
// Visual settings
setUniformRowHeights(true);
setRootIsDecorated(false);
@@ -109,6 +109,7 @@ PeerListWidget::PeerListWidget(PropertiesWidget *parent)
m_listModel->setHeaderData(PeerListColumns::FLAGS, Qt::Horizontal, tr("Flags"));
m_listModel->setHeaderData(PeerListColumns::CONNECTION, Qt::Horizontal, tr("Connection"));
m_listModel->setHeaderData(PeerListColumns::CLIENT, Qt::Horizontal, tr("Client", "i.e.: Client application"));
m_listModel->setHeaderData(PeerListColumns::PEERID_CLIENT, Qt::Horizontal, tr("Peer ID Client", "i.e.: Client resolved from Peer ID"));
m_listModel->setHeaderData(PeerListColumns::PROGRESS, Qt::Horizontal, tr("Progress", "i.e: % downloaded"));
m_listModel->setHeaderData(PeerListColumns::DOWN_SPEED, Qt::Horizontal, tr("Down Speed", "i.e: Download speed"));
m_listModel->setHeaderData(PeerListColumns::UP_SPEED, Qt::Horizontal, tr("Up Speed", "i.e: Upload speed"));
@@ -130,8 +131,16 @@ PeerListWidget::PeerListWidget(PropertiesWidget *parent)
m_proxyModel->setSourceModel(m_listModel);
m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
setModel(m_proxyModel);
hideColumn(PeerListColumns::IP_HIDDEN);
hideColumn(PeerListColumns::COL_COUNT);
// Default hidden columns
if (!columnLoaded)
{
hideColumn(PeerListColumns::PEERID_CLIENT);
}
m_resolveCountries = Preferences::instance()->resolvePeerCountries();
if (!m_resolveCountries)
hideColumn(PeerListColumns::COUNTRY);
@@ -371,9 +380,9 @@ void PeerListWidget::clear()
m_listModel->removeRows(0, nbrows);
}
void PeerListWidget::loadSettings()
bool PeerListWidget::loadSettings()
{
header()->restoreState(Preferences::instance()->getPeerListState());
return header()->restoreState(Preferences::instance()->getPeerListState());
}
void PeerListWidget::saveSettings() const
@@ -461,6 +470,8 @@ void PeerListWidget::updatePeer(const BitTorrent::Torrent *torrent, const BitTor
setModelData(row, PeerListColumns::FLAGS, peer.flags(), peer.flags(), {}, peer.flagsDescription());
const QString client = peer.client().toHtmlEscaped();
setModelData(row, PeerListColumns::CLIENT, client, client, {}, client);
const QString peerIdClient = peer.peerIdClient().toHtmlEscaped();
setModelData(row, PeerListColumns::PEERID_CLIENT, peerIdClient, peerIdClient);
setModelData(row, PeerListColumns::PROGRESS, (Utils::String::fromDouble(peer.progress() * 100, 1) + u'%'), peer.progress(), intDataTextAlignment);
const QString downSpeed = (hideValues && (peer.payloadDownSpeed() <= 0)) ? QString {} : Utils::Misc::friendlyUnit(peer.payloadDownSpeed(), true);
setModelData(row, PeerListColumns::DOWN_SPEED, downSpeed, peer.payloadDownSpeed(), intDataTextAlignment);

View File

@@ -66,6 +66,7 @@ public:
CONNECTION,
FLAGS,
CLIENT,
PEERID_CLIENT,
PROGRESS,
DOWN_SPEED,
UP_SPEED,
@@ -87,7 +88,7 @@ public:
void clear();
private slots:
void loadSettings();
bool loadSettings();
void saveSettings() const;
void displayColumnHeaderMenu();
void showPeerListMenu();

View File

@@ -750,7 +750,7 @@ void PropertiesWidget::displayWebSeedListMenu()
if (!rows.isEmpty())
{
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs), tr("Remove Web seed")
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs), tr("Remove Web seed")
, this, &PropertiesWidget::deleteSelectedUrlSeeds);
menu->addSeparator();
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-copy"_qs), tr("Copy Web seed URL")

View File

@@ -45,7 +45,7 @@ PropTabBar::PropTabBar(QWidget *parent)
// General tab
QPushButton *mainInfosButton = new QPushButton(
#ifndef Q_OS_MACOS
UIThemeManager::instance()->getIcon(u"help-about"_qs),
UIThemeManager::instance()->getIcon(u"help-about"_qs, u"document-properties"_qs),
#endif
tr("General"), parent);
mainInfosButton->setShortcut(Qt::ALT + Qt::Key_G);
@@ -54,7 +54,7 @@ PropTabBar::PropTabBar(QWidget *parent)
// Trackers tab
QPushButton *trackersButton = new QPushButton(
#ifndef Q_OS_MACOS
UIThemeManager::instance()->getIcon(u"trackers"_qs),
UIThemeManager::instance()->getIcon(u"trackers"_qs, u"network-server"_qs),
#endif
tr("Trackers"), parent);
trackersButton->setShortcut(Qt::ALT + Qt::Key_C);

View File

@@ -572,7 +572,7 @@ void TrackerListWidget::showTrackerListMenu()
{
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-rename"_qs),tr("Edit tracker URL...")
, this, &TrackerListWidget::editSelectedTracker);
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs), tr("Remove tracker")
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs), tr("Remove tracker")
, this, &TrackerListWidget::deleteSelectedTrackers);
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-copy"_qs), tr("Copy tracker URL")
, this, &TrackerListWidget::copyTrackerUrl);
@@ -580,10 +580,10 @@ void TrackerListWidget::showTrackerListMenu()
if (!torrent->isPaused())
{
menu->addAction(UIThemeManager::instance()->getIcon(u"reannounce"_qs), tr("Force reannounce to selected trackers")
menu->addAction(UIThemeManager::instance()->getIcon(u"reannounce"_qs, u"view-refresh"_qs), tr("Force reannounce to selected trackers")
, this, &TrackerListWidget::reannounceSelected);
menu->addSeparator();
menu->addAction(UIThemeManager::instance()->getIcon(u"reannounce"_qs), tr("Force reannounce to all trackers")
menu->addAction(UIThemeManager::instance()->getIcon(u"reannounce"_qs, u"view-refresh"_qs), tr("Force reannounce to all trackers")
, this, [this]()
{
BitTorrent::Torrent *h = m_properties->getCurrentTorrent();

View File

@@ -52,7 +52,7 @@ TrackersAdditionDialog::TrackersAdditionDialog(QWidget *parent, BitTorrent::Torr
{
m_ui->setupUi(this);
m_ui->downloadButton->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs));
m_ui->downloadButton->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs, u"download"_qs));
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Add"));
connect(m_ui->downloadButton, &QAbstractButton::clicked, this, &TrackersAdditionDialog::onDownloadButtonClicked);

View File

@@ -105,7 +105,7 @@ void ArticleListWidget::handleArticleRead(RSS::Article *rssArticle)
const QColor defaultColor {palette().color(QPalette::Inactive, QPalette::WindowText)};
const QBrush foregroundBrush {UIThemeManager::instance()->getColor(u"RSS.ReadArticle"_qs, defaultColor)};
item->setData(Qt::ForegroundRole, foregroundBrush);
item->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"loading"_qs));
item->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"loading"_qs, u"sphere"_qs));
checkInvariant();
}
@@ -133,14 +133,14 @@ QListWidgetItem *ArticleListWidget::createItem(RSS::Article *article) const
const QColor defaultColor {palette().color(QPalette::Inactive, QPalette::WindowText)};
const QBrush foregroundBrush {UIThemeManager::instance()->getColor(u"RSS.ReadArticle"_qs, defaultColor)};
item->setData(Qt::ForegroundRole, foregroundBrush);
item->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"loading"_qs));
item->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"loading"_qs, u"sphere"_qs));
}
else
{
const QColor defaultColor {palette().color(QPalette::Active, QPalette::Link)};
const QBrush foregroundBrush {UIThemeManager::instance()->getColor(u"RSS.UnreadArticle"_qs, defaultColor)};
item->setData(Qt::ForegroundRole, foregroundBrush);
item->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"loading"_qs));
item->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"loading"_qs, u"sphere"_qs));
}
return item;

View File

@@ -75,7 +75,7 @@ AutomatedRssDownloader::AutomatedRssDownloader(QWidget *parent)
{
m_ui->setupUi(this);
// Icons
m_ui->removeRuleBtn->setIcon(UIThemeManager::instance()->getIcon(u"edit-clear"_qs));
m_ui->removeRuleBtn->setIcon(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs));
m_ui->addRuleBtn->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_qs));
m_ui->addCategoryBtn->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_qs));
@@ -521,7 +521,7 @@ void AutomatedRssDownloader::displayRulesListMenu()
{
if (selection.count() == 1)
{
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs), tr("Delete rule")
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs), tr("Delete rule")
, this, &AutomatedRssDownloader::on_removeRuleBtn_clicked);
menu->addSeparator();
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-rename"_qs), tr("Rename rule...")
@@ -529,7 +529,7 @@ void AutomatedRssDownloader::displayRulesListMenu()
}
else
{
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs), tr("Delete selected rules")
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs), tr("Delete selected rules")
, this, &AutomatedRssDownloader::on_removeRuleBtn_clicked);
}
@@ -759,7 +759,7 @@ void AutomatedRssDownloader::updateMustLineValidity()
else
{
m_ui->lineContains->setStyleSheet(u"QLineEdit { color: #ff0000; }"_qs);
m_ui->labelMustStat->setPixmap(UIThemeManager::instance()->getIcon(u"dialog-warning"_qs).pixmap(16, 16));
m_ui->labelMustStat->setPixmap(UIThemeManager::instance()->getIcon(u"dialog-warning"_qs, u"task-attention"_qs).pixmap(16, 16));
m_ui->labelMustStat->setToolTip(error);
}
}
@@ -806,7 +806,7 @@ void AutomatedRssDownloader::updateMustNotLineValidity()
else
{
m_ui->lineNotContains->setStyleSheet(u"QLineEdit { color: #ff0000; }"_qs);
m_ui->labelMustNotStat->setPixmap(UIThemeManager::instance()->getIcon(u"dialog-warning"_qs).pixmap(16, 16));
m_ui->labelMustNotStat->setPixmap(UIThemeManager::instance()->getIcon(u"dialog-warning"_qs, u"task-attention"_qs).pixmap(16, 16));
m_ui->labelMustNotStat->setToolTip(error);
}
}
@@ -824,7 +824,7 @@ void AutomatedRssDownloader::updateEpisodeFilterValidity()
else
{
m_ui->lineEFilter->setStyleSheet(u"QLineEdit { color: #ff0000; }"_qs);
m_ui->labelEpFilterStat->setPixmap(UIThemeManager::instance()->getIcon(u"dialog-warning"_qs).pixmap(16, 16));
m_ui->labelEpFilterStat->setPixmap(UIThemeManager::instance()->getIcon(u"dialog-warning"_qs, u"task-attention"_qs).pixmap(16, 16));
}
}

View File

@@ -81,7 +81,7 @@ namespace
if (feed->isLoading())
return UIThemeManager::instance()->getIcon(u"loading"_qs);
if (feed->hasError())
return UIThemeManager::instance()->getIcon(u"task-reject"_qs);
return UIThemeManager::instance()->getIcon(u"task-reject"_qs, u"unavailable"_qs);
return loadIcon(feed->iconPath(), u"application-rss"_qs);
}

View File

@@ -64,8 +64,8 @@ RSSWidget::RSSWidget(QWidget *parent)
// Icons
m_ui->actionCopyFeedURL->setIcon(UIThemeManager::instance()->getIcon(u"edit-copy"_qs));
m_ui->actionDelete->setIcon(UIThemeManager::instance()->getIcon(u"edit-clear"_qs));
m_ui->actionDownloadTorrent->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs));
m_ui->actionMarkItemsRead->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_qs));
m_ui->actionDownloadTorrent->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs, u"download"_qs));
m_ui->actionMarkItemsRead->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_qs, u"mail-mark-read"_qs));
m_ui->actionNewFolder->setIcon(UIThemeManager::instance()->getIcon(u"folder-new"_qs));
m_ui->actionNewSubscription->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_qs));
m_ui->actionOpenNewsURL->setIcon(UIThemeManager::instance()->getIcon(u"application-url"_qs));
@@ -74,9 +74,9 @@ RSSWidget::RSSWidget(QWidget *parent)
m_ui->actionUpdateAllFeeds->setIcon(UIThemeManager::instance()->getIcon(u"view-refresh"_qs));
#ifndef Q_OS_MACOS
m_ui->newFeedButton->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_qs));
m_ui->markReadButton->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_qs));
m_ui->markReadButton->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_qs, u"mail-mark-read"_qs));
m_ui->updateAllButton->setIcon(UIThemeManager::instance()->getIcon(u"view-refresh"_qs));
m_ui->rssDownloaderBtn->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs));
m_ui->rssDownloaderBtn->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs, u"download"_qs));
#endif
m_articleListWidget = new ArticleListWidget(m_ui->splitterMain);

View File

@@ -392,7 +392,7 @@ void SearchJobWidget::contextMenuEvent(QContextMenuEvent *event)
menu->addAction(UIThemeManager::instance()->getIcon(u"download"_qs)
, tr("Open download window"), this, [this]() { downloadTorrents(AddTorrentOption::ShowDialog); });
menu->addAction(UIThemeManager::instance()->getIcon(u"downloading"_qs)
menu->addAction(UIThemeManager::instance()->getIcon(u"downloading"_qs, u"download"_qs)
, tr("Download"), this, [this]() { downloadTorrents(AddTorrentOption::SkipDialog); });
menu->addSeparator();
menu->addAction(UIThemeManager::instance()->getIcon(u"application-url"_qs), tr("Open description page")
@@ -401,11 +401,11 @@ void SearchJobWidget::contextMenuEvent(QContextMenuEvent *event)
QMenu *copySubMenu = menu->addMenu(
UIThemeManager::instance()->getIcon(u"edit-copy"_qs), tr("Copy"));
copySubMenu->addAction(UIThemeManager::instance()->getIcon(u"name"_qs), tr("Name")
copySubMenu->addAction(UIThemeManager::instance()->getIcon(u"name"_qs, u"edit-copy"_qs), tr("Name")
, this, &SearchJobWidget::copyTorrentNames);
copySubMenu->addAction(UIThemeManager::instance()->getIcon(u"insert-link"_qs), tr("Download link")
copySubMenu->addAction(UIThemeManager::instance()->getIcon(u"insert-link"_qs, u"edit-copy"_qs), tr("Download link")
, this, &SearchJobWidget::copyTorrentDownloadLinks);
copySubMenu->addAction(UIThemeManager::instance()->getIcon(u"application-url"_qs), tr("Description page URL")
copySubMenu->addAction(UIThemeManager::instance()->getIcon(u"application-url"_qs, u"edit-copy"_qs), tr("Description page URL")
, this, &SearchJobWidget::copyTorrentURLs);
menu->popup(event->globalPos());

View File

@@ -112,7 +112,7 @@ SearchWidget::SearchWidget(IGUIApplication *app, MainWindow *mainWindow)
#ifndef Q_OS_MACOS
// Icons
m_ui->searchButton->setIcon(UIThemeManager::instance()->getIcon(u"edit-find"_qs));
m_ui->pluginsButton->setIcon(UIThemeManager::instance()->getIcon(u"plugins"_qs));
m_ui->pluginsButton->setIcon(UIThemeManager::instance()->getIcon(u"plugins"_qs, u"preferences-system-network"_qs));
#else
// On macOS the icons overlap the text otherwise
QSize iconSize = m_ui->tabWidget->iconSize();

View File

@@ -48,7 +48,7 @@ StatusBar::StatusBar(QWidget *parent)
{
#ifndef Q_OS_MACOS
// Redefining global stylesheet breaks certain elements on mac like tabs.
// Qt checks whether the stylesheet class inherts("QMacStyle") and this becomes false.
// Qt checks whether the stylesheet class inherits("QMacStyle") and this becomes false.
setStyleSheet(u"QStatusBar::item { border-width: 0; }"_qs);
#endif
@@ -69,7 +69,7 @@ StatusBar::StatusBar(QWidget *parent)
connect(m_connecStatusLblIcon, &QAbstractButton::clicked, this, &StatusBar::connectionButtonClicked);
m_dlSpeedLbl = new QPushButton(this);
m_dlSpeedLbl->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs));
m_dlSpeedLbl->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs, u"downloading_small"_qs));
connect(m_dlSpeedLbl, &QAbstractButton::clicked, this, &StatusBar::capSpeed);
m_dlSpeedLbl->setFlat(true);
m_dlSpeedLbl->setFocusPolicy(Qt::NoFocus);
@@ -78,7 +78,7 @@ StatusBar::StatusBar(QWidget *parent)
m_dlSpeedLbl->setMinimumWidth(200);
m_upSpeedLbl = new QPushButton(this);
m_upSpeedLbl->setIcon(UIThemeManager::instance()->getIcon(u"upload"_qs));
m_upSpeedLbl->setIcon(UIThemeManager::instance()->getIcon(u"upload"_qs, u"seeding"_qs));
connect(m_upSpeedLbl, &QAbstractButton::clicked, this, &StatusBar::capSpeed);
m_upSpeedLbl->setFlat(true);
m_upSpeedLbl->setFocusPolicy(Qt::NoFocus);

View File

@@ -123,7 +123,7 @@ QVariant TagFilterModel::data(const QModelIndex &index, int role) const
switch (role)
{
case Qt::DecorationRole:
return UIThemeManager::instance()->getIcon(u"tags"_qs);
return UIThemeManager::instance()->getIcon(u"tags"_qs, u"inode-directory"_qs);
case Qt::DisplayRole:
return u"%1 (%2)"_qs.arg(tagDisplayName(item.tag())).arg(item.torrentsCount());
case Qt::UserRole:

View File

@@ -113,16 +113,16 @@ void TagFilterWidget::showMenu()
const auto selectedRows = selectionModel()->selectedRows();
if (!selectedRows.empty() && !TagFilterModel::isSpecialItem(selectedRows.first()))
{
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs), tr("Remove tag")
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs), tr("Remove tag")
, this, &TagFilterWidget::removeTag);
}
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs), tr("Remove unused tags")
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs), tr("Remove unused tags")
, this, &TagFilterWidget::removeUnusedTags);
menu->addSeparator();
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs), tr("Resume torrents")
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs), tr("Resume torrents")
, this, &TagFilterWidget::actionResumeTorrentsTriggered);
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs), tr("Pause torrents")
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs), tr("Pause torrents")
, this, &TagFilterWidget::actionPauseTorrentsTriggered);
menu->addAction(UIThemeManager::instance()->getIcon(u"list-remove"_qs), tr("Remove torrents")
, this, &TagFilterWidget::actionDeleteTorrentsTriggered);

View File

@@ -133,6 +133,9 @@ QString TorrentCategoryDialog::categoryName() const
void TorrentCategoryDialog::setCategoryName(const QString &categoryName)
{
m_ui->textCategoryName->setText(categoryName);
const int subcategoryNameStart = categoryName.lastIndexOf(u"/") + 1;
m_ui->textCategoryName->setSelection(subcategoryNameStart, (categoryName.size() - subcategoryNameStart));
}
BitTorrent::CategoryOptions TorrentCategoryDialog::categoryOptions() const

View File

@@ -29,13 +29,10 @@
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1">
<widget class="FileSystemPathComboEdit" name="comboSavePath" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<item row="0" column="0">
<widget class="QLabel" name="labelCategoryName">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
@@ -49,10 +46,13 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelCategoryName">
<property name="text">
<string>Name:</string>
<item row="1" column="1">
<widget class="FileSystemPathComboEdit" name="comboSavePath" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
@@ -173,6 +173,12 @@
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>textCategoryName</tabstop>
<tabstop>comboSavePath</tabstop>
<tabstop>comboUseDownloadPath</tabstop>
<tabstop>comboDownloadPath</tabstop>
</tabstops>
<resources/>
<connections>
<connection>

View File

@@ -70,7 +70,7 @@ namespace
{
public:
UnifiedFileIconProvider()
: m_textPlainIcon {UIThemeManager::instance()->getIcon(u"help-about"_qs)}
: m_textPlainIcon {UIThemeManager::instance()->getIcon(u"help-about"_qs, u"text-plain"_qs)}
{
}
@@ -541,7 +541,8 @@ void TorrentContentModel::setupModelData(const BitTorrent::AbstractFileStorage &
if (filesCount <= 0)
return;
emit layoutAboutToBeChanged();
beginResetModel();
// Initialize files_index array
qDebug("Torrent contains %d files", filesCount);
m_filesIndex.reserve(filesCount);
@@ -588,7 +589,8 @@ void TorrentContentModel::setupModelData(const BitTorrent::AbstractFileStorage &
lastParent->appendChild(fileItem);
m_filesIndex.push_back(fileItem);
}
emit layoutChanged();
endResetModel();
}
void TorrentContentModel::notifySubtreeUpdated(const QModelIndex &index, const QVector<ColumnInterval> &columns)

View File

@@ -79,25 +79,19 @@ void TorrentContentTreeView::keyPressEvent(QKeyEvent *event)
event->accept();
QModelIndex current = currentNameCell();
QVariant value = current.data(Qt::CheckStateRole);
const QVariant value = currentNameCell().data(Qt::CheckStateRole);
if (!value.isValid())
{
Q_ASSERT(false);
return;
}
Qt::CheckState state = (static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked
? Qt::Unchecked : Qt::Checked);
const Qt::CheckState state = (static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked)
? Qt::Unchecked : Qt::Checked;
const QModelIndexList selection = selectionModel()->selectedRows(TorrentContentModelItem::COL_NAME);
for (const QModelIndex &index : selection)
{
Q_ASSERT(index.column() == TorrentContentModelItem::COL_NAME);
model()->setData(index, state, Qt::CheckStateRole);
}
}
void TorrentContentTreeView::renameSelectedFile(BitTorrent::AbstractFileStorage &fileStorage)
@@ -142,16 +136,16 @@ void TorrentContentTreeView::renameSelectedFile(BitTorrent::AbstractFileStorage
}
}
QModelIndex TorrentContentTreeView::currentNameCell()
QModelIndex TorrentContentTreeView::currentNameCell() const
{
QModelIndex current = currentIndex();
const QModelIndex current = currentIndex();
if (!current.isValid())
{
Q_ASSERT(false);
return {};
}
return model()->index(current.row(), TorrentContentModelItem::COL_NAME, current.parent());
return current.siblingAtColumn(TorrentContentModelItem::COL_NAME);
}
void TorrentContentTreeView::wheelEvent(QWheelEvent *event)

View File

@@ -49,6 +49,6 @@ public:
void renameSelectedFile(BitTorrent::AbstractFileStorage &fileStorage);
private:
QModelIndex currentNameCell();
QModelIndex currentNameCell() const;
void wheelEvent(QWheelEvent *event) override;
};

View File

@@ -173,31 +173,31 @@ StatusFilterWidget::StatusFilterWidget(QWidget *parent, TransferListWidget *tran
// Add status filters
auto *all = new QListWidgetItem(this);
all->setData(Qt::DisplayRole, tr("All (0)", "this is for the status filter"));
all->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-all"_qs));
all->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-all"_qs, u"filterall"_qs));
auto *downloading = new QListWidgetItem(this);
downloading->setData(Qt::DisplayRole, tr("Downloading (0)"));
downloading->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"downloading"_qs));
auto *seeding = new QListWidgetItem(this);
seeding->setData(Qt::DisplayRole, tr("Seeding (0)"));
seeding->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"upload"_qs));
seeding->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"upload"_qs, u"uploading"_qs));
auto *completed = new QListWidgetItem(this);
completed->setData(Qt::DisplayRole, tr("Completed (0)"));
completed->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"checked-completed"_qs));
completed->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"checked-completed"_qs, u"completed"_qs));
auto *resumed = new QListWidgetItem(this);
resumed->setData(Qt::DisplayRole, tr("Resumed (0)"));
resumed->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"torrent-start"_qs));
resumed->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs));
auto *paused = new QListWidgetItem(this);
paused->setData(Qt::DisplayRole, tr("Paused (0)"));
paused->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"torrent-stop"_qs));
paused->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"stopped"_qs, u"media-playback-pause"_qs));
auto *active = new QListWidgetItem(this);
active->setData(Qt::DisplayRole, tr("Active (0)"));
active->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-active"_qs));
active->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-active"_qs, u"filteractive"_qs));
auto *inactive = new QListWidgetItem(this);
inactive->setData(Qt::DisplayRole, tr("Inactive (0)"));
inactive->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-inactive"_qs));
inactive->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-inactive"_qs, u"filterinactive"_qs));
auto *stalled = new QListWidgetItem(this);
stalled->setData(Qt::DisplayRole, tr("Stalled (0)"));
stalled->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-stalled"_qs));
stalled->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-stalled"_qs, u"filterstalled"_qs));
auto *stalledUploading = new QListWidgetItem(this);
stalledUploading->setData(Qt::DisplayRole, tr("Stalled Uploading (0)"));
stalledUploading->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"stalledUP"_qs));
@@ -206,7 +206,7 @@ StatusFilterWidget::StatusFilterWidget(QWidget *parent, TransferListWidget *tran
stalledDownloading->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"stalledDL"_qs));
auto *checking = new QListWidgetItem(this);
checking->setData(Qt::DisplayRole, tr("Checking (0)"));
checking->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"force-recheck"_qs));
checking->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"force-recheck"_qs, u"checking"_qs));
auto *moving = new QListWidgetItem(this);
moving->setData(Qt::DisplayRole, tr("Moving (0)"));
moving->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"set-location"_qs));
@@ -308,9 +308,9 @@ void StatusFilterWidget::showMenu()
QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs), tr("Resume torrents")
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs), tr("Resume torrents")
, transferList, &TransferListWidget::startVisibleTorrents);
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs), tr("Pause torrents")
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs), tr("Pause torrents")
, transferList, &TransferListWidget::pauseVisibleTorrents);
menu->addAction(UIThemeManager::instance()->getIcon(u"list-remove"_qs), tr("Remove torrents")
, transferList, &TransferListWidget::deleteVisibleTorrents);
@@ -371,16 +371,16 @@ TrackerFiltersList::TrackerFiltersList(QWidget *parent, TransferListWidget *tran
{
auto *allTrackers = new QListWidgetItem(this);
allTrackers->setData(Qt::DisplayRole, tr("All (0)", "this is for the tracker filter"));
allTrackers->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_qs));
allTrackers->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_qs, u"network-server"_qs));
auto *noTracker = new QListWidgetItem(this);
noTracker->setData(Qt::DisplayRole, tr("Trackerless (0)"));
noTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackerless"_qs));
noTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackerless"_qs, u"network-server"_qs));
auto *errorTracker = new QListWidgetItem(this);
errorTracker->setData(Qt::DisplayRole, tr("Error (0)"));
errorTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_qs));
errorTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_qs, u"dialog-error"_qs));
auto *warningTracker = new QListWidgetItem(this);
warningTracker->setData(Qt::DisplayRole, tr("Warning (0)"));
warningTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-warning"_qs));
warningTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-warning"_qs, u"dialog-warning"_qs));
m_trackers[NULL_HOST] = {{}, noTracker};
@@ -474,7 +474,7 @@ void TrackerFiltersList::addItems(const QString &trackerURL, const QVector<BitTo
else
{
trackerItem = new QListWidgetItem();
trackerItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_qs));
trackerItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_qs, u"network-server"_qs));
const TrackerData trackerData {{}, trackerItem};
trackersIt = m_trackers.insert(host, trackerData);
@@ -712,9 +712,9 @@ void TrackerFiltersList::showMenu()
QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs), tr("Resume torrents")
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs), tr("Resume torrents")
, transferList, &TransferListWidget::startVisibleTorrents);
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs), tr("Pause torrents")
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs), tr("Pause torrents")
, transferList, &TransferListWidget::pauseVisibleTorrents);
menu->addAction(UIThemeManager::instance()->getIcon(u"list-remove"_qs), tr("Remove torrents")
, transferList, &TransferListWidget::deleteVisibleTorrents);

View File

@@ -70,7 +70,7 @@ namespace
case BitTorrent::TorrentState::PausedDownloading:
return (isDarkTheme ? Color::Primer::Dark::fgMuted : Color::Primer::Light::fgMuted);
case BitTorrent::TorrentState::PausedUploading:
return (isDarkTheme ? Color::Primer::Dark::scaleBlue4 : Color::Primer::Light::scaleBlue4);
return (isDarkTheme ? Color::Primer::Dark::doneFg : Color::Primer::Light::doneFg);
case BitTorrent::TorrentState::QueuedDownloading:
case BitTorrent::TorrentState::QueuedUploading:
return (isDarkTheme ? Color::Primer::Dark::scaleYellow6 : Color::Primer::Light::scaleYellow6);
@@ -158,15 +158,15 @@ TransferListModel::TransferListModel(QObject *parent)
{BitTorrent::TorrentState::Error, tr("Errored", "Torrent status, the torrent has an error")}
}
, m_stateThemeColors {torrentStateColorsFromUITheme()}
, m_checkingIcon {UIThemeManager::instance()->getIcon(u"force-recheck"_qs)}
, m_completedIcon {UIThemeManager::instance()->getIcon(u"checked-completed"_qs)}
, m_checkingIcon {UIThemeManager::instance()->getIcon(u"force-recheck"_qs, u"checking"_qs)}
, m_completedIcon {UIThemeManager::instance()->getIcon(u"checked-completed"_qs, u"completed"_qs)}
, m_downloadingIcon {UIThemeManager::instance()->getIcon(u"downloading"_qs)}
, m_errorIcon {UIThemeManager::instance()->getIcon(u"error"_qs)}
, m_pausedIcon {UIThemeManager::instance()->getIcon(u"torrent-stop"_qs)}
, m_pausedIcon {UIThemeManager::instance()->getIcon(u"stopped"_qs, u"media-playback-pause"_qs)}
, m_queuedIcon {UIThemeManager::instance()->getIcon(u"queued"_qs)}
, m_stalledDLIcon {UIThemeManager::instance()->getIcon(u"stalledDL"_qs)}
, m_stalledUPIcon {UIThemeManager::instance()->getIcon(u"stalledUP"_qs)}
, m_uploadingIcon {UIThemeManager::instance()->getIcon(u"upload"_qs)}
, m_uploadingIcon {UIThemeManager::instance()->getIcon(u"upload"_qs, u"uploading"_qs)}
{
configure();
connect(Preferences::instance(), &Preferences::changed, this, &TransferListModel::configure);
@@ -632,9 +632,12 @@ bool TransferListModel::setData(const QModelIndex &index, const QVariant &value,
void TransferListModel::addTorrents(const QVector<BitTorrent::Torrent *> &torrents)
{
int row = m_torrentList.size();
beginInsertRows({}, row, (row + torrents.size()));
qsizetype row = m_torrentList.size();
const qsizetype total = row + torrents.size();
beginInsertRows({}, row, total);
m_torrentList.reserve(total);
for (BitTorrent::Torrent *torrent : torrents)
{
Q_ASSERT(!m_torrentMap.contains(torrent));

View File

@@ -363,12 +363,30 @@ void TransferListWidget::setSelectedTorrentsLocation()
void TransferListWidget::pauseAllTorrents()
{
// Show confirmation if user would really like to Pause All
const QMessageBox::StandardButton ret =
QMessageBox::question(this, tr("Confirm pause")
, tr("Would you like to pause all torrents?")
, (QMessageBox::Yes | QMessageBox::No));
if (ret != QMessageBox::Yes)
return;
for (BitTorrent::Torrent *const torrent : asConst(BitTorrent::Session::instance()->torrents()))
torrent->pause();
}
void TransferListWidget::resumeAllTorrents()
{
// Show confirmation if user would really like to Resume All
const QMessageBox::StandardButton ret =
QMessageBox::question(this, tr("Confirm resume")
, tr("Would you like to resume all torrents?")
, (QMessageBox::Yes | QMessageBox::No));
if (ret != QMessageBox::Yes)
return;
for (BitTorrent::Torrent *const torrent : asConst(BitTorrent::Session::instance()->torrents()))
torrent->resume();
}
@@ -912,11 +930,11 @@ void TransferListWidget::displayListMenu()
// Create actions
auto *actionStart = new QAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs), tr("&Resume", "Resume/start the torrent"), listMenu);
auto *actionStart = new QAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs), tr("&Resume", "Resume/start the torrent"), listMenu);
connect(actionStart, &QAction::triggered, this, &TransferListWidget::startSelectedTorrents);
auto *actionPause = new QAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs), tr("&Pause", "Pause the torrent"), listMenu);
auto *actionPause = new QAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs), tr("&Pause", "Pause the torrent"), listMenu);
connect(actionPause, &QAction::triggered, this, &TransferListWidget::pauseSelectedTorrents);
auto *actionForceStart = new QAction(UIThemeManager::instance()->getIcon(u"torrent-start-forced"_qs), tr("Force Resu&me", "Force Resume/start the torrent"), listMenu);
auto *actionForceStart = new QAction(UIThemeManager::instance()->getIcon(u"torrent-start-forced"_qs, u"media-playback-start"_qs), tr("Force Resu&me", "Force Resume/start the torrent"), listMenu);
connect(actionForceStart, &QAction::triggered, this, &TransferListWidget::forceStartSelectedTorrents);
auto *actionDelete = new QAction(UIThemeManager::instance()->getIcon(u"list-remove"_qs), tr("&Remove", "Remove the torrent"), listMenu);
connect(actionDelete, &QAction::triggered, this, &TransferListWidget::softDeleteSelectedTorrents);
@@ -934,21 +952,21 @@ void TransferListWidget::displayListMenu()
connect(actionTopQueuePos, &QAction::triggered, this, &TransferListWidget::topQueuePosSelectedTorrents);
auto *actionBottomQueuePos = new QAction(UIThemeManager::instance()->getIcon(u"go-bottom"_qs), tr("Move to &bottom", "i.e. Move to bottom of the queue"), listMenu);
connect(actionBottomQueuePos, &QAction::triggered, this, &TransferListWidget::bottomQueuePosSelectedTorrents);
auto *actionSetTorrentPath = new QAction(UIThemeManager::instance()->getIcon(u"set-location"_qs), tr("Set loc&ation..."), listMenu);
auto *actionSetTorrentPath = new QAction(UIThemeManager::instance()->getIcon(u"set-location"_qs, u"inode-directory"_qs), tr("Set loc&ation..."), listMenu);
connect(actionSetTorrentPath, &QAction::triggered, this, &TransferListWidget::setSelectedTorrentsLocation);
auto *actionForceRecheck = new QAction(UIThemeManager::instance()->getIcon(u"force-recheck"_qs), tr("Force rec&heck"), listMenu);
auto *actionForceRecheck = new QAction(UIThemeManager::instance()->getIcon(u"force-recheck"_qs, u"document-edit-verify"_qs), tr("Force rec&heck"), listMenu);
connect(actionForceRecheck, &QAction::triggered, this, &TransferListWidget::recheckSelectedTorrents);
auto *actionForceReannounce = new QAction(UIThemeManager::instance()->getIcon(u"reannounce"_qs), tr("Force r&eannounce"), listMenu);
auto *actionForceReannounce = new QAction(UIThemeManager::instance()->getIcon(u"reannounce"_qs, u"document-edit-verify"_qs), tr("Force r&eannounce"), listMenu);
connect(actionForceReannounce, &QAction::triggered, this, &TransferListWidget::reannounceSelectedTorrents);
auto *actionCopyMagnetLink = new QAction(UIThemeManager::instance()->getIcon(u"torrent-magnet"_qs), tr("&Magnet link"), listMenu);
auto *actionCopyMagnetLink = new QAction(UIThemeManager::instance()->getIcon(u"torrent-magnet"_qs, u"kt-magnet"_qs), tr("&Magnet link"), listMenu);
connect(actionCopyMagnetLink, &QAction::triggered, this, &TransferListWidget::copySelectedMagnetURIs);
auto *actionCopyID = new QAction(UIThemeManager::instance()->getIcon(u"help-about"_qs), tr("Torrent &ID"), listMenu);
auto *actionCopyID = new QAction(UIThemeManager::instance()->getIcon(u"help-about"_qs, u"edit-copy"_qs), tr("Torrent &ID"), listMenu);
connect(actionCopyID, &QAction::triggered, this, &TransferListWidget::copySelectedIDs);
auto *actionCopyName = new QAction(UIThemeManager::instance()->getIcon(u"name"_qs), tr("&Name"), listMenu);
auto *actionCopyName = new QAction(UIThemeManager::instance()->getIcon(u"name"_qs, u"edit-copy"_qs), tr("&Name"), listMenu);
connect(actionCopyName, &QAction::triggered, this, &TransferListWidget::copySelectedNames);
auto *actionCopyHash1 = new QAction(UIThemeManager::instance()->getIcon(u"hash"_qs), tr("Info &hash v1"), listMenu);
auto *actionCopyHash1 = new QAction(UIThemeManager::instance()->getIcon(u"hash"_qs, u"edit-copy"_qs), tr("Info &hash v1"), listMenu);
connect(actionCopyHash1, &QAction::triggered, this, [this]() { copySelectedInfohashes(CopyInfohashPolicy::Version1); });
auto *actionCopyHash2 = new QAction(UIThemeManager::instance()->getIcon(u"hash"_qs), tr("Info h&ash v2"), listMenu);
auto *actionCopyHash2 = new QAction(UIThemeManager::instance()->getIcon(u"hash"_qs, u"edit-copy"_qs), tr("Info h&ash v2"), listMenu);
connect(actionCopyHash2, &QAction::triggered, this, [this]() { copySelectedInfohashes(CopyInfohashPolicy::Version2); });
auto *actionSuperSeedingMode = new TriStateAction(tr("Super seeding mode"), listMenu);
connect(actionSuperSeedingMode, &QAction::triggered, this, &TransferListWidget::setSelectedTorrentsSuperSeeding);
@@ -1134,7 +1152,7 @@ void TransferListWidget::displayListMenu()
QStringList tags(BitTorrent::Session::instance()->tags().values());
std::sort(tags.begin(), tags.end(), Utils::Compare::NaturalLessThan<Qt::CaseInsensitive>());
QMenu *tagsMenu = listMenu->addMenu(UIThemeManager::instance()->getIcon(u"tags"_qs), tr("Ta&gs"));
QMenu *tagsMenu = listMenu->addMenu(UIThemeManager::instance()->getIcon(u"tags"_qs, u"view-categories"_qs), tr("Ta&gs"));
tagsMenu->addAction(UIThemeManager::instance()->getIcon(u"list-add"_qs), tr("&Add...", "Add / assign multiple tags...")
, this, &TransferListWidget::askAddTagsForSelection);

View File

@@ -140,7 +140,7 @@ namespace
std::unique_ptr<UIThemeSource> createUIThemeSource(const Path &themePath)
{
if (themePath.filename() == CONFIG_FILE_NAME)
return std::make_unique<FolderThemeSource>(themePath);
return std::make_unique<FolderThemeSource>(themePath.parentPath());
if ((themePath.hasExtension(u".qbtheme"_qs))
&& QResource::registerResource(themePath.data(), u"/uitheme"_qs))
@@ -167,7 +167,10 @@ void UIThemeManager::initInstance()
}
UIThemeManager::UIThemeManager()
: m_useCustomTheme(Preferences::instance()->useCustomUITheme())
: m_useCustomTheme {Preferences::instance()->useCustomUITheme()}
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
, m_useSystemIcons {Preferences::instance()->useSystemIcons()}
#endif
{
if (m_useCustomTheme)
{
@@ -196,14 +199,25 @@ void UIThemeManager::applyStyleSheet() const
qApp->setStyleSheet(QString::fromUtf8(m_themeSource->readStyleSheet()));
}
QIcon UIThemeManager::getIcon(const QString &iconId, const QString &fallback) const
QIcon UIThemeManager::getIcon(const QString &iconId, [[maybe_unused]] const QString &fallback) const
{
// Cache to avoid rescaling svg icons
const auto iter = m_iconCache.find(iconId);
if (iter != m_iconCache.end())
return *iter;
const QIcon icon {getIconPathFromResources(iconId, fallback).data()};
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
// Don't cache system icons because users might change them at run time
if (m_useSystemIcons)
{
auto icon = QIcon::fromTheme(iconId);
if (icon.name() != iconId)
icon = QIcon::fromTheme(fallback, QIcon(getIconPathFromResources(iconId).data()));
return icon;
}
#endif
const QIcon icon {getIconPathFromResources(iconId).data()};
m_iconCache[iconId] = icon;
return icon;
}
@@ -259,23 +273,33 @@ QIcon UIThemeManager::getSystrayIcon() const
Path UIThemeManager::getIconPath(const QString &iconId) const
{
return getIconPathFromResources(iconId, {});
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
if (m_useSystemIcons)
{
Path path = Utils::Fs::tempPath() / Path(iconId + u".png");
if (!path.exists())
{
const QIcon icon = QIcon::fromTheme(iconId);
if (!icon.isNull())
icon.pixmap(32).save(path.toString());
else
path = getIconPathFromResources(iconId);
}
return path;
}
#endif
return getIconPathFromResources(iconId);
}
Path UIThemeManager::getIconPathFromResources(const QString &iconId, const QString &fallback) const
Path UIThemeManager::getIconPathFromResources(const QString &iconId) const
{
if (m_useCustomTheme && m_themeSource)
{
const Path customIcon = m_themeSource->iconPath(iconId);
if (!customIcon.isEmpty())
return customIcon;
if (!fallback.isEmpty())
{
const Path fallbackIcon = m_themeSource->iconPath(fallback);
if (!fallbackIcon.isEmpty())
return fallbackIcon;
}
}
return findIcon(iconId, DEFAULT_ICONS_DIR);

View File

@@ -70,13 +70,16 @@ public:
private:
UIThemeManager(); // singleton class
Path getIconPathFromResources(const QString &iconId, const QString &fallback = {}) const;
Path getIconPathFromResources(const QString &iconId) const;
void loadColorsFromJSONConfig();
void applyPalette() const;
void applyStyleSheet() const;
static UIThemeManager *m_instance;
const bool m_useCustomTheme;
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
const bool m_useSystemIcons;
#endif
std::unique_ptr<UIThemeSource> m_themeSource;
QHash<QString, QColor> m_colors;
mutable QHash<QString, QIcon> m_iconCache;

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

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