Skip to content

Commit

Permalink
Add a Tags (multi-label) feature to the GUI. Closes qbittorrent#13.
Browse files Browse the repository at this point in the history
See qbittorrent#13 for details.
  • Loading branch information
tgregerson committed Jun 25, 2017
1 parent ff80208 commit 467e516
Show file tree
Hide file tree
Showing 27 changed files with 1,314 additions and 34 deletions.
2 changes: 2 additions & 0 deletions src/base/bittorrent/addtorrentparams.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#pragma once

#include <QSet>
#include <QString>
#include <QVector>

Expand All @@ -39,6 +40,7 @@ namespace BitTorrent
{
QString name;
QString category;
QSet<QString> tags;
QString savePath;
bool disableTempPath = false; // e.g. for imported torrents
bool sequential = false;
Expand Down
95 changes: 95 additions & 0 deletions src/base/bittorrent/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,43 @@ namespace
return result;
}

template <typename Entry>
QSet<QString> entryListToSetImpl(const Entry &entry)
{
Q_ASSERT(entry.type() == Entry::list_t);
QSet<QString> output;
for (int i = 0; i < entry.list_size(); ++i) {
const QString tag = QString::fromStdString(entry.list_string_value_at(i));
if (Session::isValidTag(tag))
output.insert(tag);
else
qWarning() << QString("Dropping invalid stored tag: %1").arg(tag);
}
return output;
}

#if LIBTORRENT_VERSION_NUM < 10100
bool isList(const libt::lazy_entry *entry)
{
return entry && (entry->type() == libt::lazy_entry::list_t);
}

QSet<QString> entryListToSet(const libt::lazy_entry *entry)
{
return entry ? entryListToSetImpl(*entry) : QSet<QString>();
}
#else
bool isList(const libt::bdecode_node &entry)
{
return entry.type() == libt::bdecode_node::list_t;
}

QSet<QString> entryListToSet(const libt::bdecode_node &entry)
{
return entryListToSetImpl(entry);
}
#endif

QString normalizePath(const QString &path)
{
QString tmp = Utils::Fs::fromNativePath(path.trimmed());
Expand Down Expand Up @@ -260,6 +297,7 @@ Session::Session(QObject *parent)
, m_isForceProxyEnabled(BITTORRENT_SESSION_KEY("ForceProxy"), true)
, m_isProxyPeerConnectionsEnabled(BITTORRENT_SESSION_KEY("ProxyPeerConnections"), false)
, m_storedCategories(BITTORRENT_SESSION_KEY("Categories"))
, m_storedTags(BITTORRENT_SESSION_KEY("Tags"))
, m_maxRatioAction(BITTORRENT_SESSION_KEY("MaxRatioAction"), Pause)
, m_defaultSavePath(BITTORRENT_SESSION_KEY("DefaultSavePath"), specialFolderLocation(SpecialFolder::Downloads), normalizePath)
, m_tempPath(BITTORRENT_SESSION_KEY("TempPath"), defaultSavePath() + "temp/", normalizePath)
Expand Down Expand Up @@ -400,6 +438,8 @@ Session::Session(QObject *parent)
m_storedCategories = map_cast(m_categories);
}

m_tags = QSet<QString>::fromList(m_storedTags.value());

m_refreshTimer = new QTimer(this);
m_refreshTimer->setInterval(refreshInterval());
connect(m_refreshTimer, SIGNAL(timeout()), SLOT(refresh()));
Expand Down Expand Up @@ -724,6 +764,47 @@ void Session::setSubcategoriesEnabled(bool value)
emit subcategoriesSupportChanged();
}

QSet<QString> Session::tags() const
{
return m_tags;
}

bool Session::isValidTag(const QString &tag)
{
return (!tag.trimmed().isEmpty() && !tag.contains(','));
}

bool Session::hasTag(const QString &tag) const
{
return m_tags.contains(tag);
}

bool Session::addTag(const QString &tag)
{
if (!isValidTag(tag))
return false;

if (!hasTag(tag)) {
m_tags.insert(tag);
m_storedTags = m_tags.toList();
emit tagAdded(tag);
return true;
}
return false;
}

bool Session::removeTag(const QString &tag)
{
if (m_tags.remove(tag)) {
foreach (TorrentHandle *const torrent, torrents())
torrent->removeTag(tag);
m_storedTags = m_tags.toList();
emit tagRemoved(tag);
return true;
}
return false;
}

