mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-17 14:08:03 -06:00
5
.github/workflows/ci_windows.yaml
vendored
5
.github/workflows/ci_windows.yaml
vendored
@@ -191,6 +191,11 @@ jobs:
|
|||||||
name: qBittorrent-CI_Windows-x64_libtorrent-${{ matrix.libt_version }}
|
name: qBittorrent-CI_Windows-x64_libtorrent-${{ matrix.libt_version }}
|
||||||
path: upload
|
path: upload
|
||||||
|
|
||||||
|
- name: Install NSIS
|
||||||
|
uses: repolevedavaj/install-nsis@265e893c16602d8ccfb0a9ca44173b084078917c # v1.0.3
|
||||||
|
with:
|
||||||
|
nsis-version: '3.11'
|
||||||
|
|
||||||
- name: Create installer
|
- name: Create installer
|
||||||
run: |
|
run: |
|
||||||
7z x -o"dist/windows/" "dist/windows/NSISPlugins.zip"
|
7z x -o"dist/windows/" "dist/windows/NSISPlugins.zip"
|
||||||
|
|||||||
@@ -86,6 +86,19 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
|||||||
PROPERTIES
|
PROPERTIES
|
||||||
MACOSX_PACKAGE_LOCATION Resources
|
MACOSX_PACKAGE_LOCATION Resources
|
||||||
)
|
)
|
||||||
|
# Generate lproj folders for the translations
|
||||||
|
foreach(TS_FILE IN LISTS QBT_TS_FILES)
|
||||||
|
string(FIND "${TS_FILE}" "_" POS)
|
||||||
|
math(EXPR START "${POS} + 1")
|
||||||
|
string(SUBSTRING "${TS_FILE}" ${START} -1 LPROJ_FOLDER)
|
||||||
|
string(REPLACE ".ts" ".lproj" LPROJ_FOLDER "${LPROJ_FOLDER}")
|
||||||
|
# @ is not valid as a language code for a lproj folder on MacOS
|
||||||
|
string(REPLACE "@" "-" LPROJ_FOLDER "${LPROJ_FOLDER}")
|
||||||
|
add_custom_command(TARGET qbt_app POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||||
|
"$<TARGET_FILE_DIR:qbt_app>/../Resources/${LPROJ_FOLDER}"
|
||||||
|
)
|
||||||
|
endforeach()
|
||||||
# provide variables for substitution in dist/mac/Info.plist
|
# provide variables for substitution in dist/mac/Info.plist
|
||||||
get_target_property(EXECUTABLE_NAME qbt_app OUTPUT_NAME)
|
get_target_property(EXECUTABLE_NAME qbt_app OUTPUT_NAME)
|
||||||
# This variable name should be changed once qmake is no longer used. Refer to the discussion in PR #14813
|
# This variable name should be changed once qmake is no longer used. Refer to the discussion in PR #14813
|
||||||
|
|||||||
@@ -147,12 +147,14 @@ namespace
|
|||||||
|
|
||||||
QString value(const QString &arg) const
|
QString value(const QString &arg) const
|
||||||
{
|
{
|
||||||
QStringList parts = arg.split(u'=');
|
const qsizetype index = arg.indexOf(u'=');
|
||||||
if (parts.size() == 2)
|
if (index == -1)
|
||||||
return Utils::String::unquote(parts[1], u"'\""_s);
|
|
||||||
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
|
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
|
||||||
"e.g. Parameter '--webui-port' must follow syntax '--webui-port=value'")
|
"e.g. Parameter '--webui-port' must follow syntax '--webui-port=value'")
|
||||||
.arg(fullParameter(), u"<value>"_s));
|
.arg(fullParameter(), u"<value>"_s));
|
||||||
|
|
||||||
|
const QStringView val = QStringView(arg).sliced(index + 1);
|
||||||
|
return Utils::String::unquote(val, u"'\""_s).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString value(const QProcessEnvironment &env, const QString &defaultValue = {}) const
|
QString value(const QProcessEnvironment &env, const QString &defaultValue = {}) const
|
||||||
@@ -168,7 +170,7 @@ namespace
|
|||||||
|
|
||||||
friend bool operator==(const StringOption &option, const QString &arg)
|
friend bool operator==(const StringOption &option, const QString &arg)
|
||||||
{
|
{
|
||||||
return arg.startsWith(option.parameterAssignment());
|
return (arg == option.fullParameter()) || arg.startsWith(option.parameterAssignment());
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -3151,7 +3151,7 @@ void SessionImpl::exportTorrentFile(const Torrent *torrent, const Path &folderPa
|
|||||||
while (newTorrentPath.exists())
|
while (newTorrentPath.exists())
|
||||||
{
|
{
|
||||||
// Append number to torrent name to make it unique
|
// Append number to torrent name to make it unique
|
||||||
torrentExportFilename = u"%1 %2.torrent"_s.arg(validName).arg(++counter);
|
torrentExportFilename = u"%1 (%2).torrent"_s.arg(validName).arg(++counter);
|
||||||
newTorrentPath = folderPath / Path(torrentExportFilename);
|
newTorrentPath = folderPath / Path(torrentExportFilename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5651,6 +5651,25 @@ void SessionImpl::fetchPendingAlerts(const lt::time_duration time)
|
|||||||
m_nativeSession->pop_alerts(&m_alerts);
|
m_nativeSession->pop_alerts(&m_alerts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SessionImpl::endAlertSequence(const int alertType, const qsizetype alertCount)
|
||||||
|
{
|
||||||
|
qDebug() << "End alert sequence. Alert:" << lt::alert_name(alertType) << "Count:" << alertCount;
|
||||||
|
|
||||||
|
if (alertType == lt::add_torrent_alert::alert_type)
|
||||||
|
{
|
||||||
|
emit addTorrentAlertsReceived(alertCount);
|
||||||
|
|
||||||
|
if (!m_loadedTorrents.isEmpty())
|
||||||
|
{
|
||||||
|
if (isRestored())
|
||||||
|
m_torrentsQueueChanged = true;
|
||||||
|
|
||||||
|
emit torrentsLoaded(m_loadedTorrents);
|
||||||
|
m_loadedTorrents.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TorrentContentLayout SessionImpl::torrentContentLayout() const
|
TorrentContentLayout SessionImpl::torrentContentLayout() const
|
||||||
{
|
{
|
||||||
return m_torrentContentLayout;
|
return m_torrentContentLayout;
|
||||||
@@ -5667,28 +5686,26 @@ void SessionImpl::readAlerts()
|
|||||||
fetchPendingAlerts();
|
fetchPendingAlerts();
|
||||||
|
|
||||||
Q_ASSERT(m_loadedTorrents.isEmpty());
|
Q_ASSERT(m_loadedTorrents.isEmpty());
|
||||||
Q_ASSERT(m_receivedAddTorrentAlertsCount == 0);
|
|
||||||
|
|
||||||
if (!isRestored())
|
if (!isRestored())
|
||||||
m_loadedTorrents.reserve(MAX_PROCESSING_RESUMEDATA_COUNT);
|
m_loadedTorrents.reserve(MAX_PROCESSING_RESUMEDATA_COUNT);
|
||||||
|
|
||||||
|
int previousAlertType = -1;
|
||||||
|
qsizetype alertSequenceSize = 0;
|
||||||
for (const lt::alert *a : m_alerts)
|
for (const lt::alert *a : m_alerts)
|
||||||
|
{
|
||||||
|
const int alertType = a->type();
|
||||||
|
if ((alertType != previousAlertType) && (previousAlertType != -1))
|
||||||
|
{
|
||||||
|
endAlertSequence(previousAlertType, alertSequenceSize);
|
||||||
|
alertSequenceSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
handleAlert(a);
|
handleAlert(a);
|
||||||
|
++alertSequenceSize;
|
||||||
if (m_receivedAddTorrentAlertsCount > 0)
|
previousAlertType = alertType;
|
||||||
{
|
|
||||||
emit addTorrentAlertsReceived(m_receivedAddTorrentAlertsCount);
|
|
||||||
m_receivedAddTorrentAlertsCount = 0;
|
|
||||||
|
|
||||||
if (!m_loadedTorrents.isEmpty())
|
|
||||||
{
|
|
||||||
if (isRestored())
|
|
||||||
m_torrentsQueueChanged = true;
|
|
||||||
|
|
||||||
emit torrentsLoaded(m_loadedTorrents);
|
|
||||||
m_loadedTorrents.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
endAlertSequence(previousAlertType, alertSequenceSize);
|
||||||
|
|
||||||
// Some torrents may become "finished" after different alerts handling.
|
// Some torrents may become "finished" after different alerts handling.
|
||||||
processPendingFinishedTorrents();
|
processPendingFinishedTorrents();
|
||||||
@@ -5696,8 +5713,6 @@ void SessionImpl::readAlerts()
|
|||||||
|
|
||||||
void SessionImpl::handleAddTorrentAlert(const lt::add_torrent_alert *alert)
|
void SessionImpl::handleAddTorrentAlert(const lt::add_torrent_alert *alert)
|
||||||
{
|
{
|
||||||
++m_receivedAddTorrentAlertsCount;
|
|
||||||
|
|
||||||
if (alert->error)
|
if (alert->error)
|
||||||
{
|
{
|
||||||
const QString msg = QString::fromStdString(alert->message());
|
const QString msg = QString::fromStdString(alert->message());
|
||||||
|
|||||||
@@ -607,6 +607,7 @@ namespace BitTorrent
|
|||||||
void populateAdditionalTrackersFromURL();
|
void populateAdditionalTrackersFromURL();
|
||||||
|
|
||||||
void fetchPendingAlerts(lt::time_duration time = lt::time_duration::zero());
|
void fetchPendingAlerts(lt::time_duration time = lt::time_duration::zero());
|
||||||
|
void endAlertSequence(int alertType, qsizetype alertCount);
|
||||||
|
|
||||||
void moveTorrentStorage(const MoveStorageJob &job) const;
|
void moveTorrentStorage(const MoveStorageJob &job) const;
|
||||||
void handleMoveTorrentStorageJobFinished(const Path &newPath);
|
void handleMoveTorrentStorageJobFinished(const Path &newPath);
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ namespace Http
|
|||||||
inline const QString HEADER_X_CONTENT_TYPE_OPTIONS = u"x-content-type-options"_s;
|
inline const QString HEADER_X_CONTENT_TYPE_OPTIONS = u"x-content-type-options"_s;
|
||||||
inline const QString HEADER_X_FORWARDED_FOR = u"x-forwarded-for"_s;
|
inline const QString HEADER_X_FORWARDED_FOR = u"x-forwarded-for"_s;
|
||||||
inline const QString HEADER_X_FORWARDED_HOST = u"x-forwarded-host"_s;
|
inline const QString HEADER_X_FORWARDED_HOST = u"x-forwarded-host"_s;
|
||||||
inline const QString HEADER_X_FORWARDED_PROTO = u"X-forwarded-proto"_s;
|
inline const QString HEADER_X_FORWARDED_PROTO = u"x-forwarded-proto"_s;
|
||||||
inline const QString HEADER_X_FRAME_OPTIONS = u"x-frame-options"_s;
|
inline const QString HEADER_X_FRAME_OPTIONS = u"x-frame-options"_s;
|
||||||
inline const QString HEADER_X_XSS_PROTECTION = u"x-xss-protection"_s;
|
inline const QString HEADER_X_XSS_PROTECTION = u"x-xss-protection"_s;
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ namespace
|
|||||||
QPointer<SearchPluginManager> SearchPluginManager::m_instance = nullptr;
|
QPointer<SearchPluginManager> SearchPluginManager::m_instance = nullptr;
|
||||||
|
|
||||||
SearchPluginManager::SearchPluginManager()
|
SearchPluginManager::SearchPluginManager()
|
||||||
: m_updateUrl(u"https://searchplugins.qbittorrent.org/nova3/engines/"_s)
|
: m_updateUrl(u"https://raw.githubusercontent.com/qbittorrent/search-plugins/refs/heads/master/nova3/engines/"_s)
|
||||||
{
|
{
|
||||||
Q_ASSERT(!m_instance); // only one instance is allowed
|
Q_ASSERT(!m_instance); // only one instance is allowed
|
||||||
m_instance = this;
|
m_instance = this;
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const Path &path)
|
|||||||
if (!dir.isEmpty(QDir::Dirs | QDir::NoDotAndDotDot))
|
if (!dir.isEmpty(QDir::Dirs | QDir::NoDotAndDotDot))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const QStringList tmpFileList = dir.entryList(QDir::Files);
|
const QStringList tmpFileList = dir.entryList(QDir::Files | QDir::Hidden);
|
||||||
|
|
||||||
// deleteFilesList contains unwanted files, usually created by the OS
|
// deleteFilesList contains unwanted files, usually created by the OS
|
||||||
// temp files on linux usually end with '~', e.g. `filename~`
|
// temp files on linux usually end with '~', e.g. `filename~`
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ namespace
|
|||||||
|
|
||||||
RandomLayer()
|
RandomLayer()
|
||||||
{
|
{
|
||||||
if (::getrandom(nullptr, 0, 0) < 0)
|
if (unsigned char buf = 0; ::getrandom(&buf, sizeof(buf), 0) < 0)
|
||||||
{
|
{
|
||||||
if (errno == ENOSYS)
|
if (errno == ENOSYS)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -508,7 +508,7 @@ void AddNewTorrentDialog::setCurrentContext(const std::shared_ptr<Context> conte
|
|||||||
{
|
{
|
||||||
m_ui->lblMetaLoading->setVisible(false);
|
m_ui->lblMetaLoading->setVisible(false);
|
||||||
m_ui->progMetaLoading->setVisible(false);
|
m_ui->progMetaLoading->setVisible(false);
|
||||||
m_ui->buttonSave->setVisible(false);
|
m_ui->buttonSave->setVisible(true);
|
||||||
setupTreeview();
|
setupTreeview();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -520,6 +520,7 @@ void AddNewTorrentDialog::setCurrentContext(const std::shared_ptr<Context> conte
|
|||||||
m_ui->labelDateData->setText(tr("Not Available", "This date is unavailable"));
|
m_ui->labelDateData->setText(tr("Not Available", "This date is unavailable"));
|
||||||
updateDiskSpaceLabel();
|
updateDiskSpaceLabel();
|
||||||
setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
|
setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
|
||||||
|
m_ui->buttonSave->setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
TMMChanged(m_ui->comboTMM->currentIndex());
|
TMMChanged(m_ui->comboTMM->currentIndex());
|
||||||
|
|||||||
@@ -38,19 +38,28 @@
|
|||||||
HidableTabWidget::HidableTabWidget(QWidget *parent)
|
HidableTabWidget::HidableTabWidget(QWidget *parent)
|
||||||
: QTabWidget(parent)
|
: QTabWidget(parent)
|
||||||
{
|
{
|
||||||
|
// Skip single tab in keyboard navigation (no point navigating to it)
|
||||||
|
tabBar()->setFocusPolicy(Qt::NoFocus);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HidableTabWidget::tabInserted(const int index)
|
void HidableTabWidget::tabInserted(const int index)
|
||||||
{
|
{
|
||||||
QTabWidget::tabInserted(index);
|
QTabWidget::tabInserted(index);
|
||||||
tabBar()->setVisible(count() != 1);
|
tabsCountChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HidableTabWidget::tabRemoved(const int index)
|
void HidableTabWidget::tabRemoved(const int index)
|
||||||
{
|
{
|
||||||
//QTabWidget::tabInserted(index);
|
|
||||||
QTabWidget::tabRemoved(index);
|
QTabWidget::tabRemoved(index);
|
||||||
tabBar()->setVisible(count() != 1);
|
tabsCountChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HidableTabWidget::tabsCountChanged()
|
||||||
|
{
|
||||||
|
const qsizetype tabsCount = count();
|
||||||
|
tabBar()->setVisible(tabsCount != 1);
|
||||||
|
// Skip single tab in keyboard navigation (no point navigating to it)
|
||||||
|
tabBar()->setFocusPolicy((tabsCount > 1) ? Qt::StrongFocus : Qt::NoFocus);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
void tabInserted(int index) override;
|
void tabInserted(int index) override;
|
||||||
void tabRemoved(int index) override;
|
void tabRemoved(int index) override;
|
||||||
|
void tabsCountChanged();
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
void paintEvent(QPaintEvent *event) override;
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
|||||||
@@ -1660,7 +1660,7 @@ void MainWindow::handleUpdateCheckFinished(ProgramUpdater *updater, const bool i
|
|||||||
updater->deleteLater();
|
updater->deleteLater();
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto newVersion = updater->getNewVersion();
|
const ProgramUpdater::Version newVersion = updater->getNewVersion();
|
||||||
if (newVersion.isValid())
|
if (newVersion.isValid())
|
||||||
{
|
{
|
||||||
const QString msg {tr("A new version is available.") + u"<br/>"
|
const QString msg {tr("A new version is available.") + u"<br/>"
|
||||||
|
|||||||
@@ -29,6 +29,8 @@
|
|||||||
|
|
||||||
#include "programupdater.h"
|
#include "programupdater.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include <libtorrent/version.hpp>
|
#include <libtorrent/version.hpp>
|
||||||
|
|
||||||
#include <QtCore/qconfig.h>
|
#include <QtCore/qconfig.h>
|
||||||
@@ -44,7 +46,6 @@
|
|||||||
#include "base/logger.h"
|
#include "base/logger.h"
|
||||||
#include "base/net/downloadmanager.h"
|
#include "base/net/downloadmanager.h"
|
||||||
#include "base/preferences.h"
|
#include "base/preferences.h"
|
||||||
#include "base/utils/version.h"
|
|
||||||
#include "base/version.h"
|
#include "base/version.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -80,32 +81,50 @@ namespace
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProgramUpdater::checkForUpdates() const
|
void ProgramUpdater::checkForUpdates()
|
||||||
{
|
{
|
||||||
const auto USER_AGENT = QStringLiteral("qBittorrent/" QBT_VERSION_2 " ProgramUpdater (www.qbittorrent.org)");
|
|
||||||
const auto RSS_URL = u"https://www.fosshub.com/feed/5b8793a7f9ee5a5c3e97a3b2.xml"_s;
|
|
||||||
const auto FALLBACK_URL = u"https://www.qbittorrent.org/versions.json"_s;
|
|
||||||
|
|
||||||
// Don't change this User-Agent. In case our updater goes haywire,
|
// Don't change this User-Agent. In case our updater goes haywire,
|
||||||
// the filehost can identify it and contact us.
|
// the filehost can identify it and contact us.
|
||||||
Net::DownloadManager::instance()->download(Net::DownloadRequest(RSS_URL).userAgent(USER_AGENT)
|
const auto USER_AGENT = QStringLiteral("qBittorrent/" QBT_VERSION_2 " ProgramUpdater (www.qbittorrent.org)");
|
||||||
, Preferences::instance()->useProxyForGeneralPurposes(), this, &ProgramUpdater::rssDownloadFinished);
|
const auto FOSSHUB_URL = u"https://www.fosshub.com/feed/5b8793a7f9ee5a5c3e97a3b2.xml"_s;
|
||||||
Net::DownloadManager::instance()->download(Net::DownloadRequest(FALLBACK_URL).userAgent(USER_AGENT)
|
const auto QBT_MAIN_URL = u"https://www.qbittorrent.org/versions.json"_s;
|
||||||
, Preferences::instance()->useProxyForGeneralPurposes(), this, &ProgramUpdater::fallbackDownloadFinished);
|
const auto QBT_BACKUP_URL = u"https://qbittorrent.github.io/qBittorrent-website/versions.json"_s;
|
||||||
|
|
||||||
m_hasCompletedOneReq = false;
|
Net::DownloadManager *netManager = Net::DownloadManager::instance();
|
||||||
|
const bool useProxy = Preferences::instance()->useProxyForGeneralPurposes();
|
||||||
|
|
||||||
|
m_pendingRequestCount = 3;
|
||||||
|
netManager->download(Net::DownloadRequest(FOSSHUB_URL).userAgent(USER_AGENT), useProxy, this, &ProgramUpdater::rssDownloadFinished);
|
||||||
|
// don't use the custom user agent for the following requests, disguise as a normal browser instead
|
||||||
|
netManager->download(Net::DownloadRequest(QBT_MAIN_URL), useProxy, this, [this](const Net::DownloadResult &result)
|
||||||
|
{
|
||||||
|
fallbackDownloadFinished(result, m_qbtMainVersion);
|
||||||
|
});
|
||||||
|
netManager->download(Net::DownloadRequest(QBT_BACKUP_URL), useProxy, this, [this](const Net::DownloadResult &result)
|
||||||
|
{
|
||||||
|
fallbackDownloadFinished(result, m_qbtBackupVersion);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ProgramUpdater::Version ProgramUpdater::getNewVersion() const
|
ProgramUpdater::Version ProgramUpdater::getNewVersion() const
|
||||||
{
|
{
|
||||||
return shouldUseFallback() ? m_fallbackRemoteVersion : m_remoteVersion;
|
switch (getLatestRemoteSource())
|
||||||
|
{
|
||||||
|
case RemoteSource::Fosshub:
|
||||||
|
return m_fosshubVersion;
|
||||||
|
case RemoteSource::QbtMain:
|
||||||
|
return m_qbtMainVersion;
|
||||||
|
case RemoteSource::QbtBackup:
|
||||||
|
return m_qbtBackupVersion;
|
||||||
|
}
|
||||||
|
Q_UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
||||||
{
|
{
|
||||||
if (result.status != Net::DownloadStatus::Success)
|
if (result.status != Net::DownloadStatus::Success)
|
||||||
{
|
{
|
||||||
LogMsg(tr("Failed to download the update info. URL: %1. Error: %2").arg(result.url, result.errorString) , Log::WARNING);
|
LogMsg(tr("Failed to download the program update info. URL: \"%1\". Error: \"%2\"").arg(result.url, result.errorString) , Log::WARNING);
|
||||||
handleFinishedRequest();
|
handleFinishedRequest();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -150,10 +169,10 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
|||||||
if (!version.isEmpty())
|
if (!version.isEmpty())
|
||||||
{
|
{
|
||||||
qDebug("Detected version is %s", qUtf8Printable(version));
|
qDebug("Detected version is %s", qUtf8Printable(version));
|
||||||
const ProgramUpdater::Version tmpVer {version};
|
const Version tmpVer {version};
|
||||||
if (isVersionMoreRecent(tmpVer))
|
if (isVersionMoreRecent(tmpVer))
|
||||||
{
|
{
|
||||||
m_remoteVersion = tmpVer;
|
m_fosshubVersion = tmpVer;
|
||||||
m_updateURL = updateLink;
|
m_updateURL = updateLink;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,11 +190,13 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
|||||||
handleFinishedRequest();
|
handleFinishedRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProgramUpdater::fallbackDownloadFinished(const Net::DownloadResult &result)
|
void ProgramUpdater::fallbackDownloadFinished(const Net::DownloadResult &result, Version &version)
|
||||||
{
|
{
|
||||||
|
version = {};
|
||||||
|
|
||||||
if (result.status != Net::DownloadStatus::Success)
|
if (result.status != Net::DownloadStatus::Success)
|
||||||
{
|
{
|
||||||
LogMsg(tr("Failed to download the update info. URL: %1. Error: %2").arg(result.url, result.errorString) , Log::WARNING);
|
LogMsg(tr("Failed to download the program update info. URL: \"%1\". Error: \"%2\"").arg(result.url, result.errorString) , Log::WARNING);
|
||||||
handleFinishedRequest();
|
handleFinishedRequest();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -190,9 +211,9 @@ void ProgramUpdater::fallbackDownloadFinished(const Net::DownloadResult &result)
|
|||||||
|
|
||||||
if (const QJsonValue verJSON = json[platformKey][u"version"_s]; verJSON.isString())
|
if (const QJsonValue verJSON = json[platformKey][u"version"_s]; verJSON.isString())
|
||||||
{
|
{
|
||||||
const ProgramUpdater::Version tmpVer {verJSON.toString()};
|
const Version tmpVer {verJSON.toString()};
|
||||||
if (isVersionMoreRecent(tmpVer))
|
if (isVersionMoreRecent(tmpVer))
|
||||||
m_fallbackRemoteVersion = tmpVer;
|
version = tmpVer;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFinishedRequest();
|
handleFinishedRequest();
|
||||||
@@ -200,18 +221,33 @@ void ProgramUpdater::fallbackDownloadFinished(const Net::DownloadResult &result)
|
|||||||
|
|
||||||
bool ProgramUpdater::updateProgram() const
|
bool ProgramUpdater::updateProgram() const
|
||||||
{
|
{
|
||||||
return QDesktopServices::openUrl(shouldUseFallback() ? u"https://www.qbittorrent.org/download"_s : m_updateURL);
|
switch (getLatestRemoteSource())
|
||||||
|
{
|
||||||
|
case RemoteSource::Fosshub:
|
||||||
|
return QDesktopServices::openUrl(m_updateURL);
|
||||||
|
case RemoteSource::QbtMain:
|
||||||
|
return QDesktopServices::openUrl(u"https://www.qbittorrent.org/download"_s);
|
||||||
|
case RemoteSource::QbtBackup:
|
||||||
|
return QDesktopServices::openUrl(u"https://qbittorrent.github.io/qBittorrent-website/download"_s);
|
||||||
|
}
|
||||||
|
Q_UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProgramUpdater::handleFinishedRequest()
|
void ProgramUpdater::handleFinishedRequest()
|
||||||
{
|
{
|
||||||
if (m_hasCompletedOneReq)
|
--m_pendingRequestCount;
|
||||||
|
if (m_pendingRequestCount == 0)
|
||||||
emit updateCheckFinished();
|
emit updateCheckFinished();
|
||||||
else
|
|
||||||
m_hasCompletedOneReq = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProgramUpdater::shouldUseFallback() const
|
ProgramUpdater::RemoteSource ProgramUpdater::getLatestRemoteSource() const
|
||||||
{
|
{
|
||||||
return m_fallbackRemoteVersion > m_remoteVersion;
|
const Version max = std::max({m_fosshubVersion, m_qbtMainVersion, m_qbtBackupVersion});
|
||||||
|
if (max == m_fosshubVersion)
|
||||||
|
return RemoteSource::Fosshub;
|
||||||
|
if (max == m_qbtMainVersion)
|
||||||
|
return RemoteSource::QbtMain;
|
||||||
|
if (max == m_qbtBackupVersion)
|
||||||
|
return RemoteSource::QbtBackup;
|
||||||
|
Q_UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,10 +45,11 @@ class ProgramUpdater final : public QObject
|
|||||||
Q_DISABLE_COPY_MOVE(ProgramUpdater)
|
Q_DISABLE_COPY_MOVE(ProgramUpdater)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using QObject::QObject;
|
|
||||||
using Version = Utils::Version<4, 3>;
|
using Version = Utils::Version<4, 3>;
|
||||||
|
|
||||||
void checkForUpdates() const;
|
using QObject::QObject;
|
||||||
|
|
||||||
|
void checkForUpdates();
|
||||||
Version getNewVersion() const;
|
Version getNewVersion() const;
|
||||||
bool updateProgram() const;
|
bool updateProgram() const;
|
||||||
|
|
||||||
@@ -57,14 +58,22 @@ signals:
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void rssDownloadFinished(const Net::DownloadResult &result);
|
void rssDownloadFinished(const Net::DownloadResult &result);
|
||||||
void fallbackDownloadFinished(const Net::DownloadResult &result);
|
void fallbackDownloadFinished(const Net::DownloadResult &result, Version &version);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handleFinishedRequest();
|
enum class RemoteSource
|
||||||
bool shouldUseFallback() const;
|
{
|
||||||
|
Fosshub,
|
||||||
|
QbtMain,
|
||||||
|
QbtBackup
|
||||||
|
};
|
||||||
|
|
||||||
mutable bool m_hasCompletedOneReq = false;
|
void handleFinishedRequest();
|
||||||
Version m_remoteVersion;
|
RemoteSource getLatestRemoteSource() const;
|
||||||
Version m_fallbackRemoteVersion;
|
|
||||||
|
int m_pendingRequestCount = 0;
|
||||||
|
Version m_fosshubVersion;
|
||||||
|
Version m_qbtMainVersion;
|
||||||
|
Version m_qbtBackupVersion;
|
||||||
QUrl m_updateURL;
|
QUrl m_updateURL;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -819,21 +819,21 @@ void TransferListWidget::exportTorrent()
|
|||||||
bool hasError = false;
|
bool hasError = false;
|
||||||
for (const BitTorrent::Torrent *torrent : torrents)
|
for (const BitTorrent::Torrent *torrent : torrents)
|
||||||
{
|
{
|
||||||
const QString validName = Utils::Fs::toValidFileName(torrent->name(), u"_"_s);
|
const QString validName = Utils::Fs::toValidFileName(torrent->name());
|
||||||
const Path filePath = savePath / Path(validName + u".torrent");
|
QString torrentExportFilename = u"%1.torrent"_s.arg(validName);
|
||||||
if (filePath.exists())
|
Path newTorrentPath = savePath / Path(torrentExportFilename);
|
||||||
|
int counter = 0;
|
||||||
|
while (newTorrentPath.exists())
|
||||||
{
|
{
|
||||||
LogMsg(errorMsg.arg(torrent->name(), filePath.toString(), tr("A file with the same name already exists")) , Log::WARNING);
|
// Append number to torrent name to make it unique
|
||||||
hasError = true;
|
torrentExportFilename = u"%1 (%2).torrent"_s.arg(validName).arg(++counter);
|
||||||
continue;
|
newTorrentPath = savePath / Path(torrentExportFilename);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nonstd::expected<void, QString> result = torrent->exportToFile(filePath);
|
if (const nonstd::expected<void, QString> result = torrent->exportToFile(newTorrentPath); !result)
|
||||||
if (!result)
|
|
||||||
{
|
{
|
||||||
LogMsg(errorMsg.arg(torrent->name(), filePath.toString(), result.error()) , Log::WARNING);
|
LogMsg(errorMsg.arg(torrent->name(), newTorrentPath.toString(), result.error()) , Log::WARNING);
|
||||||
hasError = true;
|
hasError = true;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1285,9 +1285,16 @@ void TransferListWidget::displayListMenu()
|
|||||||
listMenu->popup(QCursor::pos());
|
listMenu->popup(QCursor::pos());
|
||||||
}
|
}
|
||||||
|
|
||||||
void TransferListWidget::currentChanged(const QModelIndex ¤t, const QModelIndex&)
|
void TransferListWidget::currentChanged(const QModelIndex ¤t, const QModelIndex &previous)
|
||||||
{
|
{
|
||||||
qDebug("CURRENT CHANGED");
|
qDebug("CURRENT CHANGED");
|
||||||
|
|
||||||
|
// Call base class to ensure Qt's accessibility system is notified of focus changes.
|
||||||
|
// This is critical for screen readers to announce the currently selected torrent.
|
||||||
|
// Without this call, users relying on assistive technologies cannot effectively
|
||||||
|
// navigate the torrent list with keyboard arrow keys.
|
||||||
|
QTreeView::currentChanged(current, previous);
|
||||||
|
|
||||||
BitTorrent::Torrent *torrent = nullptr;
|
BitTorrent::Torrent *torrent = nullptr;
|
||||||
if (current.isValid())
|
if (current.isValid())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ private slots:
|
|||||||
void torrentDoubleClicked();
|
void torrentDoubleClicked();
|
||||||
void displayListMenu();
|
void displayListMenu();
|
||||||
void displayColumnHeaderMenu();
|
void displayColumnHeaderMenu();
|
||||||
void currentChanged(const QModelIndex ¤t, const QModelIndex&) override;
|
void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override;
|
||||||
void setSelectedTorrentsSuperSeeding(bool enabled) const;
|
void setSelectedTorrentsSuperSeeding(bool enabled) const;
|
||||||
void setSelectedTorrentsSequentialDownload(bool enabled) const;
|
void setSelectedTorrentsSequentialDownload(bool enabled) const;
|
||||||
void setSelectedFirstLastPiecePrio(bool enabled) const;
|
void setSelectedFirstLastPiecePrio(bool enabled) const;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<p>I would like to thank the people who volunteered to translate qBittorrent.<br>
|
<p>I would like to thank the people who volunteered to translate qBittorrent.<br>
|
||||||
Most of them translated via <a href="https://www.transifex.com/sledgehammer999/qbittorrent">Transifex</a> and some of them are mentioned below:<br>
|
Most of them translated via <a href="https://explore.transifex.com/sledgehammer999/qbittorrent/">Transifex</a> and some of them are mentioned below:<br>
|
||||||
(the list might not be up to date)
|
(the list might not be up to date)
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
@@ -1210,7 +1210,7 @@ void AppController::getDirectoryContentAction()
|
|||||||
QJsonArray ret;
|
QJsonArray ret;
|
||||||
QDirIterator it {dirPath, (QDir::NoDotAndDotDot | parseDirectoryContentMode(visibility))};
|
QDirIterator it {dirPath, (QDir::NoDotAndDotDot | parseDirectoryContentMode(visibility))};
|
||||||
while (it.hasNext())
|
while (it.hasNext())
|
||||||
ret.append(it.next());
|
ret.append(Path(it.next()).toString());
|
||||||
setResult(ret);
|
setResult(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ window.qBittorrent.DynamicTable ??= (() => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setupDynamicTableHeaderContextMenuClass: function() {
|
setupDynamicTableHeaderContextMenuClass: () => {
|
||||||
DynamicTableHeaderContextMenuClass ??= class extends window.qBittorrent.ContextMenu.ContextMenu {
|
DynamicTableHeaderContextMenuClass ??= class extends window.qBittorrent.ContextMenu.ContextMenu {
|
||||||
updateMenuItems() {
|
updateMenuItems() {
|
||||||
for (let i = 0; i < this.dynamicTable.columns.length; ++i) {
|
for (let i = 0; i < this.dynamicTable.columns.length; ++i) {
|
||||||
@@ -2316,7 +2316,7 @@ window.qBittorrent.DynamicTable ??= (() => {
|
|||||||
this.updateGlobalCheckbox();
|
this.updateGlobalCheckbox();
|
||||||
},
|
},
|
||||||
|
|
||||||
_sortNodesByColumn: function(nodes, column) {
|
_sortNodesByColumn: (nodes, column) => {
|
||||||
nodes.sort((row1, row2) => {
|
nodes.sort((row1, row2) => {
|
||||||
// list folders before files when sorting by name
|
// list folders before files when sorting by name
|
||||||
if (column.name === "original") {
|
if (column.name === "original") {
|
||||||
@@ -2633,7 +2633,7 @@ window.qBittorrent.DynamicTable ??= (() => {
|
|||||||
this.columns["availability"].updateTd = displayPercentage;
|
this.columns["availability"].updateTd = displayPercentage;
|
||||||
},
|
},
|
||||||
|
|
||||||
_sortNodesByColumn: function(nodes, column) {
|
_sortNodesByColumn: (nodes, column) => {
|
||||||
nodes.sort((row1, row2) => {
|
nodes.sort((row1, row2) => {
|
||||||
// list folders before files when sorting by name
|
// list folders before files when sorting by name
|
||||||
if (column.name === "name") {
|
if (column.name === "name") {
|
||||||
@@ -3012,7 +3012,7 @@ window.qBittorrent.DynamicTable ??= (() => {
|
|||||||
this.newColumn("checked", "", "", 30, true);
|
this.newColumn("checked", "", "", 30, true);
|
||||||
this.newColumn("name", "", "", -1, true);
|
this.newColumn("name", "", "", -1, true);
|
||||||
|
|
||||||
this.columns["checked"].updateTd = function(td, row) {
|
this.columns["checked"].updateTd = (td, row) => {
|
||||||
if ($(`cbRssDlRule${row.rowId}`) === null) {
|
if ($(`cbRssDlRule${row.rowId}`) === null) {
|
||||||
const checkbox = document.createElement("input");
|
const checkbox = document.createElement("input");
|
||||||
checkbox.type = "checkbox";
|
checkbox.type = "checkbox";
|
||||||
@@ -3110,7 +3110,7 @@ window.qBittorrent.DynamicTable ??= (() => {
|
|||||||
this.newColumn("checked", "", "", 30, true);
|
this.newColumn("checked", "", "", 30, true);
|
||||||
this.newColumn("name", "", "", -1, true);
|
this.newColumn("name", "", "", -1, true);
|
||||||
|
|
||||||
this.columns["checked"].updateTd = function(td, row) {
|
this.columns["checked"].updateTd = (td, row) => {
|
||||||
if ($(`cbRssDlFeed${row.rowId}`) === null) {
|
if ($(`cbRssDlFeed${row.rowId}`) === null) {
|
||||||
const checkbox = document.createElement("input");
|
const checkbox = document.createElement("input");
|
||||||
checkbox.type = "checkbox";
|
checkbox.type = "checkbox";
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ window.qBittorrent.FileTree ??= (() => {
|
|||||||
return nodes;
|
return nodes;
|
||||||
},
|
},
|
||||||
|
|
||||||
_getArrayOfNodes: function(node, array) {
|
_getArrayOfNodes: (node, array) => {
|
||||||
array.push(node);
|
array.push(node);
|
||||||
node.children.each((child) => {
|
node.children.each((child) => {
|
||||||
this._getArrayOfNodes(child, array);
|
this._getArrayOfNodes(child, array);
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ window.qBittorrent.pathAutofill ??= (() => {
|
|||||||
.catch(error => {});
|
.catch(error => {});
|
||||||
};
|
};
|
||||||
|
|
||||||
function attachPathAutofill() {
|
const attachPathAutofill = () => {
|
||||||
const directoryInputs = document.querySelectorAll(".pathDirectory:not(.pathAutoFillInitialized)");
|
const directoryInputs = document.querySelectorAll(".pathDirectory:not(.pathAutoFillInitialized)");
|
||||||
for (const input of directoryInputs) {
|
for (const input of directoryInputs) {
|
||||||
input.addEventListener("input", function() { showPathSuggestions(this, "dirs"); });
|
input.addEventListener("input", function() { showPathSuggestions(this, "dirs"); });
|
||||||
|
|||||||
@@ -773,7 +773,7 @@ window.qBittorrent.Search ??= (() => {
|
|||||||
$("numSearchResultsVisible").textContent = searchResultsTable.getFilteredAndSortedRows().length;
|
$("numSearchResultsVisible").textContent = searchResultsTable.getFilteredAndSortedRows().length;
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadSearchResultsData = function(searchId) {
|
const loadSearchResultsData = (searchId) => {
|
||||||
const state = searchState.get(searchId);
|
const state = searchState.get(searchId);
|
||||||
const url = new URL("api/v2/search/results", window.location);
|
const url = new URL("api/v2/search/results", window.location);
|
||||||
url.search = new URLSearchParams({
|
url.search = new URLSearchParams({
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
|
|
||||||
<div id="aboutTranslatorsContent" class="aboutTabContent invisible">
|
<div id="aboutTranslatorsContent" class="aboutTabContent invisible">
|
||||||
<p>I would like to thank the people who volunteered to translate qBittorrent.<br>
|
<p>I would like to thank the people who volunteered to translate qBittorrent.<br>
|
||||||
Most of them translated via <a target="_blank" href="https://www.transifex.com/sledgehammer999/qbittorrent/">Transifex</a> and some of them are mentioned below:<br>
|
Most of them translated via <a target="_blank" href="https://explore.transifex.com/sledgehammer999/qbittorrent/">Transifex</a> and some of them are mentioned below:<br>
|
||||||
(the list might not be up to date)
|
(the list might not be up to date)
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
Reference in New Issue
Block a user