Add a class to represent a tag

PR #20028.
Closes #19744.
This commit is contained in:
Vladimir Golovnev 2023-12-05 17:01:09 +03:00 committed by GitHub
parent 65771d66fc
commit 7a41192597
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 421 additions and 251 deletions

View file

@ -522,7 +522,10 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
str.replace(i, 2, torrent->contentPath().toString()); str.replace(i, 2, torrent->contentPath().toString());
break; break;
case u'G': case u'G':
str.replace(i, 2, torrent->tags().join(u","_s)); {
const TagSet &tags = torrent->tags();
str.replace(i, 2, QStringList(tags.cbegin(), tags.cend()).join(u","_s));
}
break; break;
case u'I': case u'I':
str.replace(i, 2, (torrent->infoHash().v1().isValid() ? torrent->infoHash().v1().toString() : u"-"_s)); str.replace(i, 2, (torrent->infoHash().v1().isValid() ? torrent->infoHash().v1().toString() : u"-"_s));

View file

@ -85,6 +85,7 @@ add_library(qbt_base STATIC
search/searchhandler.h search/searchhandler.h
search/searchpluginmanager.h search/searchpluginmanager.h
settingsstorage.h settingsstorage.h
tag.h
tagset.h tagset.h
torrentfileguard.h torrentfileguard.h
torrentfileswatcher.h torrentfileswatcher.h
@ -174,6 +175,7 @@ add_library(qbt_base STATIC
search/searchhandler.cpp search/searchhandler.cpp
search/searchpluginmanager.cpp search/searchpluginmanager.cpp
settingsstorage.cpp settingsstorage.cpp
tag.cpp
tagset.cpp tagset.cpp
torrentfileguard.cpp torrentfileguard.cpp
torrentfileswatcher.cpp torrentfileswatcher.cpp

View file

@ -60,7 +60,7 @@ namespace
{ {
TagSet tags; TagSet tags;
for (const QJsonValue &jsonVal : jsonArr) for (const QJsonValue &jsonVal : jsonArr)
tags.insert(jsonVal.toString()); tags.insert(Tag(jsonVal.toString()));
return tags; return tags;
} }
@ -68,8 +68,8 @@ namespace
QJsonArray serializeTagSet(const TagSet &tags) QJsonArray serializeTagSet(const TagSet &tags)
{ {
QJsonArray arr; QJsonArray arr;
for (const QString &tag : tags) for (const Tag &tag : tags)
arr.append(tag); arr.append(tag.toString());
return arr; return arr;
} }

View file

@ -85,8 +85,8 @@ namespace
{ {
ListType entryList; ListType entryList;
entryList.reserve(input.size()); entryList.reserve(input.size());
for (const QString &setValue : input) for (const Tag &setValue : input)
entryList.emplace_back(setValue.toStdString()); entryList.emplace_back(setValue.toString().toStdString());
return entryList; return entryList;
} }
} }
@ -263,7 +263,7 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
{ {
for (int i = 0; i < tagsNode.list_size(); ++i) for (int i = 0; i < tagsNode.list_size(); ++i)
{ {
const QString tag = fromLTString(tagsNode.list_string_value_at(i)); const Tag tag {fromLTString(tagsNode.list_string_value_at(i))};
torrentParams.tags.insert(tag); torrentParams.tags.insert(tag);
} }
} }

View file

@ -854,7 +854,7 @@ namespace
query.bindValue(DB_COLUMN_NAME.placeholder, m_resumeData.name); query.bindValue(DB_COLUMN_NAME.placeholder, m_resumeData.name);
query.bindValue(DB_COLUMN_CATEGORY.placeholder, m_resumeData.category); query.bindValue(DB_COLUMN_CATEGORY.placeholder, m_resumeData.category);
query.bindValue(DB_COLUMN_TAGS.placeholder, (m_resumeData.tags.isEmpty() query.bindValue(DB_COLUMN_TAGS.placeholder, (m_resumeData.tags.isEmpty()
? QString() : m_resumeData.tags.join(u","_s))); ? QString() : QStringList(m_resumeData.tags.cbegin(), m_resumeData.tags.cend()).join(u","_s)));
query.bindValue(DB_COLUMN_CONTENT_LAYOUT.placeholder, Utils::String::fromEnum(m_resumeData.contentLayout)); query.bindValue(DB_COLUMN_CONTENT_LAYOUT.placeholder, Utils::String::fromEnum(m_resumeData.contentLayout));
query.bindValue(DB_COLUMN_RATIO_LIMIT.placeholder, static_cast<int>(m_resumeData.ratioLimit * 1000)); query.bindValue(DB_COLUMN_RATIO_LIMIT.placeholder, static_cast<int>(m_resumeData.ratioLimit * 1000));
query.bindValue(DB_COLUMN_SEEDING_TIME_LIMIT.placeholder, m_resumeData.seedingTimeLimit); query.bindValue(DB_COLUMN_SEEDING_TIME_LIMIT.placeholder, m_resumeData.seedingTimeLimit);

View file

@ -33,6 +33,7 @@
#include <QObject> #include <QObject>
#include "base/pathfwd.h" #include "base/pathfwd.h"
#include "base/tagset.h"
#include "addtorrentparams.h" #include "addtorrentparams.h"
#include "categoryoptions.h" #include "categoryoptions.h"
#include "trackerentry.h" #include "trackerentry.h"
@ -177,11 +178,10 @@ namespace BitTorrent
virtual Path suggestedSavePath(const QString &categoryName, std::optional<bool> useAutoTMM) const = 0; virtual Path suggestedSavePath(const QString &categoryName, std::optional<bool> useAutoTMM) const = 0;
virtual Path suggestedDownloadPath(const QString &categoryName, std::optional<bool> useAutoTMM) const = 0; virtual Path suggestedDownloadPath(const QString &categoryName, std::optional<bool> useAutoTMM) const = 0;
static bool isValidTag(const QString &tag); virtual TagSet tags() const = 0;
virtual QSet<QString> tags() const = 0; virtual bool hasTag(const Tag &tag) const = 0;
virtual bool hasTag(const QString &tag) const = 0; virtual bool addTag(const Tag &tag) = 0;
virtual bool addTag(const QString &tag) = 0; virtual bool removeTag(const Tag &tag) = 0;
virtual bool removeTag(const QString &tag) = 0;
// Torrent Management Mode subsystem (TMM) // Torrent Management Mode subsystem (TMM)
// //
@ -473,8 +473,8 @@ namespace BitTorrent
void speedLimitModeChanged(bool alternative); void speedLimitModeChanged(bool alternative);
void statsUpdated(); void statsUpdated();
void subcategoriesSupportChanged(); void subcategoriesSupportChanged();
void tagAdded(const QString &tag); void tagAdded(const Tag &tag);
void tagRemoved(const QString &tag); void tagRemoved(const Tag &tag);
void torrentAboutToBeRemoved(Torrent *torrent); void torrentAboutToBeRemoved(Torrent *torrent);
void torrentAdded(Torrent *torrent); void torrentAdded(Torrent *torrent);
void torrentCategoryChanged(Torrent *torrent, const QString &oldCategory); void torrentCategoryChanged(Torrent *torrent, const QString &oldCategory);
@ -487,8 +487,8 @@ namespace BitTorrent
void torrentSavingModeChanged(Torrent *torrent); void torrentSavingModeChanged(Torrent *torrent);
void torrentsLoaded(const QVector<Torrent *> &torrents); void torrentsLoaded(const QVector<Torrent *> &torrents);
void torrentsUpdated(const QVector<Torrent *> &torrents); void torrentsUpdated(const QVector<Torrent *> &torrents);
void torrentTagAdded(Torrent *torrent, const QString &tag); void torrentTagAdded(Torrent *torrent, const Tag &tag);
void torrentTagRemoved(Torrent *torrent, const QString &tag); void torrentTagRemoved(Torrent *torrent, const Tag &tag);
void trackerError(Torrent *torrent, const QString &tracker); void trackerError(Torrent *torrent, const QString &tracker);
void trackersAdded(Torrent *torrent, const QVector<TrackerEntry> &trackers); void trackersAdded(Torrent *torrent, const QVector<TrackerEntry> &trackers);
void trackersChanged(Torrent *torrent); void trackersChanged(Torrent *torrent);

View file

