Compare commits

..

2 commits

Author SHA1 Message Date
sledgehammer999
bc7d5c1f8f
Bump to 5.1.0rc1 2025-02-11 02:01:34 +02:00
sledgehammer999
8aabef423c
Create new resources for this branch for Transifex 2025-02-11 01:59:07 +02:00
282 changed files with 109763 additions and 106456 deletions

2
.github/FUNDING.yml vendored
View file

@ -1 +1 @@
custom: "https://www.qbittorrent.org/donate"
custom: "https://www.qbittorrent.org/donate.php"

View file

@ -20,7 +20,7 @@ jobs:
matrix:
libt_version: ["2.0.11", "1.2.20"]
qbt_gui: ["GUI=ON", "GUI=OFF"]
qt_version: ["6.9.0"]
qt_version: ["6.7.0"]
env:
boost_path: "${{ github.workspace }}/../boost"
@ -52,7 +52,7 @@ jobs:
store_cache: ${{ github.ref == 'refs/heads/master' }}
update_packager_index: false
ccache_options: |
max_size=1G
max_size=2G
- name: Install boost
env:
@ -70,9 +70,6 @@ jobs:
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?"
fi
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
cd "${{ env.boost_path }}"
./bootstrap.sh
./b2 stage --stagedir=./ --with-headers
- name: Install Qt
uses: jurplel/install-qt-action@v4
@ -98,7 +95,7 @@ jobs:
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_STANDARD=20 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
-DBOOST_ROOT="${{ env.boost_path }}" \
-Ddeprecated-functions=OFF
cmake --build build
sudo cmake --install build
@ -112,7 +109,7 @@ jobs:
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
-DBOOST_ROOT="${{ env.boost_path }}" \
-DTESTING=ON \
-DVERBOSE_CONFIGURE=ON \
-D${{ matrix.qbt_gui }}

View file

@ -47,7 +47,7 @@ jobs:
store_cache: ${{ github.ref == 'refs/heads/master' }}
update_packager_index: false
ccache_options: |
max_size=1G
max_size=2G
- name: Install boost
env:
@ -65,9 +65,6 @@ jobs:
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?"
fi
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
cd "${{ env.boost_path }}"
./bootstrap.sh
./b2 stage --stagedir=./ --with-headers
- name: Install Qt
uses: jurplel/install-qt-action@v4
@ -93,7 +90,7 @@ jobs:
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_STANDARD=20 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
-DBOOST_ROOT="${{ env.boost_path }}" \
-Ddeprecated-functions=OFF
cmake --build build
sudo cmake --install build
@ -115,7 +112,7 @@ jobs:
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
-DBOOST_ROOT="${{ env.boost_path }}" \
-DCMAKE_INSTALL_PREFIX="/usr" \
-DTESTING=ON \
-DVERBOSE_CONFIGURE=ON \
@ -141,15 +138,16 @@ jobs:
- name: Install AppImage
run: |
sudo apt install libfuse2
curl \
-L \
-Z \
-O https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage \
-O https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage \
-O https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-static-x86_64.AppImage \
-O https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-static-x86_64.AppImage \
-O https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage
chmod +x \
linuxdeploy-x86_64.AppImage \
linuxdeploy-plugin-qt-x86_64.AppImage \
linuxdeploy-static-x86_64.AppImage \
linuxdeploy-plugin-qt-static-x86_64.AppImage \
linuxdeploy-plugin-appimage-x86_64.AppImage
- name: Prepare files for AppImage
@ -162,12 +160,12 @@ jobs:
- name: Package AppImage
run: |
./linuxdeploy-x86_64.AppImage --appdir qbittorrent --plugin qt
./linuxdeploy-static-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
./linuxdeploy-static-x86_64.AppImage --appdir qbittorrent --output appimage
- name: Upload build artifacts
uses: actions/upload-artifact@v4

View file

@ -34,12 +34,7 @@ jobs:
run: |
npm install
npm ls
echo "::group::npm ls --all"
npm ls --all
echo "::endgroup::"
- name: Run tests
run: npm test
- name: Lint code
run: npm run lint

View file

@ -67,7 +67,6 @@ jobs:
"set(VCPKG_BUILD_TYPE release)")
# clear buildtrees after each package installation to reduce disk space requirements
$packages = `
"boost-build:x64-windows-static-md-release",
"openssl:x64-windows-static-md-release",
"zlib:x64-windows-static-md-release"
${{ env.vcpkg_path }}/vcpkg.exe upgrade `
@ -95,18 +94,11 @@ jobs:
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."
}
move "${{ github.workspace }}/../boost_*" "${{ env.boost_path }}"
cd "${{ env.boost_path }}"
#.\bootstrap.bat
${{ env.vcpkg_path }}/installed/x64-windows-static-md-release/tools/boost-build/b2.exe `
stage `
toolset=msvc `
--stagedir=.\ `
--with-headers
- name: Install Qt
uses: jurplel/install-qt-action@v4
with:
version: "6.9.0"
version: "6.8.0"
arch: win64_msvc2022_64
archives: qtbase qtsvg qttools
cache: true
@ -130,7 +122,7 @@ jobs:
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON `
-DCMAKE_INSTALL_PREFIX="${{ env.libtorrent_path }}/install" `
-DCMAKE_TOOLCHAIN_FILE="${{ env.vcpkg_path }}/scripts/buildsystems/vcpkg.cmake" `
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" `
-DBOOST_ROOT="${{ env.boost_path }}" `
-DBUILD_SHARED_LIBS=OFF `
-Ddeprecated-functions=OFF `
-Dstatic_runtime=OFF `
@ -147,7 +139,7 @@ jobs:
-DCMAKE_BUILD_TYPE=RelWithDebInfo `
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON `
-DCMAKE_TOOLCHAIN_FILE="${{ env.vcpkg_path }}/scripts/buildsystems/vcpkg.cmake" `
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" `
-DBOOST_ROOT="${{ env.boost_path }}" `
-DLibtorrentRasterbar_DIR="${{ env.libtorrent_path }}/install/lib/cmake/LibtorrentRasterbar" `
-DMSVC_RUNTIME_DYNAMIC=ON `
-DTESTING=ON `

View file

@ -52,9 +52,6 @@ jobs:
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?"
fi
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
cd "${{ env.boost_path }}"
./bootstrap.sh
./b2 stage --stagedir=./ --with-headers
- name: Install Qt
uses: jurplel/install-qt-action@v4
@ -77,7 +74,7 @@ jobs:
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_STANDARD=20 \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
-DBOOST_ROOT="${{ env.boost_path }}" \
-Ddeprecated-functions=OFF
cmake --build build
sudo cmake --install build
@ -101,7 +98,7 @@ jobs:
-B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
-DBOOST_ROOT="${{ env.boost_path }}" \
-DVERBOSE_CONFIGURE=ON \
-D${{ matrix.qbt_gui }}
PATH="${{ env.coverity_path }}/bin:$PATH" \

View file

@ -73,7 +73,7 @@ repos:
hooks:
- id: codespell
name: Check spelling (codespell)
args: ["--ignore-words-list", "additionals,categor,curren,fo,indexIn,ist,ket,notin,searchin,sectionin,superseeding,te,ths"]
args: ["--ignore-words-list", "additionals,categor,curren,fo,ist,ket,notin,searchin,sectionin,superseeding,te,ths"]
exclude: |
(?x)^(
.*\.desktop |

View file

@ -1,7 +1,7 @@
[main]
host = https://www.transifex.com
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_master]
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_v51x]
file_filter = src/lang/qbittorrent_<lang>.ts
source_file = src/lang/qbittorrent_en.ts
source_lang = en
@ -9,7 +9,7 @@ type = QT
minimum_perc = 23
lang_map = pt: pt_PT, zh: zh_CN
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_webui]
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_webui_v51x]
file_filter = src/webui/www/translations/webui_<lang>.ts
source_file = src/webui/www/translations/webui_en.ts
source_lang = en

View file

@ -1,5 +0,0 @@
# 2.11.6
* https://github.com/qbittorrent/qBittorrent/pull/22460
* `app/setPreferences` allows only one of `max_ratio_enabled`, `max_ratio` to be present
* `app/setPreferences` allows only one of `max_seeding_time_enabled`, `max_seeding_time` to be present
* `app/setPreferences` allows only one of `max_inactive_seeding_time_enabled`, `max_inactive_seeding_time` to be present

4
dist/mac/Info.plist vendored
View file

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

View file

@ -105,7 +105,7 @@ GenericName[ka]=BitTorrent კლიენტი
Comment[ka]= BitTorrent-
Name[ka]=qBittorrent
GenericName[ko]=BitTorrent
Comment[ko]=BitTorrent
Comment[ko]=BitTorrent
Name[ko]=qBittorrent
GenericName[lt]=BitTorrent klientas
Comment[lt]=Atsisiųskite bei dalinkitės failais BitTorrent tinkle

View file

@ -62,6 +62,6 @@
<url type="contribute">https://github.com/qbittorrent/qBittorrent/blob/master/CONTRIBUTING.md</url>
<content_rating type="oars-1.1"/>
<releases>
<release version="5.2.0~alpha1" date="2025-02-11"/>
<release version="5.1.0~rc1" date="2025-02-11"/>
</releases>
</component>

View file

@ -14,7 +14,7 @@
; 4.5.1.3 -> good
; 4.5.1.3.2 -> bad
; 4.5.0beta -> bad
!define /ifndef QBT_VERSION "5.2.0"
!define /ifndef QBT_VERSION "5.1.0"
; Option that controls the installer's window name
; If set, its value will be used like this:
@ -86,7 +86,7 @@ OutFile "qbittorrent_${QBT_INSTALLER_FILENAME}_setup.exe"
;Installer Version Information
VIAddVersionKey "ProductName" "qBittorrent"
VIAddVersionKey "CompanyName" "The qBittorrent project"
VIAddVersionKey "LegalCopyright" "Copyright ©2006-2025 The qBittorrent project"
VIAddVersionKey "LegalCopyright" "Copyright ©2006-2024 The qBittorrent project"
VIAddVersionKey "FileDescription" "qBittorrent - A Bittorrent Client"
VIAddVersionKey "FileVersion" "${QBT_VERSION}"

View file

@ -7,21 +7,21 @@ LangString inst_desktop ${LANG_SWEDISH} "Skapa skrivbordsgenväg"
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
LangString inst_startmenu ${LANG_SWEDISH} "Skapa startmenygenväg"
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
LangString inst_startup ${LANG_SWEDISH} "Starta qBittorrent vid Windows-uppstart"
LangString inst_startup ${LANG_SWEDISH} "Starta qBittorrent vid Windows start"
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
LangString inst_torrent ${LANG_SWEDISH} "Öppna .torrent-filer med qBittorrent"
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
LangString inst_magnet ${LANG_SWEDISH} "Öppna magnetlänkar med qBittorrent"
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
LangString inst_firewall ${LANG_SWEDISH} "Lägg till Windows-brandväggsregel"
LangString inst_firewall ${LANG_SWEDISH} "Lägg till Windows-brandväggregel"
;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_SWEDISH} "Inaktivera gränsen för Windows-sökvägslängd (260 tecken MAX_PATH-begränsning, kräver Windows 10 1607 eller senare)"
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
LangString inst_firewallinfo ${LANG_SWEDISH} "Lägger till Windows-brandväggsregel"
LangString inst_firewallinfo ${LANG_SWEDISH} "Lägger till Windows-brandväggregel"
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
LangString inst_warning ${LANG_SWEDISH} "qBittorrent körs. Stäng programmet innan du installerar."
LangString inst_warning ${LANG_SWEDISH} "qBittorrent körs. Vänligen stäng programmet innan du installerar."
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
LangString inst_uninstall_question ${LANG_SWEDISH} "Aktuell version avinstalleras. Användarinställningar och torrenter kommer att förbli intakta."
LangString inst_uninstall_question ${LANG_SWEDISH} "Nuvarande version avinstalleras. Användarinställningar och torrenter kommer att förbli intakta."
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
LangString inst_unist ${LANG_SWEDISH} "Avinstallerar tidigare version."
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
@ -53,7 +53,7 @@ LangString remove_firewallinfo ${LANG_SWEDISH} "Tar bort Windows-brandväggsrege
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
LangString remove_cache ${LANG_SWEDISH} "Ta bort torrenter och cachade data"
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
LangString uninst_warning ${LANG_SWEDISH} "qBittorrent körs. Stäng programmet innan du avinstallerar."
LangString uninst_warning ${LANG_SWEDISH} "qBittorrent körs. Vänligen stäng programmet innan du avinstallerar."
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
LangString uninst_tor_warn ${LANG_SWEDISH} "Tar inte bort .torrent-association. Den är associerad med:"
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez
*
* This program is free software; you can redistribute it and/or
@ -124,28 +124,6 @@ namespace
const int PIXMAP_CACHE_SIZE = 64 * 1024 * 1024; // 64MiB
#endif
const QString PARAM_ADDSTOPPED = u"@addStopped"_s;
const QString PARAM_CATEGORY = u"@category"_s;
const QString PARAM_FIRSTLASTPIECEPRIORITY = u"@firstLastPiecePriority"_s;
const QString PARAM_SAVEPATH = u"@savePath"_s;
const QString PARAM_SEQUENTIAL = u"@sequential"_s;
const QString PARAM_SKIPCHECKING = u"@skipChecking"_s;
const QString PARAM_SKIPDIALOG = u"@skipDialog"_s;
QString bindParamValue(const QStringView paramName, const QStringView paramValue)
{
return paramName + u'=' + paramValue;
}
std::pair<QStringView, QStringView> parseParam(const QStringView param)
{
const qsizetype sepIndex = param.indexOf(u'=');
if (sepIndex >= 0)
return {param.first(sepIndex), param.sliced(sepIndex + 1)};
return {param, {}};
}
QString serializeParams(const QBtCommandLineParameters &params)
{
QStringList result;
@ -160,86 +138,85 @@ namespace
const BitTorrent::AddTorrentParams &addTorrentParams = params.addTorrentParams;
if (!addTorrentParams.savePath.isEmpty())
result.append(bindParamValue(PARAM_SAVEPATH, addTorrentParams.savePath.data()));
result.append(u"@savePath=" + addTorrentParams.savePath.data());
if (addTorrentParams.addStopped.has_value())
result.append(bindParamValue(PARAM_ADDSTOPPED, (*addTorrentParams.addStopped ? u"1" : u"0")));
result.append(*addTorrentParams.addStopped ? u"@addStopped=1"_s : u"@addStopped=0"_s);
if (addTorrentParams.skipChecking)
result.append(PARAM_SKIPCHECKING);
result.append(u"@skipChecking"_s);
if (!addTorrentParams.category.isEmpty())
result.append(bindParamValue(PARAM_CATEGORY, addTorrentParams.category));
result.append(u"@category=" + addTorrentParams.category);
if (addTorrentParams.sequential)
result.append(PARAM_SEQUENTIAL);
result.append(u"@sequential"_s);
if (addTorrentParams.firstLastPiecePriority)
result.append(PARAM_FIRSTLASTPIECEPRIORITY);
result.append(u"@firstLastPiecePriority"_s);
if (params.skipDialog.has_value())
result.append(bindParamValue(PARAM_SKIPDIALOG, (*params.skipDialog ? u"1" : u"0")));
result.append(*params.skipDialog ? u"@skipDialog=1"_s : u"@skipDialog=0"_s);
result += params.torrentSources;
return result.join(PARAMS_SEPARATOR);
}
QBtCommandLineParameters parseParams(const QStringView str)
QBtCommandLineParameters parseParams(const QString &str)
{
QBtCommandLineParameters parsedParams;
BitTorrent::AddTorrentParams &addTorrentParams = parsedParams.addTorrentParams;
for (QStringView param : asConst(str.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts)))
for (QString param : asConst(str.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts)))
{
param = param.trimmed();
const auto [paramName, paramValue] = parseParam(param);
// Process strings indicating options specified by the user.
if (paramName == PARAM_SAVEPATH)
if (param.startsWith(u"@savePath="))
{
addTorrentParams.savePath = Path(paramValue.toString());
addTorrentParams.savePath = Path(param.mid(10));
continue;
}
if (paramName == PARAM_ADDSTOPPED)
if (param.startsWith(u"@addStopped="))
{
addTorrentParams.addStopped = (paramValue.toInt() != 0);
addTorrentParams.addStopped = (QStringView(param).mid(11).toInt() != 0);
continue;
}
if (paramName == PARAM_SKIPCHECKING)
if (param == u"@skipChecking")
{
addTorrentParams.skipChecking = true;
continue;
}
if (paramName == PARAM_CATEGORY)
if (param.startsWith(u"@category="))
{
addTorrentParams.category = paramValue.toString();
addTorrentParams.category = param.mid(10);
continue;
}
if (paramName == PARAM_SEQUENTIAL)
if (param == u"@sequential")
{
addTorrentParams.sequential = true;
continue;
}
if (paramName == PARAM_FIRSTLASTPIECEPRIORITY)
if (param == u"@firstLastPiecePriority")
{
addTorrentParams.firstLastPiecePriority = true;
continue;
}
if (paramName == PARAM_SKIPDIALOG)
if (param.startsWith(u"@skipDialog="))
{
parsedParams.skipDialog = (paramValue.toInt() != 0);
parsedParams.skipDialog = (QStringView(param).mid(12).toInt() != 0);
continue;
}
parsedParams.torrentSources.append(param.toString());
parsedParams.torrentSources.append(param);
}
return parsedParams;
@ -659,13 +636,7 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
{
// strip redundant quotes
if (arg.startsWith(u'"') && arg.endsWith(u'"'))
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
arg.slice(1, (arg.size() - 2));
#else
arg.removeLast().removeFirst();
#endif
}
arg = arg.mid(1, (arg.size() - 2));
arg = replaceVariables(arg);
}
@ -674,9 +645,6 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
QProcess proc;
proc.setProgram(command);
proc.setArguments(args);
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
proc.setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
#endif
if (proc.startDetached())
{
@ -929,10 +897,10 @@ int Application::exec()
m_desktopIntegration->showNotification(tr("Torrent added"), tr("'%1' was added.", "e.g: xxx.avi was added.").arg(torrent->name()));
});
connect(m_addTorrentManager, &AddTorrentManager::addTorrentFailed, this
, [this](const QString &source, const BitTorrent::AddTorrentError &reason)
, [this](const QString &source, const QString &reason)
{
m_desktopIntegration->showNotification(tr("Add torrent failed")
, tr("Couldn't add torrent '%1', reason: %2.").arg(source, reason.message));
, tr("Couldn't add torrent '%1', reason: %2.").arg(source, reason));
});
disconnect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog);

View file

@ -465,13 +465,13 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
return result;
}
QString wrapText(const QString &text, const int initialIndentation = USAGE_TEXT_COLUMN, const int wrapAtColumn = WRAP_AT_COLUMN)
QString wrapText(const QString &text, int initialIndentation = USAGE_TEXT_COLUMN, int wrapAtColumn = WRAP_AT_COLUMN)
{
const QStringList words = text.split(u' ');
QStringList words = text.split(u' ');
QStringList lines = {words.first()};
int currentLineMaxLength = wrapAtColumn - initialIndentation;
for (const QString &word : asConst(words.sliced(1)))
for (const QString &word : asConst(words.mid(1)))
{
if (lines.last().length() + word.length() + 1 < currentLineMaxLength)
{

View file

@ -6,7 +6,6 @@ add_library(qbt_base STATIC
applicationcomponent.h
asyncfilestorage.h
bittorrent/abstractfilestorage.h
bittorrent/addtorrenterror.h
bittorrent/addtorrentparams.h
bittorrent/announcetimepoint.h
bittorrent/bandwidthscheduler.h
@ -55,7 +54,6 @@ add_library(qbt_base STATIC
concepts/stringable.h
digest32.h
exceptions.h
freediskspacechecker.h
global.h
http/connection.h
http/httperror.h
@ -161,7 +159,6 @@ add_library(qbt_base STATIC
bittorrent/trackerentry.cpp
bittorrent/trackerentrystatus.cpp
exceptions.cpp
freediskspacechecker.cpp
http/connection.cpp
http/httperror.cpp
http/requestparser.cpp

View file

@ -29,7 +29,6 @@
#include "addtorrentmanager.h"
#include "base/bittorrent/addtorrenterror.h"
#include "base/bittorrent/infohash.h"
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrentdescriptor.h"
@ -141,7 +140,7 @@ void AddTorrentManager::onSessionTorrentAdded(BitTorrent::Torrent *torrent)
}
}
void AddTorrentManager::onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const BitTorrent::AddTorrentError &reason)
void AddTorrentManager::onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const QString &reason)
{
if (const QString source = m_sourcesByInfoHash.take(infoHash); !source.isEmpty())
{
@ -155,7 +154,7 @@ void AddTorrentManager::onSessionAddTorrentFailed(const BitTorrent::InfoHash &in
void AddTorrentManager::handleAddTorrentFailed(const QString &source, const QString &reason)
{
LogMsg(tr("Failed to add torrent. Source: \"%1\". Reason: \"%2\"").arg(source, reason), Log::WARNING);
emit addTorrentFailed(source, {BitTorrent::AddTorrentError::Other, reason});
emit addTorrentFailed(source, reason);
}
void AddTorrentManager::handleDuplicateTorrent(const QString &source
@ -186,9 +185,9 @@ void AddTorrentManager::handleDuplicateTorrent(const QString &source
message = tr("Trackers are merged from new source");
}
LogMsg(tr("Detected an attempt to add a duplicate torrent. Source: %1. Existing torrent: \"%2\". Torrent infohash: %3. Result: %4")
.arg(source, existingTorrent->name(), existingTorrent->infoHash().toString(), message));
emit addTorrentFailed(source, {BitTorrent::AddTorrentError::DuplicateTorrent, message});
LogMsg(tr("Detected an attempt to add a duplicate torrent. Source: %1. Existing torrent: %2. Result: %3")
.arg(source, existingTorrent->name(), message));
emit addTorrentFailed(source, message);
}
void AddTorrentManager::setTorrentFileGuard(const QString &source, std::shared_ptr<TorrentFileGuard> torrentFileGuard)

View file

@ -44,7 +44,6 @@ namespace BitTorrent
class Session;
class Torrent;
class TorrentDescriptor;
struct AddTorrentError;
}
namespace Net
@ -67,7 +66,7 @@ public:
signals:
void torrentAdded(const QString &source, BitTorrent::Torrent *torrent);
void addTorrentFailed(const QString &source, const BitTorrent::AddTorrentError &reason);
void addTorrentFailed(const QString &source, const QString &reason);
protected:
bool addTorrentToSession(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
@ -80,7 +79,7 @@ protected:
private:
void onDownloadFinished(const Net::DownloadResult &result);
void onSessionTorrentAdded(BitTorrent::Torrent *torrent);
void onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const BitTorrent::AddTorrentError &reason);
void onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const QString &reason);
bool processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
, const BitTorrent::AddTorrentParams &addTorrentParams);

View file

@ -1,49 +0,0 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 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 <QMetaType>
#include <QString>
namespace BitTorrent
{
struct AddTorrentError
{
enum Kind
{
DuplicateTorrent,
Other
};
Kind kind = Other;
QString message;
};
}
Q_DECLARE_METATYPE(BitTorrent::AddTorrentError)

View file

@ -290,8 +290,6 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
lt::add_torrent_params &p = torrentParams.ltAddTorrentParams;
p = lt::read_resume_data(resumeDataRoot, ec);
if (ec)
return nonstd::make_unexpected(tr("Cannot parse resume data: %1").arg(QString::fromStdString(ec.message())));
if (!metadata.isEmpty())
{
@ -322,8 +320,6 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
p.save_path = Profile::instance()->fromPortablePath(
Path(fromLTString(p.save_path))).toString().toStdString();
if (p.save_path.empty())
return nonstd::make_unexpected(tr("Corrupted resume data: %1").arg(tr("save_path is invalid")));
torrentParams.stopped = (p.flags & lt::torrent_flags::paused) && !(p.flags & lt::torrent_flags::auto_managed);
torrentParams.operatingMode = (p.flags & lt::torrent_flags::paused) || (p.flags & lt::torrent_flags::auto_managed)

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2021-2023 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
@ -217,6 +217,80 @@ namespace
{
return u"%1 %2"_s.arg(quoted(column.name), definition);
}
LoadTorrentParams parseQueryResultRow(const QSqlQuery &query)
{
LoadTorrentParams resumeData;
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();
if (!tagsData.isEmpty())
{
const QStringList tagList = tagsData.split(u',');
resumeData.tags.insert(tagList.cbegin(), tagList.cend());
}
resumeData.hasFinishedStatus = query.value(DB_COLUMN_HAS_SEED_STATUS.name).toBool();
resumeData.firstLastPiecePriority = query.value(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.name).toBool();
resumeData.ratioLimit = query.value(DB_COLUMN_RATIO_LIMIT.name).toInt() / 1000.0;
resumeData.seedingTimeLimit = query.value(DB_COLUMN_SEEDING_TIME_LIMIT.name).toInt();
resumeData.inactiveSeedingTimeLimit = query.value(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT.name).toInt();
resumeData.shareLimitAction = Utils::String::toEnum<ShareLimitAction>(
query.value(DB_COLUMN_SHARE_LIMIT_ACTION.name).toString(), ShareLimitAction::Default);
resumeData.contentLayout = Utils::String::toEnum<TorrentContentLayout>(
query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original);
resumeData.operatingMode = Utils::String::toEnum<TorrentOperatingMode>(
query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged);
resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
resumeData.stopCondition = Utils::String::toEnum(
query.value(DB_COLUMN_STOP_CONDITION.name).toString(), Torrent::StopCondition::None);
resumeData.sslParameters =
{
.certificate = QSslCertificate(query.value(DB_COLUMN_SSL_CERTIFICATE.name).toByteArray()),
.privateKey = Utils::SSLKey::load(query.value(DB_COLUMN_SSL_PRIVATE_KEY.name).toByteArray()),
.dhParams = query.value(DB_COLUMN_SSL_DH_PARAMS.name).toByteArray()
};
resumeData.savePath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
resumeData.useAutoTMM = resumeData.savePath.isEmpty();
if (!resumeData.useAutoTMM)
{
resumeData.downloadPath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
}
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
const auto *pref = Preferences::instance();
const int bdecodeDepthLimit = pref->getBdecodeDepthLimit();
const int bdecodeTokenLimit = pref->getBdecodeTokenLimit();
lt::error_code ec;
const lt::bdecode_node resumeDataRoot = lt::bdecode(bencodedResumeData, ec
, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
p = lt::read_resume_data(resumeDataRoot, ec);
if (const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray()
; !bencodedMetadata.isEmpty())
{
const lt::bdecode_node torentInfoRoot = lt::bdecode(bencodedMetadata, ec
, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
p.ti = std::make_shared<lt::torrent_info>(torentInfoRoot, ec);
}
p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path)))
.toString().toStdString();
if (p.flags & lt::torrent_flags::stop_when_ready)
{
p.flags &= ~lt::torrent_flags::stop_when_ready;
resumeData.stopCondition = Torrent::StopCondition::FilesChecked;
}
return resumeData;
}
}
namespace BitTorrent
@ -614,90 +688,6 @@ void BitTorrent::DBResumeDataStorage::enableWALMode() const
throw RuntimeError(tr("WAL mode is probably unsupported due to filesystem limitations."));
}
LoadResumeDataResult DBResumeDataStorage::parseQueryResultRow(const QSqlQuery &query) const
{
LoadTorrentParams resumeData;
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();
if (!tagsData.isEmpty())
{
const QStringList tagList = tagsData.split(u',');
resumeData.tags.insert(tagList.cbegin(), tagList.cend());
}
resumeData.hasFinishedStatus = query.value(DB_COLUMN_HAS_SEED_STATUS.name).toBool();
resumeData.firstLastPiecePriority = query.value(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.name).toBool();
resumeData.ratioLimit = query.value(DB_COLUMN_RATIO_LIMIT.name).toInt() / 1000.0;
resumeData.seedingTimeLimit = query.value(DB_COLUMN_SEEDING_TIME_LIMIT.name).toInt();
resumeData.inactiveSeedingTimeLimit = query.value(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT.name).toInt();
resumeData.shareLimitAction = Utils::String::toEnum<ShareLimitAction>(
query.value(DB_COLUMN_SHARE_LIMIT_ACTION.name).toString(), ShareLimitAction::Default);
resumeData.contentLayout = Utils::String::toEnum<TorrentContentLayout>(
query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original);
resumeData.operatingMode = Utils::String::toEnum<TorrentOperatingMode>(
query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged);
resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
resumeData.stopCondition = Utils::String::toEnum(
query.value(DB_COLUMN_STOP_CONDITION.name).toString(), Torrent::StopCondition::None);
resumeData.sslParameters =
{
.certificate = QSslCertificate(query.value(DB_COLUMN_SSL_CERTIFICATE.name).toByteArray()),
.privateKey = Utils::SSLKey::load(query.value(DB_COLUMN_SSL_PRIVATE_KEY.name).toByteArray()),
.dhParams = query.value(DB_COLUMN_SSL_DH_PARAMS.name).toByteArray()
};
resumeData.savePath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
resumeData.useAutoTMM = resumeData.savePath.isEmpty();
if (!resumeData.useAutoTMM)
{
resumeData.downloadPath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
}
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
const auto *pref = Preferences::instance();
const int bdecodeDepthLimit = pref->getBdecodeDepthLimit();
const int bdecodeTokenLimit = pref->getBdecodeTokenLimit();
lt::error_code ec;
const lt::bdecode_node resumeDataRoot = lt::bdecode(bencodedResumeData, ec, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
if (ec)
return nonstd::make_unexpected(tr("Cannot parse resume data: %1").arg(QString::fromStdString(ec.message())));
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
p = lt::read_resume_data(resumeDataRoot, ec);
if (ec)
return nonstd::make_unexpected(tr("Cannot parse resume data: %1").arg(QString::fromStdString(ec.message())));
if (const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray()
; !bencodedMetadata.isEmpty())
{
const lt::bdecode_node torentInfoRoot = lt::bdecode(bencodedMetadata, ec
, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
if (ec)
return nonstd::make_unexpected(tr("Cannot parse torrent info: %1").arg(QString::fromStdString(ec.message())));
p.ti = std::make_shared<lt::torrent_info>(torentInfoRoot, ec);
if (ec)
return nonstd::make_unexpected(tr("Cannot parse torrent info: %1").arg(QString::fromStdString(ec.message())));
}
p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path)))
.toString().toStdString();
if (p.save_path.empty())
return nonstd::make_unexpected(tr("Corrupted resume data: %1").arg(tr("save_path is invalid")));
if (p.flags & lt::torrent_flags::stop_when_ready)
{
p.flags &= ~lt::torrent_flags::stop_when_ready;
resumeData.stopCondition = Torrent::StopCondition::FilesChecked;
}
return resumeData;
}
BitTorrent::DBResumeDataStorage::Worker::Worker(const Path &dbPath, QReadWriteLock &dbLock, QObject *parent)
: QThread(parent)
, m_path {dbPath}

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021-2025 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,10 +31,9 @@
#include <QReadWriteLock>
#include "base/pathfwd.h"
#include "base/utils/thread.h"
#include "resumedatastorage.h"
class QSqlQuery;
namespace BitTorrent
{
class DBResumeDataStorage final : public ResumeDataStorage
@ -59,7 +58,6 @@ namespace BitTorrent
void createDB() const;
void updateDB(int fromVersion) const;
void enableWALMode() const;
LoadResumeDataResult parseQueryResultRow(const QSqlQuery &query) const;
class Worker;
Worker *m_asyncWorker = nullptr;

View file

@ -29,9 +29,6 @@
#include "infohash.h"
#include <QHash>
#include <QString>
#include "base/global.h"
const int TorrentIDTypeId = qRegisterMetaType<BitTorrent::TorrentID>();
@ -89,28 +86,6 @@ BitTorrent::TorrentID BitTorrent::InfoHash::toTorrentID() const
#endif
}
QString BitTorrent::InfoHash::toString() const
{
// Returns a string that is suitable for logging purpose
QString ret;
ret.reserve(40 + 64 + 2); // v1 hash length + v2 hash length + comma
const SHA1Hash v1Hash = v1();
const bool v1IsValid = v1Hash.isValid();
if (v1IsValid)
ret += v1Hash.toString();
if (const SHA256Hash v2Hash = v2(); v2Hash.isValid())
{
if (v1IsValid)
ret += u", ";
ret += v2Hash.toString();
}
return ret;
}
BitTorrent::InfoHash::operator WrappedType() const
{
return m_nativeHash;

View file

@ -36,8 +36,6 @@
#include "base/digest32.h"
class QString;
using SHA1Hash = Digest32<160>;
using SHA256Hash = Digest32<256>;
@ -81,8 +79,6 @@ namespace BitTorrent
SHA256Hash v2() const;
TorrentID toTorrentID() const;
QString toString() const;
operator WrappedType() const;
private:

View file

@ -39,11 +39,7 @@ PeerAddress PeerAddress::parse(const QStringView address)
if (address.startsWith(u'[') && address.contains(u"]:"))
{ // IPv6
ipPort = address.split(u"]:");
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
ipPort[0].slice(1); // chop '['
#else
ipPort[0] = ipPort[0].sliced(1); // chop '['
#endif
ipPort[0] = ipPort[0].mid(1); // chop '['
}
else if (address.contains(u':'))
{ // IPv4

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -34,7 +34,6 @@
#include "base/pathfwd.h"
#include "base/tagset.h"
#include "addtorrenterror.h"
#include "addtorrentparams.h"
#include "categoryoptions.h"
#include "sharelimitaction.h"
@ -422,8 +421,6 @@ namespace BitTorrent
virtual void setUTPRateLimited(bool limited) = 0;
virtual MixedModeAlgorithm utpMixedMode() const = 0;
virtual void setUtpMixedMode(MixedModeAlgorithm mode) = 0;
virtual int hostnameCacheTTL() const = 0;
virtual void setHostnameCacheTTL(int value) = 0;
virtual bool isIDNSupportEnabled() const = 0;
virtual void setIDNSupportEnabled(bool enabled) = 0;
virtual bool multiConnectionsPerIpEnabled() const = 0;
@ -482,11 +479,9 @@ namespace BitTorrent
virtual QString lastExternalIPv4Address() const = 0;
virtual QString lastExternalIPv6Address() const = 0;
virtual qint64 freeDiskSpace() const = 0;
signals:
void startupProgressUpdated(int progress);
void addTorrentFailed(const InfoHash &infoHash, const AddTorrentError &reason);
void addTorrentFailed(const InfoHash &infoHash, const QString &reason);
void allTorrentsFinished();
void categoryAdded(const QString &categoryName);
void categoryRemoved(const QString &categoryName);
@ -524,6 +519,5 @@ namespace BitTorrent
void trackerSuccess(Torrent *torrent, const QString &tracker);
void trackerWarning(Torrent *torrent, const QString &tracker);
void trackerEntryStatusesUpdated(Torrent *torrent, const QHash<QString, TrackerEntryStatus> &updatedTrackers);
void freeDiskSpaceChecked(qint64 result);
};
}

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -76,7 +76,6 @@
#include <QUuid>
#include "base/algorithm.h"
#include "base/freediskspacechecker.h"
#include "base/global.h"
#include "base/logger.h"
#include "base/net/downloadmanager.h"
@ -116,7 +115,6 @@ using namespace BitTorrent;
const Path CATEGORIES_FILE_NAME {u"categories.json"_s};
const int MAX_PROCESSING_RESUMEDATA_COUNT = 50;
const std::chrono::seconds FREEDISKSPACE_CHECK_TIMEOUT = 30s;
namespace
{
@ -365,7 +363,7 @@ QString Session::subcategoryName(const QString &category)
{
const int sepIndex = category.lastIndexOf(u'/');
if (sepIndex >= 0)
return category.sliced(sepIndex + 1);
return category.mid(sepIndex + 1);
return category;
}
@ -374,7 +372,7 @@ QString Session::parentCategoryName(const QString &category)
{
const int sepIndex = category.lastIndexOf(u'/');
if (sepIndex >= 0)
return category.first(sepIndex);
return category.left(sepIndex);
return {};
}
@ -385,7 +383,7 @@ QStringList Session::expandCategory(const QString &category)
int index = 0;
while ((index = category.indexOf(u'/', index)) >= 0)
{
result << category.first(index);
result << category.left(index);
++index;
}
result << category;
@ -460,7 +458,6 @@ SessionImpl::SessionImpl(QObject *parent)
, m_isUTPRateLimited(BITTORRENT_SESSION_KEY(u"uTPRateLimited"_s), true)
, m_utpMixedMode(BITTORRENT_SESSION_KEY(u"uTPMixedMode"_s), MixedModeAlgorithm::TCP
, clampValue(MixedModeAlgorithm::TCP, MixedModeAlgorithm::Proportional))
, m_hostnameCacheTTL(BITTORRENT_SESSION_KEY(u"HostnameCacheTTL"_s), 1200)
, m_IDNSupportEnabled(BITTORRENT_SESSION_KEY(u"IDNSupportEnabled"_s), false)
, m_multiConnectionsPerIpEnabled(BITTORRENT_SESSION_KEY(u"MultiConnectionsPerIp"_s), false)
, m_validateHTTPSTrackerCertificate(BITTORRENT_SESSION_KEY(u"ValidateHTTPSTrackerCertificate"_s), true)
@ -471,10 +468,8 @@ SessionImpl::SessionImpl(QObject *parent)
, m_isAddTrackersFromURLEnabled(BITTORRENT_SESSION_KEY(u"AddTrackersFromURLEnabled"_s), false)
, m_additionalTrackersURL(BITTORRENT_SESSION_KEY(u"AdditionalTrackersURL"_s))
, m_globalMaxRatio(BITTORRENT_SESSION_KEY(u"GlobalMaxRatio"_s), -1, [](qreal r) { return r < 0 ? -1. : r;})
, m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxSeedingMinutes"_s), Torrent::NO_SEEDING_TIME_LIMIT
, clampValue(Torrent::NO_SEEDING_TIME_LIMIT, Torrent::MAX_SEEDING_TIME))
, m_globalMaxInactiveSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxInactiveSeedingMinutes"_s), Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT
, clampValue(Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT, Torrent::MAX_INACTIVE_SEEDING_TIME))
, m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxSeedingMinutes"_s), -1, lowerLimited(-1))
, m_globalMaxInactiveSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxInactiveSeedingMinutes"_s), -1, lowerLimited(-1))
, m_isAddTorrentToQueueTop(BITTORRENT_SESSION_KEY(u"AddTorrentToTopOfQueue"_s), false)
, m_isAddTorrentStopped(BITTORRENT_SESSION_KEY(u"AddTorrentStopped"_s), false)
, m_torrentStopCondition(BITTORRENT_SESSION_KEY(u"TorrentStopCondition"_s), Torrent::StopCondition::None)
@ -545,8 +540,6 @@ SessionImpl::SessionImpl(QObject *parent)
, m_ioThread {new QThread}
, m_asyncWorker {new QThreadPool(this)}
, m_recentErroredTorrentsTimer {new QTimer(this)}
, m_freeDiskSpaceChecker {new FreeDiskSpaceChecker(savePath())}
, m_freeDiskSpaceCheckingTimer {new QTimer(this)}
{
// It is required to perform async access to libtorrent sequentially
m_asyncWorker->setMaxThreadCount(1);
@ -607,18 +600,6 @@ SessionImpl::SessionImpl(QObject *parent)
, &Net::ProxyConfigurationManager::proxyConfigurationChanged
, this, &SessionImpl::configureDeferred);
m_freeDiskSpaceChecker->moveToThread(m_ioThread.get());
connect(m_ioThread.get(), &QThread::finished, m_freeDiskSpaceChecker, &QObject::deleteLater);
m_freeDiskSpaceCheckingTimer->setInterval(FREEDISKSPACE_CHECK_TIMEOUT);
m_freeDiskSpaceCheckingTimer->setSingleShot(true);
connect(m_freeDiskSpaceCheckingTimer, &QTimer::timeout, m_freeDiskSpaceChecker, &FreeDiskSpaceChecker::check);
connect(m_freeDiskSpaceChecker, &FreeDiskSpaceChecker::checked, this, [this](const qint64 value)
{
m_freeDiskSpace = value;
m_freeDiskSpaceCheckingTimer->start();
emit freeDiskSpaceChecked(m_freeDiskSpace);
});
m_fileSearcher = new FileSearcher;
m_fileSearcher->moveToThread(m_ioThread.get());
connect(m_ioThread.get(), &QThread::finished, m_fileSearcher, &QObject::deleteLater);
@ -632,8 +613,6 @@ SessionImpl::SessionImpl(QObject *parent)
m_ioThread->setObjectName("SessionImpl m_ioThread");
m_ioThread->start();
QMetaObject::invokeMethod(m_freeDiskSpaceChecker, &FreeDiskSpaceChecker::check);
initMetrics();
loadStatistics();
@ -995,25 +974,23 @@ bool SessionImpl::editCategory(const QString &name, const CategoryOptions &optio
if (options == currentOptions)
return false;
currentOptions = options;
storeCategories();
if (isDisableAutoTMMWhenCategorySavePathChanged())
{
// This should be done before changing the category options
// to prevent the torrent from being moved at the new save path.
for (TorrentImpl *const torrent : asConst(m_torrents))
{
if (torrent->category() == name)
torrent->setAutoTMMEnabled(false);
}
}
currentOptions = options;
storeCategories();
for (TorrentImpl *const torrent : asConst(m_torrents))
else
{
if (torrent->category() == name)
torrent->handleCategoryOptionsChanged();
for (TorrentImpl *const torrent : asConst(m_torrents))
{
if (torrent->category() == name)
torrent->handleCategoryOptionsChanged();
}
}
emit categoryOptionsChanged(name);
@ -1257,7 +1234,8 @@ int SessionImpl::globalMaxSeedingMinutes() const
void SessionImpl::setGlobalMaxSeedingMinutes(int minutes)
{
minutes = std::clamp(minutes, Torrent::NO_SEEDING_TIME_LIMIT, Torrent::MAX_SEEDING_TIME);
if (minutes < 0)
minutes = -1;
if (minutes != globalMaxSeedingMinutes())
{
@ -1273,7 +1251,7 @@ int SessionImpl::globalMaxInactiveSeedingMinutes() const
void SessionImpl::setGlobalMaxInactiveSeedingMinutes(int minutes)
{
minutes = std::clamp(minutes, Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT, Torrent::MAX_INACTIVE_SEEDING_TIME);
minutes = std::max(minutes, -1);
if (minutes != globalMaxInactiveSeedingMinutes())
{
@ -2076,8 +2054,6 @@ lt::settings_pack SessionImpl::loadLTSettings() const
break;
}
settingsPack.set_int(lt::settings_pack::resolver_cache_timeout, hostnameCacheTTL());
settingsPack.set_bool(lt::settings_pack::allow_idna, isIDNSupportEnabled());
settingsPack.set_bool(lt::settings_pack::allow_multiple_connections_per_ip, multiConnectionsPerIpEnabled());
@ -2485,12 +2461,12 @@ bool SessionImpl::removeTorrent(const TorrentID &id, const TorrentRemoveOption d
m_removingTorrents[torrentID] = {torrentName, torrent->actualStorageLocation(), {}, deleteOption};
const lt::torrent_handle nativeHandle {torrent->nativeHandle()};
const auto iter = std::find_if(m_moveStorageQueue.cbegin(), m_moveStorageQueue.cend()
const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
, [&nativeHandle](const MoveStorageJob &job)
{
return job.torrentHandle == nativeHandle;
});
if (iter != m_moveStorageQueue.cend())
if (iter != m_moveStorageQueue.end())
{
// We shouldn't actually remove torrent until existing "move storage jobs" are done
torrentQueuePositionBottom(nativeHandle);
@ -2510,12 +2486,12 @@ bool SessionImpl::removeTorrent(const TorrentID &id, const TorrentRemoveOption d
{
// Delete "move storage job" for the deleted torrent
// (note: we shouldn't delete active job)
const auto iter = std::find_if((m_moveStorageQueue.cbegin() + 1), m_moveStorageQueue.cend()
const auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
, [torrent](const MoveStorageJob &job)
{
return job.torrentHandle == torrent->nativeHandle();
});
if (iter != m_moveStorageQueue.cend())
if (iter != m_moveStorageQueue.end())
m_moveStorageQueue.erase(iter);
}
@ -2532,8 +2508,8 @@ bool SessionImpl::removeTorrent(const TorrentID &id, const TorrentRemoveOption d
bool SessionImpl::cancelDownloadMetadata(const TorrentID &id)
{
const auto downloadedMetadataIter = m_downloadedMetadata.constFind(id);
if (downloadedMetadataIter == m_downloadedMetadata.cend())
const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
if (downloadedMetadataIter == m_downloadedMetadata.end())
return false;
const lt::torrent_handle nativeHandle = downloadedMetadataIter.value();
@ -2775,10 +2751,7 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
// We should not add the torrent if it is already
// processed or is pending to add to session
if (m_loadingTorrents.contains(id) || (infoHash.isHybrid() && m_loadingTorrents.contains(altID)))
{
emit addTorrentFailed(infoHash, {AddTorrentError::DuplicateTorrent, tr("Duplicate torrent")});
return false;
}
if (Torrent *torrent = findTorrent(infoHash))
{
@ -2792,20 +2765,16 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
if (!isMergeTrackersEnabled())
{
const QString message = tr("Merging of trackers is disabled");
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: \"%1\". Torrent infohash: %2. Result: %3")
.arg(torrent->name(), torrent->infoHash().toString(), message));
emit addTorrentFailed(infoHash, {AddTorrentError::DuplicateTorrent, message});
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
.arg(torrent->name(), tr("Merging of trackers is disabled")));
return false;
}
const bool isPrivate = torrent->isPrivate() || (hasMetadata && source.info()->isPrivate());
if (isPrivate)
{
const QString message = tr("Trackers cannot be merged because it is a private torrent");
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: \"%1\". Torrent infohash: %2. Result: %3")
.arg(torrent->name(), torrent->infoHash().toString(), message));
emit addTorrentFailed(infoHash, {AddTorrentError::DuplicateTorrent, message});
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
.arg(torrent->name(), tr("Trackers cannot be merged because it is a private torrent")));
return false;
}
@ -2813,10 +2782,8 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
torrent->addTrackers(source.trackers());
torrent->addUrlSeeds(source.urlSeeds());
const QString message = tr("Trackers are merged from new source");
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: \"%1\". Torrent infohash: %2. Result: %3")
.arg(torrent->name(), torrent->infoHash().toString(), message));
emit addTorrentFailed(infoHash, {AddTorrentError::DuplicateTorrent, message});
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
.arg(torrent->name(), tr("Trackers are merged from new source")));
return false;
}
@ -3280,9 +3247,6 @@ void SessionImpl::setSavePath(const Path &path)
if (isDisableAutoTMMWhenDefaultSavePathChanged())
{
// This should be done before changing the save path
// to prevent the torrent from being moved at the new save path.
QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
{
@ -3302,14 +3266,6 @@ void SessionImpl::setSavePath(const Path &path)
m_savePath = newPath;
for (TorrentImpl *const torrent : asConst(m_torrents))
torrent->handleCategoryOptionsChanged();
m_freeDiskSpace = -1;
m_freeDiskSpaceCheckingTimer->stop();
QMetaObject::invokeMethod(m_freeDiskSpaceChecker, [checker = m_freeDiskSpaceChecker, pathToCheck = m_savePath]
{
checker->setPathToCheck(pathToCheck);
checker->check();
});
}
void SessionImpl::setDownloadPath(const Path &path)
@ -3320,9 +3276,6 @@ void SessionImpl::setDownloadPath(const Path &path)
if (isDisableAutoTMMWhenDefaultSavePathChanged())
{
// This should be done before changing the save path
// to prevent the torrent from being moved at the new save path.
QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
{
@ -5071,20 +5024,6 @@ void SessionImpl::setUtpMixedMode(const MixedModeAlgorithm mode)
configureDeferred();
}
int SessionImpl::hostnameCacheTTL() const
{
return m_hostnameCacheTTL;
}
void SessionImpl::setHostnameCacheTTL(const int value)
{
if (value == hostnameCacheTTL())
return;
m_hostnameCacheTTL = value;
configureDeferred();
}
bool SessionImpl::isIDNSupportEnabled() const
{
return m_IDNSupportEnabled;
@ -5174,11 +5113,6 @@ QString SessionImpl::lastExternalIPv6Address() const
return m_lastExternalIPv6Address;
}
qint64 SessionImpl::freeDiskSpace() const
{
return m_freeDiskSpace;
}
bool SessionImpl::isListening() const
{
return m_nativeSessionExtension->isSessionListening();
@ -5334,8 +5268,8 @@ void SessionImpl::handleTorrentFinished(TorrentImpl *const torrent)
void SessionImpl::handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data)
{
m_resumeDataStorage->store(torrent->id(), data);
const auto iter = m_changedTorrentIDs.constFind(torrent->id());
if (iter != m_changedTorrentIDs.cend())
const auto iter = m_changedTorrentIDs.find(torrent->id());
if (iter != m_changedTorrentIDs.end())
{
m_resumeDataStorage->remove(iter.value());
m_changedTorrentIDs.erase(iter);
@ -5368,11 +5302,11 @@ bool SessionImpl::addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &new
const lt::torrent_handle torrentHandle = torrent->nativeHandle();
const Path currentLocation = torrent->actualStorageLocation();
const bool torrentHasActiveJob = !m_moveStorageQueue.isEmpty() && (m_moveStorageQueue.constFirst().torrentHandle == torrentHandle);
const bool torrentHasActiveJob = !m_moveStorageQueue.isEmpty() && (m_moveStorageQueue.first().torrentHandle == torrentHandle);
if (m_moveStorageQueue.size() > 1)
{
auto iter = std::find_if((m_moveStorageQueue.cbegin() + 1), m_moveStorageQueue.cend()
auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
, [&torrentHandle](const MoveStorageJob &job)
{
return job.torrentHandle == torrentHandle;
@ -5391,7 +5325,7 @@ bool SessionImpl::addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &new
{
// if there is active job for this torrent prevent creating meaningless
// job that will move torrent to the same location as current one
if (m_moveStorageQueue.constFirst().path == newPath)
if (m_moveStorageQueue.first().path == newPath)
{
LogMsg(tr("Failed to enqueue torrent move. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\". Reason: torrent is currently moving to the destination")
.arg(torrent->name(), currentLocation.toString(), newPath.toString()));
@ -5436,7 +5370,7 @@ void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
{
const MoveStorageJob finishedJob = m_moveStorageQueue.takeFirst();
if (!m_moveStorageQueue.isEmpty())
moveTorrentStorage(m_moveStorageQueue.constFirst());
moveTorrentStorage(m_moveStorageQueue.first());
const auto iter = std::find_if(m_moveStorageQueue.cbegin(), m_moveStorageQueue.cend()
, [&finishedJob](const MoveStorageJob &job)
@ -5762,16 +5696,14 @@ void SessionImpl::handleAddTorrentAlert(const lt::add_torrent_alert *alert)
#else
const InfoHash infoHash {(hasMetadata ? params.ti->info_hash() : params.info_hash)};
#endif
if (const auto loadingTorrentsIter = m_loadingTorrents.constFind(TorrentID::fromInfoHash(infoHash))
; loadingTorrentsIter != m_loadingTorrents.cend())
if (const auto loadingTorrentsIter = m_loadingTorrents.find(TorrentID::fromInfoHash(infoHash))
; loadingTorrentsIter != m_loadingTorrents.end())
{
const AddTorrentError::Kind errorKind = (alert->error == lt::errors::duplicate_torrent)
? AddTorrentError::DuplicateTorrent : AddTorrentError::Other;
emit addTorrentFailed(infoHash, {errorKind, msg});
emit addTorrentFailed(infoHash, msg);
m_loadingTorrents.erase(loadingTorrentsIter);
}
else if (const auto downloadedMetadataIter = m_downloadedMetadata.constFind(TorrentID::fromInfoHash(infoHash))
; downloadedMetadataIter != m_downloadedMetadata.cend())
else if (const auto downloadedMetadataIter = m_downloadedMetadata.find(TorrentID::fromInfoHash(infoHash))
; downloadedMetadataIter != m_downloadedMetadata.end())
{
m_downloadedMetadata.erase(downloadedMetadataIter);
if (infoHash.isHybrid())
@ -5792,8 +5724,8 @@ void SessionImpl::handleAddTorrentAlert(const lt::add_torrent_alert *alert)
#endif
const auto torrentID = TorrentID::fromInfoHash(infoHash);
if (const auto loadingTorrentsIter = m_loadingTorrents.constFind(torrentID)
; loadingTorrentsIter != m_loadingTorrents.cend())
if (const auto loadingTorrentsIter = m_loadingTorrents.find(torrentID)
; loadingTorrentsIter != m_loadingTorrents.end())
{
const LoadTorrentParams params = loadingTorrentsIter.value();
m_loadingTorrents.erase(loadingTorrentsIter);
@ -6056,7 +5988,7 @@ void SessionImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert
const TorrentID torrentID {alert->handle.info_hash()};
bool found = false;
if (const auto iter = m_downloadedMetadata.constFind(torrentID); iter != m_downloadedMetadata.cend())
if (const auto iter = m_downloadedMetadata.find(torrentID); iter != m_downloadedMetadata.end())
{
found = true;
m_downloadedMetadata.erase(iter);
@ -6066,7 +5998,7 @@ void SessionImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert
if (infoHash.isHybrid())
{
const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
if (const auto iter = m_downloadedMetadata.constFind(altID); iter != m_downloadedMetadata.cend())
if (const auto iter = m_downloadedMetadata.find(altID); iter != m_downloadedMetadata.end())
{
found = true;
m_downloadedMetadata.erase(iter);
@ -6328,7 +6260,7 @@ void SessionImpl::handleStorageMovedAlert(const lt::storage_moved_alert *alert)
{
Q_ASSERT(!m_moveStorageQueue.isEmpty());
const MoveStorageJob &currentJob = m_moveStorageQueue.constFirst();
const MoveStorageJob &currentJob = m_moveStorageQueue.first();
Q_ASSERT(currentJob.torrentHandle == alert->handle);
const Path newPath {QString::fromUtf8(alert->storage_path())};
@ -6351,7 +6283,7 @@ void SessionImpl::handleStorageMovedFailedAlert(const lt::storage_moved_failed_a
{
Q_ASSERT(!m_moveStorageQueue.isEmpty());
const MoveStorageJob &currentJob = m_moveStorageQueue.constFirst();
const MoveStorageJob &currentJob = m_moveStorageQueue.first();
Q_ASSERT(currentJob.torrentHandle == alert->handle);
#ifdef QBT_USES_LIBTORRENT2
@ -6565,8 +6497,8 @@ void SessionImpl::updateTrackerEntryStatuses(lt::torrent_handle torrentHandle)
void SessionImpl::handleRemovedTorrent(const TorrentID &torrentID, const QString &partfileRemoveError)
{
const auto removingTorrentDataIter = m_removingTorrents.constFind(torrentID);
if (removingTorrentDataIter == m_removingTorrents.cend())
const auto removingTorrentDataIter = m_removingTorrents.find(torrentID);
if (removingTorrentDataIter == m_removingTorrents.end())
return;
if (!partfileRemoveError.isEmpty())

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -64,7 +64,6 @@ class QUrl;
class BandwidthScheduler;
class FileSearcher;
class FilterParserThread;
class FreeDiskSpaceChecker;
class NativeSessionExtension;
namespace BitTorrent
@ -391,8 +390,6 @@ namespace BitTorrent
void setUTPRateLimited(bool limited) override;
MixedModeAlgorithm utpMixedMode() const override;
void setUtpMixedMode(MixedModeAlgorithm mode) override;
int hostnameCacheTTL() const override;
void setHostnameCacheTTL(int value) override;
bool isIDNSupportEnabled() const override;
void setIDNSupportEnabled(bool enabled) override;
bool multiConnectionsPerIpEnabled() const override;
@ -451,8 +448,6 @@ namespace BitTorrent
QString lastExternalIPv4Address() const override;
QString lastExternalIPv6Address() const override;
qint64 freeDiskSpace() const override;
// Torrent interface
void handleTorrentResumeDataRequested(const TorrentImpl *torrent);
void handleTorrentShareLimitChanged(TorrentImpl *torrent);
@ -688,7 +683,6 @@ namespace BitTorrent
CachedSettingValue<BTProtocol> m_btProtocol;
CachedSettingValue<bool> m_isUTPRateLimited;
CachedSettingValue<MixedModeAlgorithm> m_utpMixedMode;
CachedSettingValue<int> m_hostnameCacheTTL;
CachedSettingValue<bool> m_IDNSupportEnabled;
CachedSettingValue<bool> m_multiConnectionsPerIpEnabled;
CachedSettingValue<bool> m_validateHTTPSTrackerCertificate;
@ -856,10 +850,6 @@ namespace BitTorrent
QList<TorrentImpl *> m_pendingFinishedTorrents;
FreeDiskSpaceChecker *m_freeDiskSpaceChecker = nullptr;
QTimer *m_freeDiskSpaceCheckingTimer = nullptr;
qint64 m_freeDiskSpace = -1;
friend void Session::initInstance();
friend void Session::freeInstance();
friend Session *Session::instance();

View file

@ -1615,20 +1615,18 @@ bool TorrentImpl::setCategory(const QString &category)
if (!category.isEmpty() && !m_session->categories().contains(category))
return false;
if (m_session->isDisableAutoTMMWhenCategoryChanged())
{
// This should be done before changing the category name
// to prevent the torrent from being moved at the path of new category.
setAutoTMMEnabled(false);
}
const QString oldCategory = m_category;
m_category = category;
deferredRequestResumeData();
m_session->handleTorrentCategoryChanged(this, oldCategory);
if (m_useAutoTMM)
adjustStorageLocation();
{
if (!m_session->isDisableAutoTMMWhenCategoryChanged())
adjustStorageLocation();
else
setAutoTMMEnabled(false);
}
}
return true;

View file

@ -380,7 +380,7 @@ void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq)
{
// Reached max size, remove a random torrent
if (m_torrents.size() >= MAX_TORRENTS)
m_torrents.erase(m_torrents.cbegin());
m_torrents.erase(m_torrents.begin());
}
m_torrents[announceReq.torrentID].setPeer(announceReq.peer);

View file

@ -155,11 +155,7 @@ void Connection::read()
sendResponse(resp);
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
m_receivedData.slice(result.frameSize);
#else
m_receivedData.remove(0, result.frameSize);
#endif
}
break;

View file

@ -69,8 +69,8 @@ namespace
return false;
}
const QString name = line.first(i).trimmed().toString().toLower();
const QString value = line.sliced(i + 1).trimmed().toString();
const QString name = line.left(i).trimmed().toString().toLower();
const QString value = line.mid(i + 1).trimmed().toString();
out[name] = value;
return true;
@ -204,15 +204,15 @@ bool RequestParser::parseRequestLine(const QString &line)
m_request.method = match.captured(1);
// Request Target
const QByteArray url {match.capturedView(2).toLatin1()};
const QByteArray url {match.captured(2).toLatin1()};
const int sepPos = url.indexOf('?');
const QByteArrayView pathComponent = ((sepPos == -1) ? url : QByteArrayView(url).first(sepPos));
const QByteArrayView pathComponent = ((sepPos == -1) ? url : QByteArrayView(url).mid(0, sepPos));
m_request.path = QString::fromUtf8(QByteArray::fromPercentEncoding(asQByteArray(pathComponent)));
if (sepPos >= 0)
{
const QByteArrayView query = QByteArrayView(url).sliced(sepPos + 1);
const QByteArrayView query = QByteArrayView(url).mid(sepPos + 1);
// [rfc3986] 2.4 When to Encode or Decode
// URL components should be separated before percent-decoding
@ -221,8 +221,8 @@ bool RequestParser::parseRequestLine(const QString &line)
const int eqCharPos = param.indexOf('=');
if (eqCharPos <= 0) continue; // ignores params without name
const QByteArrayView nameComponent = param.first(eqCharPos);
const QByteArrayView valueComponent = param.sliced(eqCharPos + 1);
const QByteArrayView nameComponent = param.mid(0, eqCharPos);
const QByteArrayView valueComponent = param.mid(eqCharPos + 1);
const QString paramName = QString::fromUtf8(
QByteArray::fromPercentEncoding(asQByteArray(nameComponent)).replace('+', ' '));
const QByteArray paramValue = QByteArray::fromPercentEncoding(asQByteArray(valueComponent)).replace('+', ' ');
@ -270,7 +270,7 @@ bool RequestParser::parsePostMessage(const QByteArrayView data)
return false;
}
const QByteArray delimiter = Utils::String::unquote(QStringView(contentType).sliced(idx + boundaryFieldName.size())).toLatin1();
const QByteArray delimiter = Utils::String::unquote(QStringView(contentType).mid(idx + boundaryFieldName.size())).toLatin1();
if (delimiter.isEmpty())
{
qWarning() << Q_FUNC_INFO << "boundary delimiter field empty!";
@ -279,7 +279,7 @@ bool RequestParser::parsePostMessage(const QByteArrayView data)
// split data by "dash-boundary"
const QByteArray dashDelimiter = QByteArray("--") + delimiter + CRLF;
QList<QByteArrayView> multipart = splitToViews(data, dashDelimiter);
QList<QByteArrayView> multipart = splitToViews(data, dashDelimiter, Qt::SkipEmptyParts);
if (multipart.isEmpty())
{
qWarning() << Q_FUNC_INFO << "multipart empty";
@ -310,8 +310,8 @@ bool RequestParser::parseFormData(const QByteArrayView data)
return false;
}
const QString headers = QString::fromLatin1(data.first(eohPos));
const QByteArrayView payload = viewWithoutEndingWith(data.sliced((eohPos + EOH.size())), CRLF);
const QString headers = QString::fromLatin1(data.mid(0, eohPos));
const QByteArrayView payload = viewWithoutEndingWith(data.mid((eohPos + EOH.size()), data.size()), CRLF);
HeaderMap headersMap;
const QList<QStringView> headerLines = QStringView(headers).split(QString::fromLatin1(CRLF), Qt::SkipEmptyParts);
@ -328,8 +328,8 @@ bool RequestParser::parseFormData(const QByteArrayView data)
if (idx < 0)
continue;
const QString name = directive.first(idx).trimmed().toString().toLower();
const QString value = Utils::String::unquote(directive.sliced(idx + 1).trimmed()).toString();
const QString name = directive.left(idx).trimmed().toString().toLower();
const QString value = Utils::String::unquote(directive.mid(idx + 1).trimmed()).toString();
headersMap[name] = value;
}
}

View file

@ -30,10 +30,8 @@
#pragma once
#include <QByteArray>
#include <QHash>
#include <QHostAddress>
#include <QList>
#include <QMap>
#include <QString>
#include "base/global.h"

View file

@ -96,9 +96,9 @@ void DNSUpdater::ipRequestFinished(const DownloadResult &result)
const QRegularExpressionMatch ipRegexMatch = QRegularExpression(u"Current IP Address:\\s+([^<]+)</body>"_s).match(QString::fromUtf8(result.data));
if (ipRegexMatch.hasMatch())
{
const QString ipStr = ipRegexMatch.captured(1);
QString ipStr = ipRegexMatch.captured(1);
qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ipStr;
const QHostAddress newIp {ipStr};
QHostAddress newIp(ipStr);
if (!newIp.isNull())
{
if (m_lastIP != newIp)

View file

@ -265,37 +265,38 @@ void Net::DownloadManager::applyProxySettings()
const auto *proxyManager = ProxyConfigurationManager::instance();
const ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
switch (proxyConfig.type)
m_proxy = QNetworkProxy(QNetworkProxy::NoProxy);
if ((proxyConfig.type == Net::ProxyType::None) || (proxyConfig.type == ProxyType::SOCKS4))
return;
// Proxy enabled
if (proxyConfig.type == ProxyType::SOCKS5)
{
case Net::ProxyType::None:
case Net::ProxyType::SOCKS4:
m_proxy = QNetworkProxy(QNetworkProxy::NoProxy);
break;
qDebug() << Q_FUNC_INFO << "using SOCKS proxy";
m_proxy.setType(QNetworkProxy::Socks5Proxy);
}
else
{
qDebug() << Q_FUNC_INFO << "using HTTP proxy";
m_proxy.setType(QNetworkProxy::HttpProxy);
}
case Net::ProxyType::HTTP:
m_proxy = QNetworkProxy(
QNetworkProxy::HttpProxy
, proxyConfig.ip
, proxyConfig.port
, (proxyConfig.authEnabled ? proxyConfig.username : QString())
, (proxyConfig.authEnabled ? proxyConfig.password : QString()));
m_proxy.setCapabilities(proxyConfig.hostnameLookupEnabled
? (m_proxy.capabilities() | QNetworkProxy::HostNameLookupCapability)
: (m_proxy.capabilities() & ~QNetworkProxy::HostNameLookupCapability));
break;
m_proxy.setHostName(proxyConfig.ip);
m_proxy.setPort(proxyConfig.port);
case Net::ProxyType::SOCKS5:
m_proxy = QNetworkProxy(
QNetworkProxy::Socks5Proxy
, proxyConfig.ip
, proxyConfig.port
, (proxyConfig.authEnabled ? proxyConfig.username : QString())
, (proxyConfig.authEnabled ? proxyConfig.password : QString()));
m_proxy.setCapabilities(proxyConfig.hostnameLookupEnabled
? (m_proxy.capabilities() | QNetworkProxy::HostNameLookupCapability)
: (m_proxy.capabilities() & ~QNetworkProxy::HostNameLookupCapability));
break;
};
// Authentication?
if (proxyConfig.authEnabled)
{
qDebug("Proxy requires authentication, authenticating...");
m_proxy.setUser(proxyConfig.username);
m_proxy.setPassword(proxyConfig.password);
}
if (proxyConfig.hostnameLookupEnabled)
m_proxy.setCapabilities(m_proxy.capabilities() | QNetworkProxy::HostNameLookupCapability);
else
m_proxy.setCapabilities(m_proxy.capabilities() & ~QNetworkProxy::HostNameLookupCapability);
}
void Net::DownloadManager::processWaitingJobs(const ServiceID &serviceID)

View file

@ -148,7 +148,8 @@ void Smtp::sendMail(const QString &from, const QString &to, const QString &subje
// Encode the body in base64
QString crlfBody = body;
const QByteArray b = crlfBody.replace(u"\n"_s, u"\r\n"_s).toUtf8().toBase64();
for (int i = 0, end = b.length(); i < end; i += 78)
const int ct = b.length();
for (int i = 0; i < ct; i += 78)
m_message += b.mid(i, 78);
m_from = from;
m_rcpt = to;
@ -189,12 +190,8 @@ void Smtp::readyRead()
{
const int pos = m_buffer.indexOf("\r\n");
if (pos < 0) return; // Loop exit condition
const QByteArray line = m_buffer.first(pos);
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
m_buffer.slice(pos + 2);
#else
const QByteArray line = m_buffer.left(pos);
m_buffer.remove(0, (pos + 2));
#endif
qDebug() << "Response line:" << line;
// Extract response code
const QByteArray code = line.left(3);

View file

@ -94,13 +94,7 @@ bool Path::isValid() const
#if defined(Q_OS_WIN)
QStringView view = m_pathStr;
if (hasDriveLetter(view))
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
view.slice(3);
#else
view = view.sliced(3);
#endif
}
view = view.mid(3);
// \\37 is using base-8 number system
const QRegularExpression regex {u"[\\0-\\37:?\"*<>|]"_s};
@ -153,9 +147,9 @@ Path Path::rootItem() const
#ifdef Q_OS_WIN
// should be `c:/` instead of `c:`
if ((slashIndex == 2) && hasDriveLetter(m_pathStr))
return createUnchecked(m_pathStr.first(slashIndex + 1));
return createUnchecked(m_pathStr.left(slashIndex + 1));
#endif
return createUnchecked(m_pathStr.first(slashIndex));
return createUnchecked(m_pathStr.left(slashIndex));
}
Path Path::parentPath() const
@ -173,9 +167,9 @@ Path Path::parentPath() const
// should be `c:/` instead of `c:`
// Windows "drive letter" is limited to one alphabet
if ((slashIndex == 2) && hasDriveLetter(m_pathStr))
return (m_pathStr.size() == 3) ? Path() : createUnchecked(m_pathStr.first(slashIndex + 1));
return (m_pathStr.size() == 3) ? Path() : createUnchecked(m_pathStr.left(slashIndex + 1));
#endif
return createUnchecked(m_pathStr.first(slashIndex));
return createUnchecked(m_pathStr.left(slashIndex));
}
QString Path::filename() const
@ -184,7 +178,7 @@ QString Path::filename() const
if (slashIndex == -1)
return m_pathStr;
return m_pathStr.sliced(slashIndex + 1);
return m_pathStr.mid(slashIndex + 1);
}
QString Path::extension() const
@ -194,9 +188,9 @@ QString Path::extension() const
return (u"." + suffix);
const int slashIndex = m_pathStr.lastIndexOf(u'/');
const auto filename = QStringView(m_pathStr).sliced(slashIndex + 1);
const auto filename = QStringView(m_pathStr).mid(slashIndex + 1);
const int dotIndex = filename.lastIndexOf(u'.', -2);
return ((dotIndex == -1) ? QString() : filename.sliced(dotIndex).toString());
return ((dotIndex == -1) ? QString() : filename.mid(dotIndex).toString());
}
bool Path::hasExtension(const QStringView ext) const
@ -299,7 +293,7 @@ Path Path::commonPath(const Path &left, const Path &right)
if (commonItemsCount > 0)
commonPathSize += (commonItemsCount - 1); // size of intermediate separators
return Path::createUnchecked(left.m_pathStr.first(commonPathSize));
return Path::createUnchecked(left.m_pathStr.left(commonPathSize));
}
Path Path::findRootFolder(const PathList &filePaths)
@ -328,13 +322,7 @@ void Path::stripRootFolder(PathList &filePaths)
return;
for (Path &filePath : filePaths)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
filePath.m_pathStr.slice(commonRootFolder.m_pathStr.size() + 1);
#else
filePath.m_pathStr.remove(0, (commonRootFolder.m_pathStr.size() + 1));
#endif
}
}
void Path::addRootFolder(PathList &filePaths, const Path &rootFolder)

View file

@ -359,19 +359,6 @@ void Preferences::setStatusbarDisplayed(const bool displayed)
setValue(u"Preferences/General/StatusbarDisplayed"_s, displayed);
}
bool Preferences::isStatusbarFreeDiskSpaceDisplayed() const
{
return value(u"Preferences/General/StatusbarFreeDiskSpaceDisplayed"_s, false);
}
void Preferences::setStatusbarFreeDiskSpaceDisplayed(const bool displayed)
{
if (displayed == isStatusbarFreeDiskSpaceDisplayed())
return;
setValue(u"Preferences/General/StatusbarFreeDiskSpaceDisplayed"_s, displayed);
}
bool Preferences::isStatusbarExternalIPDisplayed() const
{
return value(u"Preferences/General/StatusbarExternalIPDisplayed"_s, false);
@ -2067,19 +2054,6 @@ void Preferences::setAddNewTorrentDialogSavePathHistoryLength(const int value)
setValue(u"AddNewTorrentDialog/SavePathHistoryLength"_s, clampedValue);
}
bool Preferences::isAddNewTorrentDialogAttached() const
{
return value(u"AddNewTorrentDialog/Attached"_s, false);
}
void Preferences::setAddNewTorrentDialogAttached(const bool attached)
{
if (attached == isAddNewTorrentDialogAttached())
return;
setValue(u"AddNewTorrentDialog/Attached"_s, attached);
}
void Preferences::apply()
{
if (SettingsStorage::instance()->save())

View file

@ -119,8 +119,6 @@ public:
void setHideZeroComboValues(int n);
bool isStatusbarDisplayed() const;
void setStatusbarDisplayed(bool displayed);
bool isStatusbarFreeDiskSpaceDisplayed() const;
void setStatusbarFreeDiskSpaceDisplayed(bool displayed);
bool isStatusbarExternalIPDisplayed() const;
void setStatusbarExternalIPDisplayed(bool displayed);
bool isToolbarDisplayed() const;
@ -435,8 +433,6 @@ public:
void setAddNewTorrentDialogTopLevel(bool value);
int addNewTorrentDialogSavePathHistoryLength() const;
void setAddNewTorrentDialogSavePathHistoryLength(int value);
bool isAddNewTorrentDialogAttached() const;
void setAddNewTorrentDialogAttached(bool attached);
public slots:
void setStatusFilterState(bool checked);

View file

@ -43,7 +43,6 @@
#include "base/addtorrentmanager.h"
#include "base/asyncfilestorage.h"
#include "base/bittorrent/addtorrenterror.h"
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrentdescriptor.h"
#include "base/global.h"
@ -376,24 +375,10 @@ void AutoDownloader::handleTorrentAdded(const QString &source)
}
}
void AutoDownloader::handleAddTorrentFailed(const QString &source, const BitTorrent::AddTorrentError &error)
void AutoDownloader::handleAddTorrentFailed(const QString &source)
{
const auto job = m_waitingJobs.take(source);
if (!job)
return;
if (error.kind == BitTorrent::AddTorrentError::DuplicateTorrent)
{
if (Feed *feed = Session::instance()->feedByURL(job->feedURL))
{
if (Article *article = feed->articleByGUID(job->articleData.value(Article::KeyId).toString()))
article->markAsRead();
}
}
else
{
// TODO: Re-schedule job here.
}
m_waitingJobs.remove(source);
// TODO: Re-schedule job here.
}
void AutoDownloader::handleNewArticle(const Article *article)

View file

@ -47,11 +47,6 @@ class Application;
class AsyncFileStorage;
struct ProcessingJob;
namespace BitTorrent
{
struct AddTorrentError;
}
namespace RSS
{
class Article;
@ -116,7 +111,7 @@ namespace RSS
private slots:
void process();
void handleTorrentAdded(const QString &source);
void handleAddTorrentFailed(const QString &url, const BitTorrent::AddTorrentError &error);
void handleAddTorrentFailed(const QString &url);
void handleNewArticle(const Article *article);
void handleFeedURLChanged(Feed *feed, const QString &oldURL);

View file

@ -184,10 +184,14 @@ QString computeEpisodeName(const QString &article)
for (int i = 1; i <= match.lastCapturedIndex(); ++i)
{
const QString cap = match.captured(i);
if (cap.isEmpty())
continue;
ret.append(cap);
bool isInt = false;
const int x = cap.toInt(&isInt);
ret.append(isInt ? QString::number(x) : cap);
}
return ret.join(u'x');
}
@ -289,26 +293,20 @@ bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString &articleTitl
if (!matcher.hasMatch())
return false;
const QStringView season {matcher.capturedView(1)};
const QList<QStringView> episodes {matcher.capturedView(2).split(u';')};
const QString season {matcher.captured(1)};
const QStringList episodes {matcher.captured(2).split(u';')};
const int seasonOurs {season.toInt()};
for (QStringView episode : episodes)
for (QString episode : episodes)
{
if (episode.isEmpty())
continue;
// We need to trim leading zeroes, but if it's all zeros then we want episode zero.
while ((episode.size() > 1) && episode.startsWith(u'0'))
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
episode.slice(1);
#else
episode = episode.sliced(1);
#endif
}
episode = episode.right(episode.size() - 1);
if (episode.contains(u'-'))
if (episode.indexOf(u'-') != -1)
{ // Range detected
const QString partialPattern1 {u"\\bs0?(\\d{1,4})[ -_\\.]?e(0?\\d{1,4})(?:\\D|\\b)"_s};
const QString partialPattern2 {u"\\b(\\d{1,4})x(0?\\d{1,4})(?:\\D|\\b)"_s};
@ -325,25 +323,24 @@ bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString &articleTitl
if (matched)
{
const int seasonTheirs {matcher.capturedView(1).toInt()};
const int episodeTheirs {matcher.capturedView(2).toInt()};
const int seasonTheirs {matcher.captured(1).toInt()};
const int episodeTheirs {matcher.captured(2).toInt()};
if (episode.endsWith(u'-'))
{ // Infinite range
const int episodeOurs {QStringView(episode).chopped(1).toInt()};
const int episodeOurs {QStringView(episode).left(episode.size() - 1).toInt()};
if (((seasonTheirs == seasonOurs) && (episodeTheirs >= episodeOurs)) || (seasonTheirs > seasonOurs))
return true;
}
else
{ // Normal range
const QList<QStringView> range {episode.split(u'-')};
const QStringList range {episode.split(u'-')};
Q_ASSERT(range.size() == 2);
if (range.first().toInt() > range.last().toInt())
continue; // Ignore this subrule completely
const int episodeOursFirst {range.first().toInt()};
const int episodeOursLast {range.last().toInt()};
if (episodeOursFirst > episodeOursLast)
continue; // Ignore this subrule completely
if ((seasonTheirs == seasonOurs) && ((episodeOursFirst <= episodeTheirs) && (episodeOursLast >= episodeTheirs)))
return true;
}

View file

@ -1,7 +1,7 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2024 Jonathan Ketchker
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
*
@ -56,22 +56,19 @@
const QString KEY_UID = u"uid"_s;
const QString KEY_URL = u"url"_s;
const QString KEY_REFRESHINTERVAL = u"refreshInterval"_s;
const QString KEY_TITLE = u"title"_s;
const QString KEY_LASTBUILDDATE = u"lastBuildDate"_s;
const QString KEY_ISLOADING = u"isLoading"_s;
const QString KEY_HASERROR = u"hasError"_s;
const QString KEY_ARTICLES = u"articles"_s;
using namespace std::chrono_literals;
using namespace RSS;
Feed::Feed(Session *session, const QUuid &uid, const QString &url, const QString &path, const std::chrono::seconds refreshInterval)
Feed::Feed(const QUuid &uid, const QString &url, const QString &path, Session *session)
: Item(path)
, m_session {session}
, m_uid {uid}
, m_url {url}
, m_refreshInterval {refreshInterval}
, m_session(session)
, m_uid(uid)
, m_url(url)
{
const auto uidHex = QString::fromLatin1(m_uid.toRfc4122().toHex());
m_dataFileName = Path(uidHex + u".json");
@ -330,9 +327,9 @@ bool Feed::addArticle(const QVariantHash &articleData)
// Insertion sort
const int maxArticles = m_session->maxArticlesPerFeed();
const auto lowerBound = std::lower_bound(m_articlesByDate.cbegin(), m_articlesByDate.cend()
, articleData.value(Article::KeyDate).toDateTime(), Article::articleDateRecentThan);
if ((lowerBound - m_articlesByDate.cbegin()) >= maxArticles)
const auto lowerBound = std::lower_bound(m_articlesByDate.begin(), m_articlesByDate.end()
, articleData.value(Article::KeyDate).toDateTime(), Article::articleDateRecentThan);
if ((lowerBound - m_articlesByDate.begin()) >= maxArticles)
return false; // we reach max articles
auto *article = new Article(this, articleData);
@ -465,20 +462,6 @@ Path Feed::iconPath() const
return m_iconPath;
}
std::chrono::seconds Feed::refreshInterval() const
{
return m_refreshInterval;
}
void Feed::setRefreshInterval(const std::chrono::seconds refreshInterval)
{
if (refreshInterval == m_refreshInterval)
return;
const std::chrono::seconds oldRefreshInterval = std::exchange(m_refreshInterval, refreshInterval);
emit refreshIntervalChanged(oldRefreshInterval);
}
void Feed::setURL(const QString &url)
{
const QString oldURL = m_url;
@ -491,8 +474,6 @@ QJsonValue Feed::toJsonValue(const bool withData) const
QJsonObject jsonObj;
jsonObj.insert(KEY_UID, uid().toString());
jsonObj.insert(KEY_URL, url());
if (refreshInterval() > 0s)
jsonObj.insert(KEY_REFRESHINTERVAL, static_cast<qint64>(refreshInterval().count()));
if (withData)
{

View file

@ -1,7 +1,7 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2024 Jonathan Ketchker
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
*
@ -31,8 +31,6 @@
#pragma once
#include <chrono>
#include <QtContainerFwd>
#include <QBasicTimer>
#include <QHash>
@ -70,7 +68,7 @@ namespace RSS
friend class Session;
Feed(Session *session, const QUuid &uid, const QString &url, const QString &path, std::chrono::seconds refreshInterval);
Feed(const QUuid &uid, const QString &url, const QString &path, Session *session);
~Feed() override;
public:
@ -89,9 +87,6 @@ namespace RSS
Article *articleByGUID(const QString &guid) const;
Path iconPath() const;
std::chrono::seconds refreshInterval() const;
void setRefreshInterval(std::chrono::seconds refreshInterval);
QJsonValue toJsonValue(bool withData = false) const override;
signals:
@ -99,7 +94,6 @@ namespace RSS
void titleChanged(Feed *feed = nullptr);
void stateChanged(Feed *feed = nullptr);
void urlChanged(const QString &oldURL);
void refreshIntervalChanged(std::chrono::seconds oldRefreshInterval);
private slots:
void handleSessionProcessingEnabledChanged(bool enabled);
@ -129,7 +123,6 @@ namespace RSS
Private::FeedSerializer *m_serializer = nullptr;
const QUuid m_uid;
QString m_url;
std::chrono::seconds m_refreshInterval;
QString m_title;
QString m_lastBuildDate;
bool m_hasError = false;

View file

@ -97,7 +97,7 @@ QStringList Item::expandPath(const QString &path)
int index = 0;
while ((index = path.indexOf(Item::PathSeparator, index)) >= 0)
{
result << path.first(index);
result << path.left(index);
++index;
}
result << path;
@ -108,11 +108,11 @@ QStringList Item::expandPath(const QString &path)
QString Item::parentPath(const QString &path)
{
const int pos = path.lastIndexOf(Item::PathSeparator);
return (pos >= 0) ? path.first(pos) : QString();
return (pos >= 0) ? path.left(pos) : QString();
}
QString Item::relativeName(const QString &path)
{
const int pos = path.lastIndexOf(Item::PathSeparator);
return (pos >= 0) ? path.sliced(pos + 1) : path;
int pos;
return ((pos = path.lastIndexOf(Item::PathSeparator)) >= 0 ? path.right(path.size() - (pos + 1)) : path);
}

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or

View file

@ -1,7 +1,7 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2017-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2024 Jonathan Ketchker
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
*
@ -56,7 +56,6 @@ const QString CONF_FOLDER_NAME = u"rss"_s;
const QString DATA_FOLDER_NAME = u"rss/articles"_s;
const QString FEEDS_FILE_NAME = u"feeds.json"_s;
using namespace std::chrono_literals;
using namespace RSS;
QPointer<Session> Session::m_instance = nullptr;
@ -95,10 +94,12 @@ Session::Session()
m_workingThread->start();
load();
m_refreshTimer.setSingleShot(true);
connect(&m_refreshTimer, &QTimer::timeout, this, &Session::refresh);
if (isProcessingEnabled())
{
m_refreshTimer.start(std::chrono::minutes(refreshInterval()));
refresh();
}
// Remove legacy/corrupted settings
// (at least on Windows, QSettings is case-insensitive and it can get
@ -137,20 +138,19 @@ Session *Session::instance()
return m_instance;
}
nonstd::expected<Folder *, QString> Session::addFolder(const QString &path)
nonstd::expected<void, QString> Session::addFolder(const QString &path)
{
const nonstd::expected<Folder *, QString> result = prepareItemDest(path);
if (!result)
return result.get_unexpected();
auto *destFolder = result.value();
auto *folder = new Folder(path);
addItem(folder, destFolder);
addItem(new Folder(path), destFolder);
store();
return folder;
return {};
}
nonstd::expected<Feed *, QString> Session::addFeed(const QString &url, const QString &path, const std::chrono::seconds refreshInterval)
nonstd::expected<void, QString> Session::addFeed(const QString &url, const QString &path)
{
if (m_feedsByURL.contains(url))
return nonstd::make_unexpected(tr("RSS feed with given URL already exists: %1.").arg(url));
@ -160,13 +160,13 @@ nonstd::expected<Feed *, QString> Session::addFeed(const QString &url, const QSt
return result.get_unexpected();
auto *destFolder = result.value();
auto *feed = new Feed(this, generateUID(), url, path, refreshInterval);
auto *feed = new Feed(generateUID(), url, path, this);
addItem(feed, destFolder);
store();
if (isProcessingEnabled())
refreshFeed(feed, std::chrono::system_clock::now());
feed->refresh();
return feed;
return {};
}
nonstd::expected<void, QString> Session::setFeedURL(const QString &path, const QString &url)
@ -192,7 +192,7 @@ nonstd::expected<void, QString> Session::setFeedURL(Feed *feed, const QString &u
feed->setURL(url);
store();
if (isProcessingEnabled())
refreshFeed(feed, std::chrono::system_clock::now());
feed->refresh();
return {};
}
@ -214,20 +214,14 @@ nonstd::expected<void, QString> Session::moveItem(Item *item, const QString &des
Q_ASSERT(item);
Q_ASSERT(item != rootFolder());
if (item->path() == destPath)
return {};
if (auto *folder = static_cast<Folder *>(item)) // if `item` is a `Folder`
{
if (destPath.startsWith(folder->path() + Item::PathSeparator))
return nonstd::make_unexpected(tr("Can't move a folder into itself or its subfolders."));
}
const nonstd::expected<Folder *, QString> result = prepareItemDest(destPath);
if (!result)
return result.get_unexpected();
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)
{
@ -320,7 +314,7 @@ bool Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
QString url = val.toString();
if (url.isEmpty())
url = key;
addFeedToFolder(generateUID(), url, key, folder, 0s);
addFeedToFolder(generateUID(), url, key, folder);
updated = true;
}
else if (val.isObject())
@ -360,9 +354,7 @@ bool Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
updated = true;
}
const auto refreshInterval = std::chrono::seconds(valObj[u"refreshInterval"].toInteger());
addFeedToFolder(uid, valObj[u"url"].toString(), key, folder, refreshInterval);
addFeedToFolder(uid, valObj[u"url"].toString(), key, folder);
}
else
{
@ -393,14 +385,8 @@ void Session::loadLegacy()
uint i = 0;
for (QString legacyPath : legacyFeedPaths)
{
if (legacyPath.startsWith(Item::PathSeparator))
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
legacyPath.slice(1);
#else
if (Item::PathSeparator == legacyPath[0])
legacyPath.remove(0, 1);
#endif
}
const QString parentFolderPath = Item::parentPath(legacyPath);
const QString feedUrl = Item::relativeName(legacyPath);
@ -418,7 +404,7 @@ void Session::loadLegacy()
void Session::store()
{
m_confFileStorage->store(Path(FEEDS_FILE_NAME)
, QJsonDocument(rootFolder()->toJsonValue().toObject()).toJson());
, QJsonDocument(rootFolder()->toJsonValue().toObject()).toJson());
}
nonstd::expected<Folder *, QString> Session::prepareItemDest(const QString &path)
@ -444,9 +430,9 @@ Folder *Session::addSubfolder(const QString &name, Folder *parentFolder)
return folder;
}
Feed *Session::addFeedToFolder(const QUuid &uid, const QString &url, const QString &name, Folder *parentFolder, const std::chrono::seconds refreshInterval)
Feed *Session::addFeedToFolder(const QUuid &uid, const QString &url, const QString &name, Folder *parentFolder)
{
auto *feed = new Feed(this, uid, url, Item::joinPath(parentFolder->path(), name), refreshInterval);
auto *feed = new Feed(uid, url, Item::joinPath(parentFolder->path(), name), this);
addItem(feed, parentFolder);
return feed;
}
@ -468,25 +454,8 @@ void Session::addItem(Item *item, Folder *destFolder)
emit feedURLChanged(feed, oldURL);
});
connect(feed, &Feed::refreshIntervalChanged, this, [this, feed](const std::chrono::seconds oldRefreshInterval)
{
store();
std::chrono::system_clock::time_point &nextRefresh = m_refreshTimepoints[feed];
if (nextRefresh > std::chrono::system_clock::time_point())
nextRefresh += feed->refreshInterval() - oldRefreshInterval;
if (isProcessingEnabled())
{
const std::chrono::seconds oldEffectiveRefreshInterval = (oldRefreshInterval > 0s)
? oldRefreshInterval : std::chrono::minutes(refreshInterval());
if (feed->refreshInterval() < oldEffectiveRefreshInterval)
refresh();
}
});
m_feedsByUID[feed->uid()] = feed;
m_feedsByURL[feed->url()] = feed;
m_refreshTimepoints.emplace(feed, std::chrono::system_clock::time_point());
}
connect(item, &Item::pathChanged, this, &Session::itemPathChanged);
@ -507,9 +476,14 @@ void Session::setProcessingEnabled(const bool enabled)
{
m_storeProcessingEnabled = enabled;
if (enabled)
{
m_refreshTimer.start(std::chrono::minutes(refreshInterval()));
refresh();
}
else
{
m_refreshTimer.stop();
}
emit processingStateChanged(enabled);
}
@ -580,7 +554,6 @@ void Session::handleItemAboutToBeDestroyed(Item *item)
{
m_feedsByUID.remove(feed->uid());
m_feedsByURL.remove(feed->url());
m_refreshTimepoints.remove(feed);
}
}
@ -619,28 +592,6 @@ void Session::setMaxArticlesPerFeed(const int n)
void Session::refresh()
{
const auto currentTimepoint = std::chrono::system_clock::now();
std::chrono::seconds nextRefreshInterval = 0s;
for (auto it = m_refreshTimepoints.begin(); it != m_refreshTimepoints.end(); ++it)
{
Feed *feed = it.key();
std::chrono::system_clock::time_point &timepoint = it.value();
if (timepoint <= currentTimepoint)
timepoint = refreshFeed(feed, currentTimepoint);
const auto interval = std::chrono::duration_cast<std::chrono::seconds>(timepoint - currentTimepoint);
if ((interval < nextRefreshInterval) || (nextRefreshInterval == 0s))
nextRefreshInterval = interval;
}
m_refreshTimer.start(nextRefreshInterval);
}
std::chrono::system_clock::time_point Session::refreshFeed(Feed *feed, const std::chrono::system_clock::time_point &currentTimepoint)
{
feed->refresh();
const std::chrono::seconds feedRefreshInterval = feed->refreshInterval();
const std::chrono::seconds effectiveRefreshInterval = (feedRefreshInterval > 0s) ? feedRefreshInterval : std::chrono::minutes(refreshInterval());
return currentTimepoint + effectiveRefreshInterval;
// NOTE: Should we allow manually refreshing for disabled session?
rootFolder()->refresh();
}

View file

@ -1,7 +1,7 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2017-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2024 Jonathan Ketchker
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
*
@ -35,20 +35,26 @@
* RSS Session configuration file format (JSON):
*
* =============== BEGIN ===============
* {
* "folder1": {
* "subfolder1": {
* "Feed name 1 (Alias)": {
*
{
* "folder1":
{
* "subfolder1":
{
* "Feed name 1 (Alias)":
{
* "uid": "feed unique identifier",
* "url": "http://some-feed-url1"
* }
* "Feed name 2 (Alias)": {
* "Feed name 2 (Alias)":
{
* "uid": "feed unique identifier",
* "url": "http://some-feed-url2"
* }
* },
* "subfolder2": {},
* "Feed name 3 (Alias)": {
* "Feed name 3 (Alias)":
{
* "uid": "feed unique identifier",
* "url": "http://some-feed-url3"
* }
@ -114,8 +120,8 @@ namespace RSS
std::chrono::seconds fetchDelay() const;
void setFetchDelay(std::chrono::seconds delay);
nonstd::expected<Folder *, QString> addFolder(const QString &path);
nonstd::expected<Feed *, QString> addFeed(const QString &url, const QString &path, std::chrono::seconds refreshInterval = {});
nonstd::expected<void, QString> addFolder(const QString &path);
nonstd::expected<void, QString> addFeed(const QString &url, const QString &path);
nonstd::expected<void, QString> setFeedURL(const QString &path, const QString &url);
nonstd::expected<void, QString> setFeedURL(Feed *feed, const QString &url);
nonstd::expected<void, QString> moveItem(const QString &itemPath, const QString &destPath);
@ -129,6 +135,9 @@ namespace RSS
Folder *rootFolder() const;
public slots:
void refresh();
signals:
void processingStateChanged(bool enabled);
void maxArticlesPerFeedChanged(int n);
@ -151,10 +160,8 @@ namespace RSS
void store();
nonstd::expected<Folder *, QString> prepareItemDest(const QString &path);
Folder *addSubfolder(const QString &name, Folder *parentFolder);
Feed *addFeedToFolder(const QUuid &uid, const QString &url, const QString &name, Folder *parentFolder, std::chrono::seconds refreshInterval);
Feed *addFeedToFolder(const QUuid &uid, const QString &url, const QString &name, Folder *parentFolder);
void addItem(Item *item, Folder *destFolder);
void refresh();
std::chrono::system_clock::time_point refreshFeed(Feed *feed, const std::chrono::system_clock::time_point &currentTimepoint);
static QPointer<Session> m_instance;
@ -169,6 +176,5 @@ namespace RSS
QHash<QString, Item *> m_itemsByPath;
QHash<QUuid, Feed *> m_feedsByUID;
QHash<QString, Feed *> m_feedsByURL;
QHash<Feed *, std::chrono::system_clock::time_point> m_refreshTimepoints;
};
}

View file

@ -41,10 +41,7 @@ SearchDownloadHandler::SearchDownloadHandler(const QString &pluginName, const QS
, m_manager {manager}
, m_downloadProcess {new QProcess(this)}
{
m_downloadProcess->setProcessEnvironment(m_manager->proxyEnvironment());
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
m_downloadProcess->setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
#endif
m_downloadProcess->setEnvironment(QProcess::systemEnvironment());
connect(m_downloadProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished)
, this, &SearchDownloadHandler::downloadProcessFinished);
const QStringList params

View file

@ -70,11 +70,7 @@ SearchHandler::SearchHandler(const QString &pattern, const QString &category, co
, m_searchTimeout {new QTimer(this)}
{
// Load environment variables (proxy)
m_searchProcess->setProcessEnvironment(m_manager->proxyEnvironment());
m_searchProcess->setProgram(Utils::ForeignApps::pythonInfo().executableName);
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
m_searchProcess->setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
#endif
m_searchProcess->setEnvironment(QProcess::systemEnvironment());
const QStringList params
{
@ -83,6 +79,9 @@ SearchHandler::SearchHandler(const QString &pattern, const QString &category, co
m_usedPlugins.join(u','),
m_category
};
// Launch search
m_searchProcess->setProgram(Utils::ForeignApps::pythonInfo().executableName);
m_searchProcess->setArguments(params + m_pattern.split(u' '));
connect(m_searchProcess, &QProcess::errorOccurred, this, &SearchHandler::processFailed);
@ -94,7 +93,6 @@ SearchHandler::SearchHandler(const QString &pattern, const QString &category, co
connect(m_searchTimeout, &QTimer::timeout, this, &SearchHandler::cancelSearch);
m_searchTimeout->start(3min);
// Launch search
// deferred start allows clients to handle starting-related signals
QMetaObject::invokeMethod(this, [this]() { m_searchProcess->start(QIODevice::ReadOnly); }
, Qt::QueuedConnection);

View file

@ -88,7 +88,6 @@ QPointer<SearchPluginManager> SearchPluginManager::m_instance = nullptr;
SearchPluginManager::SearchPluginManager()
: m_updateUrl(u"https://searchplugins.qbittorrent.org/nova3/engines/"_s)
, m_proxyEnv {QProcessEnvironment::systemEnvironment()}
{
Q_ASSERT(!m_instance); // only one instance is allowed
m_instance = this;
@ -363,11 +362,6 @@ SearchHandler *SearchPluginManager::startSearch(const QString &pattern, const QS
return new SearchHandler(pattern, category, usedPlugins, this);
}
QProcessEnvironment SearchPluginManager::proxyEnvironment() const
{
return m_proxyEnv;
}
QString SearchPluginManager::categoryFullName(const QString &categoryName)
{
const QHash<QString, QString> categoryTable
@ -409,70 +403,50 @@ Path SearchPluginManager::engineLocation()
void SearchPluginManager::applyProxySettings()
{
// for python `urllib`: https://docs.python.org/3/library/urllib.request.html#urllib.request.ProxyHandler
const QString HTTP_PROXY = u"http_proxy"_s;
const QString HTTPS_PROXY = u"https_proxy"_s;
// for `helpers.setupSOCKSProxy()`: https://everything.curl.dev/usingcurl/proxies/socks.html
const QString SOCKS_PROXY = u"qbt_socks_proxy"_s;
const auto *proxyManager = Net::ProxyConfigurationManager::instance();
const Net::ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
if (!Preferences::instance()->useProxyForGeneralPurposes())
// Define environment variables for urllib in search engine plugins
QString proxyStrHTTP, proxyStrSOCK;
if ((proxyConfig.type != Net::ProxyType::None) && Preferences::instance()->useProxyForGeneralPurposes())
{
m_proxyEnv.remove(HTTP_PROXY);
m_proxyEnv.remove(HTTPS_PROXY);
m_proxyEnv.remove(SOCKS_PROXY);
return;
switch (proxyConfig.type)
{
case Net::ProxyType::HTTP:
if (proxyConfig.authEnabled)
{
proxyStrHTTP = u"http://%1:%2@%3:%4"_s.arg(proxyConfig.username
, proxyConfig.password, proxyConfig.ip, QString::number(proxyConfig.port));
}
else
{
proxyStrHTTP = u"http://%1:%2"_s.arg(proxyConfig.ip, QString::number(proxyConfig.port));
}
break;
case Net::ProxyType::SOCKS5:
if (proxyConfig.authEnabled)
{
proxyStrSOCK = u"%1:%2@%3:%4"_s.arg(proxyConfig.username
, proxyConfig.password, proxyConfig.ip, QString::number(proxyConfig.port));
}
else
{
proxyStrSOCK = u"%1:%2"_s.arg(proxyConfig.ip, QString::number(proxyConfig.port));
}
break;
default:
qDebug("Disabling HTTP communications proxy");
}
qDebug("HTTP communications proxy string: %s"
, qUtf8Printable((proxyConfig.type == Net::ProxyType::SOCKS5) ? proxyStrSOCK : proxyStrHTTP));
}
const Net::ProxyConfiguration proxyConfig = Net::ProxyConfigurationManager::instance()->proxyConfiguration();
switch (proxyConfig.type)
{
case Net::ProxyType::None:
m_proxyEnv.remove(HTTP_PROXY);
m_proxyEnv.remove(HTTPS_PROXY);
m_proxyEnv.remove(SOCKS_PROXY);
break;
case Net::ProxyType::HTTP:
{
const QString credential = proxyConfig.authEnabled
? (proxyConfig.username + u':' + proxyConfig.password + u'@')
: QString();
const QString proxyURL = u"http://%1%2:%3"_s
.arg(credential, proxyConfig.ip, QString::number(proxyConfig.port));
m_proxyEnv.insert(HTTP_PROXY, proxyURL);
m_proxyEnv.insert(HTTPS_PROXY, proxyURL);
m_proxyEnv.remove(SOCKS_PROXY);
}
break;
case Net::ProxyType::SOCKS5:
{
const QString scheme = proxyConfig.hostnameLookupEnabled ? u"socks5h"_s : u"socks5"_s;
const QString credential = proxyConfig.authEnabled
? (proxyConfig.username + u':' + proxyConfig.password + u'@')
: QString();
const QString proxyURL = u"%1://%2%3:%4"_s
.arg(scheme, credential, proxyConfig.ip, QString::number(proxyConfig.port));
m_proxyEnv.remove(HTTP_PROXY);
m_proxyEnv.remove(HTTPS_PROXY);
m_proxyEnv.insert(SOCKS_PROXY, proxyURL);
}
break;
case Net::ProxyType::SOCKS4:
{
const QString scheme = proxyConfig.hostnameLookupEnabled ? u"socks4a"_s : u"socks4"_s;
const QString proxyURL = u"%1://%2:%3"_s
.arg(scheme, proxyConfig.ip, QString::number(proxyConfig.port));
m_proxyEnv.remove(HTTP_PROXY);
m_proxyEnv.remove(HTTPS_PROXY);
m_proxyEnv.insert(SOCKS_PROXY, proxyURL);
}
break;
}
qputenv("http_proxy", proxyStrHTTP.toLocal8Bit());
qputenv("https_proxy", proxyStrHTTP.toLocal8Bit());
qputenv("sock_proxy", proxyStrSOCK.toLocal8Bit());
}
void SearchPluginManager::versionInfoDownloadFinished(const Net::DownloadResult &result)
@ -495,9 +469,9 @@ void SearchPluginManager::pluginDownloadFinished(const Net::DownloadResult &resu
}
else
{
const QString &url = result.url;
const QString pluginName = url.sliced(url.lastIndexOf(u'/') + 1)
.replace(u".py"_s, u""_s, Qt::CaseInsensitive);
const QString url = result.url;
QString pluginName = url.mid(url.lastIndexOf(u'/') + 1);
pluginName.replace(u".py"_s, u""_s, Qt::CaseInsensitive);
if (pluginInfo(pluginName))
emit pluginUpdateFailed(pluginName, tr("Failed to download the plugin file. %1").arg(result.errorString));
@ -523,32 +497,29 @@ void SearchPluginManager::updateNova()
packageFile2.close();
// Copy search plugin files (if necessary)
const auto updateFile = [&enginePath](const Path &filename)
const auto updateFile = [&enginePath](const Path &filename, const bool compareVersion)
{
const Path filePathBundled = Path(u":/searchengine/nova3"_s) / filename;
const Path filePathDisk = enginePath / filename;
if (getPluginVersion(filePathBundled) <= getPluginVersion(filePathDisk))
if (compareVersion && (getPluginVersion(filePathBundled) <= getPluginVersion(filePathDisk)))
return;
Utils::Fs::removeFile(filePathDisk);
Utils::Fs::copyFile(filePathBundled, filePathDisk);
};
updateFile(Path(u"helpers.py"_s));
updateFile(Path(u"nova2.py"_s));
updateFile(Path(u"nova2dl.py"_s));
updateFile(Path(u"novaprinter.py"_s));
updateFile(Path(u"socks.py"_s));
updateFile(Path(u"helpers.py"_s), true);
updateFile(Path(u"nova2.py"_s), true);
updateFile(Path(u"nova2dl.py"_s), true);
updateFile(Path(u"novaprinter.py"_s), true);
updateFile(Path(u"socks.py"_s), false);
}
void SearchPluginManager::update()
{
QProcess nova;
nova.setProcessEnvironment(proxyEnvironment());
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
nova.setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
#endif
nova.setProcessEnvironment(QProcessEnvironment::systemEnvironment());
const QStringList params
{
@ -621,14 +592,14 @@ void SearchPluginManager::parseVersionInfo(const QByteArray &info)
QHash<QString, PluginVersion> updateInfo;
int numCorrectData = 0;
const QList<QByteArrayView> lines = Utils::ByteArray::splitToViews(info, "\n");
const QList<QByteArrayView> lines = Utils::ByteArray::splitToViews(info, "\n", Qt::SkipEmptyParts);
for (QByteArrayView line : lines)
{
line = line.trimmed();
if (line.isEmpty()) continue;
if (line.startsWith('#')) continue;
const QList<QByteArrayView> list = Utils::ByteArray::splitToViews(line, ":");
const QList<QByteArrayView> list = Utils::ByteArray::splitToViews(line, ":", Qt::SkipEmptyParts);
if (list.size() != 2) continue;
const auto pluginName = QString::fromUtf8(list.first().trimmed());
@ -680,10 +651,9 @@ PluginVersion SearchPluginManager::getPluginVersion(const Path &filePath)
while (!pluginFile.atEnd())
{
const auto line = QString::fromUtf8(pluginFile.readLine(lineMaxLength)).remove(u' ');
if (!line.startsWith(u"#VERSION:", Qt::CaseInsensitive))
continue;
if (!line.startsWith(u"#VERSION:", Qt::CaseInsensitive)) continue;
const QString versionStr = line.sliced(9);
const QString versionStr = line.mid(9);
const auto version = PluginVersion::fromString(versionStr);
if (version.isValid())
return version;

View file

@ -32,7 +32,6 @@
#include <QHash>
#include <QMetaType>
#include <QObject>
#include <QProcessEnvironment>
#include "base/path.h"
#include "base/utils/version.h"
@ -88,8 +87,6 @@ public:
SearchHandler *startSearch(const QString &pattern, const QString &category, const QStringList &usedPlugins);
SearchDownloadHandler *downloadTorrent(const QString &pluginName, const QString &url);
QProcessEnvironment proxyEnvironment() const;
static PluginVersion getPluginVersion(const Path &filePath);
static QString categoryFullName(const QString &categoryName);
QString pluginFullName(const QString &pluginName) const;
@ -125,5 +122,4 @@ private:
const QString m_updateUrl;
QHash<QString, PluginInfo*> m_plugins;
QProcessEnvironment m_proxyEnv;
};

View file

@ -170,7 +170,7 @@ bool SettingsStorage::writeNativeSettings() const
// between deleting the file and recreating it. This is a safety measure.
// Write everything to qBittorrent_new.ini/qBittorrent_new.conf and if it succeeds
// replace qBittorrent.ini/qBittorrent.conf with it.
for (auto i = m_data.cbegin(); i != m_data.cend(); ++i)
for (auto i = m_data.begin(); i != m_data.end(); ++i)
nativeSettings->setValue(i.key(), i.value());
nativeSettings->sync(); // Important to get error status

View file

@ -30,30 +30,28 @@
#include "bytearray.h"
#include <QByteArray>
#include <QByteArrayMatcher>
#include <QByteArrayView>
#include <QList>
QList<QByteArrayView> Utils::ByteArray::splitToViews(const QByteArrayView in, const QByteArrayView sep)
QList<QByteArrayView> Utils::ByteArray::splitToViews(const QByteArrayView in, const QByteArrayView sep, const Qt::SplitBehavior behavior)
{
if (in.isEmpty())
return {};
if (sep.isEmpty())
return {in};
const QByteArrayMatcher matcher {sep};
QList<QByteArrayView> ret;
ret.reserve(1 + (in.size() / (sep.size() + 1)));
qsizetype head = 0;
ret.reserve((behavior == Qt::KeepEmptyParts)
? (1 + (in.size() / sep.size()))
: (1 + (in.size() / (sep.size() + 1))));
int head = 0;
while (head < in.size())
{
qsizetype end = matcher.indexIn(in, head);
int end = in.indexOf(sep, head);
if (end < 0)
end = in.size();
// omit empty parts
const QByteArrayView part = in.sliced(head, (end - head));
if (!part.isEmpty())
const QByteArrayView part = in.mid(head, (end - head));
if (!part.isEmpty() || (behavior == Qt::KeepEmptyParts))
ret += part;
head = end + sep.size();

View file

@ -37,8 +37,8 @@ class QByteArrayView;
namespace Utils::ByteArray
{
// Inspired by QStringView(in).split(sep, Qt::SkipEmptyParts)
QList<QByteArrayView> splitToViews(QByteArrayView in, QByteArrayView sep);
// Mimic QStringView(in).split(sep, behavior)
QList<QByteArrayView> splitToViews(QByteArrayView in, QByteArrayView sep, Qt::SplitBehavior behavior = Qt::KeepEmptyParts);
QByteArray asQByteArray(QByteArrayView view);
QByteArray toBase32(const QByteArray &in);

View file

@ -64,7 +64,7 @@ int Utils::Compare::naturalCompare(const QString &left, const QString &right, co
const int start = pos;
while ((pos < str.size()) && str[pos].isDigit())
++pos;
return str.sliced(start, (pos - start));
return str.mid(start, (pos - start));
};
const QStringView numViewL = numberView(left, posL);

View file

@ -57,9 +57,6 @@ namespace
info = {};
QProcess proc;
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
proc.setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
#endif
proc.start(exeName, {u"--version"_s}, QIODevice::ReadOnly);
if (proc.waitForFinished() && (proc.exitCode() == QProcess::NormalExit))
{
@ -71,7 +68,7 @@ namespace
// Software 'Anaconda' installs its own python interpreter
// and `python --version` returns a string like this:
// "Python 3.4.3 :: Anaconda 2.3.0 (64-bit)"
const QList<QByteArrayView> outputSplit = Utils::ByteArray::splitToViews(procOutput, " ");
const QList<QByteArrayView> outputSplit = Utils::ByteArray::splitToViews(procOutput, " ", Qt::SkipEmptyParts);
if (outputSplit.size() <= 1)
return false;

View file

@ -115,7 +115,7 @@ bool Utils::Password::PBKDF2::verify(const QByteArray &secret, const QString &pa
bool Utils::Password::PBKDF2::verify(const QByteArray &secret, const QByteArray &password)
{
const QList<QByteArrayView> list = ByteArray::splitToViews(secret, ":");
const QList<QByteArrayView> list = ByteArray::splitToViews(secret, ":", Qt::SkipEmptyParts);
if (list.size() != 2)
return false;

View file

@ -49,13 +49,12 @@ namespace Utils::String
template <typename T>
T unquote(const T &str, const QString &quotes = u"\""_s)
{
if (str.length() < 2)
return str;
if (str.length() < 2) return str;
for (const QChar quote : quotes)
{
if (str.startsWith(quote) && str.endsWith(quote))
return str.sliced(1, (str.length() - 2));
return str.mid(1, (str.length() - 2));
}
return str;

View file

@ -29,10 +29,10 @@
#pragma once
#define QBT_VERSION_MAJOR 5
#define QBT_VERSION_MINOR 2
#define QBT_VERSION_MINOR 1
#define QBT_VERSION_BUGFIX 0
#define QBT_VERSION_BUILD 0
#define QBT_VERSION_STATUS "alpha1" // Should be empty for stable releases!
#define QBT_VERSION_STATUS "rc1" // Should be empty for stable releases!
#define QBT__STRINGIFY(x) #x
#define QBT_STRINGIFY(x) QBT__STRINGIFY(x)

View file

@ -18,7 +18,6 @@ qt_wrap_ui(UI_HEADERS
properties/peersadditiondialog.ui
properties/propertieswidget.ui
rss/automatedrssdownloader.ui
rss/rssfeeddialog.ui
rss/rsswidget.ui
search/pluginselectdialog.ui
search/pluginsourcedialog.ui
@ -70,7 +69,6 @@ add_library(qbt_gui STATIC
log/logmodel.h
mainwindow.h
optionsdialog.h
powermanagement/inhibitor.h
powermanagement/powermanagement.h
previewlistdelegate.h
previewselectdialog.h
@ -90,7 +88,6 @@ add_library(qbt_gui STATIC
rss/automatedrssdownloader.h
rss/feedlistwidget.h
rss/htmlbrowser.h
rss/rssfeeddialog.h
rss/rsswidget.h
search/pluginselectdialog.h
search/pluginsourcedialog.h
@ -140,7 +137,6 @@ add_library(qbt_gui STATIC
uithememanager.h
uithemesource.h
utils.h
utils/keysequence.h
watchedfolderoptionsdialog.h
watchedfoldersmodel.h
windowstate.h
@ -171,7 +167,6 @@ add_library(qbt_gui STATIC
log/logmodel.cpp
mainwindow.cpp
optionsdialog.cpp
powermanagement/inhibitor.cpp
powermanagement/powermanagement.cpp
previewlistdelegate.cpp
previewselectdialog.cpp
@ -191,7 +186,6 @@ add_library(qbt_gui STATIC
rss/automatedrssdownloader.cpp
rss/feedlistwidget.cpp
rss/htmlbrowser.cpp
rss/rssfeeddialog.cpp
rss/rsswidget.cpp
search/pluginselectdialog.cpp
search/pluginsourcedialog.cpp
@ -240,7 +234,6 @@ add_library(qbt_gui STATIC
uithememanager.cpp
uithemesource.cpp
utils.cpp
utils/keysequence.cpp
watchedfolderoptionsdialog.cpp
watchedfoldersmodel.cpp
@ -266,8 +259,8 @@ if (DBUS)
notifications/dbusnotifier.cpp
notifications/dbusnotificationsinterface.h
notifications/dbusnotificationsinterface.cpp
powermanagement/inhibitordbus.h
powermanagement/inhibitordbus.cpp
powermanagement/powermanagement_x11.h
powermanagement/powermanagement_x11.cpp
)
endif()
@ -281,29 +274,21 @@ if (STACKTRACE)
)
endif()
if ((CMAKE_SYSTEM_NAME STREQUAL "Windows") OR (CMAKE_SYSTEM_NAME STREQUAL "Darwin"))
target_sources(qbt_gui PRIVATE
programupdater.h
programupdater.cpp
)
endif()
if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
target_sources(qbt_gui PRIVATE
macosdockbadge/badger.h
macosdockbadge/badger.mm
macosdockbadge/badgeview.h
macosdockbadge/badgeview.mm
macosshiftclickhandler.h
macosshiftclickhandler.cpp
macutilities.h
macutilities.mm
powermanagement/inhibitormacos.h
powermanagement/inhibitormacos.cpp
programupdater.h
programupdater.cpp
)
target_link_libraries(qbt_gui PRIVATE objc)
endif()
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
target_sources(qbt_gui PRIVATE
powermanagement/inhibitorwindows.h
powermanagement/inhibitorwindows.cpp
programupdater.h
programupdater.cpp
)
endif()

View file

@ -67,7 +67,7 @@ AboutDialog::AboutDialog(QWidget *parent)
u"</p>"_s
.arg(tr("An advanced BitTorrent client programmed in C++, based on Qt toolkit and libtorrent-rasterbar.")
.replace(u"C++"_s, u"C\u2060+\u2060+"_s) // make C++ non-breaking
, tr("Copyright %1 2006-2025 The qBittorrent project").arg(C_COPYRIGHT)
, tr("Copyright %1 2006-2024 The qBittorrent project").arg(C_COPYRIGHT)
, tr("Home Page:")
, tr("Forum:")
, tr("Bug Tracker:"));

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -122,7 +122,7 @@ namespace
fsPathEdit->setCurrentIndex(existingIndex);
}
void updatePathHistory(const QString &settingsKey, const Path &path, const qsizetype maxLength)
void updatePathHistory(const QString &settingsKey, const Path &path, const int maxLength)
{
// Add last used save path to the front of history
@ -134,10 +134,7 @@ namespace
else
pathList.prepend(path.toString());
if (pathList.size() > maxLength)
pathList.resize(maxLength);
settings()->storeValue(settingsKey, pathList);
settings()->storeValue(settingsKey, QStringList(pathList.mid(0, maxLength)));
}
}
@ -378,7 +375,8 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to
connect(Preferences::instance(), &Preferences::changed, []
{
const int length = Preferences::instance()->addNewTorrentDialogSavePathHistoryLength();
settings()->storeValue(KEY_SAVEPATHHISTORY, settings()->loadValue<QStringList>(KEY_SAVEPATHHISTORY).mid(0, length));
settings()->storeValue(KEY_SAVEPATHHISTORY
, QStringList(settings()->loadValue<QStringList>(KEY_SAVEPATHHISTORY).mid(0, length)));
});
setCurrentContext(std::make_shared<Context>(Context {torrentDescr, inParams}));
@ -386,6 +384,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to
AddNewTorrentDialog::~AddNewTorrentDialog()
{
saveState();
delete m_ui;
}
@ -399,7 +398,7 @@ void AddNewTorrentDialog::loadState()
if (const QSize dialogSize = m_storeDialogSize; dialogSize.isValid())
resize(dialogSize);
m_ui->splitter->restoreState(m_storeSplitterState);
m_ui->splitter->restoreState(m_storeSplitterState);;
}
void AddNewTorrentDialog::saveState()
@ -835,12 +834,6 @@ void AddNewTorrentDialog::reject()
QDialog::reject();
}
void AddNewTorrentDialog::done(const int result)
{
saveState();
QDialog::done(result);
}
void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata)
{
Q_ASSERT(m_currentContext);

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -68,11 +68,6 @@ signals:
void torrentAccepted(const BitTorrent::TorrentDescriptor &torrentDescriptor, const BitTorrent::AddTorrentParams &addTorrentParams);
void torrentRejected(const BitTorrent::TorrentDescriptor &torrentDescriptor);
public slots:
void accept() override;
void reject() override;
void done(int result) override;
private slots:
void updateDiskSpaceLabel();
void onSavePathChanged(const Path &newPath);
@ -82,6 +77,9 @@ private slots:
void categoryChanged(int index);
void contentLayoutChanged();
void accept() override;
void reject() override;
private:
class TorrentContentAdaptor;
struct Context;

View file

@ -40,6 +40,7 @@
#include "base/global.h"
#include "base/preferences.h"
#include "base/unicodestrings.h"
#include "gui/addnewtorrentdialog.h"
#include "gui/desktopintegration.h"
#include "gui/mainwindow.h"
#include "interfaces/iguiapplication.h"
@ -98,7 +99,6 @@ namespace
ENABLE_SPEED_WIDGET,
#ifndef Q_OS_MACOS
ENABLE_ICONS_IN_MENUS,
USE_ATTACHED_ADD_NEW_TORRENT_DIALOG,
#endif
// embedded tracker
TRACKER_STATUS,
@ -151,7 +151,6 @@ namespace
UPNP_LEASE_DURATION,
PEER_TOS,
UTP_MIX_MODE,
HOSTNAME_CACHE_TTL,
IDN_SUPPORT,
MULTI_CONNECTIONS_PER_IP,
VALIDATE_HTTPS_TRACKER_CERTIFICATE,
@ -279,8 +278,6 @@ void AdvancedSettings::saveAdvancedSettings() const
session->setPeerToS(m_spinBoxPeerToS.value());
// uTP-TCP mixed mode
session->setUtpMixedMode(m_comboBoxUtpMixedMode.currentData().value<BitTorrent::MixedModeAlgorithm>());
// Hostname resolver cache TTL
session->setHostnameCacheTTL(m_spinBoxHostnameCacheTTL.value());
// Support internationalized domain name (IDN)
session->setIDNSupportEnabled(m_checkBoxIDNSupport.isChecked());
// multiple connections per IP
@ -333,7 +330,6 @@ void AdvancedSettings::saveAdvancedSettings() const
pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked());
#ifndef Q_OS_MACOS
pref->setIconsInMenusEnabled(m_checkBoxIconsInMenusEnabled.isChecked());
pref->setAddNewTorrentDialogAttached(m_checkBoxAttachedAddNewTorrentDialog.isChecked());
#endif
// Tracker
@ -736,14 +732,6 @@ void AdvancedSettings::loadAdvancedSettings()
addRow(UTP_MIX_MODE, (tr("%1-TCP mixed mode algorithm", "uTP-TCP mixed mode algorithm").arg(C_UTP)
+ u' ' + makeLink(u"https://www.libtorrent.org/reference-Settings.html#mixed_mode_algorithm", u"(?)"))
, &m_comboBoxUtpMixedMode);
// Hostname resolver cache TTL
m_spinBoxHostnameCacheTTL.setMinimum(0);
m_spinBoxHostnameCacheTTL.setMaximum(std::numeric_limits<int>::max());
m_spinBoxHostnameCacheTTL.setValue(session->hostnameCacheTTL());
m_spinBoxHostnameCacheTTL.setSuffix(tr(" s", " seconds"));
addRow(HOSTNAME_CACHE_TTL, (tr("Internal hostname resolver cache expiry interval")
+ u' ' + makeLink(u"https://www.libtorrent.org/reference-Settings.html#resolver_cache_timeout", u"(?)"))
, &m_spinBoxHostnameCacheTTL);
// Support internationalized domain name (IDN)
m_checkBoxIDNSupport.setChecked(session->isIDNSupportEnabled());
addRow(IDN_SUPPORT, (tr("Support internationalized domain name (IDN)")
@ -868,9 +856,6 @@ void AdvancedSettings::loadAdvancedSettings()
// Enable icons in menus
m_checkBoxIconsInMenusEnabled.setChecked(pref->iconsInMenusEnabled());
addRow(ENABLE_ICONS_IN_MENUS, tr("Enable icons in menus"), &m_checkBoxIconsInMenusEnabled);
m_checkBoxAttachedAddNewTorrentDialog.setChecked(pref->isAddNewTorrentDialogAttached());
addRow(USE_ATTACHED_ADD_NEW_TORRENT_DIALOG, tr("Attach \"Add new torrent\" dialog to main window"), &m_checkBoxAttachedAddNewTorrentDialog);
#endif
// Tracker State
m_checkBoxTrackerStatus.setChecked(session->isTrackerEnabled());

View file

@ -70,7 +70,7 @@ private:
QSpinBox m_spinBoxSaveResumeDataInterval, m_spinBoxSaveStatisticsInterval, m_spinBoxTorrentFileSizeLimit, m_spinBoxBdecodeDepthLimit, m_spinBoxBdecodeTokenLimit,
m_spinBoxAsyncIOThreads, m_spinBoxFilePoolSize, m_spinBoxCheckingMemUsage, m_spinBoxDiskQueueSize,
m_spinBoxOutgoingPortsMin, m_spinBoxOutgoingPortsMax, m_spinBoxUPnPLeaseDuration, m_spinBoxPeerToS, m_spinBoxHostnameCacheTTL,
m_spinBoxOutgoingPortsMin, m_spinBoxOutgoingPortsMax, m_spinBoxUPnPLeaseDuration, m_spinBoxPeerToS,
m_spinBoxListRefresh, m_spinBoxTrackerPort, m_spinBoxSendBufferWatermark, m_spinBoxSendBufferLowWatermark,
m_spinBoxSendBufferWatermarkFactor, m_spinBoxConnectionSpeed, m_spinBoxSocketSendBufferSize, m_spinBoxSocketReceiveBufferSize, m_spinBoxSocketBacklogSize,
m_spinBoxAnnouncePort, m_spinBoxMaxConcurrentHTTPAnnounces, m_spinBoxStopTrackerTimeout, m_spinBoxSessionShutdownTimeout,
@ -108,7 +108,6 @@ private:
#ifndef Q_OS_MACOS
QCheckBox m_checkBoxIconsInMenusEnabled;
QCheckBox m_checkBoxAttachedAddNewTorrentDialog;
#endif
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)

View file

@ -234,14 +234,14 @@ void FileSystemPathEdit::setFileNameFilter(const QString &val)
const int closeBracePos = val.indexOf(u')', (openBracePos + 1));
if ((openBracePos > 0) && (closeBracePos > 0) && (closeBracePos > openBracePos + 2))
{
const QString filterString = val.sliced((openBracePos + 1), (closeBracePos - openBracePos - 1));
QString filterString = val.mid(openBracePos + 1, closeBracePos - openBracePos - 1);
if (filterString == u"*")
{ // no filters
d->m_editor->setFilenameFilters({});
}
else
{
const QStringList filters = filterString.split(u' ', Qt::SkipEmptyParts);
QStringList filters = filterString.split(u' ', Qt::SkipEmptyParts);
d->m_editor->setFilenameFilters(filters);
}
}

View file

@ -33,6 +33,7 @@
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrentdescriptor.h"
#include "base/logger.h"
#include "base/net/downloadmanager.h"
#include "base/preferences.h"
#include "base/torrentfileguard.h"
@ -81,15 +82,6 @@ GUIAddTorrentManager::GUIAddTorrentManager(IGUIApplication *app, BitTorrent::Ses
connect(btSession(), &BitTorrent::Session::metadataDownloaded, this, &GUIAddTorrentManager::onMetadataDownloaded);
}
GUIAddTorrentManager::~GUIAddTorrentManager()
{
for (AddNewTorrentDialog *dialog : asConst(m_dialogs))
{
dialog->disconnect(this);
dialog->reject();
}
}
bool GUIAddTorrentManager::addTorrent(const QString &source, const BitTorrent::AddTorrentParams &params, const AddTorrentOption option)
{
// `source`: .torrent file path, magnet URI or URL
@ -233,19 +225,12 @@ bool GUIAddTorrentManager::processTorrent(const QString &source
if (!hasMetadata)
btSession()->downloadMetadata(torrentDescr);
#ifdef Q_OS_MACOS
const bool attached = false;
#else
const bool attached = Preferences::instance()->isAddNewTorrentDialogAttached();
#endif
// By not setting a parent to the "AddNewTorrentDialog", all those dialogs
// will be displayed on top and will not overlap with the main window.
auto *dlg = new AddNewTorrentDialog(torrentDescr, params, (attached ? app()->mainWindow() : nullptr));
auto *dlg = new AddNewTorrentDialog(torrentDescr, params, nullptr);
// Qt::Window is required to avoid showing only two dialog on top (see #12852).
// Also improves the general convenience of adding multiple torrents.
if (!attached)
dlg->setWindowFlags(Qt::Window);
dlg->setWindowFlags(Qt::Window);
dlg->setAttribute(Qt::WA_DeleteOnClose);
m_dialogs[infoHash] = dlg;

View file

@ -61,7 +61,6 @@ class GUIAddTorrentManager : public GUIApplicationComponent<AddTorrentManager>
public:
GUIAddTorrentManager(IGUIApplication *app, BitTorrent::Session *session, QObject *parent = nullptr);
~GUIAddTorrentManager() override;
bool addTorrent(const QString &source, const BitTorrent::AddTorrentParams &params = {}, AddTorrentOption option = AddTorrentOption::Default);

View file

@ -1,73 +0,0 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Luke Memet (lukemmtt)
*
* 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 "macosshiftclickhandler.h"
#include <QMouseEvent>
#include <QTreeView>
MacOSShiftClickHandler::MacOSShiftClickHandler(QTreeView *treeView)
: QObject(treeView)
, m_treeView {treeView}
{
treeView->installEventFilter(this);
}
bool MacOSShiftClickHandler::eventFilter(QObject *watched, QEvent *event)
{
if ((watched == m_treeView) && (event->type() == QEvent::MouseButtonPress))
{
const auto *mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent->button() != Qt::LeftButton)
return false;
const QModelIndex clickedIndex = m_treeView->indexAt(mouseEvent->position().toPoint());
if (!clickedIndex.isValid())
return false;
const Qt::KeyboardModifiers modifiers = mouseEvent->modifiers();
const bool shiftPressed = modifiers.testFlag(Qt::ShiftModifier);
if (shiftPressed && m_lastClickedIndex.isValid())
{
const QItemSelection selection(m_lastClickedIndex, clickedIndex);
const bool commandPressed = modifiers.testFlag(Qt::ControlModifier);
if (commandPressed)
m_treeView->selectionModel()->select(selection, (QItemSelectionModel::Select | QItemSelectionModel::Rows));
else
m_treeView->selectionModel()->select(selection, (QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows));
m_treeView->selectionModel()->setCurrentIndex(clickedIndex, QItemSelectionModel::NoUpdate);
return true;
}
if (!modifiers.testFlags(Qt::AltModifier | Qt::MetaModifier))
m_lastClickedIndex = clickedIndex;
}
return QObject::eventFilter(watched, event);
}

View file

@ -1,50 +0,0 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Luke Memet (lukemmtt)
*
* 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 <QObject>
#include <QPersistentModelIndex>
class QTreeView;
// Workaround for QTBUG-115838: Shift-click range selection not working properly on macOS
class MacOSShiftClickHandler final : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(MacOSShiftClickHandler)
public:
explicit MacOSShiftClickHandler(QTreeView *treeView);
private:
bool eventFilter(QObject *watched, QEvent *event) override;
QTreeView *m_treeView = nullptr;
QPersistentModelIndex m_lastClickedIndex;
};

View file

@ -101,7 +101,6 @@
#include "ui_mainwindow.h"
#include "uithememanager.h"
#include "utils.h"
#include "utils/keysequence.h"
#ifdef Q_OS_MACOS
#include "macosdockbadge/badger.h"
@ -131,8 +130,6 @@ MainWindow::MainWindow(IGUIApplication *app, const WindowState initialState, con
, m_ui {new Ui::MainWindow}
, m_downloadRate {Utils::Misc::friendlyUnit(0, true)}
, m_uploadRate {Utils::Misc::friendlyUnit(0, true)}
, m_pwr {new PowerManagement}
, m_preventTimer {new QTimer(this)}
, m_storeExecutionLogEnabled {EXECUTIONLOG_SETTINGS_KEY(u"Enabled"_s)}
, m_storeDownloadTrackerFavicon {SETTINGS_KEY(u"DownloadTrackerFavicon"_s)}
, m_storeExecutionLogTypes {EXECUTIONLOG_SETTINGS_KEY(u"Types"_s), Log::MsgType::ALL}
@ -339,6 +336,8 @@ MainWindow::MainWindow(IGUIApplication *app, const WindowState initialState, con
connect(m_ui->actionManageCookies, &QAction::triggered, this, &MainWindow::manageCookies);
// Initialise system sleep inhibition timer
m_pwr = new PowerManagement(this);
m_preventTimer = new QTimer(this);
m_preventTimer->setSingleShot(true);
connect(m_preventTimer, &QTimer::timeout, this, &MainWindow::updatePowerManagementState);
connect(pref, &Preferences::changed, this, &MainWindow::updatePowerManagementState);
@ -838,7 +837,6 @@ void MainWindow::cleanup()
delete m_executableWatcher;
m_preventTimer->stop();
delete m_pwr;
#if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
if (m_programUpdateTimer)
@ -884,7 +882,7 @@ void MainWindow::createKeyboardShortcuts()
{
m_ui->actionCreateTorrent->setShortcut(QKeySequence::New);
m_ui->actionOpen->setShortcut(QKeySequence::Open);
m_ui->actionDelete->setShortcut(Utils::KeySequence::deleteItem());
m_ui->actionDelete->setShortcut(QKeySequence::Delete);
m_ui->actionDelete->setShortcutContext(Qt::WidgetShortcut); // nullify its effect: delete key event is handled by respective widgets, not here
m_ui->actionDownloadFromURL->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_O);
m_ui->actionExit->setShortcut(Qt::CTRL | Qt::Key_Q);
@ -1828,7 +1826,7 @@ void MainWindow::updatePowerManagementState() const
return torrent->isMoving();
});
m_pwr->setActivityState(inhibitSuspend ? PowerManagement::ActivityState::Busy : PowerManagement::ActivityState::Idle);
m_pwr->setActivityState(inhibitSuspend);
m_preventTimer->start(PREVENT_SUSPEND_INTERVAL);
}

View file

@ -149,8 +149,8 @@
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionOpen"/>
<addaction name="actionDownloadFromURL"/>
<addaction name="actionOpen"/>
<addaction name="actionDelete"/>
<addaction name="separator"/>
<addaction name="actionStart"/>

View file

@ -355,7 +355,6 @@ void OptionsDialog::loadBehaviorTabOptions()
// Groupbox's check state must be initialized after some of its children if they are manually enabled/disabled
m_ui->checkFileLog->setChecked(app()->isFileLoggerEnabled());
m_ui->checkBoxFreeDiskSpaceStatusBar->setChecked(pref->isStatusbarFreeDiskSpaceDisplayed());
m_ui->checkBoxExternalIPStatusBar->setChecked(pref->isStatusbarExternalIPDisplayed());
m_ui->checkBoxPerformanceWarning->setChecked(session->isPerformanceWarningEnabled());
@ -444,7 +443,6 @@ void OptionsDialog::loadBehaviorTabOptions()
connect(m_ui->spinFileLogAge, qSpinBoxValueChanged, this, &ThisType::enableApplyButton);
connect(m_ui->comboFileLogAgeType, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
connect(m_ui->checkBoxFreeDiskSpaceStatusBar, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkBoxExternalIPStatusBar, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkBoxPerformanceWarning, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
}
@ -538,7 +536,6 @@ void OptionsDialog::saveBehaviorTabOptions() const
app()->setStartUpWindowState(m_ui->windowStateComboBox->currentData().value<WindowState>());
pref->setStatusbarFreeDiskSpaceDisplayed(m_ui->checkBoxFreeDiskSpaceStatusBar->isChecked());
pref->setStatusbarExternalIPDisplayed(m_ui->checkBoxExternalIPStatusBar->isChecked());
session->setPerformanceWarningEnabled(m_ui->checkBoxPerformanceWarning->isChecked());
}
@ -1689,7 +1686,7 @@ void OptionsDialog::adjustProxyOptions()
if (currentProxyType == Net::ProxyType::None)
{
m_ui->labelProxyTypeUnavailable->setVisible(false);
m_ui->labelProxyTypeIncompatible->setVisible(false);
m_ui->lblProxyIP->setEnabled(false);
m_ui->textProxyIP->setEnabled(false);
@ -1714,7 +1711,7 @@ void OptionsDialog::adjustProxyOptions()
if (currentProxyType == Net::ProxyType::SOCKS4)
{
m_ui->labelProxyTypeUnavailable->setVisible(true);
m_ui->labelProxyTypeIncompatible->setVisible(true);
m_ui->checkProxyHostnameLookup->setEnabled(false);
m_ui->checkProxyRSS->setEnabled(false);
@ -1723,7 +1720,7 @@ void OptionsDialog::adjustProxyOptions()
else
{
// SOCKS5 or HTTP
m_ui->labelProxyTypeUnavailable->setVisible(false);
m_ui->labelProxyTypeIncompatible->setVisible(false);
m_ui->checkProxyHostnameLookup->setEnabled(true);
m_ui->checkProxyRSS->setEnabled(true);
@ -1859,10 +1856,10 @@ void OptionsDialog::setLocale(const QString &localeStr)
if (index < 0)
{
//Attempt to find a language match without a country
const int pos = name.indexOf(u'_');
int pos = name.indexOf(u'_');
if (pos > -1)
{
const QString lang = name.first(pos);
QString lang = name.left(pos);
index = m_ui->comboLanguage->findData(lang, Qt::UserRole);
}
}

View file

@ -819,13 +819,6 @@
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBoxFreeDiskSpaceStatusBar">
<property name="text">
<string>Show free disk space in status bar</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBoxExternalIPStatusBar">
<property name="text">
@ -2077,14 +2070,14 @@ readme[0-9].txt: filter 'readme1.txt', 'readme2.txt' but not 'readme10.txt'.</st
</layout>
</item>
<item>
<widget class="QLabel" name="labelProxyTypeUnavailable">
<widget class="QLabel" name="labelProxyTypeIncompatible">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>Some functions are unavailable with the chosen proxy type!</string>
<string>Some options are incompatible with the chosen proxy type!</string>
</property>
</widget>
</item>
@ -2144,7 +2137,7 @@ readme[0-9].txt: filter 'readme1.txt', 'readme2.txt' but not 'readme10.txt'.</st
<item>
<widget class="QLabel" name="label_23">
<property name="text">
<string>Note: The password is saved unencrypted</string>
<string>Info: The password is saved unencrypted</string>
</property>
</widget>
</item>

View file

@ -1,40 +0,0 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Mike Tzou (Chocobo1)
* Copyright (C) 2011 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 "inhibitor.h"
bool Inhibitor::requestBusy()
{
return true;
}
bool Inhibitor::requestIdle()
{
return true;
}

View file

@ -1,38 +0,0 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Mike Tzou (Chocobo1)
*
* 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
class Inhibitor
{
public:
virtual ~Inhibitor() = default;
virtual bool requestBusy();
virtual bool requestIdle();
};

View file

@ -1,46 +0,0 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Mike Tzou (Chocobo1)
* Copyright (C) 2011 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 "inhibitormacos.h"
#include <QScopeGuard>
bool InhibitorMacOS::requestBusy()
{
const CFStringRef assertName = tr("PMMacOS", "qBittorrent is active").toCFString();
[[maybe_unused]] const auto assertNameGuard = qScopeGuard([&assertName] { ::CFRelease(assertName); });
const IOReturn result = ::IOPMAssertionCreateWithName(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn
, assertName, &m_assertionID);
return result == kIOReturnSuccess;
}
bool InhibitorMacOS::requestIdle()
{
return ::IOPMAssertionRelease(m_assertionID) == kIOReturnSuccess;
}

View file

@ -1,47 +0,0 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Mike Tzou (Chocobo1)
*
* 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 <IOKit/pwr_mgt/IOPMLib.h>
#include <QCoreApplication>
#include "inhibitor.h"
class InhibitorMacOS final : public Inhibitor
{
Q_DECLARE_TR_FUNCTIONS(InhibitorMacOS)
public:
bool requestBusy() override;
bool requestIdle() override;
private:
IOPMAssertionID m_assertionID {};
};

View file

@ -1,42 +0,0 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Mike Tzou (Chocobo1)
* Copyright (C) 2011 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 "inhibitorwindows.h"
#include <windows.h>
bool InhibitorWindows::requestBusy()
{
return ::SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED) != NULL;
}
bool InhibitorWindows::requestIdle()
{
return ::SetThreadExecutionState(ES_CONTINUOUS) != NULL;
}

View file

@ -1,38 +0,0 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Mike Tzou (Chocobo1)
*
* 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 "inhibitor.h"
class InhibitorWindows final : public Inhibitor
{
public:
bool requestBusy() override;
bool requestIdle() override;
};

View file

@ -1,6 +1,5 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Mike Tzou (Chocobo1)
* Copyright (C) 2011 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
@ -31,59 +30,71 @@
#include <QtSystemDetection>
#if defined(Q_OS_MACOS)
#include "inhibitormacos.h"
using InhibitorImpl = InhibitorMacOS;
#elif defined(Q_OS_WIN)
#include "inhibitorwindows.h"
using InhibitorImpl = InhibitorWindows;
#elif defined(QBT_USES_DBUS)
#include "inhibitordbus.h"
using InhibitorImpl = InhibitorDBus;
#else
#include "inhibitor.h"
using InhibitorImpl = Inhibitor;
#ifdef Q_OS_MACOS
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <QScopeGuard>
#endif
PowerManagement::PowerManagement()
: m_inhibitor {new InhibitorImpl}
#ifdef Q_OS_WIN
#include <windows.h>
#endif
#ifdef QBT_USES_DBUS
#include "powermanagement_x11.h"
#endif
PowerManagement::PowerManagement(QObject *parent)
: QObject(parent)
#ifdef QBT_USES_DBUS
, m_inhibitor {new PowerManagementInhibitor(this)}
#endif
{
}
PowerManagement::~PowerManagement()
{
setIdle();
delete m_inhibitor;
}
void PowerManagement::setActivityState(const ActivityState state)
void PowerManagement::setActivityState(const bool busy)
{
switch (state)
{
case ActivityState::Busy:
if (busy)
setBusy();
break;
case ActivityState::Idle:
else
setIdle();
break;
};
}
void PowerManagement::setBusy()
{
if (m_state == ActivityState::Busy)
if (m_busy)
return;
m_busy = true;
if (m_inhibitor->requestBusy())
m_state = ActivityState::Busy;
#ifdef Q_OS_WIN
::SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED);
#elif defined(QBT_USES_DBUS)
m_inhibitor->requestBusy();
#elif defined(Q_OS_MACOS)
const CFStringRef assertName = tr("qBittorrent is active").toCFString();
[[maybe_unused]] const auto assertNameGuard = qScopeGuard([&assertName] { ::CFRelease(assertName); });
const IOReturn success = ::IOPMAssertionCreateWithName(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn
, assertName, &m_assertionID);
if (success != kIOReturnSuccess)
m_busy = false;
#endif
}
void PowerManagement::setIdle()
{
if (m_state == ActivityState::Idle)
if (!m_busy)
return;
m_busy = false;
if (m_inhibitor->requestIdle())
m_state = ActivityState::Idle;
#ifdef Q_OS_WIN
::SetThreadExecutionState(ES_CONTINUOUS);
#elif defined(QBT_USES_DBUS)
m_inhibitor->requestIdle();
#elif defined(Q_OS_MACOS)
::IOPMAssertionRelease(m_assertionID);
#endif
}

View file

@ -1,6 +1,5 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Mike Tzou (Chocobo1)
* Copyright (C) 2011 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
@ -29,26 +28,38 @@
#pragma once
class Inhibitor;
#include <QObject>
class PowerManagement final
#ifdef Q_OS_MACOS
// Require Mac OS X >= 10.5
#include <IOKit/pwr_mgt/IOPMLib.h>
#endif
#ifdef QBT_USES_DBUS
class PowerManagementInhibitor;
#endif
class PowerManagement final : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(PowerManagement)
public:
enum class ActivityState
{
Busy,
Idle
};
PowerManagement(QObject *parent = nullptr);
~PowerManagement() override;
PowerManagement();
~PowerManagement();
void setActivityState(ActivityState state);
void setActivityState(bool busy);
private:
void setBusy();
void setIdle();
ActivityState m_state = ActivityState::Idle;
Inhibitor *m_inhibitor = nullptr;
bool m_busy = false;
#ifdef QBT_USES_DBUS
PowerManagementInhibitor *m_inhibitor = nullptr;
#endif
#ifdef Q_OS_MACOS
IOPMAssertionID m_assertionID {};
#endif
};

View file

@ -1,6 +1,5 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Mike Tzou (Chocobo1)
* Copyright (C) 2011 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
@ -27,7 +26,7 @@
* exception statement from your version.
*/
#include "inhibitordbus.h"
#include "powermanagement_x11.h"
#include <QDBusConnection>
#include <QDBusInterface>
@ -37,7 +36,7 @@
#include "base/global.h"
#include "base/logger.h"
InhibitorDBus::InhibitorDBus(QObject *parent)
PowerManagementInhibitor::PowerManagementInhibitor(QObject *parent)
: QObject(parent)
, m_busInterface {new QDBusInterface(u"org.gnome.SessionManager"_s, u"/org/gnome/SessionManager"_s
, u"org.gnome.SessionManager"_s, QDBusConnection::sessionBus(), this)}
@ -71,26 +70,38 @@ InhibitorDBus::InhibitorDBus(QObject *parent)
}
else
{
m_state = Error;
LogMsg(tr("Power management error. Did not find a suitable D-Bus interface."), Log::WARNING);
LogMsg(tr("Power management error. Did not found suitable D-Bus interface."), Log::WARNING);
}
}
bool InhibitorDBus::requestBusy()
void PowerManagementInhibitor::requestIdle()
{
m_intendedState = Idle;
if ((m_state == Error) || (m_state == Idle) || (m_state == RequestIdle) || (m_state == RequestBusy))
return;
if (m_manager == ManagerType::Systemd)
{
m_fd = {};
m_state = Idle;
return;
}
m_state = RequestIdle;
const QString method = (m_manager == ManagerType::Gnome)
? u"Uninhibit"_s
: u"UnInhibit"_s;
const QDBusPendingCall pcall = m_busInterface->asyncCall(method, m_cookie);
const auto *watcher = new QDBusPendingCallWatcher(pcall, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, &PowerManagementInhibitor::onAsyncReply);
}
void PowerManagementInhibitor::requestBusy()
{
m_intendedState = Busy;
switch (m_state)
{
case Busy:
case RequestBusy:
return true;
case Error:
case RequestIdle:
return false;
case Idle:
break;
};
if ((m_state == Error) || (m_state == Busy) || (m_state == RequestBusy) || (m_state == RequestIdle))
return;
m_state = RequestBusy;
@ -112,45 +123,10 @@ bool InhibitorDBus::requestBusy()
const QDBusPendingCall pcall = m_busInterface->asyncCallWithArgumentList(u"Inhibit"_s, args);
const auto *watcher = new QDBusPendingCallWatcher(pcall, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, &InhibitorDBus::onAsyncReply);
return true;
connect(watcher, &QDBusPendingCallWatcher::finished, this, &PowerManagementInhibitor::onAsyncReply);
}
bool InhibitorDBus::requestIdle()
{
m_intendedState = Idle;
switch (m_state)
{
case Idle:
case RequestIdle:
return true;
case Error:
case RequestBusy:
return false;
case Busy:
break;
};
if (m_manager == ManagerType::Systemd)
{
m_fd = {};
m_state = Idle;
return true;
}
m_state = RequestIdle;
const QString method = (m_manager == ManagerType::Gnome)
? u"Uninhibit"_s
: u"UnInhibit"_s;
const QDBusPendingCall pcall = m_busInterface->asyncCall(method, m_cookie);
const auto *watcher = new QDBusPendingCallWatcher(pcall, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, &InhibitorDBus::onAsyncReply);
return true;
}
void InhibitorDBus::onAsyncReply(QDBusPendingCallWatcher *call)
void PowerManagementInhibitor::onAsyncReply(QDBusPendingCallWatcher *call)
{
call->deleteLater();

View file

@ -1,6 +1,5 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Mike Tzou (Chocobo1)
* Copyright (C) 2011 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
@ -32,21 +31,20 @@
#include <QDBusUnixFileDescriptor>
#include <QObject>
#include "inhibitor.h"
class QDBusInterface;
class QDBusPendingCallWatcher;
class InhibitorDBus final : public QObject, public Inhibitor
class PowerManagementInhibitor final : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(InhibitorDBus)
Q_DISABLE_COPY_MOVE(PowerManagementInhibitor)
public:
InhibitorDBus(QObject *parent = nullptr);
PowerManagementInhibitor(QObject *parent = nullptr);
~PowerManagementInhibitor() override = default;
bool requestBusy() override;
bool requestIdle() override;
void requestIdle();
void requestBusy();
private slots:
void onAsyncReply(QDBusPendingCallWatcher *call);

View file

@ -58,7 +58,6 @@
#include "base/utils/misc.h"
#include "base/utils/string.h"
#include "gui/uithememanager.h"
#include "gui/utils/keysequence.h"
#include "peerlistsortmodel.h"
#include "peersadditiondialog.h"
#include "propertieswidget.h"
@ -188,7 +187,7 @@ PeerListWidget::PeerListWidget(PropertiesWidget *parent)
handleSortColumnChanged(header()->sortIndicatorSection());
const auto *copyHotkey = new QShortcut(QKeySequence::Copy, this, nullptr, nullptr, Qt::WidgetShortcut);
connect(copyHotkey, &QShortcut::activated, this, &PeerListWidget::copySelectedPeers);
const auto *deleteHotkey = new QShortcut(Utils::KeySequence::deleteItem(), this, nullptr, nullptr, Qt::WidgetShortcut);
const auto *deleteHotkey = new QShortcut(QKeySequence::Delete, this, nullptr, nullptr, Qt::WidgetShortcut);
connect(deleteHotkey, &QShortcut::activated, this, &PeerListWidget::banSelectedPeers);
}

View file

@ -56,7 +56,6 @@
#include "gui/trackerlist/trackerlistwidget.h"
#include "gui/uithememanager.h"
#include "gui/utils.h"
#include "gui/utils/keysequence.h"
#include "downloadedpiecesbar.h"
#include "peerlistwidget.h"
#include "pieceavailabilitybar.h"
@ -137,7 +136,7 @@ PropertiesWidget::PropertiesWidget(QWidget *parent)
const auto *editWebSeedsHotkey = new QShortcut(Qt::Key_F2, m_ui->listWebSeeds, nullptr, nullptr, Qt::WidgetShortcut);
connect(editWebSeedsHotkey, &QShortcut::activated, this, &PropertiesWidget::editWebSeed);
const auto *deleteWebSeedsHotkey = new QShortcut(Utils::KeySequence::deleteItem(), m_ui->listWebSeeds, nullptr, nullptr, Qt::WidgetShortcut);
const auto *deleteWebSeedsHotkey = new QShortcut(QKeySequence::Delete, m_ui->listWebSeeds, nullptr, nullptr, Qt::WidgetShortcut);
connect(deleteWebSeedsHotkey, &QShortcut::activated, this, &PropertiesWidget::deleteSelectedUrlSeeds);
connect(m_ui->listWebSeeds, &QListWidget::doubleClicked, this, &PropertiesWidget::editWebSeed);

View file

@ -55,7 +55,6 @@
#include "gui/torrentcategorydialog.h"
#include "gui/uithememanager.h"
#include "gui/utils.h"
#include "gui/utils/keysequence.h"
#include "ui_automatedrssdownloader.h"
const QString EXT_JSON = u".json"_s;
@ -152,7 +151,7 @@ AutomatedRssDownloader::AutomatedRssDownloader(QWidget *parent)
const auto *editHotkey = new QShortcut(Qt::Key_F2, m_ui->ruleList, nullptr, nullptr, Qt::WidgetShortcut);
connect(editHotkey, &QShortcut::activated, this, &AutomatedRssDownloader::renameSelectedRule);
const auto *deleteHotkey = new QShortcut(Utils::KeySequence::deleteItem(), m_ui->ruleList, nullptr, nullptr, Qt::WidgetShortcut);
const auto *deleteHotkey = new QShortcut(QKeySequence::Delete, m_ui->ruleList, nullptr, nullptr, Qt::WidgetShortcut);
connect(deleteHotkey, &QShortcut::activated, this, &AutomatedRssDownloader::onRemoveRuleBtnClicked);
connect(m_ui->ruleList, &QAbstractItemView::doubleClicked, this, &AutomatedRssDownloader::renameSelectedRule);

View file

@ -1,82 +0,0 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 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 "rssfeeddialog.h"
#include <QPushButton>
#include "ui_rssfeeddialog.h"
RSSFeedDialog::RSSFeedDialog(QWidget *parent)
: QDialog(parent)
, m_ui {new Ui::RSSFeedDialog}
{
m_ui->setupUi(this);
m_ui->spinRefreshInterval->setMaximum(std::numeric_limits<int>::max());
m_ui->spinRefreshInterval->setStepType(QAbstractSpinBox::AdaptiveDecimalStepType);
m_ui->spinRefreshInterval->setSuffix(tr(" sec"));
m_ui->spinRefreshInterval->setSpecialValueText(tr("Default"));
// disable Ok button
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(m_ui->textFeedURL, &QLineEdit::textChanged, this, &RSSFeedDialog::feedURLChanged);
}
RSSFeedDialog::~RSSFeedDialog()
{
delete m_ui;
}
QString RSSFeedDialog::feedURL() const
{
return m_ui->textFeedURL->text();
}
void RSSFeedDialog::setFeedURL(const QString &feedURL)
{
m_ui->textFeedURL->setText(feedURL);
}
std::chrono::seconds RSSFeedDialog::refreshInterval() const
{
return std::chrono::seconds(m_ui->spinRefreshInterval->value());
}
void RSSFeedDialog::setRefreshInterval(const std::chrono::seconds refreshInterval)
{
m_ui->spinRefreshInterval->setValue(refreshInterval.count());
}
void RSSFeedDialog::feedURLChanged(const QString &feedURL)
{
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!feedURL.isEmpty());
}

View file

@ -1,57 +0,0 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 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 <chrono>
#include <QDialog>
namespace Ui
{
class RSSFeedDialog;
}
class RSSFeedDialog final : public QDialog
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(RSSFeedDialog)
public:
explicit RSSFeedDialog(QWidget *parent = nullptr);
~RSSFeedDialog() override;
QString feedURL() const;
void setFeedURL(const QString &feedURL);
std::chrono::seconds refreshInterval() const;
void setRefreshInterval(std::chrono::seconds refreshInterval);
private:
void feedURLChanged(const QString &feedURL);
Ui::RSSFeedDialog *m_ui = nullptr;
};

View file

@ -1,75 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RSSFeedDialog</class>
<widget class="QDialog" name="RSSFeedDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>555</width>
<height>106</height>
</rect>
</property>
<property name="windowTitle">
<string>RSS Feed Options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="labelFeedURL">
<property name="text">
<string>URL:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="textFeedURL"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelRefreshInterval">
<property name="text">
<string>Refresh interval:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="spinRefreshInterval">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -49,11 +49,9 @@
#include "gui/autoexpandabledialog.h"
#include "gui/interfaces/iguiapplication.h"
#include "gui/uithememanager.h"
#include "gui/utils/keysequence.h"
#include "articlelistwidget.h"
#include "automatedrssdownloader.h"
#include "feedlistwidget.h"
#include "rssfeeddialog.h"
#include "ui_rsswidget.h"
namespace
@ -71,8 +69,8 @@ namespace
while (iter.hasNext())
{
const QRegularExpressionMatch match = iter.next();
const QStringView scheme = match.capturedView(4);
const QStringView host = match.capturedView(5);
const QString scheme = match.captured(4);
const QString host = match.captured(5);
if (!scheme.isEmpty())
{
if (host.isEmpty())
@ -82,21 +80,15 @@ namespace
continue;
}
QStringView relativePath = match.capturedView(6);
QString relativePath = match.captured(6);
if (relativePath.startsWith(u'/'))
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
relativePath.slice(1);
#else
relativePath = relativePath.sliced(1);
#endif
}
relativePath = relativePath.mid(1);
const QString absoluteUrl = !host.isEmpty()
? QString(defaultScheme + u':' + host) : (normalizedBaseUrl + relativePath);
const QString fullMatch = match.captured(0);
const QStringView prefix = match.capturedView(1);
const QStringView suffix = match.capturedView(7);
const QString prefix = match.captured(1);
const QString suffix = match.captured(7);
html.replace(fullMatch, (prefix + absoluteUrl + suffix));
}
@ -114,7 +106,7 @@ RSSWidget::RSSWidget(IGUIApplication *app, QWidget *parent)
m_ui->actionCopyFeedURL->setIcon(UIThemeManager::instance()->getIcon(u"edit-copy"_s));
m_ui->actionDelete->setIcon(UIThemeManager::instance()->getIcon(u"edit-clear"_s));
m_ui->actionDownloadTorrent->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_s, u"download"_s));
m_ui->actionEditFeed->setIcon(UIThemeManager::instance()->getIcon(u"edit-rename"_s));
m_ui->actionEditFeedURL->setIcon(UIThemeManager::instance()->getIcon(u"edit-rename"_s));
m_ui->actionMarkItemsRead->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_s, u"mail-mark-read"_s));
m_ui->actionNewFolder->setIcon(UIThemeManager::instance()->getIcon(u"folder-new"_s));
m_ui->actionNewSubscription->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_s));
@ -141,13 +133,13 @@ RSSWidget::RSSWidget(IGUIApplication *app, QWidget *parent)
const auto *editHotkey = new QShortcut(Qt::Key_F2, m_ui->feedListWidget, nullptr, nullptr, Qt::WidgetShortcut);
connect(editHotkey, &QShortcut::activated, this, &RSSWidget::renameSelectedRSSItem);
const auto *deleteHotkey = new QShortcut(Utils::KeySequence::deleteItem(), m_ui->feedListWidget, nullptr, nullptr, Qt::WidgetShortcut);
const auto *deleteHotkey = new QShortcut(QKeySequence::Delete, m_ui->feedListWidget, nullptr, nullptr, Qt::WidgetShortcut);
connect(deleteHotkey, &QShortcut::activated, this, &RSSWidget::deleteSelectedItems);
// Feeds list actions
connect(m_ui->actionDelete, &QAction::triggered, this, &RSSWidget::deleteSelectedItems);
connect(m_ui->actionRename, &QAction::triggered, this, &RSSWidget::renameSelectedRSSItem);
connect(m_ui->actionEditFeed, &QAction::triggered, this, &RSSWidget::editSelectedRSSFeed);
connect(m_ui->actionEditFeedURL, &QAction::triggered, this, &RSSWidget::editSelectedRSSFeedURL);
connect(m_ui->actionUpdate, &QAction::triggered, this, &RSSWidget::refreshSelectedItems);
connect(m_ui->actionNewFolder, &QAction::triggered, this, &RSSWidget::askNewFolder);
connect(m_ui->actionNewSubscription, &QAction::triggered, this, &RSSWidget::on_newFeedButton_clicked);
@ -211,7 +203,7 @@ void RSSWidget::displayRSSListMenu(const QPoint &pos)
{
menu->addAction(m_ui->actionRename);
if (m_ui->feedListWidget->isFeed(selectedItem))
menu->addAction(m_ui->actionEditFeed);
menu->addAction(m_ui->actionEditFeedURL);
menu->addAction(m_ui->actionDelete);
menu->addSeparator();
if (m_ui->feedListWidget->isFolder(selectedItem))
@ -294,29 +286,36 @@ void RSSWidget::askNewFolder()
}
// Consider the case where the user clicked on Unread item
RSS::Folder *rssDestFolder = ((!destItem || (destItem == m_ui->feedListWidget->stickyUnreadItem()))
? RSS::Session::instance()->rootFolder()
: qobject_cast<RSS::Folder *>(m_ui->feedListWidget->getRSSItem(destItem)));
? RSS::Session::instance()->rootFolder()
: qobject_cast<RSS::Folder *>(m_ui->feedListWidget->getRSSItem(destItem)));
const QString newFolderPath = RSS::Item::joinPath(rssDestFolder->path(), newName);
const nonstd::expected<RSS::Folder *, QString> result = RSS::Session::instance()->addFolder(newFolderPath);
const nonstd::expected<void, QString> result = RSS::Session::instance()->addFolder(newFolderPath);
if (!result)
{
QMessageBox::warning(this, u"qBittorrent"_s, result.error(), QMessageBox::Ok);
return;
}
RSS::Folder *newFolder = result.value();
// Expand destination folder to display new feed
if (destItem && (destItem != m_ui->feedListWidget->stickyUnreadItem()))
destItem->setExpanded(true);
// As new RSS items are added synchronously, we can do the following here.
m_ui->feedListWidget->setCurrentItem(m_ui->feedListWidget->mapRSSItem(newFolder));
m_ui->feedListWidget->setCurrentItem(m_ui->feedListWidget->mapRSSItem(RSS::Session::instance()->itemByPath(newFolderPath)));
}
// add a stream by a button
void RSSWidget::on_newFeedButton_clicked()
{
// Ask for feed URL
const QString clipText = qApp->clipboard()->text();
const QString defaultURL = Net::DownloadManager::hasSupportedScheme(clipText) ? clipText : u"http://"_s;
bool ok = false;
QString newURL = AutoExpandableDialog::getText(
this, tr("Please type a RSS feed URL"), tr("Feed URL:"), QLineEdit::Normal, defaultURL, &ok);
if (!ok) return;
newURL = newURL.trimmed();
if (newURL.isEmpty()) return;
// Determine destination folder for new item
QTreeWidgetItem *destItem = nullptr;
QList<QTreeWidgetItem *> selectedItems = m_ui->feedListWidget->selectedItems();
@ -327,38 +326,21 @@ void RSSWidget::on_newFeedButton_clicked()
destItem = destItem->parent();
}
// Consider the case where the user clicked on Unread item
RSS::Folder *destFolder = ((!destItem || (destItem == m_ui->feedListWidget->stickyUnreadItem()))
? RSS::Session::instance()->rootFolder()
: qobject_cast<RSS::Folder *>(m_ui->feedListWidget->getRSSItem(destItem)));
RSS::Folder *rssDestFolder = ((!destItem || (destItem == m_ui->feedListWidget->stickyUnreadItem()))
? RSS::Session::instance()->rootFolder()
: qobject_cast<RSS::Folder *>(m_ui->feedListWidget->getRSSItem(destItem)));
// Ask for feed URL
const QString clipText = qApp->clipboard()->text();
const QString defaultURL = Net::DownloadManager::hasSupportedScheme(clipText) ? clipText : u"http://"_s;
RSS::Feed *newFeed = nullptr;
RSSFeedDialog dialog {this};
dialog.setFeedURL(defaultURL);
while (!newFeed && (dialog.exec() == RSSFeedDialog::Accepted))
{
const QString feedURL = dialog.feedURL().trimmed();
const std::chrono::seconds refreshInterval = dialog.refreshInterval();
const QString feedPath = RSS::Item::joinPath(destFolder->path(), feedURL);
const nonstd::expected<RSS::Feed *, QString> result = RSS::Session::instance()->addFeed(feedURL, feedPath, refreshInterval);
if (result)
newFeed = result.value();
else
QMessageBox::warning(&dialog, u"qBittorrent"_s, result.error(), QMessageBox::Ok);
}
if (!newFeed)
return;
// NOTE: We still add feed using legacy way (with URL as feed name)
const QString newFeedPath = RSS::Item::joinPath(rssDestFolder->path(), newURL);
const nonstd::expected<void, QString> result = RSS::Session::instance()->addFeed(newURL, newFeedPath);
if (!result)
QMessageBox::warning(this, u"qBittorrent"_s, result.error(), QMessageBox::Ok);
// Expand destination folder to display new feed
if (destItem && (destItem != m_ui->feedListWidget->stickyUnreadItem()))
destItem->setExpanded(true);
// As new RSS items are added synchronously, we can do the following here.
m_ui->feedListWidget->setCurrentItem(m_ui->feedListWidget->mapRSSItem(newFeed));
m_ui->feedListWidget->setCurrentItem(m_ui->feedListWidget->mapRSSItem(RSS::Session::instance()->itemByPath(newFeedPath)));
}
void RSSWidget::deleteSelectedItems()
@ -413,7 +395,7 @@ void RSSWidget::saveFoldersOpenState()
void RSSWidget::refreshAllFeeds()
{
RSS::Session::instance()->rootFolder()->refresh();
RSS::Session::instance()->refresh();
}
void RSSWidget::downloadSelectedTorrents()
@ -475,7 +457,7 @@ void RSSWidget::renameSelectedRSSItem()
} while (!ok);
}
void RSSWidget::editSelectedRSSFeed()
void RSSWidget::editSelectedRSSFeedURL()
{
QList<QTreeWidgetItem *> selectedItems = m_ui->feedListWidget->selectedItems();
if (selectedItems.size() != 1)
@ -487,20 +469,15 @@ void RSSWidget::editSelectedRSSFeed()
if (!rssFeed) [[unlikely]]
return;
auto *dialog = new RSSFeedDialog(this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setFeedURL(rssFeed->url());
dialog->setRefreshInterval(rssFeed->refreshInterval());
connect(dialog, &RSSFeedDialog::accepted, this, [this, dialog, rssFeed]
{
rssFeed->setRefreshInterval(dialog->refreshInterval());
bool ok = false;
QString newURL = AutoExpandableDialog::getText(this, tr("Please type a RSS feed URL")
, tr("Feed URL:"), QLineEdit::Normal, rssFeed->url(), &ok).trimmed();
if (!ok || newURL.isEmpty())
return;
const QString newURL = dialog->feedURL();
const nonstd::expected<void, QString> result = RSS::Session::instance()->setFeedURL(rssFeed, newURL);
if (!result)
QMessageBox::warning(this, u"qBittorrent"_s, result.error(), QMessageBox::Ok);
});
dialog->open();
const nonstd::expected<void, QString> result = RSS::Session::instance()->setFeedURL(rssFeed, newURL);
if (!result)
QMessageBox::warning(this, u"qBittorrent"_s, result.error(), QMessageBox::Ok);
}
void RSSWidget::refreshSelectedItems()

View file

@ -70,7 +70,7 @@ private slots:
void displayRSSListMenu(const QPoint &pos);
void displayItemsListMenu();
void renameSelectedRSSItem();
void editSelectedRSSFeed();
void editSelectedRSSFeedURL();
void refreshSelectedItems();
void copySelectedFeedsURL();
void handleCurrentFeedItemChanged(QTreeWidgetItem *currentItem);

View file

@ -198,9 +198,12 @@
<string>New folder...</string>
</property>
</action>
<action name="actionEditFeed">
<action name="actionEditFeedURL">
<property name="text">
<string>Feed options...</string>
<string>Edit feed URL...</string>
</property>
<property name="toolTip">
<string>Edit feed URL</string>
</property>
</action>
</widget>

View file

@ -46,7 +46,7 @@ void SearchSortModel::setNameFilter(const QString &searchTerm)
{
m_searchTerm = searchTerm;
if ((searchTerm.length() > 2) && searchTerm.startsWith(u'"') && searchTerm.endsWith(u'"'))
m_searchTermWords = QStringList(m_searchTerm.sliced(1, (m_searchTerm.length() - 2)));
m_searchTermWords = QStringList(m_searchTerm.mid(1, m_searchTerm.length() - 2));
else
m_searchTermWords = searchTerm.split(u' ', Qt::SkipEmptyParts);
}

View file

@ -172,10 +172,8 @@ namespace
return nonstd::make_unexpected(readResult.error().message);
}
const QList<QByteArrayView> lines = Utils::ByteArray::splitToViews(readResult.value(), "\n");
QStringList history;
history.reserve(lines.size());
for (const QByteArrayView line : lines)
for (const QByteArrayView line : asConst(Utils::ByteArray::splitToViews(readResult.value(), "\n")))
history.append(QString::fromUtf8(line));
return history;
@ -675,7 +673,7 @@ void SearchWidget::loadHistory()
}
if (history.size() > m_historyLength)
history.remove(0, (history.size() - m_historyLength));
history = history.mid(history.size() - m_historyLength);
m_searchPatternCompleterModel->setStringList(history);
});

View file

@ -44,19 +44,6 @@
#include "uithememanager.h"
#include "utils.h"
namespace
{
QWidget *createSeparator(QWidget *parent)
{
QFrame *separator = new QFrame(parent);
separator->setFrameStyle(QFrame::VLine);
#ifndef Q_OS_MACOS
separator->setFrameShadow(QFrame::Raised);
#endif
return separator;
}
}
StatusBar::StatusBar(QWidget *parent)
: QStatusBar(parent)
{
@ -100,17 +87,11 @@ StatusBar::StatusBar(QWidget *parent)
m_upSpeedLbl->setStyleSheet(u"text-align:left;"_s);
m_upSpeedLbl->setMinimumWidth(200);
m_freeDiskSpaceLbl = new QLabel(tr("Free space: N/A"));
m_freeDiskSpaceLbl->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
m_freeDiskSpaceSeparator = createSeparator(m_freeDiskSpaceLbl);
m_lastExternalIPsLbl = new QLabel(tr("External IP: N/A"));
m_lastExternalIPsLbl->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
m_lastExternalIPsSeparator = createSeparator(m_lastExternalIPsLbl);
m_DHTLbl = new QLabel(tr("DHT: %1 nodes").arg(0), this);
m_DHTLbl->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
m_DHTSeparator = createSeparator(m_DHTLbl);
m_altSpeedsBtn = new QPushButton(this);
m_altSpeedsBtn->setFlat(true);
@ -132,42 +113,52 @@ StatusBar::StatusBar(QWidget *parent)
m_connecStatusLblIcon->setMaximumWidth(Utils::Gui::largeIconSize().width());
m_altSpeedsBtn->setMaximumWidth(Utils::Gui::largeIconSize().width());
layout->addWidget(m_freeDiskSpaceLbl);
layout->addWidget(m_freeDiskSpaceSeparator);
QFrame *statusSep1 = new QFrame(this);
statusSep1->setFrameStyle(QFrame::VLine);
#ifndef Q_OS_MACOS
statusSep1->setFrameShadow(QFrame::Raised);
#endif
QFrame *statusSep2 = new QFrame(this);
statusSep2->setFrameStyle(QFrame::VLine);
#ifndef Q_OS_MACOS
statusSep2->setFrameShadow(QFrame::Raised);
#endif
QFrame *statusSep3 = new QFrame(this);
statusSep3->setFrameStyle(QFrame::VLine);
#ifndef Q_OS_MACOS
statusSep3->setFrameShadow(QFrame::Raised);
#endif
QFrame *statusSep4 = new QFrame(this);
statusSep4->setFrameStyle(QFrame::VLine);
#ifndef Q_OS_MACOS
statusSep4->setFrameShadow(QFrame::Raised);
#endif
QFrame *statusSep5 = new QFrame(this);
statusSep5->setFrameStyle(QFrame::VLine);
#ifndef Q_OS_MACOS
statusSep5->setFrameShadow(QFrame::Raised);
#endif
layout->addWidget(m_lastExternalIPsLbl);
layout->addWidget(m_lastExternalIPsSeparator);
layout->addWidget(statusSep1);
layout->addWidget(m_DHTLbl);
layout->addWidget(m_DHTSeparator);
layout->addWidget(statusSep2);
layout->addWidget(m_connecStatusLblIcon);
layout->addWidget(createSeparator(m_connecStatusLblIcon));
layout->addWidget(statusSep3);
layout->addWidget(m_altSpeedsBtn);
layout->addWidget(createSeparator(m_altSpeedsBtn));
layout->addWidget(statusSep4);
layout->addWidget(m_dlSpeedLbl);
layout->addWidget(createSeparator(m_dlSpeedLbl));
layout->addWidget(statusSep5);
layout->addWidget(m_upSpeedLbl);
addPermanentWidget(container);
setStyleSheet(u"QWidget {margin: 0;}"_s);
container->adjustSize();
adjustSize();
updateFreeDiskSpaceVisibility();
updateExternalAddressesVisibility();
// Is DHT enabled
const bool isDHTVisible = session->isDHTEnabled();
m_DHTLbl->setVisible(isDHTVisible);
m_DHTSeparator->setVisible(isDHTVisible);
m_DHTLbl->setVisible(session->isDHTEnabled());
refresh();
connect(session, &BitTorrent::Session::statsUpdated, this, &StatusBar::refresh);
updateFreeDiskSpaceLabel(session->freeDiskSpace());
connect(session, &BitTorrent::Session::freeDiskSpaceChecked, this, &StatusBar::updateFreeDiskSpaceLabel);
connect(Preferences::instance(), &Preferences::changed, this, &StatusBar::optionsSaved);
}
@ -225,28 +216,15 @@ void StatusBar::updateDHTNodesNumber()
if (BitTorrent::Session::instance()->isDHTEnabled())
{
m_DHTLbl->setVisible(true);
m_DHTSeparator->setVisible(true);
m_DHTLbl->setText(tr("DHT: %1 nodes").arg(BitTorrent::Session::instance()->status().dhtNodes));
m_DHTLbl->setText(tr("DHT: %1 nodes")
.arg(BitTorrent::Session::instance()->status().dhtNodes));
}
else
{
m_DHTLbl->setVisible(false);
m_DHTSeparator->setVisible(false);
}
}
void StatusBar::updateFreeDiskSpaceLabel(const qint64 value)
{
m_freeDiskSpaceLbl->setText(tr("Free space: ") + Utils::Misc::friendlyUnit(value));
}
void StatusBar::updateFreeDiskSpaceVisibility()
{
const bool isVisible = Preferences::instance()->isStatusbarFreeDiskSpaceDisplayed();
m_freeDiskSpaceLbl->setVisible(isVisible);
m_freeDiskSpaceSeparator->setVisible(isVisible);
}
void StatusBar::updateExternalAddressesLabel()
{
const QString lastExternalIPv4Address = BitTorrent::Session::instance()->lastExternalIPv4Address();
@ -266,9 +244,7 @@ void StatusBar::updateExternalAddressesLabel()
void StatusBar::updateExternalAddressesVisibility()
{
const bool isVisible = Preferences::instance()->isStatusbarExternalIPDisplayed();
m_lastExternalIPsLbl->setVisible(isVisible);
m_lastExternalIPsSeparator->setVisible(isVisible);
m_lastExternalIPsLbl->setVisible(Preferences::instance()->isStatusbarExternalIPDisplayed());
}
void StatusBar::updateSpeedLabels()
@ -324,6 +300,5 @@ void StatusBar::capSpeed()
void StatusBar::optionsSaved()
{
updateFreeDiskSpaceVisibility();
updateExternalAddressesVisibility();
}

View file

@ -63,20 +63,14 @@ private slots:
private:
void updateConnectionStatus();
void updateDHTNodesNumber();
void updateFreeDiskSpaceLabel(qint64 value);
void updateFreeDiskSpaceVisibility();
void updateExternalAddressesLabel();
void updateExternalAddressesVisibility();
void updateSpeedLabels();
QPushButton *m_dlSpeedLbl = nullptr;
QPushButton *m_upSpeedLbl = nullptr;
QLabel *m_freeDiskSpaceLbl = nullptr;
QWidget *m_freeDiskSpaceSeparator = nullptr;
QLabel *m_lastExternalIPsLbl = nullptr;
QWidget *m_lastExternalIPsSeparator = nullptr;
QLabel *m_DHTLbl = nullptr;
QWidget *m_DHTSeparator = nullptr;
QPushButton *m_connecStatusLblIcon = nullptr;
QPushButton *m_altSpeedsBtn = nullptr;
};

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