From 95e431a2967ca7aeb4a3a10013c8180b7b417874 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Sun, 7 Jul 2024 08:24:30 +0300 Subject: [PATCH 1/9] Fix handling of tags containing '&' character PR #21024. Closes #20773. --- src/gui/torrenttagsdialog.cpp | 9 +++++---- src/gui/transferlistwidget.cpp | 3 ++- src/gui/utils.cpp | 27 +++++++++++++++++++++++++++ src/gui/utils.h | 5 +++++ 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/gui/torrenttagsdialog.cpp b/src/gui/torrenttagsdialog.cpp index eb4d86168..31c66328d 100644 --- a/src/gui/torrenttagsdialog.cpp +++ b/src/gui/torrenttagsdialog.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2023 Vladimir Golovnev + * Copyright (C) 2023-2024 Vladimir Golovnev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -37,6 +37,7 @@ #include "base/global.h" #include "autoexpandabledialog.h" #include "flowlayout.h" +#include "utils.h" #include "ui_torrenttagsdialog.h" @@ -55,7 +56,7 @@ TorrentTagsDialog::TorrentTagsDialog(const TagSet &initialTags, QWidget *parent) auto *tagsLayout = new FlowLayout(m_ui->scrollArea); for (const QString &tag : asConst(initialTags.united(BitTorrent::Session::instance()->tags()))) { - auto *tagWidget = new QCheckBox(tag); + auto *tagWidget = new QCheckBox(Utils::Gui::tagToWidgetText(tag)); if (initialTags.contains(tag)) tagWidget->setChecked(true); tagsLayout->addWidget(tagWidget); @@ -83,7 +84,7 @@ TagSet TorrentTagsDialog::tags() const { const auto *tagWidget = static_cast(layout->itemAt(i)->widget()); if (tagWidget->isChecked()) - tags.insert(tagWidget->text()); + tags.insert(Utils::Gui::widgetTextToTag(tagWidget->text())); } return tags; @@ -113,7 +114,7 @@ void TorrentTagsDialog::addNewTag() { auto *layout = m_ui->scrollArea->layout(); auto *btn = layout->takeAt(layout->count() - 1); - auto *tagWidget = new QCheckBox(tag); + auto *tagWidget = new QCheckBox(Utils::Gui::tagToWidgetText(tag)); tagWidget->setChecked(true); layout->addWidget(tagWidget); layout->addItem(btn); diff --git a/src/gui/transferlistwidget.cpp b/src/gui/transferlistwidget.cpp index ff76167f8..7fc717ece 100644 --- a/src/gui/transferlistwidget.cpp +++ b/src/gui/transferlistwidget.cpp @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2023-2024 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -1191,7 +1192,7 @@ void TransferListWidget::displayListMenu() for (const QString &tag : asConst(tags)) { - auto *action = new TriStateAction(tag, tagsMenu); + auto *action = new TriStateAction(Utils::Gui::tagToWidgetText(tag), tagsMenu); action->setCloseOnInteraction(false); const Qt::CheckState initialState = tagsInAll.contains(tag) ? Qt::Checked diff --git a/src/gui/utils.cpp b/src/gui/utils.cpp index 9688c5f72..4a775689f 100644 --- a/src/gui/utils.cpp +++ b/src/gui/utils.cpp @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Vladimir Golovnev * Copyright (C) 2017 Mike Tzou * * This program is free software; you can redistribute it and/or @@ -218,3 +219,29 @@ void Utils::Gui::openFolderSelect(const Path &path) openPath(path.parentPath()); #endif } + +QString Utils::Gui::tagToWidgetText(const QString &tag) +{ + return QString(tag).replace(u'&', u"&&"_s); +} + +QString Utils::Gui::widgetTextToTag(const QString &text) +{ + // replace pairs of '&' with single '&' and remove non-paired occurrences of '&' + QString cleanedText; + cleanedText.reserve(text.size()); + bool amp = false; + for (const QChar c : text) + { + if (c == u'&') + { + amp = !amp; + if (amp) + continue; + } + + cleanedText.append(c); + } + + return cleanedText; +} diff --git a/src/gui/utils.h b/src/gui/utils.h index 34cfd58ee..79e415c49 100644 --- a/src/gui/utils.h +++ b/src/gui/utils.h @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Vladimir Golovnev * Copyright (C) 2017 Mike Tzou * * This program is free software; you can redistribute it and/or @@ -34,6 +35,7 @@ class QIcon; class QPixmap; class QPoint; class QSize; +class QString; class QWidget; namespace Utils::Gui @@ -51,4 +53,7 @@ namespace Utils::Gui void openPath(const Path &path); void openFolderSelect(const Path &path); + + QString tagToWidgetText(const QString &tag); + QString widgetTextToTag(const QString &text); } From 47723115f6b2c0203265bc03c8f489054268d958 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Sun, 7 Jul 2024 08:25:31 +0300 Subject: [PATCH 2/9] Show scroll bar in Torrent Tags dialog PR #21026. Closes #21022. --- src/gui/torrenttagsdialog.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/torrenttagsdialog.cpp b/src/gui/torrenttagsdialog.cpp index 31c66328d..c9ff2e713 100644 --- a/src/gui/torrenttagsdialog.cpp +++ b/src/gui/torrenttagsdialog.cpp @@ -53,7 +53,7 @@ TorrentTagsDialog::TorrentTagsDialog(const TagSet &initialTags, QWidget *parent) connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - auto *tagsLayout = new FlowLayout(m_ui->scrollArea); + auto *tagsLayout = new FlowLayout(m_ui->scrollArea->widget()); for (const QString &tag : asConst(initialTags.united(BitTorrent::Session::instance()->tags()))) { auto *tagWidget = new QCheckBox(Utils::Gui::tagToWidgetText(tag)); @@ -79,7 +79,7 @@ TorrentTagsDialog::~TorrentTagsDialog() TagSet TorrentTagsDialog::tags() const { TagSet tags; - auto *layout = m_ui->scrollArea->layout(); + auto *layout = m_ui->scrollArea->widget()->layout(); for (int i = 0; i < (layout->count() - 1); ++i) { const auto *tagWidget = static_cast(layout->itemAt(i)->widget()); @@ -112,7 +112,7 @@ void TorrentTagsDialog::addNewTag() } else { - auto *layout = m_ui->scrollArea->layout(); + auto *layout = m_ui->scrollArea->widget()->layout(); auto *btn = layout->takeAt(layout->count() - 1); auto *tagWidget = new QCheckBox(Utils::Gui::tagToWidgetText(tag)); tagWidget->setChecked(true); From efa517ea90bd6653495c0d5e0c2326401dc59299 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Mon, 8 Jul 2024 10:08:28 +0300 Subject: [PATCH 3/9] WebUI: Correctly apply changed "save path" of RSS rules PR #21030. Closes #20141. --- src/webui/www/private/views/rssDownloader.html | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/webui/www/private/views/rssDownloader.html b/src/webui/www/private/views/rssDownloader.html index f75e53280..f7418294e 100644 --- a/src/webui/www/private/views/rssDownloader.html +++ b/src/webui/www/private/views/rssDownloader.html @@ -597,6 +597,9 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also rulesList[rule].torrentParams.save_path = $('saveToText').value; rulesList[rule].torrentParams.use_auto_tmm = false; } + else { + rulesList[rule].torrentParams.save_path = ""; + } switch ($('addPausedCombobox').value) { case 'default': @@ -746,13 +749,13 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also $('lastMatchText').textContent = 'QBT_TR(Last Match: Unknown)QBT_TR[CONTEXT=AutomatedRssDownloader]'; } - if (rulesList[ruleName].torrentParams.stopped === null) - $('addPausedCombobox').value = 'default'; + if ((rulesList[ruleName].torrentParams.stopped === undefined) || (rulesList[ruleName].torrentParams.stopped === null)) + $("addStoppedCombobox").value = "default"; else $('addPausedCombobox').value = rulesList[ruleName].torrentParams.stopped ? 'always' : 'never'; - if (rulesList[ruleName].torrentParams.content_layout === null) - $('contentLayoutCombobox').value = 'Default'; + if ((rulesList[ruleName].torrentParams.content_layout === undefined) || (rulesList[ruleName].torrentParams.content_layout === null)) + $("contentLayoutCombobox").value = "Default"; else $('contentLayoutCombobox').value = rulesList[ruleName].torrentParams.content_layout; From 92db0170d5992332cdec181abc26b81207d45668 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Sat, 29 Jun 2024 21:57:59 +0300 Subject: [PATCH 4/9] Apply bulk changes to correct content widget items PR #21006. Closes #21001. --- src/gui/torrentcontentwidget.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/gui/torrentcontentwidget.cpp b/src/gui/torrentcontentwidget.cpp index 680dfd172..0fbc2fb15 100644 --- a/src/gui/torrentcontentwidget.cpp +++ b/src/gui/torrentcontentwidget.cpp @@ -56,6 +56,19 @@ #include "gui/macutilities.h" #endif +namespace +{ + QList toPersistentIndexes(const QModelIndexList &indexes) + { + QList persistentIndexes; + persistentIndexes.reserve(indexes.size()); + for (const QModelIndex &index : indexes) + persistentIndexes.append(index); + + return persistentIndexes; + } +} + TorrentContentWidget::TorrentContentWidget(QWidget *parent) : QTreeView(parent) { @@ -219,9 +232,9 @@ void TorrentContentWidget::keyPressEvent(QKeyEvent *event) const Qt::CheckState state = (static_cast(value.toInt()) == Qt::Checked) ? Qt::Unchecked : Qt::Checked; - const QModelIndexList selection = selectionModel()->selectedRows(TorrentContentModelItem::COL_NAME); + const QList selection = toPersistentIndexes(selectionModel()->selectedRows(TorrentContentModelItem::COL_NAME)); - for (const QModelIndex &index : selection) + for (const QPersistentModelIndex &index : selection) model()->setData(index, state, Qt::CheckStateRole); } @@ -248,10 +261,10 @@ void TorrentContentWidget::renameSelectedFile() void TorrentContentWidget::applyPriorities(const BitTorrent::DownloadPriority priority) { - const QModelIndexList selectedRows = selectionModel()->selectedRows(0); - for (const QModelIndex &index : selectedRows) + const QList selectedRows = toPersistentIndexes(selectionModel()->selectedRows(Priority)); + for (const QPersistentModelIndex &index : selectedRows) { - model()->setData(index.sibling(index.row(), Priority), static_cast(priority)); + model()->setData(index, static_cast(priority)); } } @@ -261,7 +274,7 @@ void TorrentContentWidget::applyPrioritiesByOrder() // a download priority that will apply to each item. The number of groups depends on how // many "download priority" are available to be assigned - const QModelIndexList selectedRows = selectionModel()->selectedRows(0); + const QList selectedRows = toPersistentIndexes(selectionModel()->selectedRows(Priority)); const qsizetype priorityGroups = 3; const auto priorityGroupSize = std::max((selectedRows.length() / priorityGroups), 1); @@ -283,8 +296,8 @@ void TorrentContentWidget::applyPrioritiesByOrder() break; } - const QModelIndex &index = selectedRows[i]; - model()->setData(index.sibling(index.row(), Priority), static_cast(priority)); + const QPersistentModelIndex &index = selectedRows[i]; + model()->setData(index, static_cast(priority)); } } From 2f8044bd26407bd190f6a7a1d963c730340dfc3d Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Fri, 12 Jul 2024 08:49:45 +0300 Subject: [PATCH 5/9] Prevent incorrect size from being used for creating array PR #21050. --- src/base/bittorrent/torrentimpl.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index fea7c3858..993b44b00 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -1364,11 +1364,13 @@ QBitArray TorrentImpl::pieces() const QBitArray TorrentImpl::downloadingPieces() const { - QBitArray result(piecesCount()); + if (!hasMetadata()) + return {}; std::vector queue; m_nativeHandle.get_download_queue(queue); + QBitArray result {piecesCount()}; for (const lt::partial_piece_info &info : queue) result.setBit(LT::toUnderlyingType(info.piece_index)); From 827f9d134514e442b9ce0bc74e9b646e4ef99056 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Thu, 8 Aug 2024 08:19:53 +0300 Subject: [PATCH 6/9] Hide zero status filters when torrents removed PR #21150. Closes #21146. --- src/gui/transferlistfilters/statusfilterwidget.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/gui/transferlistfilters/statusfilterwidget.cpp b/src/gui/transferlistfilters/statusfilterwidget.cpp index 731b4fe52..ff77e034a 100644 --- a/src/gui/transferlistfilters/statusfilterwidget.cpp +++ b/src/gui/transferlistfilters/statusfilterwidget.cpp @@ -235,10 +235,7 @@ void StatusFilterWidget::applyFilter(int row) void StatusFilterWidget::handleTorrentsLoaded(const QVector &torrents) { - for (const BitTorrent::Torrent *torrent : torrents) - updateTorrentStatus(torrent); - - updateTexts(); + update(torrents); } void StatusFilterWidget::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent) @@ -273,6 +270,12 @@ void StatusFilterWidget::torrentAboutToBeDeleted(BitTorrent::Torrent *const torr m_nbStalled = m_nbStalledUploading + m_nbStalledDownloading; updateTexts(); + + if (Preferences::instance()->getHideZeroStatusFilters()) + { + hideZeroItems(); + updateGeometry(); + } } void StatusFilterWidget::configure() From d9023c647a0b607fe9b3694262997a63c824d2cc Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Thu, 8 Aug 2024 08:20:26 +0300 Subject: [PATCH 7/9] Fix Incomplete Save Path cannot be changed for torrents without metadata PR #21152. Closes #21140. --- src/base/bittorrent/torrentimpl.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 993b44b00..f2311b2b7 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -425,6 +425,8 @@ Path TorrentImpl::savePath() const void TorrentImpl::setSavePath(const Path &path) { Q_ASSERT(!isAutoTMMEnabled()); + if (Q_UNLIKELY(isAutoTMMEnabled())) + return; const Path basePath = m_session->useCategoryPathsInManualMode() ? m_session->categorySavePath(category()) : m_session->savePath(); @@ -452,6 +454,8 @@ Path TorrentImpl::downloadPath() const void TorrentImpl::setDownloadPath(const Path &path) { Q_ASSERT(!isAutoTMMEnabled()); + if (Q_UNLIKELY(isAutoTMMEnabled())) + return; const Path basePath = m_session->useCategoryPathsInManualMode() ? m_session->categoryDownloadPath(category()) : m_session->downloadPath(); @@ -1823,8 +1827,17 @@ void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext cont { if (!hasMetadata()) { - m_savePath = newPath; - m_session->handleTorrentSavePathChanged(this); + if (context == MoveStorageContext::ChangeSavePath) + { + m_savePath = newPath; + m_session->handleTorrentSavePathChanged(this); + } + else if (context == MoveStorageContext::ChangeDownloadPath) + { + m_downloadPath = newPath; + m_session->handleTorrentSavePathChanged(this); + } + return; } From 58ba6f41f3f486495f36a5b1d0365ad12b0d00f6 Mon Sep 17 00:00:00 2001 From: skomerko <168652295+skomerko@users.noreply.github.com> Date: Sat, 10 Aug 2024 06:49:00 +0200 Subject: [PATCH 8/9] WebUI: Clear trackerList on full update Like other similar data structures, trackerList also need to be cleared in the event of a full sync update. PR #21148. --- src/webui/www/private/scripts/client.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index fdd1fa7ea..d9fdc771e 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -670,6 +670,7 @@ window.addEvent('load', function() { torrentsTable.clear(); category_list = {}; tagList = {}; + trackerList.clear(); } if (response['rid']) { syncMainDataLastResponseId = response['rid']; From 64029e0493d71b506cd27ac419255948809f4ca4 Mon Sep 17 00:00:00 2001 From: cayenne17 <47927025+cayenne17@users.noreply.github.com> Date: Fri, 10 May 2024 20:47:55 +0200 Subject: [PATCH 9/9] Update User-Agent PR #20801. --- src/base/net/downloadmanager.cpp | 2 +- src/searchengine/nova3/helpers.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/base/net/downloadmanager.cpp b/src/base/net/downloadmanager.cpp index ea57d15cf..993e6e0bc 100644 --- a/src/base/net/downloadmanager.cpp +++ b/src/base/net/downloadmanager.cpp @@ -51,7 +51,7 @@ namespace { // Disguise as Firefox to avoid web server banning - const char DEFAULT_USER_AGENT[] = "Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0"; + const char DEFAULT_USER_AGENT[] = "Mozilla/5.0 (X11; Linux x86_64; rv:125.0) Gecko/20100101 Firefox/125.0"; } class Net::DownloadManager::NetworkCookieJar final : public QNetworkCookieJar diff --git a/src/searchengine/nova3/helpers.py b/src/searchengine/nova3/helpers.py index 2633c0eea..52d929c27 100644 --- a/src/searchengine/nova3/helpers.py +++ b/src/searchengine/nova3/helpers.py @@ -1,4 +1,4 @@ -#VERSION: 1.43 +#VERSION: 1.44 # Author: # Christophe DUMEZ (chris@qbittorrent.org) @@ -40,7 +40,7 @@ import urllib.parse import urllib.request # Some sites blocks default python User-agent -user_agent = 'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0' +user_agent = 'Mozilla/5.0 (X11; Linux x86_64; rv:125.0) Gecko/20100101 Firefox/125.0' headers = {'User-Agent': user_agent} # SOCKS5 Proxy support if "sock_proxy" in os.environ and len(os.environ["sock_proxy"].strip()) > 0: