From fdc186c92f7bbda2b8949b3f53ff5f6f8f24d1dd Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Fri, 2 Apr 2021 13:45:50 +0800 Subject: [PATCH] Revise tag related implementations Fix #12690. --- src/app/application.cpp | 6 +- src/base/CMakeLists.txt | 3 + src/base/base.pri | 3 + src/base/bittorrent/addtorrentparams.h | 4 +- .../bittorrent/bencoderesumedatastorage.cpp | 3 +- src/base/bittorrent/loadtorrentparams.h | 4 +- src/base/bittorrent/torrent.h | 3 +- src/base/bittorrent/torrentimpl.cpp | 20 ++-- src/base/bittorrent/torrentimpl.h | 8 +- src/base/orderedset.h | 108 ++++++++++++++++++ src/base/tagset.cpp | 37 ++++++ src/base/tagset.h | 49 ++++++++ src/gui/tagfiltermodel.cpp | 3 +- src/gui/tagfiltermodel.h | 4 +- src/gui/transferlistmodel.cpp | 11 +- src/gui/transferlistsortmodel.cpp | 17 ++- src/gui/transferlistwidget.cpp | 14 +-- src/webui/api/serialize/serialize_torrent.cpp | 4 +- src/webui/api/serialize/serialize_torrent.h | 2 +- src/webui/api/torrentscontroller.cpp | 4 +- 20 files changed, 257 insertions(+), 50 deletions(-) create mode 100644 src/base/orderedset.h create mode 100644 src/base/tagset.cpp create mode 100644 src/base/tagset.h diff --git a/src/app/application.cpp b/src/app/application.cpp index 801010c71..e03b0cf77 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -350,11 +350,7 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const #endif break; case u'G': - { - QStringList tags = torrent->tags().values(); - std::sort(tags.begin(), tags.end(), Utils::Compare::NaturalLessThan()); - program.replace(i, 2, tags.join(',')); - } + program.replace(i, 2, torrent->tags().join(QLatin1String(","))); break; case u'I': program.replace(i, 2, torrent->id().toString()); diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 2067d5316..b90dfd8af 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -58,6 +58,7 @@ add_library(qbt_base STATIC net/proxyconfigurationmanager.h net/reverseresolution.h net/smtp.h + orderedset.h preferences.h profile.h profile_p.h @@ -74,6 +75,7 @@ add_library(qbt_base STATIC search/searchhandler.h search/searchpluginmanager.h settingsstorage.h + tagset.h torrentfileguard.h torrentfilter.h types.h @@ -152,6 +154,7 @@ add_library(qbt_base STATIC search/searchhandler.cpp search/searchpluginmanager.cpp settingsstorage.cpp + tagset.cpp torrentfileguard.cpp torrentfilter.cpp utils/bytearray.cpp diff --git a/src/base/base.pri b/src/base/base.pri index 4b0738c22..e39ae6600 100644 --- a/src/base/base.pri +++ b/src/base/base.pri @@ -57,6 +57,7 @@ HEADERS += \ $$PWD/net/proxyconfigurationmanager.h \ $$PWD/net/reverseresolution.h \ $$PWD/net/smtp.h \ + $$PWD/orderedset.h \ $$PWD/preferences.h \ $$PWD/profile.h \ $$PWD/profile_p.h \ @@ -74,6 +75,7 @@ HEADERS += \ $$PWD/search/searchpluginmanager.h \ $$PWD/settingsstorage.h \ $$PWD/settingvalue.h \ + $$PWD/tagset.h \ $$PWD/torrentfileguard.h \ $$PWD/torrentfilter.h \ $$PWD/types.h \ @@ -152,6 +154,7 @@ SOURCES += \ $$PWD/search/searchhandler.cpp \ $$PWD/search/searchpluginmanager.cpp \ $$PWD/settingsstorage.cpp \ + $$PWD/tagset.cpp \ $$PWD/torrentfileguard.cpp \ $$PWD/torrentfilter.cpp \ $$PWD/utils/bytearray.cpp \ diff --git a/src/base/bittorrent/addtorrentparams.h b/src/base/bittorrent/addtorrentparams.h index 04be354e3..edb62cc2a 100644 --- a/src/base/bittorrent/addtorrentparams.h +++ b/src/base/bittorrent/addtorrentparams.h @@ -30,10 +30,10 @@ #include -#include #include #include +#include "base/tagset.h" #include "torrent.h" #include "torrentcontentlayout.h" @@ -45,7 +45,7 @@ namespace BitTorrent { QString name; QString category; - QSet tags; + TagSet tags; QString savePath; bool disableTempPath = false; // e.g. for imported torrents bool sequential = false; diff --git a/src/base/bittorrent/bencoderesumedatastorage.cpp b/src/base/bittorrent/bencoderesumedatastorage.cpp index c7d49cf26..ecf45ee3d 100644 --- a/src/base/bittorrent/bencoderesumedatastorage.cpp +++ b/src/base/bittorrent/bencoderesumedatastorage.cpp @@ -45,6 +45,7 @@ #include "base/global.h" #include "base/logger.h" #include "base/profile.h" +#include "base/tagset.h" #include "base/utils/fs.h" #include "base/utils/io.h" #include "base/utils/string.h" @@ -82,7 +83,7 @@ namespace using ListType = lt::entry::list_type; - ListType setToEntryList(const QSet &input) + ListType setToEntryList(const TagSet &input) { ListType entryList; entryList.reserve(input.size()); diff --git a/src/base/bittorrent/loadtorrentparams.h b/src/base/bittorrent/loadtorrentparams.h index a4e946f71..7bac0fd48 100644 --- a/src/base/bittorrent/loadtorrentparams.h +++ b/src/base/bittorrent/loadtorrentparams.h @@ -30,9 +30,9 @@ #include -#include #include +#include "base/tagset.h" #include "torrent.h" #include "torrentcontentlayout.h" @@ -44,7 +44,7 @@ namespace BitTorrent QString name; QString category; - QSet tags; + TagSet tags; QString savePath; TorrentContentLayout contentLayout = TorrentContentLayout::Original; TorrentOperatingMode operatingMode = TorrentOperatingMode::AutoManaged; diff --git a/src/base/bittorrent/torrent.h b/src/base/bittorrent/torrent.h index e1416c063..6da873021 100644 --- a/src/base/bittorrent/torrent.h +++ b/src/base/bittorrent/torrent.h @@ -33,6 +33,7 @@ #include #include +#include "base/tagset.h" #include "abstractfilestorage.h" class QBitArray; @@ -168,7 +169,7 @@ namespace BitTorrent virtual bool belongsToCategory(const QString &category) const = 0; virtual bool setCategory(const QString &category) = 0; - virtual QSet tags() const = 0; + virtual TagSet tags() const = 0; virtual bool hasTag(const QString &tag) const = 0; virtual bool addTag(const QString &tag) = 0; virtual bool removeTag(const QString &tag) = 0; diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 6c53301d5..ecb44432e 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -703,7 +703,7 @@ bool TorrentImpl::belongsToCategory(const QString &category) const return false; } -QSet TorrentImpl::tags() const +TagSet TorrentImpl::tags() const { return m_tags; } @@ -717,18 +717,18 @@ bool TorrentImpl::addTag(const QString &tag) { if (!Session::isValidTag(tag)) return false; + if (hasTag(tag)) + return false; - if (!hasTag(tag)) + if (!m_session->hasTag(tag)) { - if (!m_session->hasTag(tag)) - if (!m_session->addTag(tag)) - return false; - m_tags.insert(tag); - m_session->handleTorrentNeedSaveResumeData(this); - m_session->handleTorrentTagAdded(this, tag); - return true; + if (!m_session->addTag(tag)) + return false; } - return false; + m_tags.insert(tag); + m_session->handleTorrentNeedSaveResumeData(this); + m_session->handleTorrentTagAdded(this, tag); + return true; } bool TorrentImpl::removeTag(const QString &tag) diff --git a/src/base/bittorrent/torrentimpl.h b/src/base/bittorrent/torrentimpl.h index 8f59e722f..59e0d7ac6 100644 --- a/src/base/bittorrent/torrentimpl.h +++ b/src/base/bittorrent/torrentimpl.h @@ -42,10 +42,10 @@ #include #include #include -#include #include #include +#include "base/tagset.h" #include "infohash.h" #include "speedmonitor.h" #include "torrent.h" @@ -115,7 +115,7 @@ namespace BitTorrent bool belongsToCategory(const QString &category) const override; bool setCategory(const QString &category) override; - QSet tags() const override; + TagSet tags() const override; bool hasTag(const QString &tag) const override; bool addTag(const QString &tag) override; bool removeTag(const QString &tag) override; @@ -247,7 +247,7 @@ namespace BitTorrent QString actualStorageLocation() const; private: - typedef std::function EventTrigger; + using EventTrigger = std::function; void updateStatus(); void updateStatus(const lt::torrent_status &nativeStatus); @@ -315,7 +315,7 @@ namespace BitTorrent QString m_name; QString m_savePath; QString m_category; - QSet m_tags; + TagSet m_tags; qreal m_ratioLimit; int m_seedingTimeLimit; TorrentOperatingMode m_operatingMode; diff --git a/src/base/orderedset.h b/src/base/orderedset.h new file mode 100644 index 000000000..f3266690e --- /dev/null +++ b/src/base/orderedset.h @@ -0,0 +1,108 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2021 Mike Tzou (Chocobo1) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#pragma once + +#include +#include +#include + +#include "algorithm.h" + +template > +class OrderedSet : public std::set +{ + using ThisType = OrderedSet; + +public: + using BaseType = std::set; + + using key_type = typename BaseType::key_type; + using value_type = typename BaseType::value_type; + + using BaseType::BaseType; + using BaseType::operator=; + + // The following are custom functions that are in line with Qt API interface, such as `QSet` + +#if __cplusplus < 202002L + bool contains(const key_type &value) const + { + return (BaseType::find(value) != BaseType::cend()); + } +#endif + + int count() const + { + return static_cast(BaseType::size()); + } + + ThisType &intersect(const ThisType &other) + { + Algorithm::removeIf(*this, [&other](const value_type &value) -> bool + { + return !other.contains(value); + }); + return *this; + } + + bool isEmpty() const + { + return BaseType::empty(); + } + + template , int> = 0> + QString join(const QString &separator) const + { + auto iter = BaseType::cbegin(); + if (iter == BaseType::cend()) + return {}; + + QString ret = *iter; + ++iter; + + while (iter != BaseType::cend()) + { + ret.push_back(separator + *iter); + ++iter; + } + + return ret; + } + + bool remove(const key_type &value) + { + return (BaseType::erase(value) > 0); + } + + ThisType &unite(const ThisType &other) + { + BaseType::insert(other.cbegin(), other.cend()); + return *this; + } +}; diff --git a/src/base/tagset.cpp b/src/base/tagset.cpp new file mode 100644 index 000000000..a63156d0f --- /dev/null +++ b/src/base/tagset.cpp @@ -0,0 +1,37 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2021 Mike Tzou (Chocobo1) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include "tagset.h" + +bool TagLessThan::operator()(const QString &left, const QString &right) const +{ + const int result = m_compare(left, right); + if (result != 0) + return (result < 0); + return (m_subCompare(left, right) < 0); +} diff --git a/src/base/tagset.h b/src/base/tagset.h new file mode 100644 index 000000000..329bc59de --- /dev/null +++ b/src/base/tagset.h @@ -0,0 +1,49 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2021 Mike Tzou (Chocobo1) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#pragma once + +#include +#include + +#include "base/orderedset.h" +#include "base/utils/compare.h" + +class TagLessThan +{ +public: + bool operator()(const QString &left, const QString &right) const; + +private: + Utils::Compare::NaturalCompare m_compare; + Utils::Compare::NaturalCompare m_subCompare; +}; + +using TagSet = OrderedSet; + +Q_DECLARE_METATYPE(TagSet) diff --git a/src/gui/tagfiltermodel.cpp b/src/gui/tagfiltermodel.cpp index 0713ad177..9b18c0781 100644 --- a/src/gui/tagfiltermodel.cpp +++ b/src/gui/tagfiltermodel.cpp @@ -30,7 +30,6 @@ #include #include -#include #include #include "base/bittorrent/session.h" @@ -315,7 +314,7 @@ TagModelItem *TagFilterModel::findItem(const QString &tag) return &m_tagItems[row]; } -QVector TagFilterModel::findItems(const QSet &tags) +QVector TagFilterModel::findItems(const TagSet &tags) { QVector items; items.reserve(tags.size()); diff --git a/src/gui/tagfiltermodel.h b/src/gui/tagfiltermodel.h index 761ae1eda..0b44a784d 100644 --- a/src/gui/tagfiltermodel.h +++ b/src/gui/tagfiltermodel.h @@ -31,6 +31,8 @@ #include #include +#include "base/tagset.h" + class QModelIndex; class TagModelItem; @@ -76,7 +78,7 @@ private: bool isValidRow(int row) const; int findRow(const QString &tag) const; TagModelItem *findItem(const QString &tag); - QVector findItems(const QSet &tags); + QVector findItems(const TagSet &tags); TagModelItem *allTagsItem(); TagModelItem *untaggedItem(); diff --git a/src/gui/transferlistmodel.cpp b/src/gui/transferlistmodel.cpp index debe04e7b..19af76c2b 100644 --- a/src/gui/transferlistmodel.cpp +++ b/src/gui/transferlistmodel.cpp @@ -323,13 +323,6 @@ QString TransferListModel::displayValue(const BitTorrent::Torrent *torrent, cons , Utils::Misc::userFriendlyDuration(seedingTime)); }; - const auto tagsString = [](const QSet &tags) -> QString - { - QStringList tagsList = tags.values(); - tagsList.sort(); - return tagsList.join(", "); - }; - const auto progressString = [](const qreal progress) -> QString { return (progress >= 1) @@ -373,7 +366,7 @@ QString TransferListModel::displayValue(const BitTorrent::Torrent *torrent, cons case TR_CATEGORY: return torrent->category(); case TR_TAGS: - return tagsString(torrent->tags()); + return torrent->tags().join(QLatin1String(", ")); case TR_ADD_DATE: return QLocale().toString(torrent->addedTime().toLocalTime(), QLocale::ShortFormat); case TR_SEED_DATE: @@ -442,7 +435,7 @@ QVariant TransferListModel::internalValue(const BitTorrent::Torrent *torrent, co case TR_CATEGORY: return torrent->category(); case TR_TAGS: - return QStringList {torrent->tags().values()}; + return QVariant::fromValue(torrent->tags()); case TR_ADD_DATE: return torrent->addedTime(); case TR_SEED_DATE: diff --git a/src/gui/transferlistsortmodel.cpp b/src/gui/transferlistsortmodel.cpp index fddf232e6..f7e0a1fab 100644 --- a/src/gui/transferlistsortmodel.cpp +++ b/src/gui/transferlistsortmodel.cpp @@ -58,6 +58,19 @@ namespace return isLeftValid ? -1 : 1; } + int customCompare(const TagSet &left, const TagSet &right, const Utils::Compare::NaturalCompare &compare) + { + for (auto leftIter = left.cbegin(), rightIter = right.cbegin(); + (leftIter != left.cend()) && (rightIter != right.cend()); + ++leftIter, ++rightIter) + { + const int result = compare(*leftIter, *rightIter); + if (result != 0) + return result; + } + return threeWayCompare(left.size(), right.size()); + } + template int customCompare(const T left, const T right) { @@ -140,10 +153,12 @@ int TransferListSortModel::compare(const QModelIndex &left, const QModelIndex &r case TransferListModel::TR_CATEGORY: case TransferListModel::TR_NAME: case TransferListModel::TR_SAVE_PATH: - case TransferListModel::TR_TAGS: case TransferListModel::TR_TRACKER: return m_naturalCompare(leftValue.toString(), rightValue.toString()); + case TransferListModel::TR_TAGS: + return customCompare(leftValue.value(), rightValue.value(), m_naturalCompare); + case TransferListModel::TR_AMOUNT_DOWNLOADED: case TransferListModel::TR_AMOUNT_DOWNLOADED_SESSION: case TransferListModel::TR_AMOUNT_LEFT: diff --git a/src/gui/transferlistwidget.cpp b/src/gui/transferlistwidget.cpp index 151d059c7..ff3408428 100644 --- a/src/gui/transferlistwidget.cpp +++ b/src/gui/transferlistwidget.cpp @@ -858,8 +858,8 @@ void TransferListWidget::displayListMenu(const QPoint &) bool firstAutoTMM = false; QString firstCategory; bool first = true; - QSet tagsInAny; - QSet tagsInAll; + TagSet tagsInAny; + TagSet tagsInAll; for (const QModelIndex &index : selectedIndexes) { @@ -873,16 +873,17 @@ void TransferListWidget::displayListMenu(const QPoint &) if (firstCategory != torrent->category()) allSameCategory = false; - tagsInAny.unite(torrent->tags()); + const TagSet torrentTags = torrent->tags(); + tagsInAny.unite(torrentTags); if (first) { firstAutoTMM = torrent->isAutoTMMEnabled(); - tagsInAll = torrent->tags(); + tagsInAll = torrentTags; } else { - tagsInAll.intersect(torrent->tags()); + tagsInAll.intersect(torrentTags); } if (firstAutoTMM != torrent->isAutoTMMEnabled()) @@ -1011,8 +1012,7 @@ void TransferListWidget::displayListMenu(const QPoint &) action->setCloseOnTriggered(false); const Qt::CheckState initialState = tagsInAll.contains(tag) ? Qt::Checked - : tagsInAny.contains(tag) ? Qt::PartiallyChecked - : Qt::Unchecked; + : tagsInAny.contains(tag) ? Qt::PartiallyChecked : Qt::Unchecked; action->setCheckState(initialState); connect(action, &QAction::triggered, this, [this, tag](const bool checked) diff --git a/src/webui/api/serialize/serialize_torrent.cpp b/src/webui/api/serialize/serialize_torrent.cpp index bd356050e..c6f138c6a 100644 --- a/src/webui/api/serialize/serialize_torrent.cpp +++ b/src/webui/api/serialize/serialize_torrent.cpp @@ -29,12 +29,12 @@ #include "serialize_torrent.h" #include -#include #include #include "base/bittorrent/infohash.h" #include "base/bittorrent/torrent.h" #include "base/bittorrent/trackerentry.h" +#include "base/tagset.h" #include "base/utils/fs.h" namespace @@ -116,7 +116,7 @@ QVariantMap serialize(const BitTorrent::Torrent &torrent) {KEY_TORRENT_FIRST_LAST_PIECE_PRIO, torrent.hasFirstLastPiecePriority()}, {KEY_TORRENT_CATEGORY, torrent.category()}, - {KEY_TORRENT_TAGS, torrent.tags().values().join(", ")}, + {KEY_TORRENT_TAGS, torrent.tags().join(QLatin1String(", "))}, {KEY_TORRENT_SUPER_SEEDING, torrent.superSeeding()}, {KEY_TORRENT_FORCE_START, torrent.isForced()}, {KEY_TORRENT_SAVE_PATH, Utils::Fs::toNativePath(torrent.savePath())}, diff --git a/src/webui/api/serialize/serialize_torrent.h b/src/webui/api/serialize/serialize_torrent.h index 10cc56ce4..95238b9a3 100644 --- a/src/webui/api/serialize/serialize_torrent.h +++ b/src/webui/api/serialize/serialize_torrent.h @@ -28,7 +28,7 @@ #pragma once -#include +#include namespace BitTorrent { diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index 8596b112f..8b4d03250 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -637,7 +637,7 @@ void TorrentsController::addAction() const std::optional addPaused = parseBool(params()["paused"]); const QString savepath = params()["savepath"].trimmed(); const QString category = params()["category"]; - const QSet tags = List::toSet(params()["tags"].split(',', QString::SkipEmptyParts)); + const QStringList tags = params()["tags"].split(',', QString::SkipEmptyParts); const QString torrentName = params()["rename"].trimmed(); const int upLimit = parseInt(params()["upLimit"]).value_or(-1); const int dlLimit = parseInt(params()["dlLimit"]).value_or(-1); @@ -676,7 +676,7 @@ void TorrentsController::addAction() addTorrentParams.contentLayout = contentLayout; addTorrentParams.savePath = savepath; addTorrentParams.category = category; - addTorrentParams.tags = tags; + addTorrentParams.tags.insert(tags.cbegin(), tags.cend()); addTorrentParams.name = torrentName; addTorrentParams.uploadLimit = upLimit; addTorrentParams.downloadLimit = dlLimit;