diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index fea7c3858..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(); @@ -1364,11 +1368,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)); @@ -1821,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; } 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/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)); } } diff --git a/src/gui/torrenttagsdialog.cpp b/src/gui/torrenttagsdialog.cpp index eb4d86168..c9ff2e713 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" @@ -52,10 +53,10 @@ 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(tag); + auto *tagWidget = new QCheckBox(Utils::Gui::tagToWidgetText(tag)); if (initialTags.contains(tag)) tagWidget->setChecked(true); tagsLayout->addWidget(tagWidget); @@ -78,12 +79,12 @@ 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()); if (tagWidget->isChecked()) - tags.insert(tagWidget->text()); + tags.insert(Utils::Gui::widgetTextToTag(tagWidget->text())); } return tags; @@ -111,9 +112,9 @@ 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(tag); + auto *tagWidget = new QCheckBox(Utils::Gui::tagToWidgetText(tag)); tagWidget->setChecked(true); layout->addWidget(tagWidget); layout->addItem(btn); 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() 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); } 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: 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']; 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;