@ -80,12 +80,9 @@
#include "base/net/proxyconfigurationmanager.h" #include "base/net/proxyconfigurationmanager.h"
#include "base/preferences.h" #include "base/preferences.h"
#include "base/profile.h" #include "base/profile.h"
#include "base/torrentfilter.h"
#include "base/unicodestrings.h" #include "base/unicodestrings.h"
#include "base/utils/bytearray.h"
#include "base/utils/fs.h" #include "base/utils/fs.h"
#include "base/utils/io.h" #include "base/utils/io.h"
#include "base/utils/misc.h"
#include "base/utils/net.h" #include "base/utils/net.h"
#include "base/utils/random.h" #include "base/utils/random.h"
#include "base/version.h" #include "base/version.h"
@ -373,11 +370,6 @@ QString Session::parentCategoryName(const QString &category)
return {}; return {};
} }
bool Session::isValidTag(const QString &tag)
{
return (!tag.trimmed().isEmpty() && !tag.contains(u','));
}
QStringList Session::expandCategory(const QString &category) QStringList Session::expandCategory(const QString &category)
{ {
QStringList result; QStringList result;
@ -560,7 +552,8 @@ SessionImpl::SessionImpl(QObject *parent)
} }
const QStringList storedTags = m_storedTags.get(); const QStringList storedTags = m_storedTags.get();
m_tags = {storedTags.cbegin(), storedTags.cend()}; m_tags.insert(storedTags.cbegin(), storedTags.cend());
std::erase_if(m_tags, [](const Tag &tag) { return !tag.isValid(); });
updateSeedingLimitTimer(); updateSeedingLimitTimer();
populateAdditionalTrackers(); populateAdditionalTrackers();
@ -1022,34 +1015,37 @@ Path SessionImpl::suggestedDownloadPath(const QString &categoryName, std::option
return path; return path;
} }
QSet<QString> SessionImpl::tags() const TagSet SessionImpl::tags() const
{ {
return m_tags; return m_tags;
} }
bool SessionImpl::hasTag(const QString &tag) const bool SessionImpl::hasTag(const Tag &tag) const
{ {
return m_tags.contains(tag); return m_tags.contains(tag);
} }
bool SessionImpl::addTag(const QString &tag) bool SessionImpl::addTag(const Tag &tag)
{ {
if (!isValidTag(tag) || hasTag(tag)) if (!tag.isValid() || hasTag(tag))
return false; return false;
m_tags.insert(tag); m_tags.insert(tag);
m_storedTags = m_tags.values(); m_storedTags = QStringList(m_tags.cbegin(), m_tags.cend());
emit tagAdded(tag); emit tagAdded(tag);
return true; return true;
} }
bool SessionImpl::removeTag(const QString &tag) bool SessionImpl::removeTag(const Tag &tag)
{ {
if (m_tags.remove(tag)) if (m_tags.remove(tag))
{ {
for (TorrentImpl *const torrent : asConst(m_torrents)) for (TorrentImpl *const torrent : asConst(m_torrents))
torrent->removeTag(tag); torrent->removeTag(tag);
m_storedTags = m_tags.values();
m_storedTags = QStringList(m_tags.cbegin(), m_tags.cend());
emit tagRemoved(tag); emit tagRemoved(tag);
return true; return true;
} }
@ -1472,7 +1468,7 @@ void SessionImpl::processNextResumeData(ResumeSessionContext *context)
} }
} }
std::erase_if(resumeData.tags, [this, &torrentID](const QString &tag) std::erase_if(resumeData.tags, [this, &torrentID](const Tag &tag)
{ {
if (hasTag(tag)) if (hasTag(tag))
return false; return false;
@ -1481,12 +1477,12 @@ void SessionImpl::processNextResumeData(ResumeSessionContext *context)
{ {
LogMsg(tr("Detected inconsistent data: tag is missing from the configuration file." LogMsg(tr("Detected inconsistent data: tag is missing from the configuration file."
" Tag will be recovered." " Tag will be recovered."
" Torrent: \"%1\". Tag: \"%2\"").arg(torrentID.toString(), tag), Log::WARNING); " Torrent: \"%1\". Tag: \"%2\"").arg(torrentID.toString(), tag.toString()), Log::WARNING);
return false; return false;
} }
LogMsg(tr("Detected inconsistent data: invalid tag. Torrent: \"%1\". Tag: \"%2\"") LogMsg(tr("Detected inconsistent data: invalid tag. Torrent: \"%1\". Tag: \"%2\"")
.arg(torrentID.toString(), tag), Log::WARNING); .arg(torrentID.toString(), tag.toString()), Log::WARNING);
return true; return true;
}); });
@ -2701,7 +2697,7 @@ LoadTorrentParams SessionImpl::initLoadTorrentParams(const AddTorrentParams &add
} }
} }
for (const QString &tag : addTorrentParams.tags) for (const Tag &tag : addTorrentParams.tags)
{ {
if (hasTag(tag) || addTag(tag)) if (hasTag(tag) || addTag(tag))
loadTorrentParams.tags.insert(tag); loadTorrentParams.tags.insert(tag);
@ -4863,12 +4859,12 @@ void SessionImpl::handleTorrentCategoryChanged(TorrentImpl *const torrent, const
emit torrentCategoryChanged(torrent, oldCategory); emit torrentCategoryChanged(torrent, oldCategory);
} }
void SessionImpl::handleTorrentTagAdded(TorrentImpl *const torrent, const QString &tag) void SessionImpl::handleTorrentTagAdded(TorrentImpl *const torrent, const Tag &tag)
{ {
emit torrentTagAdded(torrent, tag); emit torrentTagAdded(torrent, tag);
} }
void SessionImpl::handleTorrentTagRemoved(TorrentImpl *const torrent, const QString &tag) void SessionImpl::handleTorrentTagRemoved(TorrentImpl *const torrent, const Tag &tag)
{ {
emit torrentTagRemoved(torrent, tag); emit torrentTagRemoved(torrent, tag);
} }

View file

@ -156,10 +156,10 @@ namespace BitTorrent
Path suggestedSavePath(const QString &categoryName, std::optional<bool> useAutoTMM) const override; Path suggestedSavePath(const QString &categoryName, std::optional<bool> useAutoTMM) const override;
Path suggestedDownloadPath(const QString &categoryName, std::optional<bool> useAutoTMM) const override; Path suggestedDownloadPath(const QString &categoryName, std::optional<bool> useAutoTMM) const override;
QSet<QString> tags() const override; TagSet tags() const override;
bool hasTag(const QString &tag) const override; bool hasTag(const Tag &tag) const override;
bool addTag(const QString &tag) override; bool addTag(const Tag &tag) override;
bool removeTag(const QString &tag) override; bool removeTag(const Tag &tag) override;
bool isAutoTMMDisabledByDefault() const override; bool isAutoTMMDisabledByDefault() const override;
void setAutoTMMDisabledByDefault(bool value) override; void setAutoTMMDisabledByDefault(bool value) override;
@ -433,8 +433,8 @@ namespace BitTorrent
void handleTorrentNameChanged(TorrentImpl *torrent); void handleTorrentNameChanged(TorrentImpl *torrent);
void handleTorrentSavePathChanged(TorrentImpl *torrent); void handleTorrentSavePathChanged(TorrentImpl *torrent);
void handleTorrentCategoryChanged(TorrentImpl *torrent, const QString &oldCategory); void handleTorrentCategoryChanged(TorrentImpl *torrent, const QString &oldCategory);
void handleTorrentTagAdded(TorrentImpl *torrent, const QString &tag); void handleTorrentTagAdded(TorrentImpl *torrent, const Tag &tag);
void handleTorrentTagRemoved(TorrentImpl *torrent, const QString &tag); void handleTorrentTagRemoved(TorrentImpl *torrent, const Tag &tag);
void handleTorrentSavingModeChanged(TorrentImpl *torrent); void handleTorrentSavingModeChanged(TorrentImpl *torrent);
void handleTorrentMetadataReceived(TorrentImpl *torrent); void handleTorrentMetadataReceived(TorrentImpl *torrent);
void handleTorrentPaused(TorrentImpl *torrent); void handleTorrentPaused(TorrentImpl *torrent);
@ -755,7 +755,7 @@ namespace BitTorrent
QSet<TorrentID> m_needSaveResumeDataTorrents; QSet<TorrentID> m_needSaveResumeDataTorrents;
QHash<TorrentID, TorrentID> m_changedTorrentIDs; QHash<TorrentID, TorrentID> m_changedTorrentIDs;
QMap<QString, CategoryOptions> m_categories; QMap<QString, CategoryOptions> m_categories;
QSet<QString> m_tags; TagSet m_tags;
// This field holds amounts of peers reported by trackers in their responses to announces // This field holds amounts of peers reported by trackers in their responses to announces
// (torrent.tracker_name.tracker_local_endpoint.protocol_version.num_peers) // (torrent.tracker_name.tracker_local_endpoint.protocol_version.num_peers)

View file

@ -202,9 +202,9 @@ namespace BitTorrent
virtual bool setCategory(const QString &category) = 0; virtual bool setCategory(const QString &category) = 0;
virtual TagSet tags() const = 0; virtual TagSet tags() const = 0;
virtual bool hasTag(const QString &tag) const = 0; virtual bool hasTag(const Tag &tag) const = 0;
virtual bool addTag(const QString &tag) = 0; virtual bool addTag(const Tag &tag) = 0;
virtual bool removeTag(const QString &tag) = 0; virtual bool removeTag(const Tag &tag) = 0;
virtual void removeAllTags() = 0; virtual void removeAllTags() = 0;
virtual int piecesCount() const = 0; virtual int piecesCount() const = 0;

View file

@ -863,14 +863,14 @@ TagSet TorrentImpl::tags() const
return m_tags; return m_tags;
} }
bool TorrentImpl::hasTag(const QString &tag) const bool TorrentImpl::hasTag(const Tag &tag) const
{ {
return m_tags.contains(tag); return m_tags.contains(tag);
} }
bool TorrentImpl::addTag(const QString &tag) bool TorrentImpl::addTag(const Tag &tag)
{ {
if (!m_session->isValidTag(tag)) if (!tag.isValid())
return false; return false;
if (hasTag(tag)) if (hasTag(tag))
return false; return false;
@ -886,7 +886,7 @@ bool TorrentImpl::addTag(const QString &tag)
return true; return true;
} }
bool TorrentImpl::removeTag(const QString &tag) bool TorrentImpl::removeTag(const Tag &tag)
{ {
if (m_tags.remove(tag)) if (m_tags.remove(tag))
{ {
@ -899,7 +899,7 @@ bool TorrentImpl::removeTag(const QString &tag)
void TorrentImpl::removeAllTags() void TorrentImpl::removeAllTags()
{ {
for (const QString &tag : asConst(tags())) for (const Tag &tag : asConst(tags()))
removeTag(tag); removeTag(tag);
} }

View file

@ -128,9 +128,9 @@ namespace BitTorrent
bool setCategory(const QString &category) override; bool setCategory(const QString &category) override;
TagSet tags() const override; TagSet tags() const override;
bool hasTag(const QString &tag) const override; bool hasTag(const Tag &tag) const override;
bool addTag(const QString &tag) override; bool addTag(const Tag &tag) override;
bool removeTag(const QString &tag) override; bool removeTag(const Tag &tag) override;
void removeAllTags() override; void removeAllTags() override;
int filesCount() const override; int filesCount() const override;

92
src/base/tag.cpp Normal file
View file

@ -0,0 +1,92 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
*
* 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 "tag.h"
#include <QDataStream>
#include "base/concepts/stringable.h"
#include "base/global.h"
namespace
{
QString cleanTag(const QString &tag)
{
return tag.trimmed();
}
}
// `Tag` should satisfy `Stringable` concept in order to be stored in settings as string
static_assert(Stringable<Tag>);
Tag::Tag(const QString &tagStr)
: m_tagStr {cleanTag(tagStr)}
{
}
Tag::Tag(const std::string &tagStr)
: Tag(QString::fromStdString(tagStr))
{
}
bool Tag::isValid() const
{
if (isEmpty())
return false;
return !m_tagStr.contains(u',');
}
bool Tag::isEmpty() const
{
return m_tagStr.isEmpty();
}
QString Tag::toString() const noexcept
{
return m_tagStr;
}
Tag::operator QString() const noexcept
{
return toString();
}
QDataStream &operator<<(QDataStream &out, const Tag &tag)
{
out << tag.toString();
return out;
}
QDataStream &operator>>(QDataStream &in, Tag &tag)
{
QString tagStr;
in >> tagStr;
tag = Tag(tagStr);
return in;
}

67
src/base/tag.h Normal file
View file

@ -0,0 +1,67 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
*
* 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 <QMetaType>
#include <QString>
class Tag final
{
public:
Tag() = default;
explicit Tag(const QString &tagStr);
explicit Tag(const std::string &tagStr);
bool isValid() const;
bool isEmpty() const;
QString toString() const noexcept;
explicit operator QString() const noexcept;
friend bool operator==(const Tag &, const Tag &) = default;
private:
QString m_tagStr;
};
Q_DECLARE_METATYPE(Tag)
QDataStream &operator<<(QDataStream &out, const Tag &tag);
QDataStream &operator>>(QDataStream &in, Tag &tag);
template <>
struct std::hash<Tag>
{
std::size_t operator()(const Tag &tag, const std::size_t seed = 0) const noexcept
{
return qHash(tag.toString(), seed);
}
};

View file

@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2021 Mike Tzou (Chocobo1) * Copyright (C) 2021 Mike Tzou (Chocobo1)
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -28,10 +29,10 @@
#include "tagset.h" #include "tagset.h"
bool TagLessThan::operator()(const QString &left, const QString &right) const bool TagLessThan::operator()(const Tag &left, const Tag &right) const
{ {
const int result = m_compare(left, right); const int result = m_compare(left.toString(), right.toString());
if (result != 0) if (result != 0)
return (result < 0); return (result < 0);
return (m_subCompare(left, right) < 0); return (m_subCompare(left.toString(), right.toString()) < 0);
} }

View file

@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2021 Mike Tzou (Chocobo1) * Copyright (C) 2021 Mike Tzou (Chocobo1)
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -29,21 +30,21 @@
#pragma once #pragma once
#include <QMetaType> #include <QMetaType>
#include <QString>
#include "base/orderedset.h" #include "orderedset.h"
#include "base/utils/compare.h" #include "tag.h"
#include "utils/compare.h"
class TagLessThan class TagLessThan
{ {
public: public:
bool operator()(const QString &left, const QString &right) const; bool operator()(const Tag &left, const Tag &right) const;
private: private:
Utils::Compare::NaturalCompare<Qt::CaseInsensitive> m_compare; Utils::Compare::NaturalCompare<Qt::CaseInsensitive> m_compare;
Utils::Compare::NaturalCompare<Qt::CaseSensitive> m_subCompare; Utils::Compare::NaturalCompare<Qt::CaseSensitive> m_subCompare;
}; };
using TagSet = OrderedSet<QString, TagLessThan>; using TagSet = OrderedSet<Tag, TagLessThan>;
Q_DECLARE_METATYPE(TagSet) Q_DECLARE_METATYPE(TagSet)

View file

@ -33,7 +33,7 @@
const std::optional<QString> TorrentFilter::AnyCategory; const std::optional<QString> TorrentFilter::AnyCategory;
const std::optional<TorrentIDSet> TorrentFilter::AnyID; const std::optional<TorrentIDSet> TorrentFilter::AnyID;
const std::optional<QString> TorrentFilter::AnyTag; const std::optional<Tag> TorrentFilter::AnyTag;
const TorrentFilter TorrentFilter::DownloadingTorrent(TorrentFilter::Downloading); const TorrentFilter TorrentFilter::DownloadingTorrent(TorrentFilter::Downloading);
const TorrentFilter TorrentFilter::SeedingTorrent(TorrentFilter::Seeding); const TorrentFilter TorrentFilter::SeedingTorrent(TorrentFilter::Seeding);
@ -52,19 +52,19 @@ const TorrentFilter TorrentFilter::ErroredTorrent(TorrentFilter::Errored);
using BitTorrent::Torrent; using BitTorrent::Torrent;
TorrentFilter::TorrentFilter(const Type type, const std::optional<TorrentIDSet> &idSet TorrentFilter::TorrentFilter(const Type type, const std::optional<TorrentIDSet> &idSet
, const std::optional<QString> &category, const std::optional<QString> &tag) , const std::optional<QString> &category, const std::optional<Tag> &tag)
: m_type(type) : m_type {type}
, m_category(category) , m_category {category}
, m_tag(tag) , m_tag {tag}
, m_idSet(idSet) , m_idSet {idSet}
{ {
} }
TorrentFilter::TorrentFilter(const QString &filter, const std::optional<TorrentIDSet> &idSet TorrentFilter::TorrentFilter(const QString &filter, const std::optional<TorrentIDSet> &idSet
, const std::optional<QString> &category, const std::optional<QString> &tag) , const std::optional<QString> &category, const std::optional<Tag> &tag)
: m_category(category) : m_category {category}
, m_tag(tag) , m_tag {tag}
, m_idSet(idSet) , m_idSet {idSet}
{ {
setTypeByName(filter); setTypeByName(filter);
} }
@ -136,7 +136,7 @@ bool TorrentFilter::setCategory(const std::optional<QString> &category)
return false; return false;
} }
bool TorrentFilter::setTag(const std::optional<QString> &tag) bool TorrentFilter::setTag(const std::optional<Tag> &tag)
{ {
if (m_tag != tag) if (m_tag != tag)
{ {

View file

@ -34,6 +34,7 @@
#include <QString> #include <QString>
#include "base/bittorrent/infohash.h" #include "base/bittorrent/infohash.h"
#include "base/tag.h"
namespace BitTorrent namespace BitTorrent
{ {
@ -66,7 +67,7 @@ public:
// These mean any permutation, including no category / tag. // These mean any permutation, including no category / tag.
static const std::optional<QString> AnyCategory; static const std::optional<QString> AnyCategory;
static const std::optional<TorrentIDSet> AnyID; static const std::optional<TorrentIDSet> AnyID;
static const std::optional<QString> AnyTag; static const std::optional<Tag> AnyTag;
static const TorrentFilter DownloadingTorrent; static const TorrentFilter DownloadingTorrent;
static const TorrentFilter SeedingTorrent; static const TorrentFilter SeedingTorrent;
@ -85,15 +86,15 @@ public:
TorrentFilter() = default; TorrentFilter() = default;
// category & tags: pass empty string for uncategorized / untagged torrents. // category & tags: pass empty string for uncategorized / untagged torrents.
TorrentFilter(Type type, const std::optional<TorrentIDSet> &idSet = AnyID TorrentFilter(Type type, const std::optional<TorrentIDSet> &idSet = AnyID
, const std::optional<QString> &category = AnyCategory, const std::optional<QString> &tag = AnyTag); , const std::optional<QString> &category = AnyCategory, const std::optional<Tag> &tag = AnyTag);
TorrentFilter(const QString &filter, const std::optional<TorrentIDSet> &idSet = AnyID TorrentFilter(const QString &filter, const std::optional<TorrentIDSet> &idSet = AnyID
, const std::optional<QString> &category = AnyCategory, const std::optional<QString> &tags = AnyTag); , const std::optional<QString> &category = AnyCategory, const std::optional<Tag> &tags = AnyTag);
bool setType(Type type); bool setType(Type type);
bool setTypeByName(const QString &filter); bool setTypeByName(const QString &filter);
bool setTorrentIDSet(const std::optional<TorrentIDSet> &idSet); bool setTorrentIDSet(const std::optional<TorrentIDSet> &idSet);
bool setCategory(const std::optional<QString> &category); bool setCategory(const std::optional<QString> &category);
bool setTag(const std::optional<QString> &tag); bool setTag(const std::optional<Tag> &tag);
bool match(const BitTorrent::Torrent *torrent) const; bool match(const BitTorrent::Torrent *torrent) const;
@ -105,6 +106,6 @@ private:
Type m_type {All}; Type m_type {All};
std::optional<QString> m_category; std::optional<QString> m_category;
std::optional<QString> m_tag; std::optional<Tag> m_tag;
std::optional<TorrentIDSet> m_idSet; std::optional<TorrentIDSet> m_idSet;
}; };

View file

@ -363,7 +363,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to
connect(m_ui->categoryComboBox, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::categoryChanged); connect(m_ui->categoryComboBox, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::categoryChanged);
m_ui->tagsLineEdit->setText(m_torrentParams.tags.join(u", "_s)); m_ui->tagsLineEdit->setText(QStringList(m_torrentParams.tags.cbegin(), m_torrentParams.tags.cend()).join(u", "_s));
connect(m_ui->tagsEditButton, &QAbstractButton::clicked, this, [this] connect(m_ui->tagsEditButton, &QAbstractButton::clicked, this, [this]
{ {
auto *dlg = new TorrentTagsDialog(m_torrentParams.tags, this); auto *dlg = new TorrentTagsDialog(m_torrentParams.tags, this);
@ -371,7 +371,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to
connect(dlg, &TorrentTagsDialog::accepted, this, [this, dlg] connect(dlg, &TorrentTagsDialog::accepted, this, [this, dlg]
{ {
m_torrentParams.tags = dlg->tags(); m_torrentParams.tags = dlg->tags();
m_ui->tagsLineEdit->setText(m_torrentParams.tags.join(u", "_s)); m_ui->tagsLineEdit->setText(QStringList(m_torrentParams.tags.cbegin(), m_torrentParams.tags.cend()).join(u", "_s));
}); });
dlg->open(); dlg->open();
}); });

View file

@ -112,7 +112,7 @@ AddTorrentParamsWidget::AddTorrentParamsWidget(BitTorrent::AddTorrentParams addT
connect(dlg, &TorrentTagsDialog::accepted, this, [this, dlg] connect(dlg, &TorrentTagsDialog::accepted, this, [this, dlg]
{ {
m_addTorrentParams.tags = dlg->tags(); m_addTorrentParams.tags = dlg->tags();
m_ui->tagsLineEdit->setText(m_addTorrentParams.tags.join(u", "_s)); m_ui->tagsLineEdit->setText(QStringList(m_addTorrentParams.tags.cbegin(), m_addTorrentParams.tags.cend()).join(u", "_s));
}); });
dlg->open(); dlg->open();
}); });
@ -230,7 +230,7 @@ void AddTorrentParamsWidget::populate()
m_addTorrentParams.stopCondition = data.value<BitTorrent::Torrent::StopCondition>(); m_addTorrentParams.stopCondition = data.value<BitTorrent::Torrent::StopCondition>();
}); });
m_ui->tagsLineEdit->setText(m_addTorrentParams.tags.join(u", "_s)); m_ui->tagsLineEdit->setText(QStringList(m_addTorrentParams.tags.cbegin(), m_addTorrentParams.tags.cend()).join(u", "_s));
m_ui->startTorrentComboBox->disconnect(this); m_ui->startTorrentComboBox->disconnect(this);
m_ui->startTorrentComboBox->setCurrentIndex(m_addTorrentParams.addPaused m_ui->startTorrentComboBox->setCurrentIndex(m_addTorrentParams.addPaused

View file

@ -464,7 +464,7 @@ MainWindow::MainWindow(IGUIApplication *app, WindowState initialState)
{ {
m_transferListWidget->applyStatusFilter(pref->getTransSelFilter()); m_transferListWidget->applyStatusFilter(pref->getTransSelFilter());
m_transferListWidget->applyCategoryFilter(QString()); m_transferListWidget->applyCategoryFilter(QString());
m_transferListWidget->applyTagFilter(QString()); m_transferListWidget->applyTagFilter(std::nullopt);
m_transferListWidget->applyTrackerFilterAll(); m_transferListWidget->applyTrackerFilterAll();
} }

View file

@ -53,9 +53,9 @@ TorrentTagsDialog::TorrentTagsDialog(const TagSet &initialTags, QWidget *parent)
connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
auto *tagsLayout = new FlowLayout(m_ui->scrollArea); auto *tagsLayout = new FlowLayout(m_ui->scrollArea);
for (const QString &tag : asConst(initialTags.united(BitTorrent::Session::instance()->tags()))) for (const Tag &tag : asConst(initialTags.united(BitTorrent::Session::instance()->tags())))
{ {
auto *tagWidget = new QCheckBox(tag); auto *tagWidget = new QCheckBox(tag.toString());
if (initialTags.contains(tag)) if (initialTags.contains(tag))
tagWidget->setChecked(true); tagWidget->setChecked(true);
tagsLayout->addWidget(tagWidget); tagsLayout->addWidget(tagWidget);
@ -83,7 +83,7 @@ TagSet TorrentTagsDialog::tags() const
{ {
const auto *tagWidget = static_cast<QCheckBox *>(layout->itemAt(i)->widget()); const auto *tagWidget = static_cast<QCheckBox *>(layout->itemAt(i)->widget());
if (tagWidget->isChecked()) if (tagWidget->isChecked())
tags.insert(tagWidget->text()); tags.insert(Tag(tagWidget->text()));
} }
return tags; return tags;
@ -92,18 +92,18 @@ TagSet TorrentTagsDialog::tags() const
void TorrentTagsDialog::addNewTag() void TorrentTagsDialog::addNewTag()
{ {
bool done = false; bool done = false;
QString tag; Tag tag;
while (!done) while (!done)
{ {
bool ok = false; bool ok = false;
tag = AutoExpandableDialog::getText(this tag = Tag(AutoExpandableDialog::getText(this, tr("New Tag")
, tr("New Tag"), tr("Tag:"), QLineEdit::Normal, tag, &ok).trimmed(); , tr("Tag:"), QLineEdit::Normal, tag.toString(), &ok));
if (!ok || tag.isEmpty()) if (!ok || tag.isEmpty())
break; break;
if (!BitTorrent::Session::isValidTag(tag)) if (!tag.isValid())
{ {
QMessageBox::warning(this, tr("Invalid tag name"), tr("Tag name '%1' is invalid.").arg(tag)); QMessageBox::warning(this, tr("Invalid tag name"), tr("Tag name '%1' is invalid.").arg(tag.toString()));
} }
else if (BitTorrent::Session::instance()->tags().contains(tag)) else if (BitTorrent::Session::instance()->tags().contains(tag))
{ {
@ -113,7 +113,7 @@ void TorrentTagsDialog::addNewTag()
{ {
auto *layout = m_ui->scrollArea->layout(); auto *layout = m_ui->scrollArea->layout();
auto *btn = layout->takeAt(layout->count() - 1); auto *btn = layout->takeAt(layout->count() - 1);
auto *tagWidget = new QCheckBox(tag); auto *tagWidget = new QCheckBox(tag.toString());
tagWidget->setChecked(true); tagWidget->setChecked(true);
layout->addWidget(tagWidget); layout->addWidget(tagWidget);
layout->addItem(btn); layout->addItem(btn);

View file

@ -29,7 +29,6 @@
#include "tagfiltermodel.h" #include "tagfiltermodel.h"
#include <QDebug>
#include <QIcon> #include <QIcon>
#include <QVector> #include <QVector>
@ -40,33 +39,16 @@
const int ROW_ALL = 0; const int ROW_ALL = 0;
const int ROW_UNTAGGED = 1; const int ROW_UNTAGGED = 1;
namespace
{
QString getSpecialAllTag()
{
const QString ALL_TAG = u" "_s;
Q_ASSERT(!BitTorrent::Session::isValidTag(ALL_TAG));
return ALL_TAG;
}
QString getSpecialUntaggedTag()
{
const QString UNTAGGED_TAG = u" "_s;
Q_ASSERT(!BitTorrent::Session::isValidTag(UNTAGGED_TAG));
return UNTAGGED_TAG;
}
}
class TagModelItem class TagModelItem
{ {
public: public:
TagModelItem(const QString &tag, int torrentsCount = 0) TagModelItem(const Tag &tag, int torrentsCount = 0)
: m_tag(tag) : m_tag {tag}
, m_torrentsCount(torrentsCount) , m_torrentsCount {torrentsCount}
{ {
} }
QString tag() const Tag tag() const
{ {
return m_tag; return m_tag;
} }
@ -88,7 +70,7 @@ public:
} }
private: private:
QString m_tag; Tag m_tag;
int m_torrentsCount; int m_torrentsCount;
}; };
@ -124,12 +106,25 @@ QVariant TagFilterModel::data(const QModelIndex &index, int role) const
Q_ASSERT(isValidRow(row)); Q_ASSERT(isValidRow(row));
const TagModelItem &item = m_tagItems[row]; const TagModelItem &item = m_tagItems[row];
const auto displayName = [&row, tag = item.tag()]
{
switch (row)
{
case 0:
return tr("All");
case 1:
return tr("Untagged");
default:
return tag.toString();
}
};
switch (role) switch (role)
{ {
case Qt::DecorationRole: case Qt::DecorationRole:
return UIThemeManager::instance()->getIcon(u"tags"_s, u"inode-directory"_s); return UIThemeManager::instance()->getIcon(u"tags"_s, u"inode-directory"_s);
case Qt::DisplayRole: case Qt::DisplayRole:
return u"%1 (%2)"_s.arg(tagDisplayName(item.tag())).arg(item.torrentsCount()); return u"%1 (%2)"_s.arg(displayName(), QString::number(item.torrentsCount()));
case Qt::UserRole: case Qt::UserRole:
return item.torrentsCount(); return item.torrentsCount();
default: default:
@ -171,7 +166,7 @@ bool TagFilterModel::isValidRow(int row) const
return (row >= 0) && (row < m_tagItems.size()); return (row >= 0) && (row < m_tagItems.size());
} }
QModelIndex TagFilterModel::index(const QString &tag) const QModelIndex TagFilterModel::index(const Tag &tag) const
{ {
const int row = findRow(tag); const int row = findRow(tag);
if (!isValidRow(row)) if (!isValidRow(row))
@ -179,7 +174,7 @@ QModelIndex TagFilterModel::index(const QString &tag) const
return index(row, 0, QModelIndex()); return index(row, 0, QModelIndex());
} }
QString TagFilterModel::tag(const QModelIndex &index) const Tag TagFilterModel::tag(const QModelIndex &index) const
{ {
if (!index.isValid()) if (!index.isValid())
return {}; return {};
@ -188,7 +183,7 @@ QString TagFilterModel::tag(const QModelIndex &index) const
return m_tagItems[row].tag(); return m_tagItems[row].tag();
} }
void TagFilterModel::tagAdded(const QString &tag) void TagFilterModel::tagAdded(const Tag &tag)
{ {
const int row = m_tagItems.count(); const int row = m_tagItems.count();
beginInsertRows(QModelIndex(), row, row); beginInsertRows(QModelIndex(), row, row);
@ -196,7 +191,7 @@ void TagFilterModel::tagAdded(const QString &tag)
endInsertRows(); endInsertRows();
} }
void TagFilterModel::tagRemoved(const QString &tag) void TagFilterModel::tagRemoved(const Tag &tag)
{ {
QModelIndex i = index(tag); QModelIndex i = index(tag);
beginRemoveRows(i.parent(), i.row(), i.row()); beginRemoveRows(i.parent(), i.row(), i.row());
@ -204,7 +199,7 @@ void TagFilterModel::tagRemoved(const QString &tag)
endRemoveRows(); endRemoveRows();
} }
void TagFilterModel::torrentTagAdded(BitTorrent::Torrent *const torrent, const QString &tag) void TagFilterModel::torrentTagAdded(BitTorrent::Torrent *const torrent, const Tag &tag)
{ {
if (torrent->tags().count() == 1) if (torrent->tags().count() == 1)
{ {
@ -222,7 +217,7 @@ void TagFilterModel::torrentTagAdded(BitTorrent::Torrent *const torrent, const Q
emit dataChanged(i, i); emit dataChanged(i, i);
} }
void TagFilterModel::torrentTagRemoved(BitTorrent::Torrent *const torrent, const QString &tag) void TagFilterModel::torrentTagRemoved(BitTorrent::Torrent *const torrent, const Tag &tag)
{ {
if (torrent->tags().empty()) if (torrent->tags().empty())
{ {
@ -275,7 +270,7 @@ void TagFilterModel::torrentAboutToBeRemoved(BitTorrent::Torrent *const torrent)
} }
else else
{ {
for (const QString &tag : asConst(torrent->tags())) for (const Tag &tag : asConst(torrent->tags()))
{ {
const int row = findRow(tag); const int row = findRow(tag);
Q_ASSERT(isValidRow(row)); Q_ASSERT(isValidRow(row));
@ -289,15 +284,6 @@ void TagFilterModel::torrentAboutToBeRemoved(BitTorrent::Torrent *const torrent)
} }
} }
QString TagFilterModel::tagDisplayName(const QString &tag)
{
if (tag == getSpecialAllTag())
return tr("All");
if (tag == getSpecialUntaggedTag())
return tr("Untagged");
return tag;
}
void TagFilterModel::populate() void TagFilterModel::populate()
{ {
using Torrent = BitTorrent::Torrent; using Torrent = BitTorrent::Torrent;
@ -306,21 +292,21 @@ void TagFilterModel::populate()
const auto torrents = session->torrents(); const auto torrents = session->torrents();
// All torrents // All torrents
addToModel(getSpecialAllTag(), torrents.count()); addToModel(Tag(), torrents.count());
const int untaggedCount = std::count_if(torrents.cbegin(), torrents.cend(), const int untaggedCount = std::count_if(torrents.cbegin(), torrents.cend()
[](Torrent *torrent) { return torrent->tags().isEmpty(); }); , [](Torrent *torrent) { return torrent->tags().isEmpty(); });
addToModel(getSpecialUntaggedTag(), untaggedCount); addToModel(Tag(), untaggedCount);
for (const QString &tag : asConst(session->tags())) for (const Tag &tag : asConst(session->tags()))
{ {
const int count = std::count_if(torrents.cbegin(), torrents.cend(), const int count = std::count_if(torrents.cbegin(), torrents.cend()
[tag](Torrent *torrent) { return torrent->hasTag(tag); }); , [tag](Torrent *torrent) { return torrent->hasTag(tag); });
addToModel(tag, count); addToModel(tag, count);
} }
} }
void TagFilterModel::addToModel(const QString &tag, int count) void TagFilterModel::addToModel(const Tag &tag, const int count)
{ {
m_tagItems.append(TagModelItem(tag, count)); m_tagItems.append(TagModelItem(tag, count));
} }
@ -331,9 +317,9 @@ void TagFilterModel::removeFromModel(int row)
m_tagItems.removeAt(row); m_tagItems.removeAt(row);
} }
int TagFilterModel::findRow(const QString &tag) const int TagFilterModel::findRow(const Tag &tag) const
{ {
if (!BitTorrent::Session::isValidTag(tag)) if (!tag.isValid())
return -1; return -1;
for (int i = 0; i < m_tagItems.size(); ++i) for (int i = 0; i < m_tagItems.size(); ++i)
@ -345,7 +331,7 @@ int TagFilterModel::findRow(const QString &tag) const
return -1; return -1;
} }
TagModelItem *TagFilterModel::findItem(const QString &tag) TagModelItem *TagFilterModel::findItem(const Tag &tag)
{ {
const int row = findRow(tag); const int row = findRow(tag);
if (!isValidRow(row)) if (!isValidRow(row))
@ -357,13 +343,13 @@ QVector<TagModelItem *> TagFilterModel::findItems(const TagSet &tags)
{ {
QVector<TagModelItem *> items; QVector<TagModelItem *> items;
items.reserve(tags.count()); items.reserve(tags.count());
for (const QString &tag : tags) for (const Tag &tag : tags)
{ {
TagModelItem *item = findItem(tag); TagModelItem *item = findItem(tag);
if (item) if (item)
items.push_back(item); items.push_back(item);
else else
qWarning() << u"Requested tag '%1' missing from the model."_s.arg(tag); qWarning() << u"Requested tag '%1' missing from the model."_s.arg(tag.toString());
} }
return items; return items;
} }

View file

@ -56,26 +56,24 @@ public:
QModelIndex index(int row, int column, const QModelIndex &parent = {}) const override; QModelIndex index(int row, int column, const QModelIndex &parent = {}) const override;
int rowCount(const QModelIndex &parent = {}) const override; int rowCount(const QModelIndex &parent = {}) const override;
QModelIndex index(const QString &tag) const; QModelIndex index(const Tag &tag) const;
QString tag(const QModelIndex &index) const; Tag tag(const QModelIndex &index) const;
private slots: private slots:
void tagAdded(const QString &tag); void tagAdded(const Tag &tag);
void tagRemoved(const QString &tag); void tagRemoved(const Tag &tag);
void torrentTagAdded(BitTorrent::Torrent *torrent, const QString &tag); void torrentTagAdded(BitTorrent::Torrent *torrent, const Tag &tag);
void torrentTagRemoved(BitTorrent::Torrent *, const QString &tag); void torrentTagRemoved(BitTorrent::Torrent *, const Tag &tag);
void torrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents); void torrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents);
void torrentAboutToBeRemoved(BitTorrent::Torrent *torrent); void torrentAboutToBeRemoved(BitTorrent::Torrent *torrent);
private: private:
static QString tagDisplayName(const QString &tag);
void populate(); void populate();
void addToModel(const QString &tag, int count); void addToModel(const Tag &tag, int count);
void removeFromModel(int row); void removeFromModel(int row);
bool isValidRow(int row) const; bool isValidRow(int row) const;
int findRow(const QString &tag) const; int findRow(const Tag &tag) const;
TagModelItem *findItem(const QString &tag); TagModelItem *findItem(const Tag &tag);
QVector<TagModelItem *> findItems(const TagSet &tags); QVector<TagModelItem *> findItems(const TagSet &tags);
TagModelItem *allTagsItem(); TagModelItem *allTagsItem();
TagModelItem *untaggedItem(); TagModelItem *untaggedItem();

View file

@ -35,12 +35,12 @@ TagFilterProxyModel::TagFilterProxyModel(QObject *parent)
{ {
} }
QModelIndex TagFilterProxyModel::index(const QString &tag) const QModelIndex TagFilterProxyModel::index(const Tag &tag) const
{ {
return mapFromSource(static_cast<TagFilterModel *>(sourceModel())->index(tag)); return mapFromSource(static_cast<TagFilterModel *>(sourceModel())->index(tag));
} }
QString TagFilterProxyModel::tag(const QModelIndex &index) const Tag TagFilterProxyModel::tag(const QModelIndex &index) const
{ {
return static_cast<TagFilterModel *>(sourceModel())->tag(mapToSource(index)); return static_cast<TagFilterModel *>(sourceModel())->tag(mapToSource(index));
} }

View file

@ -34,14 +34,16 @@
class QString; class QString;
class Tag;
class TagFilterProxyModel final : public QSortFilterProxyModel class TagFilterProxyModel final : public QSortFilterProxyModel
{ {
public: public:
explicit TagFilterProxyModel(QObject *parent = nullptr); explicit TagFilterProxyModel(QObject *parent = nullptr);
// TagFilterModel methods which we need to relay // TagFilterModel methods which we need to relay
QModelIndex index(const QString &tag) const; QModelIndex index(const Tag &tag) const;
QString tag(const QModelIndex &index) const; Tag tag(const QModelIndex &index) const;
protected: protected:
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;

View file

@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2017 Tony Gregerson <tony.gregerson@gmail.com> * Copyright (C) 2017 Tony Gregerson <tony.gregerson@gmail.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -41,17 +42,15 @@
namespace namespace
{ {
QString getTagFilter(const TagFilterProxyModel *const model, const QModelIndex &index) std::optional<Tag> getTagFilter(const TagFilterProxyModel *const model, const QModelIndex &index)
{ {
QString tagFilter; // Defaults to All if (!index.isValid() || (index.row() == 0))
if (index.isValid()) return std::nullopt; // All tags
{
if (index.row() == 1) if (index.row() == 1)
tagFilter = u""_s; // Untagged return Tag(); // Untagged
else if (index.row() > 1)
tagFilter = model->tag(index); return model->tag(index);
}
return tagFilter;
} }
} }
@ -85,7 +84,7 @@ TagFilterWidget::TagFilterWidget(QWidget *parent)
connect(model(), &QAbstractItemModel::modelReset, this, &TagFilterWidget::callUpdateGeometry); connect(model(), &QAbstractItemModel::modelReset, this, &TagFilterWidget::callUpdateGeometry);
} }
QString TagFilterWidget::currentTag() const std::optional<Tag> TagFilterWidget::currentTag() const
{ {
QModelIndex current; QModelIndex current;
const auto selectedRows = selectionModel()->selectedRows(); const auto selectedRows = selectionModel()->selectedRows();
@ -157,35 +156,36 @@ void TagFilterWidget::rowsInserted(const QModelIndex &parent, int start, int end
updateGeometry(); updateGeometry();
} }
QString TagFilterWidget::askTagName() Tag TagFilterWidget::askTagName()
{ {
bool ok = false; bool ok = false;
QString tag = u""_s; Tag tag;
bool invalid = true; bool invalid = true;
while (invalid) while (invalid)
{ {
invalid = false; invalid = false;
tag = AutoExpandableDialog::getText( tag = Tag(AutoExpandableDialog::getText(this, tr("New Tag"), tr("Tag:")
this, tr("New Tag"), tr("Tag:"), QLineEdit::Normal, tag, &ok).trimmed(); , QLineEdit::Normal, tag.toString(), &ok));
if (ok && !tag.isEmpty()) if (ok && !tag.isEmpty())
{ {
if (!BitTorrent::Session::isValidTag(tag)) if (!tag.isValid())
{ {
QMessageBox::warning( QMessageBox::warning(
this, tr("Invalid tag name") this, tr("Invalid tag name")
, tr("Tag name '%1' is invalid").arg(tag)); , tr("Tag name '%1' is invalid").arg(tag.toString()));
invalid = true; invalid = true;
} }
} }
} }
return ok ? tag : QString(); return ok ? tag : Tag();
} }
void TagFilterWidget::addTag() void TagFilterWidget::addTag()
{ {
const QString tag = askTagName(); const Tag tag = askTagName();
if (tag.isEmpty()) return; if (tag.isEmpty())
return;
if (BitTorrent::Session::instance()->tags().contains(tag)) if (BitTorrent::Session::instance()->tags().contains(tag))
QMessageBox::warning(this, tr("Tag exists"), tr("Tag name already exists.")); QMessageBox::warning(this, tr("Tag exists"), tr("Tag name already exists."));
@ -207,7 +207,7 @@ void TagFilterWidget::removeTag()
void TagFilterWidget::removeUnusedTags() void TagFilterWidget::removeUnusedTags()
{ {
auto *session = BitTorrent::Session::instance(); auto *session = BitTorrent::Session::instance();
for (const QString &tag : asConst(session->tags())) for (const Tag &tag : asConst(session->tags()))
if (model()->data(static_cast<TagFilterProxyModel *>(model())->index(tag), Qt::UserRole) == 0) if (model()->data(static_cast<TagFilterProxyModel *>(model())->index(tag), Qt::UserRole) == 0)
session->removeTag(tag); session->removeTag(tag);
updateGeometry(); updateGeometry();

View file

@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2017 Tony Gregerson <tony.gregerson@gmail.com> * Copyright (C) 2017 Tony Gregerson <tony.gregerson@gmail.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -30,6 +31,8 @@
#include <QTreeView> #include <QTreeView>
class Tag;
class TagFilterWidget final : public QTreeView class TagFilterWidget final : public QTreeView
{ {
Q_OBJECT Q_OBJECT
@ -38,10 +41,10 @@ class TagFilterWidget final : public QTreeView
public: public:
explicit TagFilterWidget(QWidget *parent = nullptr); explicit TagFilterWidget(QWidget *parent = nullptr);
QString currentTag() const; std::optional<Tag> currentTag() const;
signals: signals:
void tagChanged(const QString &tag); void tagChanged(const std::optional<Tag> &tag);
void actionResumeTorrentsTriggered(); void actionResumeTorrentsTriggered();
void actionPauseTorrentsTriggered(); void actionPauseTorrentsTriggered();
void actionDeleteTorrentsTriggered(); void actionDeleteTorrentsTriggered();
@ -58,5 +61,5 @@ private:
QSize sizeHint() const override; QSize sizeHint() const override;
QSize minimumSizeHint() const override; QSize minimumSizeHint() const override;
void rowsInserted(const QModelIndex &parent, int start, int end) override; void rowsInserted(const QModelIndex &parent, int start, int end) override;
QString askTagName(); Tag askTagName();
}; };

View file

@ -218,5 +218,5 @@ void TransferListFiltersWidget::onTagFilterStateChanged(bool enabled)
void TransferListFiltersWidget::toggleTagFilter(bool enabled) void TransferListFiltersWidget::toggleTagFilter(bool enabled)
{ {
m_tagFilterWidget->setVisible(enabled); m_tagFilterWidget->setVisible(enabled);
m_transferList->applyTagFilter(enabled ? m_tagFilterWidget->currentTag() : QString()); m_transferList->applyTagFilter(enabled ? m_tagFilterWidget->currentTag() : std::nullopt);
} }

View file

@ -380,7 +380,10 @@ QString TransferListModel::displayValue(const BitTorrent::Torrent *torrent, cons
case TR_CATEGORY: case TR_CATEGORY:
return torrent->category(); return torrent->category();
case TR_TAGS: case TR_TAGS:
return torrent->tags().join(u", "_s); {
const TagSet &tags = torrent->tags();
return QStringList(tags.cbegin(), tags.cend()).join(u", "_s);
}
case TR_ADD_DATE: case TR_ADD_DATE:
return QLocale().toString(torrent->addedTime().toLocalTime(), QLocale::ShortFormat); return QLocale().toString(torrent->addedTime().toLocalTime(), QLocale::ShortFormat);
case TR_SEED_DATE: case TR_SEED_DATE:

View file

@ -59,10 +59,10 @@ namespace
int customCompare(const TagSet &left, const TagSet &right, const Utils::Compare::NaturalCompare<Qt::CaseInsensitive> &compare) int customCompare(const TagSet &left, const TagSet &right, const Utils::Compare::NaturalCompare<Qt::CaseInsensitive> &compare)
{ {
for (auto leftIter = left.cbegin(), rightIter = right.cbegin(); for (auto leftIter = left.cbegin(), rightIter = right.cbegin();
(leftIter != left.cend()) && (rightIter != right.cend()); (leftIter != left.cend()) && (rightIter != right.cend());
++leftIter, ++rightIter) ++leftIter, ++rightIter)
{ {
const int result = compare(*leftIter, *rightIter); const int result = compare(leftIter->toString(), rightIter->toString());
if (result != 0) if (result != 0)
return result; return result;
} }
@ -130,7 +130,7 @@ void TransferListSortModel::disableCategoryFilter()
invalidateFilter(); invalidateFilter();
} }
void TransferListSortModel::setTagFilter(const QString &tag) void TransferListSortModel::setTagFilter(const Tag &tag)
{ {
if (m_filter.setTag(tag)) if (m_filter.setTag(tag))
invalidateFilter(); invalidateFilter();

View file

@ -52,7 +52,7 @@ public:
void setStatusFilter(TorrentFilter::Type filter); void setStatusFilter(TorrentFilter::Type filter);
void setCategoryFilter(const QString &category); void setCategoryFilter(const QString &category);
void disableCategoryFilter(); void disableCategoryFilter();
void setTagFilter(const QString &tag); void setTagFilter(const Tag &tag);
void disableTagFilter(); void disableTagFilter();
void setTrackerFilter(const QSet<BitTorrent::TorrentID> &torrentIDs); void setTrackerFilter(const QSet<BitTorrent::TorrentID> &torrentIDs);
void disableTrackerFilter(); void disableTrackerFilter();

View file

@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -770,8 +771,8 @@ void TransferListWidget::askNewCategoryForSelection()
void TransferListWidget::askAddTagsForSelection() void TransferListWidget::askAddTagsForSelection()
{ {
const QStringList tags = askTagsForSelection(tr("Add Tags")); const TagSet tags = askTagsForSelection(tr("Add Tags"));
for (const QString &tag : tags) for (const Tag &tag : tags)
addSelectionTag(tag); addSelectionTag(tag);
} }
@ -871,9 +872,9 @@ void TransferListWidget::confirmRemoveAllTagsForSelection()
clearSelectionTags(); clearSelectionTags();
} }
QStringList TransferListWidget::askTagsForSelection(const QString &dialogTitle) TagSet TransferListWidget::askTagsForSelection(const QString &dialogTitle)
{ {
QStringList tags; TagSet tags;
bool invalid = true; bool invalid = true;
while (invalid) while (invalid)
{ {
@ -883,18 +884,23 @@ QStringList TransferListWidget::askTagsForSelection(const QString &dialogTitle)
this, dialogTitle, tr("Comma-separated tags:"), QLineEdit::Normal, {}, &ok).trimmed(); this, dialogTitle, tr("Comma-separated tags:"), QLineEdit::Normal, {}, &ok).trimmed();
if (!ok || tagsInput.isEmpty()) if (!ok || tagsInput.isEmpty())
return {}; return {};
tags = tagsInput.split(u',', Qt::SkipEmptyParts);
for (QString &tag : tags) const QStringList tagStrings = tagsInput.split(u',', Qt::SkipEmptyParts);
tags.clear();
for (const QString &tagStr : tagStrings)
{ {
tag = tag.trimmed(); const Tag tag {tagStr};
if (!BitTorrent::Session::isValidTag(tag)) if (!tag.isValid())
{ {
QMessageBox::warning(this, tr("Invalid tag") QMessageBox::warning(this, tr("Invalid tag"), tr("Tag name: '%1' is invalid").arg(tag.toString()));
, tr("Tag name: '%1' is invalid").arg(tag));
invalid = true; invalid = true;
} }
if (!invalid)
tags.insert(tag);
} }
} }
return tags; return tags;
} }
@ -939,12 +945,12 @@ void TransferListWidget::setSelectionCategory(const QString &category)
applyToSelectedTorrents([&category](BitTorrent::Torrent *torrent) { torrent->setCategory(category); }); applyToSelectedTorrents([&category](BitTorrent::Torrent *torrent) { torrent->setCategory(category); });
} }
void TransferListWidget::addSelectionTag(const QString &tag) void TransferListWidget::addSelectionTag(const Tag &tag)
{ {
applyToSelectedTorrents([&tag](BitTorrent::Torrent *const torrent) { torrent->addTag(tag); }); applyToSelectedTorrents([&tag](BitTorrent::Torrent *const torrent) { torrent->addTag(tag); });
} }
void TransferListWidget::removeSelectionTag(const QString &tag) void TransferListWidget::removeSelectionTag(const Tag &tag)
{ {
applyToSelectedTorrents([&tag](BitTorrent::Torrent *const torrent) { torrent->removeTag(tag); }); applyToSelectedTorrents([&tag](BitTorrent::Torrent *const torrent) { torrent->removeTag(tag); });
} }
@ -1187,9 +1193,6 @@ void TransferListWidget::displayListMenu()
} }
// Tag Menu // Tag Menu
QStringList tags(BitTorrent::Session::instance()->tags().values());
std::sort(tags.begin(), tags.end(), Utils::Compare::NaturalLessThan<Qt::CaseInsensitive>());
QMenu *tagsMenu = listMenu->addMenu(UIThemeManager::instance()->getIcon(u"tags"_s, u"view-categories"_s), tr("Ta&gs")); QMenu *tagsMenu = listMenu->addMenu(UIThemeManager::instance()->getIcon(u"tags"_s, u"view-categories"_s), tr("Ta&gs"));
tagsMenu->addAction(UIThemeManager::instance()->getIcon(u"list-add"_s), tr("&Add...", "Add / assign multiple tags...") tagsMenu->addAction(UIThemeManager::instance()->getIcon(u"list-add"_s), tr("&Add...", "Add / assign multiple tags...")
@ -1204,9 +1207,10 @@ void TransferListWidget::displayListMenu()
}); });
tagsMenu->addSeparator(); tagsMenu->addSeparator();
for (const QString &tag : asConst(tags)) const TagSet tags = BitTorrent::Session::instance()->tags();
for (const Tag &tag : asConst(tags))
{ {
auto *action = new TriStateAction(tag, tagsMenu); auto *action = new TriStateAction(tag.toString(), tagsMenu);
action->setCloseOnInteraction(false); action->setCloseOnInteraction(false);
const Qt::CheckState initialState = tagsInAll.contains(tag) ? Qt::Checked const Qt::CheckState initialState = tagsInAll.contains(tag) ? Qt::Checked
@ -1320,12 +1324,12 @@ void TransferListWidget::applyCategoryFilter(const QString &category)
m_sortFilterModel->setCategoryFilter(category); m_sortFilterModel->setCategoryFilter(category);
} }
void TransferListWidget::applyTagFilter(const QString &tag) void TransferListWidget::applyTagFilter(const std::optional<Tag> &tag)
{ {
if (tag.isNull()) if (!tag)
m_sortFilterModel->disableTagFilter(); m_sortFilterModel->disableTagFilter();
else else
m_sortFilterModel->setTagFilter(tag); m_sortFilterModel->setTagFilter(*tag);
} }
void TransferListWidget::applyTrackerFilterAll() void TransferListWidget::applyTrackerFilterAll()

View file

@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -63,8 +64,8 @@ public:
public slots: public slots:
void setSelectionCategory(const QString &category); void setSelectionCategory(const QString &category);
void addSelectionTag(const QString &tag); void addSelectionTag(const Tag &tag);
void removeSelectionTag(const QString &tag); void removeSelectionTag(const Tag &tag);
void clearSelectionTags(); void clearSelectionTags();
void setSelectedTorrentsLocation(); void setSelectedTorrentsLocation();
void pauseAllTorrents(); void pauseAllTorrents();
@ -96,7 +97,7 @@ public slots:
void applyFilter(const QString &name, const TransferListModel::Column &type); void applyFilter(const QString &name, const TransferListModel::Column &type);
void applyStatusFilter(int f); void applyStatusFilter(int f);
void applyCategoryFilter(const QString &category); void applyCategoryFilter(const QString &category);
void applyTagFilter(const QString &tag); void applyTagFilter(const std::optional<Tag> &tag);
void applyTrackerFilterAll(); void applyTrackerFilterAll();
void applyTrackerFilter(const QSet<BitTorrent::TorrentID> &torrentIDs); void applyTrackerFilter(const QSet<BitTorrent::TorrentID> &torrentIDs);
void previewFile(const Path &filePath); void previewFile(const Path &filePath);
@ -128,7 +129,7 @@ private:
void editTorrentTrackers(); void editTorrentTrackers();
void exportTorrent(); void exportTorrent();
void confirmRemoveAllTagsForSelection(); void confirmRemoveAllTagsForSelection();
QStringList askTagsForSelection(const QString &dialogTitle); TagSet askTagsForSelection(const QString &dialogTitle);
void applyToSelectedTorrents(const std::function<void (BitTorrent::Torrent *const)> &fn); void applyToSelectedTorrents(const std::function<void (BitTorrent::Torrent *const)> &fn);
QVector<BitTorrent::Torrent *> getVisibleTorrents() const; QVector<BitTorrent::Torrent *> getVisibleTorrents() const;
int visibleColumnsCount() const; int visibleColumnsCount() const;

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2018-2023 Vladimir Golovnev <glassez@yandex.ru>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -29,6 +29,7 @@
#include "serialize_torrent.h" #include "serialize_torrent.h"
#include <QDateTime> #include <QDateTime>
#include <QStringList>
#include <QVector> #include <QVector>
#include "base/bittorrent/infohash.h" #include "base/bittorrent/infohash.h"
@ -36,7 +37,6 @@
#include "base/bittorrent/trackerentry.h" #include "base/bittorrent/trackerentry.h"
#include "base/path.h" #include "base/path.h"
#include "base/tagset.h" #include "base/tagset.h"
#include "base/utils/fs.h"
namespace namespace
{ {
@ -106,6 +106,8 @@ QVariantMap serialize(const BitTorrent::Torrent &torrent)
: (QDateTime::currentDateTime().toSecsSinceEpoch() - timeSinceActivity); : (QDateTime::currentDateTime().toSecsSinceEpoch() - timeSinceActivity);
}; };
const TagSet &tags = torrent.tags();
return { return {
{KEY_TORRENT_ID, torrent.id().toString()}, {KEY_TORRENT_ID, torrent.id().toString()},
{KEY_TORRENT_INFOHASHV1, torrent.infoHash().v1().toString()}, {KEY_TORRENT_INFOHASHV1, torrent.infoHash().v1().toString()},
@ -128,7 +130,7 @@ QVariantMap serialize(const BitTorrent::Torrent &torrent)
{KEY_TORRENT_FIRST_LAST_PIECE_PRIO, torrent.hasFirstLastPiecePriority()}, {KEY_TORRENT_FIRST_LAST_PIECE_PRIO, torrent.hasFirstLastPiecePriority()},
{KEY_TORRENT_CATEGORY, torrent.category()}, {KEY_TORRENT_CATEGORY, torrent.category()},
{KEY_TORRENT_TAGS, torrent.tags().join(u", "_s)}, {KEY_TORRENT_TAGS, QStringList(tags.cbegin(), tags.cend()).join(u", "_s)},
{KEY_TORRENT_SUPER_SEEDING, torrent.superSeeding()}, {KEY_TORRENT_SUPER_SEEDING, torrent.superSeeding()},
{KEY_TORRENT_FORCE_START, torrent.isForced()}, {KEY_TORRENT_FORCE_START, torrent.isForced()},
{KEY_TORRENT_SAVE_PATH, torrent.savePath().toString()}, {KEY_TORRENT_SAVE_PATH, torrent.savePath().toString()},

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2018-2023 Vladimir Golovnev <glassez@yandex.ru>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License

View file

@ -540,8 +540,8 @@ void SyncController::makeMaindataSnapshot()
m_maindataSnapshot.categories[categoryName] = category.toVariantMap(); m_maindataSnapshot.categories[categoryName] = category.toVariantMap();
} }
for (const QString &tag : asConst(session->tags())) for (const Tag &tag : asConst(session->tags()))
m_maindataSnapshot.tags.append(tag); m_maindataSnapshot.tags.append(tag.toString());
for (auto trackersIter = m_knownTrackers.cbegin(); trackersIter != m_knownTrackers.cend(); ++trackersIter) for (auto trackersIter = m_knownTrackers.cbegin(); trackersIter != m_knownTrackers.cend(); ++trackersIter)
{ {
@ -815,16 +815,16 @@ void SyncController::onSubcategoriesSupportChanged()
} }
} }
void SyncController::onTagAdded(const QString &tag) void SyncController::onTagAdded(const Tag &tag)
{ {
m_removedTags.remove(tag); m_removedTags.remove(tag.toString());
m_addedTags.insert(tag); m_addedTags.insert(tag.toString());
} }
void SyncController::onTagRemoved(const QString &tag) void SyncController::onTagRemoved(const Tag &tag)
{ {
m_addedTags.remove(tag); m_addedTags.remove(tag.toString());
m_removedTags.insert(tag); m_removedTags.insert(tag.toString());
} }
void SyncController::onTorrentAdded(BitTorrent::Torrent *torrent) void SyncController::onTorrentAdded(BitTorrent::Torrent *torrent)
@ -902,12 +902,12 @@ void SyncController::onTorrentSavingModeChanged(BitTorrent::Torrent *torrent)
m_updatedTorrents.insert(torrent->id()); m_updatedTorrents.insert(torrent->id());
} }
void SyncController::onTorrentTagAdded(BitTorrent::Torrent *torrent, [[maybe_unused]] const QString &tag) void SyncController::onTorrentTagAdded(BitTorrent::Torrent *torrent, [[maybe_unused]] const Tag &tag)
{ {
m_updatedTorrents.insert(torrent->id()); m_updatedTorrents.insert(torrent->id());
} }
void SyncController::onTorrentTagRemoved(BitTorrent::Torrent *torrent, [[maybe_unused]] const QString &tag) void SyncController::onTorrentTagRemoved(BitTorrent::Torrent *torrent, [[maybe_unused]] const Tag &tag)
{ {
m_updatedTorrents.insert(torrent->id()); m_updatedTorrents.insert(torrent->id());
} }

View file

@ -32,6 +32,7 @@
#include <QVariantMap> #include <QVariantMap>
#include "base/bittorrent/infohash.h" #include "base/bittorrent/infohash.h"
#include "base/tag.h"
#include "apicontroller.h" #include "apicontroller.h"
namespace BitTorrent namespace BitTorrent
@ -64,8 +65,8 @@ private:
void onCategoryRemoved(const QString &categoryName); void onCategoryRemoved(const QString &categoryName);
void onCategoryOptionsChanged(const QString &categoryName); void onCategoryOptionsChanged(const QString &categoryName);
void onSubcategoriesSupportChanged(); void onSubcategoriesSupportChanged();
void onTagAdded(const QString &tag); void onTagAdded(const Tag &tag);
void onTagRemoved(const QString &tag); void onTagRemoved(const Tag &tag);
void onTorrentAdded(BitTorrent::Torrent *torrent); void onTorrentAdded(BitTorrent::Torrent *torrent);
void onTorrentAboutToBeRemoved(BitTorrent::Torrent *torrent); void onTorrentAboutToBeRemoved(BitTorrent::Torrent *torrent);
void onTorrentCategoryChanged(BitTorrent::Torrent *torrent, const QString &oldCategory); void onTorrentCategoryChanged(BitTorrent::Torrent *torrent, const QString &oldCategory);
@ -74,8 +75,8 @@ private:
void onTorrentResumed(BitTorrent::Torrent *torrent); void onTorrentResumed(BitTorrent::Torrent *torrent);
void onTorrentSavePathChanged(BitTorrent::Torrent *torrent); void onTorrentSavePathChanged(BitTorrent::Torrent *torrent);
void onTorrentSavingModeChanged(BitTorrent::Torrent *torrent); void onTorrentSavingModeChanged(BitTorrent::Torrent *torrent);
void onTorrentTagAdded(BitTorrent::Torrent *torrent, const QString &tag); void onTorrentTagAdded(BitTorrent::Torrent *torrent, const Tag &tag);
void onTorrentTagRemoved(BitTorrent::Torrent *torrent, const QString &tag); void onTorrentTagRemoved(BitTorrent::Torrent *torrent, const Tag &tag);
void onTorrentsUpdated(const QVector<BitTorrent::Torrent *> &torrents); void onTorrentsUpdated(const QVector<BitTorrent::Torrent *> &torrents);
void onTorrentTrackersChanged(BitTorrent::Torrent *torrent); void onTorrentTrackersChanged(BitTorrent::Torrent *torrent);

View file

@ -152,6 +152,15 @@ namespace
return it.value(); return it.value();
} }
std::optional<Tag> getOptionalTag(const StringMap &params, const QString &name)
{
const auto it = params.constFind(name);
if (it == params.cend())
return std::nullopt;
return Tag(it.value());
}
QJsonArray getStickyTrackers(const BitTorrent::Torrent *const torrent) QJsonArray getStickyTrackers(const BitTorrent::Torrent *const torrent)
{ {
int seedsDHT = 0, seedsPeX = 0, seedsLSD = 0, leechesDHT = 0, leechesPeX = 0, leechesLSD = 0; int seedsDHT = 0, seedsPeX = 0, seedsLSD = 0, leechesDHT = 0, leechesPeX = 0, leechesLSD = 0;
@ -273,7 +282,7 @@ void TorrentsController::infoAction()
{ {
const QString filter {params()[u"filter"_s]}; const QString filter {params()[u"filter"_s]};
const std::optional<QString> category = getOptionalString(params(), u"category"_s); const std::optional<QString> category = getOptionalString(params(), u"category"_s);
const std::optional<QString> tag = getOptionalString(params(), u"tag"_s); const std::optional<Tag> tag = getOptionalTag(params(), u"tag"_s);
const QString sortedColumn {params()[u"sort"_s]}; const QString sortedColumn {params()[u"sort"_s]};
const bool reverse {parseBool(params()[u"reverse"_s]).value_or(false)}; const bool reverse {parseBool(params()[u"reverse"_s]).value_or(false)};
int limit {params()[u"limit"_s].toInt()}; int limit {params()[u"limit"_s].toInt()};
@ -1317,12 +1326,11 @@ void TorrentsController::addTagsAction()
const QStringList hashes {params()[u"hashes"_s].split(u'|')}; const QStringList hashes {params()[u"hashes"_s].split(u'|')};
const QStringList tags {params()[u"tags"_s].split(u',', Qt::SkipEmptyParts)}; const QStringList tags {params()[u"tags"_s].split(u',', Qt::SkipEmptyParts)};
for (const QString &tag : tags) for (const QString &tagStr : tags)
{ {
const QString tagTrimmed {tag.trimmed()}; applyToTorrents(hashes, [&tagStr](BitTorrent::Torrent *const torrent)
applyToTorrents(hashes, [&tagTrimmed](BitTorrent::Torrent *const torrent)
{ {
torrent->addTag(tagTrimmed); torrent->addTag(Tag(tagStr));
}); });
} }
} }
@ -1334,12 +1342,11 @@ void TorrentsController::removeTagsAction()
const QStringList hashes {params()[u"hashes"_s].split(u'|')}; const QStringList hashes {params()[u"hashes"_s].split(u'|')};
const QStringList tags {params()[u"tags"_s].split(u',', Qt::SkipEmptyParts)}; const QStringList tags {params()[u"tags"_s].split(u',', Qt::SkipEmptyParts)};
for (const QString &tag : tags) for (const QString &tagStr : tags)
{ {
const QString tagTrimmed {tag.trimmed()}; applyToTorrents(hashes, [&tagStr](BitTorrent::Torrent *const torrent)
applyToTorrents(hashes, [&tagTrimmed](BitTorrent::Torrent *const torrent)
{ {
torrent->removeTag(tagTrimmed); torrent->removeTag(Tag(tagStr));
}); });
} }
@ -1358,8 +1365,8 @@ void TorrentsController::createTagsAction()
const QStringList tags {params()[u"tags"_s].split(u',', Qt::SkipEmptyParts)}; const QStringList tags {params()[u"tags"_s].split(u',', Qt::SkipEmptyParts)};
for (const QString &tag : tags) for (const QString &tagStr : tags)
BitTorrent::Session::instance()->addTag(tag.trimmed()); BitTorrent::Session::instance()->addTag(Tag(tagStr));
} }
void TorrentsController::deleteTagsAction() void TorrentsController::deleteTagsAction()
@ -1367,15 +1374,15 @@ void TorrentsController::deleteTagsAction()
requireParams({u"tags"_s}); requireParams({u"tags"_s});
const QStringList tags {params()[u"tags"_s].split(u',', Qt::SkipEmptyParts)}; const QStringList tags {params()[u"tags"_s].split(u',', Qt::SkipEmptyParts)};
for (const QString &tag : tags) for (const QString &tagStr : tags)
BitTorrent::Session::instance()->removeTag(tag.trimmed()); BitTorrent::Session::instance()->removeTag(Tag(tagStr));
} }
void TorrentsController::tagsAction() void TorrentsController::tagsAction()
{ {
QJsonArray result; QJsonArray result;
for (const QString &tag : asConst(BitTorrent::Session::instance()->tags())) for (const Tag &tag : asConst(BitTorrent::Session::instance()->tags()))
result << tag; result << tag.toString();
setResult(result); setResult(result);
} }