mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-17 14:08:03 -06:00
Compare commits
141 Commits
release-5.
...
release-4.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8763f08da | ||
|
|
b8259969ac | ||
|
|
d3a57e3e01 | ||
|
|
fbd228b360 | ||
|
|
23176b1a56 | ||
|
|
46afeb0f32 | ||
|
|
114e2ee1ab | ||
|
|
b5af0f71b9 | ||
|
|
bd9c42e004 | ||
|
|
272ff11d65 | ||
|
|
04bd33e5b3 | ||
|
|
6a4c423b88 | ||
|
|
112ab86a6b | ||
|
|
fcd38a497e | ||
|
|
6e75866ed7 | ||
|
|
1f9dde0c37 | ||
|
|
8b13d8f222 | ||
|
|
8d654f9802 | ||
|
|
cfc73da312 | ||
|
|
9801cd0323 | ||
|
|
c01d7a34c1 | ||
|
|
af41a0eece | ||
|
|
c509f57b66 | ||
|
|
8de091f4dd | ||
|
|
e246745776 | ||
|
|
9d42657468 | ||
|
|
2b4fcda463 | ||
|
|
70a2d7bd58 | ||
|
|
a9c85ab321 | ||
|
|
e574bc1c57 | ||
|
|
fc90953f91 | ||
|
|
7016aa372b | ||
|
|
cbe9a27a92 | ||
|
|
97853f31f2 | ||
|
|
66ffb7328d | ||
|
|
9d9101186d | ||
|
|
621ec4e92f | ||
|
|
4b752cba4f | ||
|
|
38c0864bf2 | ||
|
|
c21c3d2300 | ||
|
|
3be5273246 | ||
|
|
37e348ed92 | ||
|
|
36364121ba | ||
|
|
df08bd331c | ||
|
|
abd1ab5539 | ||
|
|
632d33b266 | ||
|
|
35f7e1c896 | ||
|
|
792301dfe4 | ||
|
|
e31cf5ac23 | ||
|
|
0bfe6ff64b | ||
|
|
d40c7e8833 | ||
|
|
8e81d44b3c | ||
|
|
97a30218bc | ||
|
|
e9884b9513 | ||
|
|
a63269e3e1 | ||
|
|
d03e715708 | ||
|
|
927732f190 | ||
|
|
88c991880f | ||
|
|
29290fa109 | ||
|
|
0a8d604ef3 | ||
|
|
532c985b50 | ||
|
|
a32182f794 | ||
|
|
1aebcd3258 | ||
|
|
9f743aab86 | ||
|
|
ece839739e | ||
|
|
2204757eca | ||
|
|
bfda520ef4 | ||
|
|
af91f4ed51 | ||
|
|
41c3a8af01 | ||
|
|
cc7f8372a8 | ||
|
|
d20633f9cc | ||
|
|
961e05e9a8 | ||
|
|
eb98a04245 | ||
|
|
5dc1c10848 | ||
|
|
dcbff74dc0 | ||
|
|
5e29960da2 | ||
|
|
aa43fc8ff4 | ||
|
|
2517e688d9 | ||
|
|
40d94fd8e9 | ||
|
|
eb97e640cb | ||
|
|
2123c1c259 | ||
|
|
6cf1351a77 | ||
|
|
c924904308 | ||
|
|
904bcc14d5 | ||
|
|
c3abe4c2a6 | ||
|
|
7144454a1f | ||
|
|
daaa88fa0d | ||
|
|
0b7c8497f9 | ||
|
|
e3562be0d6 | ||
|
|
e0d0efcc20 | ||
|
|
fb22b58ce6 | ||
|
|
c78ac614f5 | ||
|
|
de15907ea7 | ||
|
|
a4289a517f | ||
|
|
967c3bb55d | ||
|
|
c57896df8f | ||
|
|
911f0d4039 | ||
|
|
e822d4fca7 | ||
|
|
0da132b69e | ||
|
|
691cb4fe2b | ||
|
|
97a053916b | ||
|
|
24bf8eef6d | ||
|
|
4314bbdf9c | ||
|
|
65611cd3dc | ||
|
|
6a4bb1356a | ||
|
|
06593e3678 | ||
|
|
18577d9cb0 | ||
|
|
701b84dc48 | ||
|
|
9a95237b85 | ||
|
|
a6a99fbd36 | ||
|
|
86671bee46 | ||
|
|
f1432a2e3d | ||
|
|
480e3f02ca | ||
|
|
6b05c716a8 | ||
|
|
c697829b1b | ||
|
|
9a2ec6912b | ||
|
|
7601163d32 | ||
|
|
8e2bda2b7a | ||
|
|
1761f6c58e | ||
|
|
419cdde4e1 | ||
|
|
6ec46a90d1 | ||
|
|
f4051034d7 | ||
|
|
1a8ba00f2c | ||
|
|
de4c1c9265 | ||
|
|
bff9189e52 | ||
|
|
076b3628b1 | ||
|
|
75ccce705e | ||
|
|
964bf31775 | ||
|
|
507ced2fa2 | ||
|
|
e62f9ef56a | ||
|
|
a5a242377b | ||
|
|
0758109d15 | ||
|
|
3970d91d19 | ||
|
|
4e98b7f0cf | ||
|
|
27a69d9cca | ||
|
|
d884ec1731 | ||
|
|
62b2959cb4 | ||
|
|
2bdc91c53f | ||
|
|
d829df99aa | ||
|
|
4f2ac34440 | ||
|
|
94e9e9fdb2 |
@@ -37,8 +37,6 @@ install:
|
||||
RMDIR /S /Q "%CACHE_DIR%" & MKDIR "%CACHE_DIR%" &&
|
||||
appveyor DownloadFile "%QBT_LIB_URL%" -FileName "c:\qbt_lib.7z" && 7z x "c:\qbt_lib.7z" -o"%CACHE_DIR%" > nul &&
|
||||
COPY "c:\version_new" "%CACHE_DIR%\version")
|
||||
# Qt stay compressed in cache
|
||||
- 7z x "%CACHE_DIR%\qt5_64.7z" -o"c:\qbt" > nul
|
||||
|
||||
before_build:
|
||||
# setup env
|
||||
@@ -47,6 +45,7 @@ before_build:
|
||||
# setup project
|
||||
- COPY /Y "%CACHE_DIR%\conf.pri" "%REPO_DIR%"
|
||||
# workarounds
|
||||
- MKDIR "c:\qbt"
|
||||
- MKLINK /J "c:\qbt\base" "%CACHE_DIR%\base"
|
||||
|
||||
build_script:
|
||||
@@ -69,8 +68,11 @@ after_build:
|
||||
- COPY src\release\qbittorrent.exe upload
|
||||
- COPY src\release\qbittorrent.pdb upload
|
||||
- COPY "%CACHE_DIR%\base\bin\libcrypto-1_1-x64.dll" upload
|
||||
- COPY "%CACHE_DIR%\base\bin\libcrypto-1_1-x64.pdb" upload
|
||||
- COPY "%CACHE_DIR%\base\bin\libssl-1_1-x64.dll" upload
|
||||
- COPY "%CACHE_DIR%\base\lib\torrent-rasterbar.dll" upload
|
||||
- COPY "%CACHE_DIR%\base\bin\libssl-1_1-x64.pdb" upload
|
||||
- COPY "%CACHE_DIR%\base\bin\torrent-rasterbar.dll" upload
|
||||
- COPY "%CACHE_DIR%\base\bin\torrent-rasterbar.pdb" upload
|
||||
- COPY "%CACHE_DIR%\base\lib\zlib1.dll" upload
|
||||
- COPY C:\Qt\5.15.2\msvc2019_64\bin\Qt5Core.dll upload
|
||||
- COPY C:\Qt\5.15.2\msvc2019_64\bin\Qt5Gui.dll upload
|
||||
|
||||
2
.github/workflows/ci_file_health.yaml
vendored
2
.github/workflows/ci_file_health.yaml
vendored
@@ -2,6 +2,8 @@ name: CI - File health
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.head_ref != '' }}
|
||||
|
||||
27
.github/workflows/ci_macos.yaml
vendored
27
.github/workflows/ci_macos.yaml
vendored
@@ -2,6 +2,9 @@ name: CI - macOS
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.head_ref != '' }}
|
||||
@@ -31,6 +34,9 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
export \
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 \
|
||||
HOMEBREW_NO_INSTALL_CLEANUP=1
|
||||
brew update > /dev/null
|
||||
brew install \
|
||||
cmake ninja \
|
||||
@@ -46,7 +52,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 }}"
|
||||
|
||||
@@ -68,6 +74,7 @@ jobs:
|
||||
cmake \
|
||||
-B build \
|
||||
-G "Ninja" \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_STANDARD=17 \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
@@ -80,11 +87,12 @@ jobs:
|
||||
- name: Build qBittorrent (Qt5)
|
||||
if: ${{ startsWith(matrix.qt_version, 5) }}
|
||||
run: |
|
||||
CXXFLAGS="$CXXFLAGS -Werror -Wno-error=deprecated-declarations" \
|
||||
LDFLAGS="$LDFLAGS -gz" \
|
||||
cmake \
|
||||
-B build \
|
||||
-G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_FLAGS="-Werror -Wno-error=deprecated-declarations" \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}" \
|
||||
@@ -98,11 +106,12 @@ jobs:
|
||||
- name: Build qBittorrent (Qt6)
|
||||
if: ${{ startsWith(matrix.qt_version, 6) }}
|
||||
run: |
|
||||
CXXFLAGS="$CXXFLAGS -Wno-gnu-zero-variadic-macro-arguments -Werror -Wno-error=deprecated-declarations" \
|
||||
LDFLAGS="$LDFLAGS -gz" \
|
||||
cmake \
|
||||
-B build \
|
||||
-G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_FLAGS="-Wno-gnu-zero-variadic-macro-arguments -Werror -Wno-error=deprecated-declarations" \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}" \
|
||||
@@ -116,7 +125,17 @@ jobs:
|
||||
|
||||
- name: Prepare build artifacts
|
||||
run: |
|
||||
# create .dmg
|
||||
appName="qbittorrent"
|
||||
if [ "${{ matrix.qbt_gui }}" = "GUI=OFF" ]; then
|
||||
appName="qbittorrent-nox"
|
||||
fi
|
||||
pushd build
|
||||
macdeployqt "$appName.app" -dmg -no-strip
|
||||
popd
|
||||
# prepare upload folder
|
||||
mkdir upload
|
||||
cp "build/$appName.dmg" upload
|
||||
mkdir upload/cmake
|
||||
cp build/compile_commands.json upload/cmake
|
||||
mkdir upload/cmake/libtorrent
|
||||
@@ -125,5 +144,5 @@ jobs:
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: build-info_macOS_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
|
||||
name: qBittorrent-CI_macOS_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
|
||||
path: upload
|
||||
|
||||
40
.github/workflows/ci_ubuntu.yaml
vendored
40
.github/workflows/ci_ubuntu.yaml
vendored
@@ -2,6 +2,9 @@ name: CI - Ubuntu
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.head_ref != '' }}
|
||||
@@ -30,7 +33,7 @@ jobs:
|
||||
sudo apt update
|
||||
sudo apt install \
|
||||
build-essential cmake ninja-build pkg-config \
|
||||
libboost-dev libssl-dev zlib1g-dev
|
||||
libboost-dev libssl-dev libxkbcommon-x11-dev zlib1g-dev
|
||||
|
||||
- name: Setup ccache
|
||||
uses: Chocobo1/setup-ccache-action@v1
|
||||
@@ -65,11 +68,12 @@ jobs:
|
||||
- name: Build qBittorrent (Qt5)
|
||||
if: ${{ startsWith(matrix.qt_version, 5) }}
|
||||
run: |
|
||||
CXXFLAGS="$CXXFLAGS -Werror -Wno-error=deprecated-declarations" \
|
||||
LDFLAGS="$LDFLAGS -gz" \
|
||||
cmake \
|
||||
-B build \
|
||||
-G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_FLAGS="-Werror -Wno-error=deprecated-declarations" \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DCMAKE_INSTALL_PREFIX="/usr" \
|
||||
-DTESTING=ON \
|
||||
@@ -83,11 +87,12 @@ jobs:
|
||||
- name: Build qBittorrent (Qt6)
|
||||
if: ${{ startsWith(matrix.qt_version, 6) }}
|
||||
run: |
|
||||
CXXFLAGS="$CXXFLAGS -Werror" \
|
||||
LDFLAGS="$LDFLAGS -gz" \
|
||||
cmake \
|
||||
-B build \
|
||||
-G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_FLAGS="-Werror" \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DCMAKE_INSTALL_PREFIX="/usr" \
|
||||
-DQT6=ON \
|
||||
@@ -107,8 +112,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
|
||||
|
||||
2
.github/workflows/ci_webui.yaml
vendored
2
.github/workflows/ci_webui.yaml
vendored
@@ -2,6 +2,8 @@ name: CI - WebUI
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.head_ref != '' }}
|
||||
|
||||
7
.github/workflows/ci_windows.yaml
vendored
7
.github/workflows/ci_windows.yaml
vendored
@@ -2,6 +2,9 @@ name: CI - Windows
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.head_ref != '' }}
|
||||
@@ -67,7 +70,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 }}/.."
|
||||
@@ -149,6 +152,8 @@ jobs:
|
||||
copy "${{ env.Qt6_DIR }}/plugins/sqldrivers/qsqlite.dll" upload/plugins/sqldrivers
|
||||
mkdir upload/plugins/styles
|
||||
copy "${{ env.Qt6_DIR }}/plugins/styles/qwindowsvistastyle.dll" upload/plugins/styles
|
||||
mkdir upload/plugins/tls
|
||||
copy "${{ env.Qt6_DIR }}/plugins/tls/qschannelbackend.dll" upload/plugins/tls
|
||||
# cmake additionals
|
||||
mkdir upload/cmake
|
||||
copy build/compile_commands.json upload/cmake
|
||||
|
||||
2
.github/workflows/coverity-scan.yml
vendored
2
.github/workflows/coverity-scan.yml
vendored
@@ -5,6 +5,8 @@ on:
|
||||
- cron: '0 0 1 * *' # Monthly (1st day of month at midnight)
|
||||
workflow_dispatch: # Mainly for testing. Don't forget the Coverity usage limits.
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
coverity_scan:
|
||||
name: Scan
|
||||
|
||||
11
.github/workflows/helper/appimage/export_vars.sh
vendored
Normal file
11
.github/workflows/helper/appimage/export_vars.sh
vendored
Normal 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}"
|
||||
6
.github/workflows/helper/appimage/org.qbittorrent.qBittorrent.desktop
vendored
Normal file
6
.github/workflows/helper/appimage/org.qbittorrent.qBittorrent.desktop
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[Desktop Entry]
|
||||
Name=qBittorrent
|
||||
Exec=qbittorrent-nox %U
|
||||
Icon=qbittorrent
|
||||
Type=Application
|
||||
Categories=Network
|
||||
3
.github/workflows/stale_bot.yaml
vendored
3
.github/workflows/stale_bot.yaml
vendored
@@ -4,6 +4,9 @@ on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
35
.tx/config
35
.tx/config
@@ -1,27 +1,24 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[qbittorrent.qbittorrent_master]
|
||||
file_filter = src/lang/qbittorrent_<lang>.ts
|
||||
lang_map = pt: pt_PT
|
||||
source_file = src/lang/qbittorrent_en.ts
|
||||
source_lang = en
|
||||
type = QT
|
||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_v45x]
|
||||
file_filter = src/lang/qbittorrent_<lang>.ts
|
||||
source_file = src/lang/qbittorrent_en.ts
|
||||
source_lang = en
|
||||
type = QT
|
||||
minimum_perc = 23
|
||||
mode = developer
|
||||
lang_map = pt: pt_PT, zh: zh_CN
|
||||
|
||||
[qbittorrent.qbittorrentdesktop_master]
|
||||
source_file = dist/unix/org.qbittorrent.qBittorrent.desktop
|
||||
source_lang = en
|
||||
type = DESKTOP
|
||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_webui_v45x]
|
||||
file_filter = src/webui/www/translations/webui_<lang>.ts
|
||||
source_file = src/webui/www/translations/webui_en.ts
|
||||
source_lang = en
|
||||
type = QT
|
||||
minimum_perc = 23
|
||||
mode = developer
|
||||
lang_map = pt: pt_PT, zh: zh_CN
|
||||
|
||||
[qbittorrent.qbittorrent_webui]
|
||||
file_filter = src/webui/www/translations/webui_<lang>.ts
|
||||
lang_map = pt: pt_PT
|
||||
source_file = src/webui/www/translations/webui_en.ts
|
||||
source_lang = en
|
||||
type = QT
|
||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrentdesktop_master]
|
||||
source_file = dist/unix/org.qbittorrent.qBittorrent.desktop
|
||||
source_lang = en
|
||||
type = DESKTOP
|
||||
minimum_perc = 23
|
||||
mode = developer
|
||||
|
||||
143
Changelog
143
Changelog
@@ -1,4 +1,145 @@
|
||||
Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.0
|
||||
Sun Jun 18 2023 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.4
|
||||
- BUGFIX: Allow to disable confirmation of Pause/Resume All (glassez)
|
||||
- BUGFIX: Sync flag icons with upstream (Priit Uring)
|
||||
- WEBUI: Fix category save path (Raymond Ha)
|
||||
|
||||
Sun May 28 2023 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.3
|
||||
- BUGFIX: Correctly check if database needs to be updated (glassez)
|
||||
- BUGFIX: Prevent incorrect log message about torrent content deletion (glassez)
|
||||
- BUGFIX: Improve finished torrent handling (glassez)
|
||||
- BUGFIX: Correctly initialize group box children as disabled in Preferences (thalieht)
|
||||
- BUGFIX: Don't miss saving "download path" in SQLite storage (glassez)
|
||||
- BUGFIX: Improve logging of running external program (glassez)
|
||||
- WEBUI: Disable UPnP for web UI by default (glassez)
|
||||
- WEBUI: Use workaround for IOS file picker (DivineHawk)
|
||||
- WEBUI: Work around Chrome download limit (Chocobo1)
|
||||
- WEBUI: Improve 'exporting torrent' behavior (Chocobo1)
|
||||
- WINDOWS: NSIS: Add Slovak translation (Christian Danížek)
|
||||
|
||||
Tue Feb 28 2023 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.2
|
||||
- BUGFIX: Don't unexpectedly activate queued torrents when prefetching metadata for added magnets (glassez)
|
||||
- BUGFIX: Update the cached torrent state once recheck is started (glassez)
|
||||
- BUGFIX: Be more likely to allow the system to use power saving modes (glassez)
|
||||
- WEBUI: Migrate away from unsafe function (Chocobo1)
|
||||
- WEBUI: Blacklist bad ciphers for TLS in the server (sledgehammer999)
|
||||
- WEBUI: Allow only TLS 1.2+ in the server (sledgehammer999)
|
||||
- WEBUI: Allow to set read-only directory as torrent location (glassez)
|
||||
- WEBUI: Reject requests that contain backslash in path (glassez)
|
||||
- RSS: Prevent RSS folder from being moved into itself (glassez)
|
||||
- WINDOWS: NSIS: Update Turkish, Uzbek translation (Burak Yavuz, shitcod3r)
|
||||
|
||||
Sun Feb 12 2023 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.1
|
||||
- FEATURE: Re-allow to use icons from system theme (glassez)
|
||||
- BUGFIX: Fix Speed limit icon size (Nowshed H. Imran)
|
||||
- BUGFIX: Revise and fix some text colors (Chocobo1, Nowshed H. Imran)
|
||||
- BUGFIX: Correctly load folder based UI theme (glassez)
|
||||
- BUGFIX: Fix crash due to invalid encoding of tracker URLs (glassez)
|
||||
- BUGFIX: Don't drop !qB extension when renaming incomplete file (glassez)
|
||||
- BUGFIX: Correctly count the number of torrents in subcategories (glassez)
|
||||
- BUGFIX: Use "additional trackers" when metadata retrieving (glassez)
|
||||
- BUGFIX: Apply correct tab order to Category options dialog (glassez)
|
||||
- BUGFIX: Add all torrents passed via the command line (glassez)
|
||||
- BUGFIX: Fix startup performance on Qt5 (glassez)
|
||||
- BUGFIX: Automatic move will now overwrite existing files (aka previous behavior) (glassez)
|
||||
- BUGFIX: Some fixes for loading Chinese locales (sledgehammer999)
|
||||
- BUGFIX: New Pause icon color for toolbar/menu (Nowshed H. Imran, sledgehammer999)
|
||||
- BUGFIX: Adjust env variable for PDB discovery (sledgehammer999)
|
||||
- WEBUI: Fix missing "queued" icon (thalieht)
|
||||
- WEBUI: Return paths using platform-independent separator format (glassez)
|
||||
- WEBUI: Change order of accepted types of file input (Jason Carr)
|
||||
- WEBUI: Add missing icons (brvphoenix)
|
||||
- WEBUI: Add "Resume data storage type" option (thalieht)
|
||||
- WEBUI: Make rename file dialog resizable (Torsten Schwarz)
|
||||
- WEBUI: Prevent incorrect line breaking (David Xuang)
|
||||
- WEBUI: Improve hotkeys (Fidel Selva)
|
||||
- WEBUI: Remove suggestions while searching for torrents (Midhun V Nadh)
|
||||
- WEBUI: Expose "IS PRIVATE" flag (sotiris-bos)
|
||||
- WEBUI: Return name/hash/infohash_v1/infohash_v2 torrent properties (qbittorrentfan)
|
||||
- WINDOWS: Correctly detect drive letter in path (glassez)
|
||||
- WINDOWS: NSIS: Update Swedish, Lithuanian translations (Jonatan, Deividas)
|
||||
- LINUX: Fix tray icon issues (glassez)
|
||||
|
||||
Sat Nov 26 2022 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.0
|
||||
- FEATURE: Add `Auto resize columns` functionality (Chocobo1)
|
||||
- FEATURE: 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
20
configure
vendored
@@ -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.4.
|
||||
#
|
||||
# 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.4'
|
||||
PACKAGE_STRING='qbittorrent v4.5.4'
|
||||
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.4 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.4:";;
|
||||
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.4
|
||||
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.4, 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.4'
|
||||
|
||||
|
||||
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.4, 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.4
|
||||
configured by $0, generated by GNU Autoconf 2.71,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
AC_INIT([qbittorrent], [v4.5.0beta1], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
|
||||
AC_INIT([qbittorrent], [v4.5.4], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
|
||||
AC_CONFIG_AUX_DIR([build-aux])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
: ${CFLAGS=""}
|
||||
|
||||
8
dist/docker/.env
vendored
8
dist/docker/.env
vendored
@@ -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
|
||||
62
dist/docker/Dockerfile
vendored
62
dist/docker/Dockerfile
vendored
@@ -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
101
dist/docker/Readme.md
vendored
@@ -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
|
||||
```
|
||||
25
dist/docker/docker-compose.yml
vendored
25
dist/docker/docker-compose.yml
vendored
@@ -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
|
||||
35
dist/docker/entrypoint.sh
vendored
35
dist/docker/entrypoint.sh
vendored
@@ -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
2
dist/mac/Info.plist
vendored
@@ -55,7 +55,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.5.0</string>
|
||||
<string>4.5.4</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
||||
@@ -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.4" date="2023-06-18"/>
|
||||
</releases>
|
||||
</component>
|
||||
|
||||
10
dist/unix/org.qbittorrent.qBittorrent.desktop
vendored
10
dist/unix/org.qbittorrent.qBittorrent.desktop
vendored
@@ -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 კლიენტი
|
||||
@@ -160,7 +160,7 @@ Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్
|
||||
GenericName[te]=క్యు బిట్ టొరెంట్ క్లయింట్
|
||||
Name[te]=qBittorrent
|
||||
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ผ่าน BitTorrent
|
||||
GenericName[th]=ไคลเอนต์ BitTorrent
|
||||
GenericName[th]=โปรแกรมบิททอเร้นท์
|
||||
Name[th]=qBittorrent
|
||||
Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın
|
||||
GenericName[tr]=BitTorrent istemcisi
|
||||
@@ -178,7 +178,7 @@ Comment[zh_HK]=經由BitTorrent下載並分享檔案
|
||||
GenericName[zh_HK]=BitTorrent用戶端
|
||||
Name[zh_HK]=qBittorrent
|
||||
Comment[zh_TW]=經由 BitTorrent 下載並分享檔案
|
||||
GenericName[zh_TW]=BitTorrent 客戶端
|
||||
GenericName[zh_TW]=BitTorrent 用戶端
|
||||
Name[zh_TW]=qBittorrent
|
||||
Comment[eo]=Elŝutu kaj kunhavigu dosierojn per BitTorrent
|
||||
GenericName[eo]=BitTorrent-kliento
|
||||
@@ -208,7 +208,7 @@ Name[ltg]=qBittorrent
|
||||
Comment[hi_IN]=BitTorrent द्वारा फाइल डाउनलोड व सहभाजन
|
||||
GenericName[hi_IN]=Bittorrent साधन
|
||||
Name[hi_IN]=qBittorrent
|
||||
Comment[az@latin]=Faylları BitTorrent vasitəsilə göndərin və paylaşın
|
||||
Comment[az@latin]=Faylları BitTorrent vasitəsilə endirin və paylaşın
|
||||
GenericName[az@latin]=BitTorrent client
|
||||
Name[az@latin]=qBittorrent
|
||||
Comment[lv_LV]=Lejupielādēt un koplietot failus ar BitTorrent
|
||||
|
||||
2
dist/windows/config.nsi
vendored
2
dist/windows/config.nsi
vendored
@@ -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.4"
|
||||
|
||||
; Option that controls the installer's window name
|
||||
; If set, its value will be used like this:
|
||||
|
||||
@@ -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:"
|
||||
|
||||
56
dist/windows/installer-translations/slovak.nsi
vendored
56
dist/windows/installer-translations/slovak.nsi
vendored
@@ -1,62 +1,62 @@
|
||||
;Installer strings
|
||||
|
||||
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_SLOVAK} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_SLOVAK} "qBittorrent (požadované)"
|
||||
;LangString inst_dekstop ${LANG_ENGLISH} "Create Desktop Shortcut"
|
||||
LangString inst_dekstop ${LANG_SLOVAK} "Create Desktop Shortcut"
|
||||
LangString inst_dekstop ${LANG_SLOVAK} "Vytvoriť odkaz na pracovnej ploche"
|
||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_SLOVAK} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_SLOVAK} "Vytvoriť odkaz v štart menu"
|
||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_SLOVAK} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_SLOVAK} "Sputiť qBittorrent pri štarte Windowsu"
|
||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_SLOVAK} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_SLOVAK} "Otvárať .torrent súbory v qBittorrent"
|
||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_SLOVAK} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_SLOVAK} "Otvárať magnet odkazy v qBittorrent"
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_SLOVAK} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_SLOVAK} "Pridať pravidlo do Windows Firewall"
|
||||
;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_SLOVAK} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_SLOVAK} "Vypnúť limit dĺžky cesty Windowsu (260 znaková MAX_PATH limitácia, vyžaduje Windows 10 1607 alebo novšie)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_SLOVAK} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_SLOVAK} "Pridáva sa pravidlo do Windows Firewall"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_SLOVAK} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_SLOVAK} "qBittorrent je spustený. Zatvorte prosím aplikáciu pred inštaláciou."
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_SLOVAK} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_SLOVAK} "Aktuálna verzia bude odinštalovaná. Užívateľské nastavenia a torrenty sa zachovajú."
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_SLOVAK} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_SLOVAK} "Odinštalácia predchádzajúcej verzie."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_SLOVAK} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_SLOVAK} "Spustiť qBittorrent."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_SLOVAK} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_SLOVAK} "Táto inštalácia funguje iba na 64-bitových verziách Windowsu."
|
||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_SLOVAK} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_SLOVAK} "Táto qBittorrent verzia vyžaduje aspoň Windows 7."
|
||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 1809."
|
||||
LangString inst_requires_win10 ${LANG_SLOVAK} "This installer requires at least Windows 10 1809."
|
||||
LangString inst_requires_win10 ${LANG_SLOVAK} "Tento inštalátor vyžaduje aspoň Windows 10 1809."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_SLOVAK} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_SLOVAK} "Odinštalovať qBittorrent"
|
||||
|
||||
;------------------------------------
|
||||
;Uninstaller strings
|
||||
|
||||
;LangString remove_files ${LANG_ENGLISH} "Remove files"
|
||||
LangString remove_files ${LANG_SLOVAK} "Remove files"
|
||||
LangString remove_files ${LANG_SLOVAK} "Odstrániť súbory"
|
||||
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_SLOVAK} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_SLOVAK} "Odstrániť odkazy"
|
||||
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
||||
LangString remove_associations ${LANG_SLOVAK} "Remove file associations"
|
||||
LangString remove_associations ${LANG_SLOVAK} "Odstrániť asociácie súborov"
|
||||
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_SLOVAK} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_SLOVAK} "Odstrániť kľúče registrov"
|
||||
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_SLOVAK} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_SLOVAK} "Odstrániť konfiguračné súbory"
|
||||
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_SLOVAK} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_SLOVAK} "Odstrániť pravidlo z Windows Firewall"
|
||||
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_SLOVAK} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_SLOVAK} "Odstraňuje sa pravidlo z Windows Firewall"
|
||||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_SLOVAK} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_SLOVAK} "Odstrániť torrenty a dáta z vyrovnávacej pamäti"
|
||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_SLOVAK} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_SLOVAK} "qBittorrent je spustený. Zatvorte prosím aplikáciu pred odinštaláciou."
|
||||
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_SLOVAK} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_SLOVAK} "Neodstraňuje sa .torrent asociácia. Asociované s:"
|
||||
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_SLOVAK} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_SLOVAK} "Neodstraňuje sa magnet asociácia. Asociované s:"
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ LangString inst_requires_64bit ${LANG_TURKISH} "Bu yükleyici sadece 64-bit Wind
|
||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_TURKISH} "Bu qBittorrent sürümü en az Windows 7 gerektirir."
|
||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 1809."
|
||||
LangString inst_requires_win10 ${LANG_TURKISH} "This installer requires at least Windows 10 1809."
|
||||
LangString inst_requires_win10 ${LANG_TURKISH} "Bu yükleyici en az Windows 10 1809 gerektirir."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_TURKISH} "qBittorrent'i kaldır"
|
||||
|
||||
|
||||
56
dist/windows/installer-translations/uzbek.nsi
vendored
56
dist/windows/installer-translations/uzbek.nsi
vendored
@@ -1,62 +1,62 @@
|
||||
;Installer strings
|
||||
|
||||
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_UZBEK} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_UZBEK} "qBittorrent (talab qilinadi)"
|
||||
;LangString inst_dekstop ${LANG_ENGLISH} "Create Desktop Shortcut"
|
||||
LangString inst_dekstop ${LANG_UZBEK} "Create Desktop Shortcut"
|
||||
LangString inst_dekstop ${LANG_UZBEK} "Ish Stolida Yorliq Yaratilsin"
|
||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_UZBEK} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_UZBEK} "Boshlash Menyusida Yorliq Yaratilsin"
|
||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_UZBEK} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_UZBEK} "qBittorrent Windows bilan birga ishga tushirilsin"
|
||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_UZBEK} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_UZBEK} ".torrent fayllar qBittorrent bilan ochilsin"
|
||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_UZBEK} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_UZBEK} "Magnit havolalar qBittorrent bilan ochilsin"
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_UZBEK} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_UZBEK} "Windows Xavfsizlik Devori qoidasi qoʻshilsin"
|
||||
;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_UZBEK} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_UZBEK} "Windows yoʻl uzunligi cheklovi olib tashlansin (260 belgi MAX_PATH cheklovi, Windows 10 1607 va yuqorisi talab qilinadi)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_UZBEK} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_UZBEK} "Windows Xavfsizlik Devori qoidasini qoʻshish"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_UZBEK} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_UZBEK} "qBittorrent ishga tushgan. Iltimos, oʻrnatishdan oldin dasturni yoping."
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_UZBEK} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_UZBEK} "Hozirgi versiya oʻchiriladi. Foydalanuvchi sozlamalari va torrentlar oʻzgarishsiz qoladi."
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_UZBEK} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_UZBEK} "Oldingi versiyani oʻchirish."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_UZBEK} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_UZBEK} "qBittorrent ishga tushirilsin."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_UZBEK} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_UZBEK} "Bu oʻrnatuvchi faqat Windows 64-bit versiyalarda ishlaydi."
|
||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_UZBEK} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_UZBEK} "qBittorrent bu versiyasi kamida Windows 7 talab qiladi."
|
||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 1809."
|
||||
LangString inst_requires_win10 ${LANG_UZBEK} "This installer requires at least Windows 10 1809."
|
||||
LangString inst_requires_win10 ${LANG_UZBEK} "Bu oʻrnatuvchi kamida Windows 10 1809 talab qiladi."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_UZBEK} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_UZBEK} "qBittorrent oʻchirilsin"
|
||||
|
||||
;------------------------------------
|
||||
;Uninstaller strings
|
||||
|
||||
;LangString remove_files ${LANG_ENGLISH} "Remove files"
|
||||
LangString remove_files ${LANG_UZBEK} "Remove files"
|
||||
LangString remove_files ${LANG_UZBEK} "Fayllar oʻchirilsin"
|
||||
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_UZBEK} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_UZBEK} "Yorliqlar oʻchirilsin"
|
||||
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
||||
LangString remove_associations ${LANG_UZBEK} "Remove file associations"
|
||||
LangString remove_associations ${LANG_UZBEK} "Fayl birlashmalari oʻchirilsin"
|
||||
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_UZBEK} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_UZBEK} "Reyester kalitlari oʻchirilsin"
|
||||
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_UZBEK} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_UZBEK} "Sozlama fayllari oʻchirilsin"
|
||||
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_UZBEK} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_UZBEK} "Windows Xavfsizlik Devori qoidasi oʻchirilsin"
|
||||
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_UZBEK} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_UZBEK} "Windows Xavfsizlik Devori qoidasini oʻchirish"
|
||||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_UZBEK} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_UZBEK} "Torrentlar va keshlangan maʼlumotlar oʻchirilsin"
|
||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_UZBEK} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_UZBEK} "qBittorrent ishga tushgan. Iltimos, oʻchirishdan oldin dasturni yoping."
|
||||
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_UZBEK} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_UZBEK} ".torrent birlashmasi oʻchirilmadi. U quyidagi bilan birlashgan:"
|
||||
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_UZBEK} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_UZBEK} "Magnit birlashmasi oʻchirilmadi. U quyidagi bilan birlashgan:"
|
||||
|
||||
2
dist/windows/installer.nsi
vendored
2
dist/windows/installer.nsi
vendored
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
@@ -440,6 +453,7 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
|
||||
};
|
||||
|
||||
const QString logMsg = tr("Running external program. Torrent: \"%1\". Command: `%2`");
|
||||
const QString logMsgError = tr("Failed to run external program. Torrent: \"%1\". Command: `%2`");
|
||||
|
||||
// The processing sequenece is different for Windows and other OS, this is intentional
|
||||
#if defined(Q_OS_WIN)
|
||||
@@ -459,8 +473,6 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
|
||||
for (int i = 1; i < argCount; ++i)
|
||||
argList += QString::fromWCharArray(args[i]);
|
||||
|
||||
LogMsg(logMsg.arg(torrent->name(), program));
|
||||
|
||||
QProcess proc;
|
||||
proc.setProgram(QString::fromWCharArray(args[0]));
|
||||
proc.setArguments(argList);
|
||||
@@ -485,7 +497,11 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
|
||||
args->startupInfo->hStdOutput = nullptr;
|
||||
args->startupInfo->hStdError = nullptr;
|
||||
});
|
||||
proc.startDetached();
|
||||
|
||||
if (proc.startDetached())
|
||||
LogMsg(logMsg.arg(torrent->name(), program));
|
||||
else
|
||||
LogMsg(logMsgError.arg(torrent->name(), program));
|
||||
#else // Q_OS_WIN
|
||||
QStringList args = Utils::String::splitCommand(programTemplate);
|
||||
|
||||
@@ -501,11 +517,21 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
|
||||
arg = replaceVariables(arg);
|
||||
}
|
||||
|
||||
// show intended command in log
|
||||
LogMsg(logMsg.arg(torrent->name(), replaceVariables(programTemplate)));
|
||||
|
||||
const QString command = args.takeFirst();
|
||||
QProcess::startDetached(command, args);
|
||||
QProcess proc;
|
||||
proc.setProgram(command);
|
||||
proc.setArguments(args);
|
||||
|
||||
if (proc.startDetached())
|
||||
{
|
||||
// show intended command in log
|
||||
LogMsg(logMsg.arg(torrent->name(), replaceVariables(programTemplate)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// show intended command in log
|
||||
LogMsg(logMsgError.arg(torrent->name(), replaceVariables(programTemplate)));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -658,8 +684,7 @@ Application::AddTorrentParams Application::parseParams(const QStringList ¶ms
|
||||
continue;
|
||||
}
|
||||
|
||||
parsedParams.torrentSource = param;
|
||||
break;
|
||||
parsedParams.torrentSources.append(param);
|
||||
}
|
||||
|
||||
return parsedParams;
|
||||
@@ -675,10 +700,16 @@ void Application::processParams(const AddTorrentParams ¶ms)
|
||||
// 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 ¶ms)
|
||||
@@ -706,7 +737,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;
|
||||
@@ -714,7 +745,7 @@ try
|
||||
actionExit->setIcon(UIThemeManager::instance()->getIcon(u"application-exit"_qs));
|
||||
actionExit->setMenuRole(QAction::QuitRole);
|
||||
actionExit->setShortcut(Qt::CTRL | Qt::Key_Q);
|
||||
connect(actionExit, &QAction::triggered, this, [this]()
|
||||
connect(actionExit, &QAction::triggered, this, []
|
||||
{
|
||||
QApplication::exit();
|
||||
});
|
||||
@@ -788,12 +819,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 +910,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 +1229,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
|
||||
|
||||
@@ -148,7 +148,7 @@ private slots:
|
||||
private:
|
||||
struct AddTorrentParams
|
||||
{
|
||||
QString torrentSource;
|
||||
QStringList torrentSources;
|
||||
BitTorrent::AddTorrentParams torrentParams;
|
||||
std::optional<bool> skipTorrentDialog;
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
@@ -287,7 +283,7 @@ void showSplashScreen()
|
||||
painter.drawText(224 - painter.fontMetrics().horizontalAdvance(version), 270, version);
|
||||
QSplashScreen *splash = new QSplashScreen(splashImg);
|
||||
splash->show();
|
||||
QTimer::singleShot(1500ms, splash, &QObject::deleteLater);
|
||||
QTimer::singleShot(1500ms, Qt::CoarseTimer, splash, &QObject::deleteLater);
|
||||
qApp->processEvents();
|
||||
}
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,5 +35,5 @@ enum class DefaultPreferencesMode
|
||||
};
|
||||
|
||||
void handleChangedDefaults(DefaultPreferencesMode mode);
|
||||
bool upgrade(bool ask = true);
|
||||
bool upgrade();
|
||||
void setCurrentMigrationVersion();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -273,13 +272,13 @@ BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject
|
||||
else
|
||||
{
|
||||
const int dbVersion = (!db.record(DB_TABLE_TORRENTS).contains(DB_COLUMN_DOWNLOAD_PATH.name) ? 1 : currentDBVersion());
|
||||
if (dbVersion != DB_VERSION)
|
||||
if (dbVersion < DB_VERSION)
|
||||
updateDB(dbVersion);
|
||||
}
|
||||
|
||||
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
|
||||
@@ -535,18 +531,28 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
|
||||
{
|
||||
if (fromVersion == 1)
|
||||
{
|
||||
const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_qs
|
||||
.arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, "TEXT"));
|
||||
if (!query.exec(alterTableTorrentsQuery))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
const auto testQuery = u"SELECT COUNT(%1) FROM %2;"_qs
|
||||
.arg(quoted(DB_COLUMN_DOWNLOAD_PATH.name), quoted(DB_TABLE_TORRENTS));
|
||||
if (!query.exec(testQuery))
|
||||
{
|
||||
const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_qs
|
||||
.arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, "TEXT"));
|
||||
if (!query.exec(alterTableTorrentsQuery))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
}
|
||||
}
|
||||
|
||||
if (fromVersion <= 2)
|
||||
{
|
||||
const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_qs
|
||||
.arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_STOP_CONDITION, "TEXT NOT NULL DEFAULT `None`"));
|
||||
if (!query.exec(alterTableTorrentsQuery))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
const auto testQuery = u"SELECT COUNT(%1) FROM %2;"_qs
|
||||
.arg(quoted(DB_COLUMN_STOP_CONDITION.name), quoted(DB_TABLE_TORRENTS));
|
||||
if (!query.exec(testQuery))
|
||||
{
|
||||
const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_qs
|
||||
.arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_STOP_CONDITION, "TEXT NOT NULL DEFAULT `None`"));
|
||||
if (!query.exec(alterTableTorrentsQuery))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
}
|
||||
}
|
||||
|
||||
const QString updateMetaVersionQuery = makeUpdateStatement(DB_TABLE_META, {DB_COLUMN_NAME, DB_COLUMN_VALUE});
|
||||
@@ -621,6 +627,7 @@ void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const L
|
||||
DB_COLUMN_CATEGORY,
|
||||
DB_COLUMN_TAGS,
|
||||
DB_COLUMN_TARGET_SAVE_PATH,
|
||||
DB_COLUMN_DOWNLOAD_PATH,
|
||||
DB_COLUMN_CONTENT_LAYOUT,
|
||||
DB_COLUMN_RATIO_LIMIT,
|
||||
DB_COLUMN_SEEDING_TIME_LIMIT,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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?
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,26 +35,6 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
void handleAddTorrentAlert([[maybe_unused]] const lt::add_torrent_alert *alert)
|
||||
{
|
||||
#ifndef QBT_USES_LIBTORRENT2
|
||||
if (alert->error)
|
||||
return;
|
||||
|
||||
// libtorrent < 2.0.7 has a bug that add_torrent_alert is posted too early
|
||||
// (before torrent is fully initialized and torrent extensions are created)
|
||||
// so we have to fill "extension data" in add_torrent_alert handler
|
||||
|
||||
// NOTE: `data` may not exist if a torrent is added behind the scenes to download metadata
|
||||
auto *data = static_cast<ExtensionData *>(alert->params.userdata);
|
||||
if (data)
|
||||
{
|
||||
data->status = alert->handle.status({});
|
||||
data->trackers = alert->handle.trackers();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void handleFastresumeRejectedAlert(const lt::fastresume_rejected_alert *alert)
|
||||
{
|
||||
alert->handle.unset_flags(lt::torrent_flags::auto_managed);
|
||||
@@ -76,9 +56,6 @@ void NativeSessionExtension::on_alert(const lt::alert *alert)
|
||||
{
|
||||
switch (alert->type())
|
||||
{
|
||||
case lt::add_torrent_alert::alert_type:
|
||||
handleAddTorrentAlert(static_cast<const lt::add_torrent_alert *>(alert));
|
||||
break;
|
||||
case lt::fastresume_rejected_alert::alert_type:
|
||||
handleFastresumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert *>(alert));
|
||||
break;
|
||||
|
||||
@@ -36,18 +36,11 @@ NativeTorrentExtension::NativeTorrentExtension(const lt::torrent_handle &torrent
|
||||
{
|
||||
// NOTE: `data` may not exist if a torrent is added behind the scenes to download metadata
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
// libtorrent < 2.0.7 has a bug that add_torrent_alert is posted too early
|
||||
// (before torrent is fully initialized and torrent extensions are created)
|
||||
// so we have to fill "extension data" in add_torrent_alert handler and
|
||||
// we have it already filled at this point
|
||||
|
||||
if (m_data)
|
||||
{
|
||||
m_data->status = m_torrentHandle.status({});
|
||||
m_data->status = m_torrentHandle.status();
|
||||
m_data->trackers = m_torrentHandle.trackers();
|
||||
}
|
||||
#endif
|
||||
|
||||
on_state(m_data ? m_data->status.state : m_torrentHandle.status({}).state);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -78,6 +78,7 @@ namespace BitTorrent
|
||||
|
||||
PeerAddress address() const;
|
||||
QString client() const;
|
||||
QString peerIdClient() const;
|
||||
qreal progress() const;
|
||||
int payloadUpSpeed() const;
|
||||
int payloadDownSpeed() const;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -1051,29 +1050,17 @@ void SessionImpl::setGlobalMaxSeedingMinutes(int minutes)
|
||||
}
|
||||
}
|
||||
|
||||
void SessionImpl::adjustLimits()
|
||||
{
|
||||
if (isQueueingSystemEnabled())
|
||||
{
|
||||
lt::settings_pack settingsPack = m_nativeSession->get_settings();
|
||||
adjustLimits(settingsPack);
|
||||
m_nativeSession->apply_settings(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,39 +1377,47 @@ 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();
|
||||
|
||||
m_nativeSession->resume();
|
||||
if (m_refreshEnqueued)
|
||||
m_refreshEnqueued = false;
|
||||
else
|
||||
enqueueRefresh();
|
||||
|
||||
m_statisticsLastUpdateTimer.start();
|
||||
|
||||
// Regular saving of fastresume data
|
||||
connect(m_resumeDataTimer, &QTimer::timeout, this, &SessionImpl::generateResumeData);
|
||||
const int saveInterval = saveResumeDataInterval();
|
||||
if (saveInterval > 0)
|
||||
connect(context, &QObject::destroyed, this, [this]
|
||||
{
|
||||
m_resumeDataTimer->setInterval(std::chrono::minutes(saveInterval));
|
||||
m_resumeDataTimer->start();
|
||||
}
|
||||
m_nativeSession->resume();
|
||||
if (m_refreshEnqueued)
|
||||
m_refreshEnqueued = false;
|
||||
else
|
||||
enqueueRefresh();
|
||||
|
||||
m_isRestored = true;
|
||||
emit startupProgressUpdated(100);
|
||||
emit restored();
|
||||
m_statisticsLastUpdateTimer.start();
|
||||
|
||||
// Regular saving of fastresume data
|
||||
connect(m_resumeDataTimer, &QTimer::timeout, this, &SessionImpl::generateResumeData);
|
||||
const int saveInterval = saveResumeDataInterval();
|
||||
if (saveInterval > 0)
|
||||
{
|
||||
m_resumeDataTimer->setInterval(std::chrono::minutes(saveInterval));
|
||||
m_resumeDataTimer->start();
|
||||
}
|
||||
|
||||
m_isRestored = true;
|
||||
emit startupProgressUpdated(100);
|
||||
emit restored();
|
||||
});
|
||||
}
|
||||
|
||||
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 +1434,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,30 +1492,6 @@ void SessionImpl::processBannedIPs(lt::ip_filter &filter)
|
||||
}
|
||||
}
|
||||
|
||||
void SessionImpl::adjustLimits(lt::settings_pack &settingsPack) 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());
|
||||
}
|
||||
|
||||
void SessionImpl::initMetrics()
|
||||
{
|
||||
const auto findMetricIndex = [](const char *name) -> int
|
||||
@@ -1564,8 +1534,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 +1555,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,8 +1693,8 @@ void SessionImpl::loadLTSettings(lt::settings_pack &settingsPack)
|
||||
// Queueing System
|
||||
if (isQueueingSystemEnabled())
|
||||
{
|
||||
adjustLimits(settingsPack);
|
||||
|
||||
settingsPack.set_int(lt::settings_pack::active_downloads, maxActiveDownloads());
|
||||
settingsPack.set_int(lt::settings_pack::active_limit, maxActiveTorrents());
|
||||
settingsPack.set_int(lt::settings_pack::active_seeds, maxActiveUploads());
|
||||
settingsPack.set_bool(lt::settings_pack::dont_count_slow_torrents, ignoreSlowTorrentsForQueueing());
|
||||
settingsPack.set_int(lt::settings_pack::inactive_down_rate, downloadRateForSlowTorrents() * 1024); // KiB to Bytes
|
||||
@@ -1835,9 +1809,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 +1956,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 +2168,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;
|
||||
@@ -2305,8 +2268,6 @@ bool SessionImpl::cancelDownloadMetadata(const TorrentID &id)
|
||||
}
|
||||
#endif
|
||||
m_downloadedMetadata.erase(downloadedMetadataIter);
|
||||
--m_extraLimit;
|
||||
adjustLimits();
|
||||
m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_files);
|
||||
return true;
|
||||
}
|
||||
@@ -2753,9 +2714,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 +2763,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())
|
||||
@@ -2844,8 +2815,6 @@ bool SessionImpl::downloadMetadata(const MagnetUri &magnetUri)
|
||||
const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
|
||||
m_downloadedMetadata.insert(altID);
|
||||
}
|
||||
++m_extraLimit;
|
||||
adjustLimits();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -2898,10 +2867,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 +4601,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();
|
||||
}
|
||||
|
||||
@@ -4960,7 +4931,7 @@ void SessionImpl::enqueueRefresh()
|
||||
{
|
||||
Q_ASSERT(!m_refreshEnqueued);
|
||||
|
||||
QTimer::singleShot(refreshInterval(), this, [this] ()
|
||||
QTimer::singleShot(refreshInterval(), Qt::CoarseTimer, this, [this]
|
||||
{
|
||||
m_nativeSession->post_torrent_updates();
|
||||
m_nativeSession->post_session_stats();
|
||||
@@ -5207,9 +5178,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 +5196,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()));
|
||||
}
|
||||
@@ -5270,10 +5241,13 @@ void SessionImpl::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p)
|
||||
#endif
|
||||
|
||||
const auto removingTorrentDataIter = m_removingTorrents.find(id);
|
||||
|
||||
if (removingTorrentDataIter == m_removingTorrents.end())
|
||||
return;
|
||||
|
||||
// torrent_deleted_alert can also be posted due to deletion of partfile. Ignore it in such a case.
|
||||
if (removingTorrentDataIter->deleteOption == DeleteTorrent)
|
||||
return;
|
||||
|
||||
Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
|
||||
LogMsg(tr("Removed torrent and deleted its content. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
|
||||
m_removingTorrents.erase(removingTorrentDataIter);
|
||||
@@ -5288,7 +5262,6 @@ void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed
|
||||
#endif
|
||||
|
||||
const auto removingTorrentDataIter = m_removingTorrents.find(id);
|
||||
|
||||
if (removingTorrentDataIter == m_removingTorrents.end())
|
||||
return;
|
||||
|
||||
@@ -5298,7 +5271,7 @@ void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed
|
||||
// so we remove the directory ourselves
|
||||
Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
|
||||
|
||||
LogMsg(tr("Removed torrent but failed to delete its content. Torrent: \"%1\". Error: \"%2\"")
|
||||
LogMsg(tr("Removed torrent but failed to delete its content and/or partfile. Torrent: \"%1\". Error: \"%2\"")
|
||||
.arg(removingTorrentDataIter->name, QString::fromLocal8Bit(p->error.message().c_str()))
|
||||
, Log::WARNING);
|
||||
}
|
||||
@@ -5306,6 +5279,7 @@ void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed
|
||||
{
|
||||
LogMsg(tr("Removed torrent. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
|
||||
}
|
||||
|
||||
m_removingTorrents.erase(removingTorrentDataIter);
|
||||
}
|
||||
|
||||
@@ -5334,9 +5308,6 @@ void SessionImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert
|
||||
if (found)
|
||||
{
|
||||
const TorrentInfo metadata {*p->handle.torrent_file()};
|
||||
|
||||
--m_extraLimit;
|
||||
adjustLimits();
|
||||
m_nativeSession->remove_torrent(p->handle, lt::session::delete_files);
|
||||
|
||||
emit metadataDownloaded(metadata);
|
||||
|
||||
@@ -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,13 +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;
|
||||
void initMetrics();
|
||||
void adjustLimits();
|
||||
void applyBandwidthLimits();
|
||||
void processBannedIPs(lt::ip_filter &filter);
|
||||
QStringList getListeningIPs() const;
|
||||
@@ -553,7 +548,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;
|
||||
@@ -671,7 +666,6 @@ namespace BitTorrent
|
||||
const bool m_wasPexEnabled = m_isPeXEnabled;
|
||||
|
||||
int m_numResumeData = 0;
|
||||
int m_extraLimit = 0;
|
||||
QVector<TrackerEntry> m_additionalTrackerList;
|
||||
QVector<QRegularExpression> m_excludedFileNamesRegExpList;
|
||||
|
||||
@@ -690,7 +684,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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -723,7 +734,13 @@ qreal TorrentImpl::progress() const
|
||||
return 1.;
|
||||
|
||||
const qreal progress = static_cast<qreal>(m_nativeStatus.total_wanted_done) / m_nativeStatus.total_wanted;
|
||||
Q_ASSERT((progress >= 0.f) && (progress <= 1.f));
|
||||
if ((progress < 0.f) || (progress > 1.f))
|
||||
{
|
||||
LogMsg(tr("Unexpected data detected. Torrent: %1. Data: total_wanted=%2 total_wanted_done=%3.")
|
||||
.arg(name(), QString::number(m_nativeStatus.total_wanted), QString::number(m_nativeStatus.total_wanted_done))
|
||||
, Log::WARNING);
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
@@ -1430,6 +1447,10 @@ void TorrentImpl::forceRecheck()
|
||||
if (!hasMetadata()) return;
|
||||
|
||||
m_nativeHandle.force_recheck();
|
||||
// We have to force update the cached state, otherwise someone will be able to get
|
||||
// an incorrect one during the interval until the cached state is updated in a regular way.
|
||||
m_nativeStatus.state = lt::torrent_status::checking_resume_data;
|
||||
|
||||
m_hasMissingFiles = false;
|
||||
m_unchecked = false;
|
||||
m_completedFiles.fill(false);
|
||||
@@ -1530,20 +1551,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 +1752,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)
|
||||
@@ -1828,25 +1847,24 @@ void TorrentImpl::handleTorrentFinishedAlert(const lt::torrent_finished_alert *p
|
||||
|
||||
m_statusUpdatedTriggers.enqueue([this]()
|
||||
{
|
||||
m_hasSeedStatus = true;
|
||||
|
||||
adjustStorageLocation();
|
||||
manageIncompleteFiles();
|
||||
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
|
||||
const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
|
||||
if (isMoveInProgress() || (m_renameCount > 0))
|
||||
if (recheckTorrentsOnCompletion && m_unchecked)
|
||||
{
|
||||
if (recheckTorrentsOnCompletion)
|
||||
m_moveFinishedTriggers.enqueue([this]() { forceRecheck(); });
|
||||
m_moveFinishedTriggers.enqueue([this]() { m_session->handleTorrentFinished(this); });
|
||||
forceRecheck();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (recheckTorrentsOnCompletion && m_unchecked)
|
||||
forceRecheck();
|
||||
m_session->handleTorrentFinished(this);
|
||||
m_hasSeedStatus = true;
|
||||
|
||||
if (isMoveInProgress() || (m_renameCount > 0))
|
||||
m_moveFinishedTriggers.enqueue([this]() { m_session->handleTorrentFinished(this); });
|
||||
else
|
||||
m_session->handleTorrentFinished(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1990,13 +2008,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 +2065,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 +2172,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 +2179,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 +2195,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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -56,10 +56,33 @@ namespace
|
||||
QList<QSslCipher> safeCipherList()
|
||||
{
|
||||
const QStringList badCiphers {u"idea"_qs, u"rc4"_qs};
|
||||
// Contains Ciphersuites that use RSA for the Key Exchange but they don't mention it in their name
|
||||
const QStringList badRSAShorthandSuites {
|
||||
u"AES256-GCM-SHA384"_qs, u"AES128-GCM-SHA256"_qs, u"AES256-SHA256"_qs,
|
||||
u"AES128-SHA256"_qs, u"AES256-SHA"_qs, u"AES128-SHA"_qs};
|
||||
// Contains Ciphersuites that use AES CBC mode but they don't mention it in their name
|
||||
const QStringList badAESShorthandSuites {
|
||||
u"ECDHE-ECDSA-AES256-SHA384"_qs, u"ECDHE-RSA-AES256-SHA384"_qs, u"DHE-RSA-AES256-SHA256"_qs,
|
||||
u"ECDHE-ECDSA-AES128-SHA256"_qs, u"ECDHE-RSA-AES128-SHA256"_qs, u"DHE-RSA-AES128-SHA256"_qs,
|
||||
u"ECDHE-ECDSA-AES256-SHA"_qs, u"ECDHE-RSA-AES256-SHA"_qs, u"DHE-RSA-AES256-SHA"_qs,
|
||||
u"ECDHE-ECDSA-AES128-SHA"_qs, u"ECDHE-RSA-AES128-SHA"_qs, u"DHE-RSA-AES128-SHA"_qs};
|
||||
const QList<QSslCipher> allCiphers {QSslConfiguration::supportedCiphers()};
|
||||
QList<QSslCipher> safeCiphers;
|
||||
std::copy_if(allCiphers.cbegin(), allCiphers.cend(), std::back_inserter(safeCiphers), [&badCiphers](const QSslCipher &cipher)
|
||||
std::copy_if(allCiphers.cbegin(), allCiphers.cend(), std::back_inserter(safeCiphers),
|
||||
[&badCiphers, &badRSAShorthandSuites, &badAESShorthandSuites](const QSslCipher &cipher)
|
||||
{
|
||||
const QString name = cipher.name();
|
||||
if (name.contains(u"-cbc-"_qs, Qt::CaseInsensitive) // AES CBC mode is considered vulnerable to BEAST attack
|
||||
|| name.startsWith(u"adh-"_qs, Qt::CaseInsensitive) // Key Exchange: Diffie-Hellman, doesn't support Perfect Forward Secrecy
|
||||
|| name.startsWith(u"aecdh-"_qs, Qt::CaseInsensitive) // Key Exchange: Elliptic Curve Diffie-Hellman, doesn't support Perfect Forward Secrecy
|
||||
|| name.startsWith(u"psk-"_qs, Qt::CaseInsensitive) // Key Exchange: Pre-Shared Key, doesn't support Perfect Forward Secrecy
|
||||
|| name.startsWith(u"rsa-"_qs, Qt::CaseInsensitive) // Key Exchange: Rivest Shamir Adleman (RSA), doesn't support Perfect Forward Secrecy
|
||||
|| badRSAShorthandSuites.contains(name, Qt::CaseInsensitive)
|
||||
|| badAESShorthandSuites.contains(name, Qt::CaseInsensitive))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return std::none_of(badCiphers.cbegin(), badCiphers.cend(), [&cipher](const QString &badCipher)
|
||||
{
|
||||
return cipher.name().contains(badCipher, Qt::CaseInsensitive);
|
||||
@@ -78,6 +101,7 @@ Server::Server(IRequestHandler *requestHandler, QObject *parent)
|
||||
setProxy(QNetworkProxy::NoProxy);
|
||||
|
||||
QSslConfiguration sslConf {QSslConfiguration::defaultConfiguration()};
|
||||
sslConf.setProtocol(QSsl::TlsV1_2OrLater);
|
||||
sslConf.setCiphers(safeCipherList());
|
||||
QSslConfiguration::setDefaultConfiguration(sslConf);
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -599,11 +599,7 @@ void Preferences::setWebUiPort(const quint16 port)
|
||||
|
||||
bool Preferences::useUPnPForWebUIPort() const
|
||||
{
|
||||
#ifdef DISABLE_GUI
|
||||
return value(u"Preferences/WebUI/UseUPnP"_qs, true);
|
||||
#else
|
||||
return value(u"Preferences/WebUI/UseUPnP"_qs, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Preferences::setUPnPForWebUIPort(const bool enabled)
|
||||
@@ -996,6 +992,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 +1172,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
|
||||
{
|
||||
@@ -1206,6 +1224,16 @@ void Preferences::setConfirmRemoveAllTags(const bool enabled)
|
||||
setValue(u"Preferences/Advanced/confirmRemoveAllTags"_qs, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::confirmPauseAndResumeAll() const
|
||||
{
|
||||
return value(u"GUI/ConfirmActions/PauseAndResumeAllTorrents"_qs, true);
|
||||
}
|
||||
|
||||
void Preferences::setConfirmPauseAndResumeAll(const bool enabled)
|
||||
{
|
||||
setValue(u"GUI/ConfirmActions/PauseAndResumeAllTorrents"_qs, enabled);
|
||||
}
|
||||
|
||||
#ifndef Q_OS_MACOS
|
||||
TrayIcon::Style Preferences::trayIconStyle() const
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
@@ -309,6 +315,8 @@ public:
|
||||
void setConfirmTorrentRecheck(bool enabled);
|
||||
bool confirmRemoveAllTags() const;
|
||||
void setConfirmRemoveAllTags(bool enabled);
|
||||
bool confirmPauseAndResumeAll() const;
|
||||
void setConfirmPauseAndResumeAll(bool enabled);
|
||||
#ifndef Q_OS_MACOS
|
||||
bool systemTrayEnabled() const;
|
||||
void setSystemTrayEnabled(bool enabled);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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.";
|
||||
}
|
||||
|
||||
@@ -190,8 +185,11 @@ nonstd::expected<void, QString> Session::moveItem(Item *item, const QString &des
|
||||
if (!result)
|
||||
return result.get_unexpected();
|
||||
|
||||
auto srcFolder = static_cast<Folder *>(m_itemsByPath.value(Item::parentPath(item->path())));
|
||||
const auto destFolder = result.value();
|
||||
if (static_cast<Item *>(destFolder) == item)
|
||||
return nonstd::make_unexpected(tr("Couldn't move folder into itself."));
|
||||
|
||||
auto srcFolder = static_cast<Folder *>(m_itemsByPath.value(Item::parentPath(item->path())));
|
||||
if (srcFolder != destFolder)
|
||||
{
|
||||
srcFolder->removeItem(item);
|
||||
@@ -487,7 +485,7 @@ void Session::setRefreshInterval(const int refreshInterval)
|
||||
|
||||
QThread *Session::workingThread() const
|
||||
{
|
||||
return m_workingThread;
|
||||
return m_workingThread.get();
|
||||
}
|
||||
|
||||
void Session::handleItemAboutToBeDestroyed(Item *item)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
@@ -504,7 +503,7 @@ void TorrentFilesWatcher::Worker::removeWatchedFolder(const Path &path)
|
||||
|
||||
void TorrentFilesWatcher::Worker::scheduleWatchedFolderProcessing(const Path &path)
|
||||
{
|
||||
QTimer::singleShot(2s, this, [this, path]()
|
||||
QTimer::singleShot(2s, Qt::CoarseTimer, this, [this, path]
|
||||
{
|
||||
processWatchedFolder(path);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
38
src/base/utils/thread.cpp
Normal 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
43
src/base/utils/thread.h
Normal 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>;
|
||||
}
|
||||
@@ -30,9 +30,9 @@
|
||||
|
||||
#define QBT_VERSION_MAJOR 4
|
||||
#define QBT_VERSION_MINOR 5
|
||||
#define QBT_VERSION_BUGFIX 0
|
||||
#define QBT_VERSION_BUGFIX 4
|
||||
#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)
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -49,6 +49,7 @@ class DesktopIntegration final : public QObject
|
||||
|
||||
public:
|
||||
explicit DesktopIntegration(QObject *parent = nullptr);
|
||||
~DesktopIntegration() override;
|
||||
|
||||
bool isActive() const;
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
@@ -322,6 +252,7 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||
m_ui->checkStartMinimized->setChecked(pref->startMinimized());
|
||||
m_ui->checkProgramExitConfirm->setChecked(pref->confirmOnExit());
|
||||
m_ui->checkProgramAutoExitConfirm->setChecked(!pref->dontConfirmAutoExit());
|
||||
m_ui->checkConfirmPauseAndResumeAll->setChecked(pref->confirmPauseAndResumeAll());
|
||||
|
||||
#if !(defined(Q_OS_WIN) || defined(Q_OS_MACOS))
|
||||
m_ui->groupFileAssociation->setVisible(false);
|
||||
@@ -363,17 +294,18 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||
m_ui->checkPreventFromSuspendWhenDownloading->setChecked(pref->preventFromSuspendWhenDownloading());
|
||||
m_ui->checkPreventFromSuspendWhenSeeding->setChecked(pref->preventFromSuspendWhenSeeding());
|
||||
|
||||
m_ui->checkFileLog->setChecked(app()->isFileLoggerEnabled());
|
||||
const bool fileLogEnabled = app()->isFileLoggerEnabled();
|
||||
m_ui->checkFileLog->setChecked(fileLogEnabled);
|
||||
m_ui->textFileLogPath->setDialogCaption(tr("Choose a save directory"));
|
||||
m_ui->textFileLogPath->setMode(FileSystemPathEdit::Mode::DirectorySave);
|
||||
m_ui->textFileLogPath->setSelectedPath(app()->fileLoggerPath());
|
||||
const bool fileLogBackup = app()->isFileLoggerBackup();
|
||||
m_ui->checkFileLogBackup->setChecked(fileLogBackup);
|
||||
m_ui->spinFileLogSize->setEnabled(fileLogBackup);
|
||||
m_ui->spinFileLogSize->setEnabled(fileLogEnabled && fileLogBackup);
|
||||
const bool fileLogDelete = app()->isFileLoggerDeleteOld();
|
||||
m_ui->checkFileLogDelete->setChecked(fileLogDelete);
|
||||
m_ui->spinFileLogAge->setEnabled(fileLogDelete);
|
||||
m_ui->comboFileLogAgeType->setEnabled(fileLogDelete);
|
||||
m_ui->spinFileLogAge->setEnabled(fileLogEnabled && fileLogDelete);
|
||||
m_ui->comboFileLogAgeType->setEnabled(fileLogEnabled && fileLogDelete);
|
||||
m_ui->spinFileLogSize->setValue(app()->fileLoggerMaxSize() / 1024);
|
||||
m_ui->spinFileLogAge->setValue(app()->fileLoggerAge());
|
||||
m_ui->comboFileLogAgeType->setCurrentIndex(app()->fileLoggerAgeType());
|
||||
@@ -382,6 +314,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);
|
||||
|
||||
@@ -400,6 +335,7 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||
connect(m_ui->checkStartMinimized, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkProgramExitConfirm, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkProgramAutoExitConfirm, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkConfirmPauseAndResumeAll, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkShowSystray, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkMinimizeToSysTray, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkCloseToSystray, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
@@ -419,7 +355,14 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||
m_ui->checkPreventFromSuspendWhenSeeding->setDisabled(true);
|
||||
#endif
|
||||
|
||||
connect(m_ui->checkFileLog, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkFileLog, &QGroupBox::toggled, this, [this](const bool checked)
|
||||
{
|
||||
m_ui->spinFileLogSize->setEnabled(checked && m_ui->checkFileLogBackup->isChecked());
|
||||
const bool bothChecked = checked && m_ui->checkFileLogDelete->isChecked();
|
||||
m_ui->spinFileLogAge->setEnabled(bothChecked);
|
||||
m_ui->comboFileLogAgeType->setEnabled(bothChecked);
|
||||
enableApplyButton();
|
||||
});
|
||||
connect(m_ui->textFileLogPath, &FileSystemPathEdit::selectedPathChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkFileLogBackup, &QAbstractButton::toggled, m_ui->spinFileLogSize, &QWidget::setEnabled);
|
||||
connect(m_ui->checkFileLogBackup, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
@@ -451,6 +394,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());
|
||||
|
||||
@@ -466,6 +412,7 @@ void OptionsDialog::saveBehaviorTabOptions() const
|
||||
pref->setStartMinimized(startMinimized());
|
||||
pref->setConfirmOnExit(m_ui->checkProgramExitConfirm->isChecked());
|
||||
pref->setDontConfirmAutoExit(!m_ui->checkProgramAutoExitConfirm->isChecked());
|
||||
pref->setConfirmPauseAndResumeAll(m_ui->checkConfirmPauseAndResumeAll->isChecked());
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
pref->setWinStartup(WinStartup());
|
||||
@@ -1315,25 +1262,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -225,6 +232,19 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkConfirmPauseAndResumeAll">
|
||||
<property name="toolTip">
|
||||
<string>Shows a confirmation dialog upon pausing/resuming all the torrents</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Confirm "Pause/Resume all" actions</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkAltRowColors">
|
||||
<property name="text">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -105,7 +105,8 @@ FeedListWidget::FeedListWidget(QWidget *parent)
|
||||
m_rssToTreeItemMapping[RSS::Session::instance()->rootFolder()] = invisibleRootItem();
|
||||
|
||||
m_unreadStickyItem = new FeedListItem(this);
|
||||
m_unreadStickyItem->setData(0, Qt::UserRole, QVariant::fromValue(RSS::Session::instance()->rootFolder()));
|
||||
m_unreadStickyItem->setData(0, Qt::UserRole, QVariant::fromValue(
|
||||
reinterpret_cast<intptr_t>(RSS::Session::instance()->rootFolder())));
|
||||
m_unreadStickyItem->setText(0, tr("Unread (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
|
||||
m_unreadStickyItem->setData(0, Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"mail-inbox"_qs));
|
||||
m_unreadStickyItem->setData(0, StickyItemTagRole, true);
|
||||
@@ -211,9 +212,10 @@ QList<QTreeWidgetItem *> FeedListWidget::getAllOpenedFolders(QTreeWidgetItem *pa
|
||||
|
||||
RSS::Item *FeedListWidget::getRSSItem(QTreeWidgetItem *item) const
|
||||
{
|
||||
if (!item) return nullptr;
|
||||
if (!item)
|
||||
return nullptr;
|
||||
|
||||
return item->data(0, Qt::UserRole).value<RSS::Item *>();
|
||||
return reinterpret_cast<RSS::Item *>(item->data(0, Qt::UserRole).value<intptr_t>());
|
||||
}
|
||||
|
||||
QTreeWidgetItem *FeedListWidget::mapRSSItem(RSS::Item *rssItem) const
|
||||
@@ -275,7 +277,7 @@ QTreeWidgetItem *FeedListWidget::createItem(RSS::Item *rssItem, QTreeWidgetItem
|
||||
{
|
||||
auto *item = new FeedListItem;
|
||||
item->setData(0, Qt::DisplayRole, u"%1 (%2)"_qs.arg(rssItem->name(), QString::number(rssItem->unreadCount())));
|
||||
item->setData(0, Qt::UserRole, QVariant::fromValue(rssItem));
|
||||
item->setData(0, Qt::UserRole, QVariant::fromValue(reinterpret_cast<intptr_t>(rssItem)));
|
||||
m_rssToTreeItemMapping[rssItem] = item;
|
||||
|
||||
QIcon icon;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user