diff --git a/.github/workflows/ci_windows.yaml b/.github/workflows/ci_windows.yaml index 95118d51c..cf46b8b6c 100644 --- a/.github/workflows/ci_windows.yaml +++ b/.github/workflows/ci_windows.yaml @@ -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" diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index b5174aa45..4c007ce1e 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -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 + "$/../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 diff --git a/src/app/cmdoptions.cpp b/src/app/cmdoptions.cpp index 3cbf53e37..c4159329b 100644 --- a/src/app/cmdoptions.cpp +++ b/src/app/cmdoptions.cpp @@ -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""_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: diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index da1419f49..b18c4ee8b 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -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()); diff --git a/src/base/bittorrent/sessionimpl.h b/src/base/bittorrent/sessionimpl.h index 960f0cd99..322eab125 100644 --- a/src/base/bittorrent/sessionimpl.h +++ b/src/base/bittorrent/sessionimpl.h @@ -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); diff --git a/src/base/http/types.h b/src/base/http/types.h index ebe342a6f..25bb04849 100644 --- a/src/base/http/types.h +++ b/src/base/http/types.h @@ -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; diff --git a/src/base/search/searchpluginmanager.cpp b/src/base/search/searchpluginmanager.cpp index ab53c5d5e..7178164f9 100644 --- a/src/base/search/searchpluginmanager.cpp +++ b/src/base/search/searchpluginmanager.cpp @@ -87,7 +87,7 @@ namespace QPointer 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; diff --git a/src/base/utils/fs.cpp b/src/base/utils/fs.cpp index 4dd02e3de..d0ac3bbc3 100644 --- a/src/base/utils/fs.cpp +++ b/src/base/utils/fs.cpp @@ -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~` diff --git a/src/base/utils/randomlayer_linux.cpp b/src/base/utils/randomlayer_linux.cpp index 81310f3bb..25608139b 100644 --- a/src/base/utils/randomlayer_linux.cpp +++ b/src/base/utils/randomlayer_linux.cpp @@ -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) { diff --git a/src/gui/addnewtorrentdialog.cpp b/src/gui/addnewtorrentdialog.cpp index 0f4359579..7da640791 100644 --- a/src/gui/addnewtorrentdialog.cpp +++ b/src/gui/addnewtorrentdialog.cpp @@ -508,7 +508,7 @@ void AddNewTorrentDialog::setCurrentContext(const std::shared_ptr 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 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()); diff --git a/src/gui/hidabletabwidget.cpp b/src/gui/hidabletabwidget.cpp index fb2e88df0..7256a05b7 100644 --- a/src/gui/hidabletabwidget.cpp +++ b/src/gui/hidabletabwidget.cpp @@ -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 diff --git a/src/gui/hidabletabwidget.h b/src/gui/hidabletabwidget.h index 66442475b..2c54556fe 100644 --- a/src/gui/hidabletabwidget.h +++ b/src/gui/hidabletabwidget.h @@ -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; diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index dd07ea2ef..fc346b8e0 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -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"
" diff --git a/src/gui/programupdater.cpp b/src/gui/programupdater.cpp index dc6965f29..f0fcc5e97 100644 --- a/src/gui/programupdater.cpp +++ b/src/gui/programupdater.cpp @@ -29,6 +29,8 @@ #include "programupdater.h" +#include + #include #include @@ -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(); } diff --git a/src/gui/programupdater.h b/src/gui/programupdater.h index 531d12118..528dbdf8b 100644 --- a/src/gui/programupdater.h +++ b/src/gui/programupdater.h @@ -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; }; diff --git a/src/gui/transferlistwidget.cpp b/src/gui/transferlistwidget.cpp index f553f5c6b..3de291199 100644 --- a/src/gui/transferlistwidget.cpp +++ b/src/gui/transferlistwidget.cpp @@ -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 result = torrent->exportToFile(filePath); - if (!result) + if (const nonstd::expected 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()) { diff --git a/src/gui/transferlistwidget.h b/src/gui/transferlistwidget.h index 097dd809a..b19c0511a 100644 --- a/src/gui/transferlistwidget.h +++ b/src/gui/transferlistwidget.h @@ -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; diff --git a/src/gui/translators.html b/src/gui/translators.html index 88693e285..a73a4b9e9 100644 --- a/src/gui/translators.html +++ b/src/gui/translators.html @@ -6,7 +6,7 @@

I would like to thank the people who volunteered to translate qBittorrent.
- Most of them translated via Transifex and some of them are mentioned below:
+ Most of them translated via Transifex and some of them are mentioned below:
(the list might not be up to date)

    diff --git a/src/webui/api/appcontroller.cpp b/src/webui/api/appcontroller.cpp index a1fc0e1da..6cc014d46 100644 --- a/src/webui/api/appcontroller.cpp +++ b/src/webui/api/appcontroller.cpp @@ -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); } diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index dcaf5ee2e..6d44d36da 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -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"; diff --git a/src/webui/www/private/scripts/file-tree.js b/src/webui/www/private/scripts/file-tree.js index 3622b5842..06fae5394 100644 --- a/src/webui/www/private/scripts/file-tree.js +++ b/src/webui/www/private/scripts/file-tree.js @@ -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); diff --git a/src/webui/www/private/scripts/pathAutofill.js b/src/webui/www/private/scripts/pathAutofill.js index 2e260f145..601b9a0bf 100644 --- a/src/webui/www/private/scripts/pathAutofill.js +++ b/src/webui/www/private/scripts/pathAutofill.js @@ -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"); }); diff --git a/src/webui/www/private/scripts/search.js b/src/webui/www/private/scripts/search.js index 7c56fe8a2..d8845f464 100644 --- a/src/webui/www/private/scripts/search.js +++ b/src/webui/www/private/scripts/search.js @@ -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({ diff --git a/src/webui/www/private/views/about.html b/src/webui/www/private/views/about.html index cf265f585..6a081615e 100644 --- a/src/webui/www/private/views/about.html +++ b/src/webui/www/private/views/about.html @@ -73,7 +73,7 @@