From a1c78a0455e4787e1fe95b95b767dd1c2667982e Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Mon, 20 Nov 2023 20:37:17 +0300 Subject: [PATCH] Follow the parent category options PR #19957. Closes #19941. --- src/base/CMakeLists.txt | 2 + src/base/bittorrent/categoryoptions.cpp | 8 +- src/base/bittorrent/categoryoptions.h | 10 +-- src/base/bittorrent/downloadpathoption.cpp | 34 ++++++++ src/base/bittorrent/downloadpathoption.h | 42 ++++++++++ src/base/bittorrent/session.h | 4 + src/base/bittorrent/sessionimpl.cpp | 91 ++++++++++++++++++---- src/base/bittorrent/sessionimpl.h | 3 + src/gui/torrentcategorydialog.cpp | 12 +-- 9 files changed, 170 insertions(+), 36 deletions(-) create mode 100644 src/base/bittorrent/downloadpathoption.cpp create mode 100644 src/base/bittorrent/downloadpathoption.h diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 0b517c7fb..c173339d0 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -14,6 +14,7 @@ add_library(qbt_base STATIC bittorrent/common.h bittorrent/customstorage.h bittorrent/dbresumedatastorage.h + bittorrent/downloadpathoption.h bittorrent/downloadpriority.h bittorrent/extensiondata.h bittorrent/filesearcher.h @@ -118,6 +119,7 @@ add_library(qbt_base STATIC bittorrent/categoryoptions.cpp bittorrent/customstorage.cpp bittorrent/dbresumedatastorage.cpp + bittorrent/downloadpathoption.cpp bittorrent/downloadpriority.cpp bittorrent/filesearcher.cpp bittorrent/filterparserthread.cpp diff --git a/src/base/bittorrent/categoryoptions.cpp b/src/base/bittorrent/categoryoptions.cpp index d4f250e79..fec609ff7 100644 --- a/src/base/bittorrent/categoryoptions.cpp +++ b/src/base/bittorrent/categoryoptions.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2021 Vladimir Golovnev + * Copyright (C) 2021-2023 Vladimir Golovnev * * This program is free software; you can redistribute it and/or * 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) { return ((left.savePath == right.savePath) diff --git a/src/base/bittorrent/categoryoptions.h b/src/base/bittorrent/categoryoptions.h index 6f834ea48..cbf8fedce 100644 --- a/src/base/bittorrent/categoryoptions.h +++ b/src/base/bittorrent/categoryoptions.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2021 Vladimir Golovnev + * Copyright (C) 2021-2023 Vladimir Golovnev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -33,6 +33,7 @@ #include #include "base/path.h" +#include "downloadpathoption.h" class QJsonObject; @@ -40,12 +41,6 @@ namespace BitTorrent { struct CategoryOptions { - struct DownloadPathOption - { - bool enabled; - Path path; - }; - Path savePath; std::optional downloadPath; @@ -53,6 +48,5 @@ namespace BitTorrent QJsonObject toJSON() const; }; - bool operator==(const CategoryOptions::DownloadPathOption &left, const CategoryOptions::DownloadPathOption &right); bool operator==(const CategoryOptions &left, const CategoryOptions &right); } diff --git a/src/base/bittorrent/downloadpathoption.cpp b/src/base/bittorrent/downloadpathoption.cpp new file mode 100644 index 000000000..4842a1a4d --- /dev/null +++ b/src/base/bittorrent/downloadpathoption.cpp @@ -0,0 +1,34 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2021-2023 Vladimir Golovnev + * + * 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)); +} diff --git a/src/base/bittorrent/downloadpathoption.h b/src/base/bittorrent/downloadpathoption.h new file mode 100644 index 000000000..dba1b296d --- /dev/null +++ b/src/base/bittorrent/downloadpathoption.h @@ -0,0 +1,42 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2021-2023 Vladimir Golovnev + * + * 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); +} diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index 6dd3b6c22..b84085b48 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -155,13 +155,17 @@ namespace BitTorrent virtual void setDownloadPathEnabled(bool enabled) = 0; 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 static QStringList expandCategory(const QString &category); virtual QStringList categories() 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 CategoryOptions &options) 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 editCategory(const QString &name, const CategoryOptions &options) = 0; virtual bool removeCategory(const QString &name) = 0; diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index 4f6e0d467..1ad0b4b60 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -355,6 +355,24 @@ bool Session::isValidCategoryName(const QString &name) 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) { 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 { - 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()) return basePath; - Path path = m_categories.value(categoryName).savePath; - if (path.isEmpty()) // use implicit save path - path = Utils::Fs::toValidPath(categoryName); + Path path = options.savePath; + 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); + } + } return (path.isAbsolute() ? path : (basePath / path)); } Path SessionImpl::categoryDownloadPath(const QString &categoryName) const { - const CategoryOptions categoryOptions = m_categories.value(categoryName); - const CategoryOptions::DownloadPathOption downloadPathOption = - categoryOptions.downloadPath.value_or(CategoryOptions::DownloadPathOption {isDownloadPathEnabled(), downloadPath()}); + return categoryDownloadPath(categoryName, categoryOptions(categoryName)); +} + +Path SessionImpl::categoryDownloadPath(const QString &categoryName, const CategoryOptions &options) const +{ + const DownloadPathOption downloadPathOption = resolveCategoryDownloadPathOption(categoryName, options.downloadPath); if (!downloadPathOption.enabled) return {}; - const Path basePath = downloadPath(); if (categoryName.isEmpty()) - return basePath; + return downloadPath(); - const Path path = (!downloadPathOption.path.isEmpty() - ? downloadPathOption.path - : Utils::Fs::toValidPath(categoryName)); // use implicit download path + const bool useSubcategories = isSubcategoriesEnabled(); + const QString name = useSubcategories ? subcategoryName(categoryName) : categoryName; + const Path path = !downloadPathOption.path.isEmpty() + ? downloadPathOption.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 &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) @@ -3163,8 +3224,8 @@ void SessionImpl::setDownloadPath(const Path &path) { const QString &categoryName = it.key(); const CategoryOptions &categoryOptions = it.value(); - const CategoryOptions::DownloadPathOption downloadPathOption = - categoryOptions.downloadPath.value_or(CategoryOptions::DownloadPathOption {isDownloadPathEnabled(), downloadPath()}); + const DownloadPathOption downloadPathOption = + categoryOptions.downloadPath.value_or(DownloadPathOption {isDownloadPathEnabled(), downloadPath()}); if (downloadPathOption.enabled && downloadPathOption.path.isRelative()) affectedCatogories.insert(categoryName); } diff --git a/src/base/bittorrent/sessionimpl.h b/src/base/bittorrent/sessionimpl.h index a0124d7d3..a7807ff27 100644 --- a/src/base/bittorrent/sessionimpl.h +++ b/src/base/bittorrent/sessionimpl.h @@ -142,7 +142,9 @@ namespace BitTorrent QStringList categories() const override; CategoryOptions categoryOptions(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 CategoryOptions &options) const override; bool addCategory(const QString &name, const CategoryOptions &options = {}) override; bool editCategory(const QString &name, const CategoryOptions &options) override; bool removeCategory(const QString &name) override; @@ -574,6 +576,7 @@ namespace BitTorrent void loadCategories(); void storeCategories() const; void upgradeCategories(); + DownloadPathOption resolveCategoryDownloadPathOption(const QString &categoryName, const std::optional &option) const; void saveStatistics() const; void loadStatistics(); diff --git a/src/gui/torrentcategorydialog.cpp b/src/gui/torrentcategorydialog.cpp index f93b08017..a9762d81d 100644 --- a/src/gui/torrentcategorydialog.cpp +++ b/src/gui/torrentcategorydialog.cpp @@ -169,14 +169,13 @@ void TorrentCategoryDialog::setCategoryOptions(const BitTorrent::CategoryOptions void TorrentCategoryDialog::categoryNameChanged(const QString &categoryName) { - const Path categoryPath = Utils::Fs::toValidPath(categoryName); 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 bool useDownloadPath = (index == 1) || ((index == 0) && BitTorrent::Session::instance()->isDownloadPathEnabled()); + const bool useDownloadPath = (index == 1) || ((index == 0) && btSession->isDownloadPathEnabled()); 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()); } @@ -190,8 +189,9 @@ void TorrentCategoryDialog::useDownloadPathChanged(const int index) m_ui->comboDownloadPath->setEnabled(index == 1); m_ui->comboDownloadPath->setSelectedPath((index == 1) ? m_lastEnteredDownloadPath : Path()); + const auto *btSession = BitTorrent::Session::instance(); 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) && BitTorrent::Session::instance()->isDownloadPathEnabled()); + const bool useDownloadPath = (index == 1) || ((index == 0) && btSession->isDownloadPathEnabled()); + const Path categoryPath = btSession->categoryDownloadPath(categoryName, categoryOptions()); m_ui->comboDownloadPath->setPlaceholder(useDownloadPath ? categoryPath : Path()); }