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 }}
|
||||
path: upload
|
||||
|
||||
- name: Install NSIS
|
||||
uses: repolevedavaj/install-nsis@265e893c16602d8ccfb0a9ca44173b084078917c # v1.0.3
|
||||
with:
|
||||
nsis-version: '3.11'
|
||||
|
||||
- name: Create installer
|
||||
run: |
|
||||
7z x -o"dist/windows/" "dist/windows/NSISPlugins.zip"
|
||||
|
||||
@@ -86,6 +86,19 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
PROPERTIES
|
||||
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
|
||||
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
|
||||
|
||||
@@ -147,12 +147,14 @@ namespace
|
||||
|
||||
QString value(const QString &arg) const
|
||||
{
|
||||
QStringList parts = arg.split(u'=');
|
||||
if (parts.size() == 2)
|
||||
return Utils::String::unquote(parts[1], u"'\""_s);
|
||||
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
|
||||
const qsizetype index = arg.indexOf(u'=');
|
||||
if (index == -1)
|
||||
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
|
||||
"e.g. Parameter '--webui-port' must follow syntax '--webui-port=value'")
|
||||
.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
|
||||
@@ -168,7 +170,7 @@ namespace
|
||||
|
||||
friend bool operator==(const StringOption &option, const QString &arg)
|
||||
{
|
||||
return arg.startsWith(option.parameterAssignment());
|
||||
return (arg == option.fullParameter()) || arg.startsWith(option.parameterAssignment());
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -3151,7 +3151,7 @@ void SessionImpl::exportTorrentFile(const Torrent *torrent, const Path &folderPa
|
||||
while (newTorrentPath.exists())
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -5651,6 +5651,25 @@ void SessionImpl::fetchPendingAlerts(const lt::time_duration time)
|
||||
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
|
||||
{
|
||||
return m_torrentContentLayout;
|
||||
@@ -5667,28 +5686,26 @@ void SessionImpl::readAlerts()
|
||||
fetchPendingAlerts();
|
||||
|
||||
Q_ASSERT(m_loadedTorrents.isEmpty());
|
||||
Q_ASSERT(m_receivedAddTorrentAlertsCount == 0);
|
||||
|
||||
if (!isRestored())
|
||||
m_loadedTorrents.reserve(MAX_PROCESSING_RESUMEDATA_COUNT);
|
||||
|
||||
int previousAlertType = -1;
|
||||
qsizetype alertSequenceSize = 0;
|
||||
for (const lt::alert *a : m_alerts)
|
||||
handleAlert(a);
|
||||
|
||||
if (m_receivedAddTorrentAlertsCount > 0)
|
||||
{
|
||||
emit addTorrentAlertsReceived(m_receivedAddTorrentAlertsCount);
|
||||
m_receivedAddTorrentAlertsCount = 0;
|
||||
|
||||
if (!m_loadedTorrents.isEmpty())
|
||||
const int alertType = a->type();
|
||||
if ((alertType != previousAlertType) && (previousAlertType != -1))
|
||||
{
|
||||
if (isRestored())
|
||||
m_torrentsQueueChanged = true;
|
||||
|
||||
emit torrentsLoaded(m_loadedTorrents);
|
||||
m_loadedTorrents.clear();
|
||||
endAlertSequence(previousAlertType, alertSequenceSize);
|
||||
alertSequenceSize = 0;
|
||||
}
|
||||
|
||||
handleAlert(a);
|
||||
++alertSequenceSize;
|
||||
previousAlertType = alertType;
|
||||
}
|
||||
endAlertSequence(previousAlertType, alertSequenceSize);
|
||||
|
||||
// Some torrents may become "finished" after different alerts handling.
|
||||
processPendingFinishedTorrents();
|
||||
@@ -5696,8 +5713,6 @@ void SessionImpl::readAlerts()
|
||||
|
||||
void SessionImpl::handleAddTorrentAlert(const lt::add_torrent_alert *alert)
|
||||
{
|
||||
++m_receivedAddTorrentAlertsCount;
|
||||
|
||||
if (alert->error)
|
||||
{
|
||||
const QString msg = QString::fromStdString(alert->message());
|
||||
|
||||
@@ -607,6 +607,7 @@ namespace BitTorrent
|
||||
void populateAdditionalTrackersFromURL();
|
||||
|
||||
void fetchPendingAlerts(lt::time_duration time = lt::time_duration::zero());
|
||||
void endAlertSequence(int alertType, qsizetype alertCount);
|
||||
|
||||
void moveTorrentStorage(const MoveStorageJob &job) const;
|
||||
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_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_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_XSS_PROTECTION = u"x-xss-protection"_s;
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace
|
||||
QPointer<SearchPluginManager> SearchPluginManager::m_instance = nullptr;
|
||||
|
||||
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
|
||||
m_instance = this;
|
||||
|
||||
@@ -104,7 +104,7 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const Path &path)
|
||||
if (!dir.isEmpty(QDir::Dirs | QDir::NoDotAndDotDot))
|
||||
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
|
||||
// temp files on linux usually end with '~', e.g. `filename~`
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace
|
||||
|
||||
RandomLayer()
|
||||
{
|
||||
if (::getrandom(nullptr, 0, 0) < 0)
|
||||
if (unsigned char buf = 0; ::getrandom(&buf, sizeof(buf), 0) < 0)
|
||||
{
|
||||
if (errno == ENOSYS)
|
||||
{
|
||||
|
||||
@@ -508,7 +508,7 @@ void AddNewTorrentDialog::setCurrentContext(const std::shared_ptr<Context> conte
|
||||
{
|
||||
m_ui->lblMetaLoading->setVisible(false);
|
||||
m_ui->progMetaLoading->setVisible(false);
|
||||
m_ui->buttonSave->setVisible(false);
|
||||
m_ui->buttonSave->setVisible(true);
|
||||
setupTreeview();
|
||||
}
|
||||
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"));
|
||||
updateDiskSpaceLabel();
|
||||
setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
|
||||
m_ui->buttonSave->setVisible(false);
|
||||
}
|
||||
|
||||
TMMChanged(m_ui->comboTMM->currentIndex());
|
||||
|
||||
@@ -38,19 +38,28 @@
|
||||
HidableTabWidget::HidableTabWidget(QWidget *parent)
|
||||
: QTabWidget(parent)
|
||||
{
|
||||
// Skip single tab in keyboard navigation (no point navigating to it)
|
||||
tabBar()->setFocusPolicy(Qt::NoFocus);
|
||||
}
|
||||
|
||||
void HidableTabWidget::tabInserted(const int index)
|
||||
{
|
||||
QTabWidget::tabInserted(index);
|
||||
tabBar()->setVisible(count() != 1);
|
||||
tabsCountChanged();
|
||||
}
|
||||
|
||||
void HidableTabWidget::tabRemoved(const int index)
|
||||
{
|
||||
//QTabWidget::tabInserted(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
|
||||
|
||||
@@ -42,6 +42,7 @@ public:
|
||||
private:
|
||||
void tabInserted(int index) override;
|
||||
void tabRemoved(int index) override;
|
||||
void tabsCountChanged();
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
@@ -1660,7 +1660,7 @@ void MainWindow::handleUpdateCheckFinished(ProgramUpdater *updater, const bool i
|
||||
updater->deleteLater();
|
||||
};
|
||||
|
||||
const auto newVersion = updater->getNewVersion();
|
||||
const ProgramUpdater::Version newVersion = updater->getNewVersion();
|
||||
if (newVersion.isValid())
|
||||
{
|
||||
const QString msg {tr("A new version is available.") + u"<br/>"
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
#include "programupdater.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <libtorrent/version.hpp>
|
||||
|
||||
#include <QtCore/qconfig.h>
|
||||
@@ -44,7 +46,6 @@
|
||||
#include "base/logger.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/utils/version.h"
|
||||
#include "base/version.h"
|
||||
|
||||
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,
|
||||
// the filehost can identify it and contact us.
|
||||
Net::DownloadManager::instance()->download(Net::DownloadRequest(RSS_URL).userAgent(USER_AGENT)
|
||||
, Preferences::instance()->useProxyForGeneralPurposes(), this, &ProgramUpdater::rssDownloadFinished);
|
||||
Net::DownloadManager::instance()->download(Net::DownloadRequest(FALLBACK_URL).userAgent(USER_AGENT)
|
||||
, Preferences::instance()->useProxyForGeneralPurposes(), this, &ProgramUpdater::fallbackDownloadFinished);
|
||||
const auto USER_AGENT = QStringLiteral("qBittorrent/" QBT_VERSION_2 " ProgramUpdater (www.qbittorrent.org)");
|
||||
const auto FOSSHUB_URL = u"https://www.fosshub.com/feed/5b8793a7f9ee5a5c3e97a3b2.xml"_s;
|
||||
const auto QBT_MAIN_URL = u"https://www.qbittorrent.org/versions.json"_s;
|
||||
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
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();
|
||||
return;
|
||||
}
|
||||
@@ -150,10 +169,10 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
||||
if (!version.isEmpty())
|
||||
{
|
||||
qDebug("Detected version is %s", qUtf8Printable(version));
|
||||
const ProgramUpdater::Version tmpVer {version};
|
||||
const Version tmpVer {version};
|
||||
if (isVersionMoreRecent(tmpVer))
|
||||
{
|
||||
m_remoteVersion = tmpVer;
|
||||
m_fosshubVersion = tmpVer;
|
||||
m_updateURL = updateLink;
|
||||
}
|
||||
}
|
||||
@@ -171,11 +190,13 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
||||
handleFinishedRequest();
|
||||
}
|
||||
|
||||
void ProgramUpdater::fallbackDownloadFinished(const Net::DownloadResult &result)
|
||||
void ProgramUpdater::fallbackDownloadFinished(const Net::DownloadResult &result, Version &version)
|
||||
{
|
||||
version = {};
|
||||
|
||||
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();
|
||||
return;
|
||||
}
|
||||
@@ -190,9 +211,9 @@ void ProgramUpdater::fallbackDownloadFinished(const Net::DownloadResult &result)
|
||||
|
||||
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))
|
||||
m_fallbackRemoteVersion = tmpVer;
|
||||
version = tmpVer;
|
||||
}
|
||||
|
||||
handleFinishedRequest();
|
||||
@@ -200,18 +221,33 @@ void ProgramUpdater::fallbackDownloadFinished(const Net::DownloadResult &result)
|
||||
|
||||
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()
|
||||
{
|
||||
if (m_hasCompletedOneReq)
|
||||
--m_pendingRequestCount;
|
||||
if (m_pendingRequestCount == 0)
|
||||
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)
|
||||
|
||||
public:
|
||||
using QObject::QObject;
|
||||
using Version = Utils::Version<4, 3>;
|
||||
|
||||
void checkForUpdates() const;
|
||||
using QObject::QObject;
|
||||
|
||||
void checkForUpdates();
|
||||
Version getNewVersion() const;
|
||||
bool updateProgram() const;
|
||||
|
||||
@@ -57,14 +58,22 @@ signals:
|
||||
|
||||
private slots:
|
||||
void rssDownloadFinished(const Net::DownloadResult &result);
|
||||
void fallbackDownloadFinished(const Net::DownloadResult &result);
|
||||
void fallbackDownloadFinished(const Net::DownloadResult &result, Version &version);
|
||||
|
||||
private:
|
||||
void handleFinishedRequest();
|
||||
bool shouldUseFallback() const;
|
||||
enum class RemoteSource
|
||||
{
|
||||
Fosshub,
|
||||
QbtMain,
|
||||
QbtBackup
|
||||
};
|
||||
|
||||
mutable bool m_hasCompletedOneReq = false;
|
||||
Version m_remoteVersion;
|
||||
Version m_fallbackRemoteVersion;
|
||||
void handleFinishedRequest();
|
||||
RemoteSource getLatestRemoteSource() const;
|
||||
|
||||
int m_pendingRequestCount = 0;
|
||||
Version m_fosshubVersion;
|
||||
Version m_qbtMainVersion;
|
||||
Version m_qbtBackupVersion;
|
||||
QUrl m_updateURL;
|
||||
};
|
||||
|
||||
@@ -819,21 +819,21 @@ void TransferListWidget::exportTorrent()
|
||||
bool hasError = false;
|
||||
for (const BitTorrent::Torrent *torrent : torrents)
|
||||
{
|
||||
const QString validName = Utils::Fs::toValidFileName(torrent->name(), u"_"_s);
|
||||
const Path filePath = savePath / Path(validName + u".torrent");
|
||||
if (filePath.exists())
|
||||
const QString validName = Utils::Fs::toValidFileName(torrent->name());
|
||||
QString torrentExportFilename = u"%1.torrent"_s.arg(validName);
|
||||
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);
|
||||
hasError = true;
|
||||
continue;
|
||||
// Append number to torrent name to make it unique
|
||||
torrentExportFilename = u"%1 (%2).torrent"_s.arg(validName).arg(++counter);
|
||||
newTorrentPath = savePath / Path(torrentExportFilename);
|
||||
}
|
||||
|
||||
const nonstd::expected<void, QString> result = torrent->exportToFile(filePath);
|
||||
if (!result)
|
||||
if (const nonstd::expected<void, QString> result = torrent->exportToFile(newTorrentPath); !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;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1285,9 +1285,16 @@ void TransferListWidget::displayListMenu()
|
||||
listMenu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
void TransferListWidget::currentChanged(const QModelIndex ¤t, const QModelIndex&)
|
||||
void TransferListWidget::currentChanged(const QModelIndex ¤t, const QModelIndex &previous)
|
||||
{
|
||||
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;
|
||||
if (current.isValid())
|
||||
{
|
||||
|
||||
@@ -110,7 +110,7 @@ private slots:
|
||||
void torrentDoubleClicked();
|
||||
void displayListMenu();
|
||||
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 setSelectedTorrentsSequentialDownload(bool enabled) const;
|
||||
void setSelectedFirstLastPiecePrio(bool enabled) const;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<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)
|
||||
</p>
|
||||
<ul>
|
||||
|
||||
@@ -1210,7 +1210,7 @@ void AppController::getDirectoryContentAction()
|
||||
QJsonArray ret;
|
||||
QDirIterator it {dirPath, (QDir::NoDotAndDotDot | parseDirectoryContentMode(visibility))};
|
||||
while (it.hasNext())
|
||||
ret.append(it.next());
|
||||
ret.append(Path(it.next()).toString());
|
||||
setResult(ret);
|
||||
}
|
||||
|
||||
|
||||
@@ -349,7 +349,7 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||
}
|
||||
},
|
||||
|
||||
setupDynamicTableHeaderContextMenuClass: function() {
|
||||
setupDynamicTableHeaderContextMenuClass: () => {
|
||||
DynamicTableHeaderContextMenuClass ??= class extends window.qBittorrent.ContextMenu.ContextMenu {
|
||||
updateMenuItems() {
|
||||
for (let i = 0; i < this.dynamicTable.columns.length; ++i) {
|
||||
@@ -2316,7 +2316,7 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||
this.updateGlobalCheckbox();
|
||||
},
|
||||
|
||||
_sortNodesByColumn: function(nodes, column) {
|
||||
_sortNodesByColumn: (nodes, column) => {
|
||||
nodes.sort((row1, row2) => {
|
||||
// list folders before files when sorting by name
|
||||
if (column.name === "original") {
|
||||
@@ -2633,7 +2633,7 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||
this.columns["availability"].updateTd = displayPercentage;
|
||||
},
|
||||
|
||||
_sortNodesByColumn: function(nodes, column) {
|
||||
_sortNodesByColumn: (nodes, column) => {
|
||||
nodes.sort((row1, row2) => {
|
||||
// list folders before files when sorting by name
|
||||
if (column.name === "name") {
|
||||
@@ -3012,7 +3012,7 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||
this.newColumn("checked", "", "", 30, true);
|
||||
this.newColumn("name", "", "", -1, true);
|
||||
|
||||
this.columns["checked"].updateTd = function(td, row) {
|
||||
this.columns["checked"].updateTd = (td, row) => {
|
||||
if ($(`cbRssDlRule${row.rowId}`) === null) {
|
||||
const checkbox = document.createElement("input");
|
||||
checkbox.type = "checkbox";
|
||||
@@ -3110,7 +3110,7 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||
this.newColumn("checked", "", "", 30, true);
|
||||
this.newColumn("name", "", "", -1, true);
|
||||
|
||||
this.columns["checked"].updateTd = function(td, row) {
|
||||
this.columns["checked"].updateTd = (td, row) => {
|
||||
if ($(`cbRssDlFeed${row.rowId}`) === null) {
|
||||
const checkbox = document.createElement("input");
|
||||
checkbox.type = "checkbox";
|
||||
|
||||
@@ -103,7 +103,7 @@ window.qBittorrent.FileTree ??= (() => {
|
||||
return nodes;
|
||||
},
|
||||
|
||||
_getArrayOfNodes: function(node, array) {
|
||||
_getArrayOfNodes: (node, array) => {
|
||||
array.push(node);
|
||||
node.children.each((child) => {
|
||||
this._getArrayOfNodes(child, array);
|
||||
|
||||
@@ -73,7 +73,7 @@ window.qBittorrent.pathAutofill ??= (() => {
|
||||
.catch(error => {});
|
||||
};
|
||||
|
||||
function attachPathAutofill() {
|
||||
const attachPathAutofill = () => {
|
||||
const directoryInputs = document.querySelectorAll(".pathDirectory:not(.pathAutoFillInitialized)");
|
||||
for (const input of directoryInputs) {
|
||||
input.addEventListener("input", function() { showPathSuggestions(this, "dirs"); });
|
||||
|
||||
@@ -773,7 +773,7 @@ window.qBittorrent.Search ??= (() => {
|
||||
$("numSearchResultsVisible").textContent = searchResultsTable.getFilteredAndSortedRows().length;
|
||||
};
|
||||
|
||||
const loadSearchResultsData = function(searchId) {
|
||||
const loadSearchResultsData = (searchId) => {
|
||||
const state = searchState.get(searchId);
|
||||
const url = new URL("api/v2/search/results", window.location);
|
||||
url.search = new URLSearchParams({
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
|
||||
<div id="aboutTranslatorsContent" class="aboutTabContent invisible">
|
||||
<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)
|
||||
</p>
|
||||
<ul>
|
||||
|
||||
Reference in New Issue
Block a user