bool Session::isAutoTMMDisabledByDefault() const
{
return m_isAutoTMMDisabledByDefault;
Expand Down Expand Up @@ -2997,6 +3078,16 @@ void Session::handleTorrentCategoryChanged(TorrentHandle *const torrent, const Q
emit torrentCategoryChanged(torrent, oldCategory);
}

void Session::handleTorrentTagAdded(TorrentHandle *const torrent, const QString &tag)
{
emit torrentTagAdded(torrent, tag);
}

void Session::handleTorrentTagRemoved(TorrentHandle *const torrent, const QString &tag)
{
emit torrentTagRemoved(torrent, tag);
}

void Session::handleTorrentSavingModeChanged(TorrentHandle * const torrent)
{
emit torrentSavingModeChanged(torrent);
Expand Down Expand Up @@ -3930,6 +4021,10 @@ namespace
if (torrentData.category.isEmpty())
// **************************************************************************************
torrentData.category = QString::fromStdString(fast.dict_find_string_value("qBt-category"));
// auto because the return type depends on the #if above.
const auto tagsEntry = fast.dict_find_list("qBt-tags");
if (isList(tagsEntry))
torrentData.tags = entryListToSet(tagsEntry);
torrentData.name = QString::fromStdString(fast.dict_find_string_value("qBt-name"));
torrentData.hasSeedStatus = fast.dict_find_int_value("qBt-seedStatus");
torrentData.disableTempPath = fast.dict_find_int_value("qBt-tempPathDisabled");
Expand Down
15 changes: 15 additions & 0 deletions src/base/bittorrent/session.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#endif
#include <QNetworkConfigurationManager>
#include <QPointer>
#include <QSet>
#include <QStringList>
#include <QVector>
#include <QWaitCondition>
Expand Down Expand Up @@ -223,6 +224,12 @@ namespace BitTorrent
bool isSubcategoriesEnabled() const;
void setSubcategoriesEnabled(bool value);

static bool isValidTag(const QString &tag);
QSet<QString> tags() const;
bool hasTag(const QString &tag) const;
bool addTag(const QString &tag);
bool removeTag(const QString &tag);

// Torrent Management Mode subsystem (TMM)
//
// Each torrent can be either in Manual mode or in Automatic mode
Expand Down Expand Up @@ -400,6 +407,8 @@ namespace BitTorrent
void handleTorrentShareLimitChanged(TorrentHandle *const torrent);
void handleTorrentSavePathChanged(TorrentHandle *const torrent);
void handleTorrentCategoryChanged(TorrentHandle *const torrent, const QString &oldCategory);
void handleTorrentTagAdded(TorrentHandle *const torrent, const QString &tag);
void handleTorrentTagRemoved(TorrentHandle *const torrent, const QString &tag);
void handleTorrentSavingModeChanged(TorrentHandle *const torrent);
void handleTorrentMetadataReceived(TorrentHandle *const torrent);
void handleTorrentPaused(TorrentHandle *const torrent);
Expand Down Expand Up @@ -431,6 +440,8 @@ namespace BitTorrent
void torrentFinishedChecking(BitTorrent::TorrentHandle *const torrent);
void torrentSavePathChanged(BitTorrent::TorrentHandle *const torrent);
void torrentCategoryChanged(BitTorrent::TorrentHandle *const torrent, const QString &oldCategory);
void torrentTagAdded(TorrentHandle *const torrent, const QString &tag);
void torrentTagRemoved(TorrentHandle *const torrent, const QString &tag);
void torrentSavingModeChanged(BitTorrent::TorrentHandle *const torrent);
void allTorrentsFinished();
void metadataLoaded(const BitTorrent::TorrentInfo &info);
Expand All @@ -452,6 +463,8 @@ namespace BitTorrent
void categoryAdded(const QString &categoryName);
void categoryRemoved(const QString &categoryName);
void subcategoriesSupportChanged();
void tagAdded(const QString &tag);
void tagRemoved(const QString &tag);

private slots:
void configureDeferred();
Expand Down Expand Up @@ -606,6 +619,7 @@ namespace BitTorrent
CachedSettingValue<bool> m_isForceProxyEnabled;
CachedSettingValue<bool> m_isProxyPeerConnectionsEnabled;
CachedSettingValue<QVariantMap> m_storedCategories;
CachedSettingValue<QStringList> m_storedTags;
CachedSettingValue<int> m_maxRatioAction;
CachedSettingValue<QString> m_defaultSavePath;
CachedSettingValue<QString> m_tempPath;
Expand Down Expand Up @@ -650,6 +664,7 @@ namespace BitTorrent
QHash<QString, AddTorrentParams> m_downloadedTorrents;
TorrentStatusReport m_torrentStatusReport;
QStringMap m_categories;
QSet<QString> m_tags;

#if LIBTORRENT_VERSION_NUM < 10100
QMutex m_alertsMutex;
Expand Down
60 changes: 60 additions & 0 deletions src/base/bittorrent/torrenthandle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ const QString QB_EXT {".!qB"};
namespace libt = libtorrent;
using namespace BitTorrent;

namespace
{
using ListType = libt::entry::list_type;

ListType setToEntryList(const QSet<QString> &input)
{
ListType entryList;
foreach (const QString &setValue, input)
entryList.emplace_back(setValue.toStdString());
return entryList;
}
}

// AddTorrentData

AddTorrentData::AddTorrentData()
Expand All @@ -89,6 +102,7 @@ AddTorrentData::AddTorrentData(const AddTorrentParams &params)
: resumed(false)
, name(params.name)
, category(params.category)
, tags(params.tags)
, savePath(params.savePath)
, disableTempPath(params.disableTempPath)
, sequential(params.sequential)
Expand Down Expand Up @@ -213,6 +227,7 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle
, m_name(data.name)
, m_savePath(Utils::Fs::toNativePath(data.savePath))
, m_category(data.category)
, m_tags(data.tags)
, m_hasSeedStatus(data.hasSeedStatus)
, m_ratioLimit(data.ratioLimit)
, m_seedingTimeLimit(data.seedingTimeLimit)
Expand Down Expand Up @@ -578,6 +593,50 @@ bool TorrentHandle::belongsToCategory(const QString &category) const
return false;
}

QSet<QString> TorrentHandle::tags() const
{
return m_tags;
}

bool TorrentHandle::hasTag(const QString &tag) const
{
return m_tags.contains(tag);
}

bool TorrentHandle::addTag(const QString &tag)
{
if (!Session::isValidTag(tag))
return false;

if (!hasTag(tag)) {
if (!m_session->hasTag(tag))
if (!m_session->addTag(tag))
return false;
m_tags.insert(tag);
m_session->handleTorrentTagAdded(this, tag);
m_needSaveResumeData = true;
return true;
}
return false;
}

bool TorrentHandle::removeTag(const QString &tag)
{
if (m_tags.remove(tag)) {
m_session->handleTorrentTagRemoved(this, tag);
m_needSaveResumeData = true;
return true;
}
return false;
}

void TorrentHandle::removeAllTags()
{
// QT automatically copies the container in foreach, so it's safe to mutate it.
foreach (const QString &tag, m_tags)
removeTag(tag);
}

QDateTime TorrentHandle::addedTime() const
{
return QDateTime::fromTime_t(m_nativeStatus.added_time);
Expand Down Expand Up @@ -1617,6 +1676,7 @@ void TorrentHandle::handleSaveResumeDataAlert(libtorrent::save_resume_data_alert
resumeData["qBt-ratioLimit"] = QString::number(m_ratioLimit).toStdString();
resumeData["qBt-seedingTimeLimit"] = QString::number(m_seedingTimeLimit).toStdString();
resumeData["qBt-category"] = m_category.toStdString();
resumeData["qBt-tags"] = setToEntryList(m_tags);
resumeData["qBt-name"] = m_name.toStdString();
resumeData["qBt-seedStatus"] = m_hasSeedStatus;
resumeData["qBt-tempPathDisabled"] = m_tempPathDisabled;
Expand Down
15 changes: 12 additions & 3 deletions src/base/bittorrent/torrenthandle.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@
#ifndef BITTORRENT_TORRENTHANDLE_H
#define BITTORRENT_TORRENTHANDLE_H

#include <QObject>
#include <QString>
#include <QDateTime>
#include <QHash>
#include <QObject>
#include <QQueue>
#include <QSet>
#include <QString>
#include <QVector>
#include <QHash>

#include <libtorrent/torrent_handle.hpp>
#include <libtorrent/version.hpp>
Expand Down Expand Up @@ -93,6 +94,7 @@ namespace BitTorrent
// for both new and resumed torrents
QString name;
QString category;
QSet<QString> tags;
QString savePath;
bool disableTempPath;
bool sequential;
Expand Down Expand Up @@ -248,6 +250,12 @@ namespace BitTorrent
bool belongsToCategory(const QString &category) const;
bool setCategory(const QString &category);

QSet<QString> tags() const;
bool hasTag(const QString &tag) const;
bool addTag(const QString &tag);
bool removeTag(const QString &tag);
void removeAllTags();

bool hasRootFolder() const;

int filesCount() const;
Expand Down Expand Up @@ -445,6 +453,7 @@ namespace BitTorrent
QString m_name;
QString m_savePath;
QString m_category;
QSet<QString> m_tags;
bool m_hasSeedStatus;
qreal m_ratioLimit;
int m_seedingTimeLimit;
Expand Down
20 changes: 20 additions & 0 deletions src/base/preferences.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,16 @@ void Preferences::setConfirmTorrentRecheck(bool enabled)
setValue("Preferences/Advanced/confirmTorrentRecheck", enabled);
}

bool Preferences::confirmRemoveAllTags() const
{
return value("Preferences/Advanced/confirmRemoveAllTags", true).toBool();
}

void Preferences::setConfirmRemoveAllTags(bool enabled)
{
setValue("Preferences/Advanced/confirmRemoveAllTags", enabled);
}

TrayIcon::Style Preferences::trayIconStyle() const
{
return TrayIcon::Style(value("Preferences/Advanced/TrayIconStyle", TrayIcon::NORMAL).toInt());
Expand Down Expand Up @@ -1327,6 +1337,16 @@ void Preferences::setCategoryFilterState(const bool checked)
setValue("TransferListFilters/CategoryFilterState", checked);
}

bool Preferences::getTagFilterState() const
{
return value("TransferListFilters/TagFilterState", true).toBool();
}

void Preferences::setTagFilterState(const bool checked)
{
setValue("TransferListFilters/TagFilterState", checked);
}

bool Preferences::getTrackerFilterState() const
{
return value("TransferListFilters/trackerFilterState", true).toBool();
Expand Down
Loading

0 comments on commit 467e516

Please sign in to comment.