Follow the parent category options

PR #19957.
Closes #19941.
This commit is contained in:
Vladimir Golovnev 2023-11-20 20:37:17 +03:00 committed by GitHub
parent 32fbacf615
commit a1c78a0455
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 170 additions and 36 deletions

View file

@ -14,6 +14,7 @@ add_library(qbt_base STATIC
bittorrent/common.h bittorrent/common.h
bittorrent/customstorage.h bittorrent/customstorage.h
bittorrent/dbresumedatastorage.h bittorrent/dbresumedatastorage.h
bittorrent/downloadpathoption.h
bittorrent/downloadpriority.h bittorrent/downloadpriority.h
bittorrent/extensiondata.h bittorrent/extensiondata.h
bittorrent/filesearcher.h bittorrent/filesearcher.h
@ -118,6 +119,7 @@ add_library(qbt_base STATIC
bittorrent/categoryoptions.cpp bittorrent/categoryoptions.cpp
bittorrent/customstorage.cpp bittorrent/customstorage.cpp
bittorrent/dbresumedatastorage.cpp bittorrent/dbresumedatastorage.cpp
bittorrent/downloadpathoption.cpp
bittorrent/downloadpriority.cpp bittorrent/downloadpriority.cpp
bittorrent/filesearcher.cpp bittorrent/filesearcher.cpp
bittorrent/filterparserthread.cpp bittorrent/filterparserthread.cpp

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2021-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
@ -67,12 +67,6 @@ QJsonObject BitTorrent::CategoryOptions::toJSON() const
}; };
} }
bool BitTorrent::operator==(const BitTorrent::CategoryOptions::DownloadPathOption &left, const BitTorrent::CategoryOptions::DownloadPathOption &right)
{
return ((left.enabled == right.enabled)
&& (left.path == right.path));
}
bool BitTorrent::operator==(const BitTorrent::CategoryOptions &left, const BitTorrent::CategoryOptions &right) bool BitTorrent::operator==(const BitTorrent::CategoryOptions &left, const BitTorrent::CategoryOptions &right)
{ {
return ((left.savePath == right.savePath) return ((left.savePath == right.savePath)

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2021-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
@ -33,6 +33,7 @@
#include <QString> #include <QString>
#include "base/path.h" #include "base/path.h"
#include "downloadpathoption.h"
class QJsonObject; class QJsonObject;
@ -40,12 +41,6 @@ namespace BitTorrent
{ {
struct CategoryOptions struct CategoryOptions
{ {
struct DownloadPathOption
{
bool enabled;
Path path;
};
Path savePath; Path savePath;
std::optional<DownloadPathOption> downloadPath; std::optional<DownloadPathOption> downloadPath;
@ -53,6 +48,5 @@ namespace BitTorrent
QJsonObject toJSON() const; QJsonObject toJSON() const;
}; };
bool operator==(const CategoryOptions::DownloadPathOption &left, const CategoryOptions::DownloadPathOption &right);
bool operator==(const CategoryOptions &left, const CategoryOptions &right); bool operator==(const CategoryOptions &left, const CategoryOptions &right);
} }

View file

@ -0,0 +1,34 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021-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 "downloadpathoption.h"
bool BitTorrent::operator==(const DownloadPathOption &left, const DownloadPathOption &right)
{
return ((left.enabled == right.enabled) && (left.path == right.path));
}

View file

@ -0,0 +1,42 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021-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 "base/path.h"
namespace BitTorrent
{
struct DownloadPathOption
{
bool enabled = false;
Path path;
};
bool operator==(const DownloadPathOption &left, const DownloadPathOption &right);
}

View file

@ -155,13 +155,17 @@ namespace BitTorrent
virtual void setDownloadPathEnabled(bool enabled) = 0; virtual void setDownloadPathEnabled(bool enabled) = 0;
static bool isValidCategoryName(const QString &name); static bool isValidCategoryName(const QString &name);
static QString subcategoryName(const QString &category);
static QString parentCategoryName(const QString &category);
// returns category itself and all top level categories // returns category itself and all top level categories
static QStringList expandCategory(const QString &category); static QStringList expandCategory(const QString &category);
virtual QStringList categories() const = 0; virtual QStringList categories() const = 0;
virtual CategoryOptions categoryOptions(const QString &categoryName) const = 0; virtual CategoryOptions categoryOptions(const QString &categoryName) const = 0;
virtual Path categorySavePath(const QString &categoryName) const = 0; virtual Path categorySavePath(const QString &categoryName) const = 0;
virtual Path categorySavePath(const QString &categoryName, const CategoryOptions &options) const = 0;
virtual Path categoryDownloadPath(const QString &categoryName) const = 0; virtual Path categoryDownloadPath(const QString &categoryName) const = 0;
virtual Path categoryDownloadPath(const QString &categoryName, const CategoryOptions &options) const = 0;
virtual bool addCategory(const QString &name, const CategoryOptions &options = {}) = 0; virtual bool addCategory(const QString &name, const CategoryOptions &options = {}) = 0;
virtual bool editCategory(const QString &name, const CategoryOptions &options) = 0; virtual bool editCategory(const QString &name, const CategoryOptions &options) = 0;
virtual bool removeCategory(const QString &name) = 0; virtual bool removeCategory(const QString &name) = 0;

View file

@ -355,6 +355,24 @@ bool Session::isValidCategoryName(const QString &name)
return (name.isEmpty() || (name.indexOf(re) == 0)); return (name.isEmpty() || (name.indexOf(re) == 0));
} }
QString Session::subcategoryName(const QString &category)
{
const int sepIndex = category.lastIndexOf(u'/');
if (sepIndex >= 0)
return category.mid(sepIndex + 1);
return category;
}
QString Session::parentCategoryName(const QString &category)
{
const int sepIndex = category.lastIndexOf(u'/');
if (sepIndex >= 0)
return category.left(sepIndex);
return {};
}
bool Session::isValidTag(const QString &tag) bool Session::isValidTag(const QString &tag)
{ {
return (!tag.trimmed().isEmpty() && !tag.contains(u',')); return (!tag.trimmed().isEmpty() && !tag.contains(u','));
@ -782,34 +800,77 @@ CategoryOptions SessionImpl::categoryOptions(const QString &categoryName) const
Path SessionImpl::categorySavePath(const QString &categoryName) const Path SessionImpl::categorySavePath(const QString &categoryName) const
{ {
const Path basePath = savePath(); return categorySavePath(categoryName, categoryOptions(categoryName));
}
Path SessionImpl::categorySavePath(const QString &categoryName, const CategoryOptions &options) const
{
Path basePath = savePath();
if (categoryName.isEmpty()) if (categoryName.isEmpty())
return basePath; return basePath;
Path path = m_categories.value(categoryName).savePath; Path path = options.savePath;
if (path.isEmpty()) // use implicit save path if (path.isEmpty())
{
// use implicit save path
if (isSubcategoriesEnabled())
{
path = Utils::Fs::toValidPath(subcategoryName(categoryName));
basePath = categorySavePath(parentCategoryName(categoryName));
}
else
{
path = Utils::Fs::toValidPath(categoryName); path = Utils::Fs::toValidPath(categoryName);
}
}
return (path.isAbsolute() ? path : (basePath / path)); return (path.isAbsolute() ? path : (basePath / path));
} }
Path SessionImpl::categoryDownloadPath(const QString &categoryName) const Path SessionImpl::categoryDownloadPath(const QString &categoryName) const
{ {
const CategoryOptions categoryOptions = m_categories.value(categoryName); return categoryDownloadPath(categoryName, categoryOptions(categoryName));
const CategoryOptions::DownloadPathOption downloadPathOption = }
categoryOptions.downloadPath.value_or(CategoryOptions::DownloadPathOption {isDownloadPathEnabled(), downloadPath()});
Path SessionImpl::categoryDownloadPath(const QString &categoryName, const CategoryOptions &options) const
{
const DownloadPathOption downloadPathOption = resolveCategoryDownloadPathOption(categoryName, options.downloadPath);
if (!downloadPathOption.enabled) if (!downloadPathOption.enabled)
return {}; return {};
const Path basePath = downloadPath();
if (categoryName.isEmpty()) if (categoryName.isEmpty())
return basePath; return downloadPath();
const Path path = (!downloadPathOption.path.isEmpty() const bool useSubcategories = isSubcategoriesEnabled();
const QString name = useSubcategories ? subcategoryName(categoryName) : categoryName;
const Path path = !downloadPathOption.path.isEmpty()
? downloadPathOption.path ? downloadPathOption.path
: Utils::Fs::toValidPath(categoryName)); // use implicit download path : Utils::Fs::toValidPath(name); // use implicit download path
return (path.isAbsolute() ? path : (basePath / path)); if (path.isAbsolute())
return path;
const QString parentName = useSubcategories ? parentCategoryName(categoryName) : QString();
CategoryOptions parentOptions = categoryOptions(parentName);
// Even if download path of parent category is disabled (directly or by inheritance)
// we need to construct the one as if it would be enabled.
if (!parentOptions.downloadPath || !parentOptions.downloadPath->enabled)
parentOptions.downloadPath = {true, {}};
const Path parentDownloadPath = categoryDownloadPath(parentName, parentOptions);
const Path basePath = parentDownloadPath.isEmpty() ? downloadPath() : parentDownloadPath;
return (basePath / path);
}
DownloadPathOption SessionImpl::resolveCategoryDownloadPathOption(const QString &categoryName, const std::optional<DownloadPathOption> &option) const
{
if (categoryName.isEmpty())
return {isDownloadPathEnabled(), Path()};
if (option.has_value())
return *option;
const QString parentName = isSubcategoriesEnabled() ? parentCategoryName(categoryName) : QString();
return resolveCategoryDownloadPathOption(parentName, categoryOptions(parentName).downloadPath);
} }
bool SessionImpl::addCategory(const QString &name, const CategoryOptions &options) bool SessionImpl::addCategory(const QString &name, const CategoryOptions &options)
@ -3163,8 +3224,8 @@ void SessionImpl::setDownloadPath(const Path &path)
{ {
const QString &categoryName = it.key(); const QString &categoryName = it.key();
const CategoryOptions &categoryOptions = it.value(); const CategoryOptions &categoryOptions = it.value();
const CategoryOptions::DownloadPathOption downloadPathOption = const DownloadPathOption downloadPathOption =
categoryOptions.downloadPath.value_or(CategoryOptions::DownloadPathOption {isDownloadPathEnabled(), downloadPath()}); categoryOptions.downloadPath.value_or(DownloadPathOption {isDownloadPathEnabled(), downloadPath()});
if (downloadPathOption.enabled && downloadPathOption.path.isRelative()) if (downloadPathOption.enabled && downloadPathOption.path.isRelative())
affectedCatogories.insert(categoryName); affectedCatogories.insert(categoryName);
} }

View file

@ -142,7 +142,9 @@ namespace BitTorrent
QStringList categories() const override; QStringList categories() const override;
CategoryOptions categoryOptions(const QString &categoryName) const override; CategoryOptions categoryOptions(const QString &categoryName) const override;
Path categorySavePath(const QString &categoryName) const override; Path categorySavePath(const QString &categoryName) const override;
Path categorySavePath(const QString &categoryName, const CategoryOptions &options) const override;
Path categoryDownloadPath(const QString &categoryName) const override; Path categoryDownloadPath(const QString &categoryName) const override;
Path categoryDownloadPath(const QString &categoryName, const CategoryOptions &options) const override;
bool addCategory(const QString &name, const CategoryOptions &options = {}) override; bool addCategory(const QString &name, const CategoryOptions &options = {}) override;
bool editCategory(const QString &name, const CategoryOptions &options) override; bool editCategory(const QString &name, const CategoryOptions &options) override;
bool removeCategory(const QString &name) override; bool removeCategory(const QString &name) override;
@ -574,6 +576,7 @@ namespace BitTorrent
void loadCategories(); void loadCategories();
void storeCategories() const; void storeCategories() const;
void upgradeCategories(); void upgradeCategories();
DownloadPathOption resolveCategoryDownloadPathOption(const QString &categoryName, const std::optional<DownloadPathOption> &option) const;
void saveStatistics() const; void saveStatistics() const;
void loadStatistics(); void loadStatistics();

View file

@ -169,14 +169,13 @@ void TorrentCategoryDialog::setCategoryOptions(const BitTorrent::CategoryOptions
void TorrentCategoryDialog::categoryNameChanged(const QString &categoryName) void TorrentCategoryDialog::categoryNameChanged(const QString &categoryName)
{ {
const Path categoryPath = Utils::Fs::toValidPath(categoryName);
const auto *btSession = BitTorrent::Session::instance(); const auto *btSession = BitTorrent::Session::instance();
m_ui->comboSavePath->setPlaceholder(btSession->savePath() / categoryPath); m_ui->comboSavePath->setPlaceholder(btSession->categorySavePath(categoryName, categoryOptions()));
const int index = m_ui->comboUseDownloadPath->currentIndex(); const int index = m_ui->comboUseDownloadPath->currentIndex();
const bool useDownloadPath = (index == 1) || ((index == 0) && BitTorrent::Session::instance()->isDownloadPathEnabled()); const bool useDownloadPath = (index == 1) || ((index == 0) && btSession->isDownloadPathEnabled());
if (useDownloadPath) if (useDownloadPath)
m_ui->comboDownloadPath->setPlaceholder(btSession->downloadPath() / categoryPath); m_ui->comboDownloadPath->setPlaceholder(btSession->categoryDownloadPath(categoryName, categoryOptions()));
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!categoryName.isEmpty()); m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!categoryName.isEmpty());
} }
@ -190,8 +189,9 @@ void TorrentCategoryDialog::useDownloadPathChanged(const int index)
m_ui->comboDownloadPath->setEnabled(index == 1); m_ui->comboDownloadPath->setEnabled(index == 1);
m_ui->comboDownloadPath->setSelectedPath((index == 1) ? m_lastEnteredDownloadPath : Path()); m_ui->comboDownloadPath->setSelectedPath((index == 1) ? m_lastEnteredDownloadPath : Path());
const auto *btSession = BitTorrent::Session::instance();
const QString categoryName = m_ui->textCategoryName->text(); const QString categoryName = m_ui->textCategoryName->text();
const Path categoryPath = BitTorrent::Session::instance()->downloadPath() / Utils::Fs::toValidPath(categoryName); const bool useDownloadPath = (index == 1) || ((index == 0) && btSession->isDownloadPathEnabled());
const bool useDownloadPath = (index == 1) || ((index == 0) && BitTorrent::Session::instance()->isDownloadPathEnabled()); const Path categoryPath = btSession->categoryDownloadPath(categoryName, categoryOptions());
m_ui->comboDownloadPath->setPlaceholder(useDownloadPath ? categoryPath : Path()); m_ui->comboDownloadPath->setPlaceholder(useDownloadPath ? categoryPath : Path());
} }