diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 6898346ec..2403c2e11 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -110,6 +110,7 @@ add_library(qbt_base STATIC applicationcomponent.cpp asyncfilestorage.cpp bittorrent/abstractfilestorage.cpp + bittorrent/addtorrentparams.cpp bittorrent/bandwidthscheduler.cpp bittorrent/bencoderesumedatastorage.cpp bittorrent/categoryoptions.cpp diff --git a/src/base/base.pri b/src/base/base.pri index 7686090b3..e6fd1ca3b 100644 --- a/src/base/base.pri +++ b/src/base/base.pri @@ -110,6 +110,7 @@ SOURCES += \ $$PWD/applicationcomponent.cpp \ $$PWD/asyncfilestorage.cpp \ $$PWD/bittorrent/abstractfilestorage.cpp \ + $$PWD/bittorrent/addtorrentparams.cpp \ $$PWD/bittorrent/bandwidthscheduler.cpp \ $$PWD/bittorrent/bencoderesumedatastorage.cpp \ $$PWD/bittorrent/categoryoptions.cpp \ diff --git a/src/base/bittorrent/addtorrentparams.cpp b/src/base/bittorrent/addtorrentparams.cpp new file mode 100644 index 000000000..8e0725a6c --- /dev/null +++ b/src/base/bittorrent/addtorrentparams.cpp @@ -0,0 +1,170 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015-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 "addtorrentparams.h" + +#include + +#include +#include +#include + +#include "base/utils/string.h" + +const QString PARAM_CATEGORY = u"category"_qs; +const QString PARAM_TAGS = u"tags"_qs; +const QString PARAM_SAVEPATH = u"save_path"_qs; +const QString PARAM_USEDOWNLOADPATH = u"use_download_path"_qs; +const QString PARAM_DOWNLOADPATH = u"download_path"_qs; +const QString PARAM_OPERATINGMODE = u"operating_mode"_qs; +const QString PARAM_QUEUETOP = u"add_to_top_of_queue"_qs; +const QString PARAM_STOPPED = u"stopped"_qs; +const QString PARAM_SKIPCHECKING = u"skip_checking"_qs; +const QString PARAM_CONTENTLAYOUT = u"content_layout"_qs; +const QString PARAM_AUTOTMM = u"use_auto_tmm"_qs; +const QString PARAM_UPLOADLIMIT = u"upload_limit"_qs; +const QString PARAM_DOWNLOADLIMIT = u"download_limit"_qs; +const QString PARAM_SEEDINGTIMELIMIT = u"seeding_time_limit"_qs; +const QString PARAM_RATIOLIMIT = u"ratio_limit"_qs; + +namespace +{ + TagSet parseTagSet(const QJsonArray &jsonArr) + { + TagSet tags; + for (const QJsonValue &jsonVal : jsonArr) + tags.insert(jsonVal.toString()); + + return tags; + } + + QJsonArray serializeTagSet(const TagSet &tags) + { + QJsonArray arr; + for (const QString &tag : tags) + arr.append(tag); + + return arr; + } + + std::optional getOptionalBool(const QJsonObject &jsonObj, const QString &key) + { + const QJsonValue jsonVal = jsonObj.value(key); + if (jsonVal.isUndefined() || jsonVal.isNull()) + return std::nullopt; + + return jsonVal.toBool(); + } + + template + std::optional getOptionalEnum(const QJsonObject &jsonObj, const QString &key) + { + const QJsonValue jsonVal = jsonObj.value(key); + if (jsonVal.isUndefined() || jsonVal.isNull()) + return std::nullopt; + + return Utils::String::toEnum(jsonVal.toString(), {}); + } + + template + Enum getEnum(const QJsonObject &jsonObj, const QString &key) + { + const QJsonValue jsonVal = jsonObj.value(key); + return Utils::String::toEnum(jsonVal.toString(), {}); + } +} + +bool BitTorrent::operator==(const AddTorrentParams &lhs, const AddTorrentParams &rhs) +{ + return std::tie(lhs.name, lhs.category, lhs.tags, + lhs.savePath, lhs.useDownloadPath, lhs.downloadPath, + lhs.sequential, lhs.firstLastPiecePriority, lhs.addForced, + lhs.addToQueueTop, lhs.addPaused, lhs.stopCondition, + lhs.filePaths, lhs.filePriorities, lhs.skipChecking, + lhs.contentLayout, lhs.useAutoTMM, lhs.uploadLimit, + lhs.downloadLimit, lhs.seedingTimeLimit, lhs.ratioLimit) + == std::tie(rhs.name, rhs.category, rhs.tags, + rhs.savePath, rhs.useDownloadPath, rhs.downloadPath, + rhs.sequential, rhs.firstLastPiecePriority, rhs.addForced, + rhs.addToQueueTop, rhs.addPaused, rhs.stopCondition, + rhs.filePaths, rhs.filePriorities, rhs.skipChecking, + rhs.contentLayout, rhs.useAutoTMM, rhs.uploadLimit, + rhs.downloadLimit, rhs.seedingTimeLimit, rhs.ratioLimit); +} + +BitTorrent::AddTorrentParams BitTorrent::parseAddTorrentParams(const QJsonObject &jsonObj) +{ + AddTorrentParams params; + params.category = jsonObj.value(PARAM_CATEGORY).toString(); + params.tags = parseTagSet(jsonObj.value(PARAM_TAGS).toArray()); + params.savePath = Path(jsonObj.value(PARAM_SAVEPATH).toString()); + params.useDownloadPath = getOptionalBool(jsonObj, PARAM_USEDOWNLOADPATH); + params.downloadPath = Path(jsonObj.value(PARAM_DOWNLOADPATH).toString()); + params.addForced = (getEnum(jsonObj, PARAM_OPERATINGMODE) == BitTorrent::TorrentOperatingMode::Forced); + params.addToQueueTop = getOptionalBool(jsonObj, PARAM_QUEUETOP); + params.addPaused = getOptionalBool(jsonObj, PARAM_STOPPED); + params.skipChecking = jsonObj.value(PARAM_SKIPCHECKING).toBool(); + params.contentLayout = getOptionalEnum(jsonObj, PARAM_CONTENTLAYOUT); + params.useAutoTMM = getOptionalBool(jsonObj, PARAM_AUTOTMM); + params.uploadLimit = jsonObj.value(PARAM_UPLOADLIMIT).toInt(-1); + params.downloadLimit = jsonObj.value(PARAM_DOWNLOADLIMIT).toInt(-1); + params.seedingTimeLimit = jsonObj.value(PARAM_SEEDINGTIMELIMIT).toInt(BitTorrent::Torrent::USE_GLOBAL_SEEDING_TIME); + params.ratioLimit = jsonObj.value(PARAM_RATIOLIMIT).toDouble(BitTorrent::Torrent::USE_GLOBAL_RATIO); + + return params; +} + +QJsonObject BitTorrent::serializeAddTorrentParams(const AddTorrentParams ¶ms) +{ + QJsonObject jsonObj { + {PARAM_CATEGORY, params.category}, + {PARAM_TAGS, serializeTagSet(params.tags)}, + {PARAM_SAVEPATH, params.savePath.data()}, + {PARAM_DOWNLOADPATH, params.downloadPath.data()}, + {PARAM_OPERATINGMODE, Utils::String::fromEnum(params.addForced + ? BitTorrent::TorrentOperatingMode::Forced : BitTorrent::TorrentOperatingMode::AutoManaged)}, + {PARAM_SKIPCHECKING, params.skipChecking}, + {PARAM_UPLOADLIMIT, params.uploadLimit}, + {PARAM_DOWNLOADLIMIT, params.downloadLimit}, + {PARAM_SEEDINGTIMELIMIT, params.seedingTimeLimit}, + {PARAM_RATIOLIMIT, params.ratioLimit} + }; + + if (params.addToQueueTop) + jsonObj[PARAM_QUEUETOP] = *params.addToQueueTop; + if (params.addPaused) + jsonObj[PARAM_STOPPED] = *params.addPaused; + if (params.contentLayout) + jsonObj[PARAM_CONTENTLAYOUT] = Utils::String::fromEnum(*params.contentLayout); + if (params.useAutoTMM) + jsonObj[PARAM_AUTOTMM] = *params.useAutoTMM; + if (params.useDownloadPath) + jsonObj[PARAM_USEDOWNLOADPATH] = *params.useDownloadPath; + + return jsonObj; +} diff --git a/src/base/bittorrent/addtorrentparams.h b/src/base/bittorrent/addtorrentparams.h index 6039df669..66efe527c 100644 --- a/src/base/bittorrent/addtorrentparams.h +++ b/src/base/bittorrent/addtorrentparams.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2015 Vladimir Golovnev + * Copyright (C) 2015-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 @@ -39,6 +39,8 @@ #include "torrent.h" #include "torrentcontentlayout.h" +class QJsonObject; + namespace BitTorrent { enum class DownloadPriority; @@ -67,6 +69,11 @@ namespace BitTorrent int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME; qreal ratioLimit = Torrent::USE_GLOBAL_RATIO; }; + + bool operator==(const AddTorrentParams &lhs, const AddTorrentParams &rhs); + + AddTorrentParams parseAddTorrentParams(const QJsonObject &jsonObj); + QJsonObject serializeAddTorrentParams(const AddTorrentParams ¶ms); } Q_DECLARE_METATYPE(BitTorrent::AddTorrentParams) diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index 663cb29fe..1303c4d58 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2015-2022 Vladimir Golovnev + * Copyright (C) 2015-2023 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -170,6 +170,9 @@ namespace BitTorrent virtual bool useCategoryPathsInManualMode() const = 0; virtual void setUseCategoryPathsInManualMode(bool value) = 0; + virtual Path suggestedSavePath(const QString &categoryName, std::optional useAutoTMM) const = 0; + virtual Path suggestedDownloadPath(const QString &categoryName, std::optional useAutoTMM) const = 0; + static bool isValidTag(const QString &tag); virtual QSet tags() const = 0; virtual bool hasTag(const QString &tag) const = 0; diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index 091224bd2..80ee809ea 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -759,11 +759,13 @@ void SessionImpl::setFinishedTorrentExportDirectory(const Path &path) Path SessionImpl::savePath() const { + // TODO: Make sure it is always non-empty return m_savePath; } Path SessionImpl::downloadPath() const { + // TODO: Make sure it is always non-empty return m_downloadPath; } @@ -943,6 +945,21 @@ void SessionImpl::setUseCategoryPathsInManualMode(const bool value) m_useCategoryPathsInManualMode = value; } +Path SessionImpl::suggestedSavePath(const QString &categoryName, std::optional useAutoTMM) const +{ + const bool useCategoryPaths = useAutoTMM.value_or(!isAutoTMMDisabledByDefault()) || useCategoryPathsInManualMode(); + const auto path = (useCategoryPaths ? categorySavePath(categoryName) : savePath()); + return path; +} + +Path SessionImpl::suggestedDownloadPath(const QString &categoryName, std::optional useAutoTMM) const +{ + const bool useCategoryPaths = useAutoTMM.value_or(!isAutoTMMDisabledByDefault()) || useCategoryPathsInManualMode(); + const auto categoryDownloadPath = this->categoryDownloadPath(categoryName); + const auto path = ((useCategoryPaths && !categoryDownloadPath.isEmpty()) ? categoryDownloadPath : downloadPath()); + return path; +} + QSet SessionImpl::tags() const { return m_tags; @@ -2560,14 +2577,13 @@ LoadTorrentParams SessionImpl::initLoadTorrentParams(const AddTorrentParams &add LoadTorrentParams loadTorrentParams; loadTorrentParams.name = addTorrentParams.name; - loadTorrentParams.useAutoTMM = addTorrentParams.useAutoTMM.value_or(!isAutoTMMDisabledByDefault()); loadTorrentParams.firstLastPiecePriority = addTorrentParams.firstLastPiecePriority; loadTorrentParams.hasFinishedStatus = addTorrentParams.skipChecking; // do not react on 'torrent_finished_alert' when skipping loadTorrentParams.contentLayout = addTorrentParams.contentLayout.value_or(torrentContentLayout()); loadTorrentParams.operatingMode = (addTorrentParams.addForced ? TorrentOperatingMode::Forced : TorrentOperatingMode::AutoManaged); loadTorrentParams.stopped = addTorrentParams.addPaused.value_or(isAddTorrentPaused()); loadTorrentParams.stopCondition = addTorrentParams.stopCondition.value_or(torrentStopCondition()); - loadTorrentParams.addToQueueTop = addTorrentParams.addToQueueTop.value_or(false); + loadTorrentParams.addToQueueTop = addTorrentParams.addToQueueTop.value_or(isAddTorrentToQueueTop()); loadTorrentParams.ratioLimit = addTorrentParams.ratioLimit; loadTorrentParams.seedingTimeLimit = addTorrentParams.seedingTimeLimit; @@ -2577,29 +2593,53 @@ LoadTorrentParams SessionImpl::initLoadTorrentParams(const AddTorrentParams &add else loadTorrentParams.category = category; - if (!loadTorrentParams.useAutoTMM) + const auto defaultSavePath = suggestedSavePath(loadTorrentParams.category, addTorrentParams.useAutoTMM); + const auto defaultDownloadPath = suggestedDownloadPath(loadTorrentParams.category, addTorrentParams.useAutoTMM); + + loadTorrentParams.useAutoTMM = addTorrentParams.useAutoTMM.value_or(!isAutoTMMDisabledByDefault()); + + if (!addTorrentParams.useAutoTMM.has_value()) { - if (addTorrentParams.savePath.isAbsolute()) - { - loadTorrentParams.savePath = addTorrentParams.savePath; - } - else + // Default TMM settings + + if (!loadTorrentParams.useAutoTMM) { - const Path basePath = useCategoryPathsInManualMode() ? categorySavePath(loadTorrentParams.category) : savePath(); - loadTorrentParams.savePath = basePath / addTorrentParams.savePath; + loadTorrentParams.savePath = defaultSavePath; + if (isDownloadPathEnabled()) + loadTorrentParams.downloadPath = (!defaultDownloadPath.isEmpty() ? defaultDownloadPath : downloadPath()); } + } + else + { + // Overridden TMM settings - const bool useDownloadPath = addTorrentParams.useDownloadPath.value_or(isDownloadPathEnabled()); - if (useDownloadPath) + if (!loadTorrentParams.useAutoTMM) { - if (addTorrentParams.downloadPath.isAbsolute()) + if (addTorrentParams.savePath.isAbsolute()) + loadTorrentParams.savePath = addTorrentParams.savePath; + else + loadTorrentParams.savePath = defaultSavePath / addTorrentParams.savePath; + + if (!addTorrentParams.useDownloadPath.has_value()) { - loadTorrentParams.downloadPath = addTorrentParams.downloadPath; + // Default "Download path" settings + + if (isDownloadPathEnabled()) + loadTorrentParams.downloadPath = (!defaultDownloadPath.isEmpty() ? defaultDownloadPath : downloadPath()); } - else + else if (addTorrentParams.useDownloadPath.value()) { - const Path basePath = useCategoryPathsInManualMode() ? categoryDownloadPath(loadTorrentParams.category) : downloadPath(); - loadTorrentParams.downloadPath = basePath / addTorrentParams.downloadPath; + // Overridden "Download path" settings + + if (addTorrentParams.downloadPath.isAbsolute()) + { + loadTorrentParams.downloadPath = addTorrentParams.downloadPath; + } + else + { + const Path basePath = (!defaultDownloadPath.isEmpty() ? defaultDownloadPath : downloadPath()); + loadTorrentParams.downloadPath = basePath / addTorrentParams.downloadPath; + } } } } diff --git a/src/base/bittorrent/sessionimpl.h b/src/base/bittorrent/sessionimpl.h index 449a7f44e..b2b93cb8d 100644 --- a/src/base/bittorrent/sessionimpl.h +++ b/src/base/bittorrent/sessionimpl.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2015-2022 Vladimir Golovnev + * Copyright (C) 2015-2023 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -160,6 +160,9 @@ namespace BitTorrent bool useCategoryPathsInManualMode() const override; void setUseCategoryPathsInManualMode(bool value) override; + Path suggestedSavePath(const QString &categoryName, std::optional useAutoTMM) const override; + Path suggestedDownloadPath(const QString &categoryName, std::optional useAutoTMM) const override; + QSet tags() const override; bool hasTag(const QString &tag) const override; bool addTag(const QString &tag) override; diff --git a/src/base/rss/rss_autodownloader.cpp b/src/base/rss/rss_autodownloader.cpp index 6aab0504a..582390daf 100644 --- a/src/base/rss/rss_autodownloader.cpp +++ b/src/base/rss/rss_autodownloader.cpp @@ -430,15 +430,8 @@ void AutoDownloader::processJob(const QSharedPointer &job) m_dirty = true; storeDeferred(); - BitTorrent::AddTorrentParams params; - params.savePath = rule.savePath(); - params.category = rule.assignedCategory(); - params.addPaused = rule.addPaused(); - params.contentLayout = rule.torrentContentLayout(); - if (!rule.savePath().isEmpty()) - params.useAutoTMM = false; const auto torrentURL = job->articleData.value(Article::KeyTorrentURL).toString(); - BitTorrent::Session::instance()->addTorrent(torrentURL, params); + BitTorrent::Session::instance()->addTorrent(torrentURL, rule.addTorrentParams()); if (BitTorrent::MagnetUri(torrentURL).isValid()) { diff --git a/src/base/rss/rss_autodownloadrule.cpp b/src/base/rss/rss_autodownloadrule.cpp index e6db0aede..476f0fcb3 100644 --- a/src/base/rss/rss_autodownloadrule.cpp +++ b/src/base/rss/rss_autodownloadrule.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2017 Vladimir Golovnev + * Copyright (C) 2017-2023 Vladimir Golovnev * Copyright (C) 2010 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -101,22 +101,24 @@ namespace } } -const QString Str_Name = u"name"_qs; -const QString Str_Enabled = u"enabled"_qs; -const QString Str_UseRegex = u"useRegex"_qs; -const QString Str_MustContain = u"mustContain"_qs; -const QString Str_MustNotContain = u"mustNotContain"_qs; -const QString Str_EpisodeFilter = u"episodeFilter"_qs; -const QString Str_AffectedFeeds = u"affectedFeeds"_qs; -const QString Str_SavePath = u"savePath"_qs; -const QString Str_AssignedCategory = u"assignedCategory"_qs; -const QString Str_LastMatch = u"lastMatch"_qs; -const QString Str_IgnoreDays = u"ignoreDays"_qs; -const QString Str_AddPaused = u"addPaused"_qs; -const QString Str_CreateSubfolder = u"createSubfolder"_qs; -const QString Str_ContentLayout = u"torrentContentLayout"_qs; -const QString Str_SmartFilter = u"smartFilter"_qs; -const QString Str_PreviouslyMatched = u"previouslyMatchedEpisodes"_qs; +const QString S_NAME = u"name"_qs; +const QString S_ENABLED = u"enabled"_qs; +const QString S_USE_REGEX = u"useRegex"_qs; +const QString S_MUST_CONTAIN = u"mustContain"_qs; +const QString S_MUST_NOT_CONTAIN = u"mustNotContain"_qs; +const QString S_EPISODE_FILTER = u"episodeFilter"_qs; +const QString S_AFFECTED_FEEDS = u"affectedFeeds"_qs; +const QString S_LAST_MATCH = u"lastMatch"_qs; +const QString S_IGNORE_DAYS = u"ignoreDays"_qs; +const QString S_SMART_FILTER = u"smartFilter"_qs; +const QString S_PREVIOUSLY_MATCHED = u"previouslyMatchedEpisodes"_qs; + +const QString S_SAVE_PATH = u"savePath"_qs; +const QString S_ASSIGNED_CATEGORY = u"assignedCategory"_qs; +const QString S_ADD_PAUSED = u"addPaused"_qs; +const QString S_CONTENT_LAYOUT = u"torrentContentLayout"_qs; + +const QString S_TORRENT_PARAMS = u"torrentParams"_qs; namespace RSS { @@ -133,10 +135,7 @@ namespace RSS int ignoreDays = 0; QDateTime lastMatch; - Path savePath; - QString category; - std::optional addPaused; - std::optional contentLayout; + BitTorrent::AddTorrentParams addTorrentParams; bool smartFilter = false; QStringList previouslyMatchedEpisodes; @@ -155,11 +154,8 @@ namespace RSS && (left.useRegex == right.useRegex) && (left.ignoreDays == right.ignoreDays) && (left.lastMatch == right.lastMatch) - && (left.savePath == right.savePath) - && (left.category == right.category) - && (left.addPaused == right.addPaused) - && (left.contentLayout == right.contentLayout) - && (left.smartFilter == right.smartFilter); + && (left.smartFilter == right.smartFilter) + && (left.addTorrentParams == right.addTorrentParams); } }; @@ -458,64 +454,45 @@ AutoDownloadRule &AutoDownloadRule::operator=(const AutoDownloadRule &other) QJsonObject AutoDownloadRule::toJsonObject() const { - return {{Str_Enabled, isEnabled()} - , {Str_UseRegex, useRegex()} - , {Str_MustContain, mustContain()} - , {Str_MustNotContain, mustNotContain()} - , {Str_EpisodeFilter, episodeFilter()} - , {Str_AffectedFeeds, QJsonArray::fromStringList(feedURLs())} - , {Str_SavePath, savePath().toString()} - , {Str_AssignedCategory, assignedCategory()} - , {Str_LastMatch, lastMatch().toString(Qt::RFC2822Date)} - , {Str_IgnoreDays, ignoreDays()} - , {Str_AddPaused, toJsonValue(addPaused())} - , {Str_ContentLayout, contentLayoutToJsonValue(torrentContentLayout())} - , {Str_SmartFilter, useSmartFilter()} - , {Str_PreviouslyMatched, QJsonArray::fromStringList(previouslyMatchedEpisodes())}}; + const BitTorrent::AddTorrentParams &addTorrentParams = m_dataPtr->addTorrentParams; + + return {{S_ENABLED, isEnabled()} + , {S_USE_REGEX, useRegex()} + , {S_MUST_CONTAIN, mustContain()} + , {S_MUST_NOT_CONTAIN, mustNotContain()} + , {S_EPISODE_FILTER, episodeFilter()} + , {S_AFFECTED_FEEDS, QJsonArray::fromStringList(feedURLs())} + , {S_LAST_MATCH, lastMatch().toString(Qt::RFC2822Date)} + , {S_IGNORE_DAYS, ignoreDays()} + , {S_SMART_FILTER, useSmartFilter()} + , {S_PREVIOUSLY_MATCHED, QJsonArray::fromStringList(previouslyMatchedEpisodes())} + + // TODO: The following code is deprecated. Replace with the commented one after several releases in 4.6.x. + // === BEGIN DEPRECATED CODE === // + , {S_ADD_PAUSED, toJsonValue(addTorrentParams.addPaused)} + , {S_CONTENT_LAYOUT, contentLayoutToJsonValue(addTorrentParams.contentLayout)} + , {S_SAVE_PATH, addTorrentParams.savePath.toString()} + , {S_ASSIGNED_CATEGORY, addTorrentParams.category} + // === END DEPRECATED CODE === // + + , {S_TORRENT_PARAMS, BitTorrent::serializeAddTorrentParams(addTorrentParams)} + }; } AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, const QString &name) { - AutoDownloadRule rule(name.isEmpty() ? jsonObj.value(Str_Name).toString() : name); - - rule.setUseRegex(jsonObj.value(Str_UseRegex).toBool(false)); - rule.setMustContain(jsonObj.value(Str_MustContain).toString()); - rule.setMustNotContain(jsonObj.value(Str_MustNotContain).toString()); - rule.setEpisodeFilter(jsonObj.value(Str_EpisodeFilter).toString()); - rule.setEnabled(jsonObj.value(Str_Enabled).toBool(true)); - rule.setSavePath(Path(jsonObj.value(Str_SavePath).toString())); - rule.setCategory(jsonObj.value(Str_AssignedCategory).toString()); - rule.setAddPaused(toOptionalBool(jsonObj.value(Str_AddPaused))); - - // TODO: The following code is deprecated. Replace with the commented one after several releases in 4.4.x. - // === BEGIN DEPRECATED CODE === // - if (jsonObj.contains(Str_ContentLayout)) - { - rule.setTorrentContentLayout(jsonValueToContentLayout(jsonObj.value(Str_ContentLayout))); - } - else - { - const std::optional createSubfolder = toOptionalBool(jsonObj.value(Str_CreateSubfolder)); - std::optional contentLayout; - if (createSubfolder.has_value()) - { - contentLayout = (*createSubfolder - ? BitTorrent::TorrentContentLayout::Original - : BitTorrent::TorrentContentLayout::NoSubfolder); - } - - rule.setTorrentContentLayout(contentLayout); - } - // === END DEPRECATED CODE === // - // === BEGIN REPLACEMENT CODE === // -// rule.setTorrentContentLayout(jsonValueToContentLayout(jsonObj.value(Str_ContentLayout))); - // === END REPLACEMENT CODE === // + AutoDownloadRule rule {(name.isEmpty() ? jsonObj.value(S_NAME).toString() : name)}; - rule.setLastMatch(QDateTime::fromString(jsonObj.value(Str_LastMatch).toString(), Qt::RFC2822Date)); - rule.setIgnoreDays(jsonObj.value(Str_IgnoreDays).toInt()); - rule.setUseSmartFilter(jsonObj.value(Str_SmartFilter).toBool(false)); + rule.setUseRegex(jsonObj.value(S_USE_REGEX).toBool(false)); + rule.setMustContain(jsonObj.value(S_MUST_CONTAIN).toString()); + rule.setMustNotContain(jsonObj.value(S_MUST_NOT_CONTAIN).toString()); + rule.setEpisodeFilter(jsonObj.value(S_EPISODE_FILTER).toString()); + rule.setEnabled(jsonObj.value(S_ENABLED).toBool(true)); + rule.setLastMatch(QDateTime::fromString(jsonObj.value(S_LAST_MATCH).toString(), Qt::RFC2822Date)); + rule.setIgnoreDays(jsonObj.value(S_IGNORE_DAYS).toInt()); + rule.setUseSmartFilter(jsonObj.value(S_SMART_FILTER).toBool(false)); - const QJsonValue feedsVal = jsonObj.value(Str_AffectedFeeds); + const QJsonValue feedsVal = jsonObj.value(S_AFFECTED_FEEDS); QStringList feedURLs; if (feedsVal.isString()) feedURLs << feedsVal.toString(); @@ -523,7 +500,7 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co feedURLs << urlVal.toString(); rule.setFeedURLs(feedURLs); - const QJsonValue previouslyMatchedVal = jsonObj.value(Str_PreviouslyMatched); + const QJsonValue previouslyMatchedVal = jsonObj.value(S_PREVIOUSLY_MATCHED); QStringList previouslyMatched; if (previouslyMatchedVal.isString()) { @@ -536,20 +513,61 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co } rule.setPreviouslyMatchedEpisodes(previouslyMatched); + // TODO: The following code is deprecated. Replace with the commented one after several releases in 4.6.x. + // === BEGIN DEPRECATED CODE === // + BitTorrent::AddTorrentParams addTorrentParams; + if (auto it = jsonObj.find(S_TORRENT_PARAMS); it != jsonObj.end()) + { + addTorrentParams = BitTorrent::parseAddTorrentParams(it->toObject()); + } + else + { + addTorrentParams.savePath = Path(jsonObj.value(S_SAVE_PATH).toString()); + addTorrentParams.category = jsonObj.value(S_ASSIGNED_CATEGORY).toString(); + addTorrentParams.addPaused = toOptionalBool(jsonObj.value(S_ADD_PAUSED)); + if (!addTorrentParams.savePath.isEmpty()) + addTorrentParams.useAutoTMM = false; + + if (jsonObj.contains(S_CONTENT_LAYOUT)) + { + addTorrentParams.contentLayout = jsonValueToContentLayout(jsonObj.value(S_CONTENT_LAYOUT)); + } + else + { + const std::optional createSubfolder = toOptionalBool(jsonObj.value(u"createSubfolder")); + std::optional contentLayout; + if (createSubfolder.has_value()) + { + contentLayout = (*createSubfolder + ? BitTorrent::TorrentContentLayout::Original + : BitTorrent::TorrentContentLayout::NoSubfolder); + } + + addTorrentParams.contentLayout = contentLayout; + } + } + rule.setAddTorrentParams(addTorrentParams); + // === END DEPRECATED CODE === // + // === BEGIN REPLACEMENT CODE === // + // rule.setAddTorrentParams(BitTorrent::parseAddTorrentParams(jsonObj.value(S_TORRENT_PARAMS).object())); + // === END REPLACEMENT CODE === // + return rule; } QVariantHash AutoDownloadRule::toLegacyDict() const { + const BitTorrent::AddTorrentParams &addTorrentParams = m_dataPtr->addTorrentParams; + return {{u"name"_qs, name()}, {u"must_contain"_qs, mustContain()}, {u"must_not_contain"_qs, mustNotContain()}, - {u"save_path"_qs, savePath().toString()}, + {u"save_path"_qs, addTorrentParams.savePath.toString()}, {u"affected_feeds"_qs, feedURLs()}, {u"enabled"_qs, isEnabled()}, - {u"category_assigned"_qs, assignedCategory()}, + {u"category_assigned"_qs, addTorrentParams.category}, {u"use_regex"_qs, useRegex()}, - {u"add_paused"_qs, toAddPausedLegacy(addPaused())}, + {u"add_paused"_qs, toAddPausedLegacy(addTorrentParams.addPaused)}, {u"episode_filter"_qs, episodeFilter()}, {u"last_match"_qs, lastMatch()}, {u"ignore_days"_qs, ignoreDays()}}; @@ -557,7 +575,14 @@ QVariantHash AutoDownloadRule::toLegacyDict() const AutoDownloadRule AutoDownloadRule::fromLegacyDict(const QVariantHash &dict) { - AutoDownloadRule rule(dict.value(u"name"_qs).toString()); + BitTorrent::AddTorrentParams addTorrentParams; + addTorrentParams.savePath = Path(dict.value(u"save_path"_qs).toString()); + addTorrentParams.category = dict.value(u"category_assigned"_qs).toString(); + addTorrentParams.addPaused = addPausedLegacyToOptionalBool(dict.value(u"add_paused"_qs).toInt()); + if (!addTorrentParams.savePath.isEmpty()) + addTorrentParams.useAutoTMM = false; + + AutoDownloadRule rule {dict.value(u"name"_qs).toString()}; rule.setUseRegex(dict.value(u"use_regex"_qs, false).toBool()); rule.setMustContain(dict.value(u"must_contain"_qs).toString()); @@ -565,11 +590,9 @@ AutoDownloadRule AutoDownloadRule::fromLegacyDict(const QVariantHash &dict) rule.setEpisodeFilter(dict.value(u"episode_filter"_qs).toString()); rule.setFeedURLs(dict.value(u"affected_feeds"_qs).toStringList()); rule.setEnabled(dict.value(u"enabled"_qs, false).toBool()); - rule.setSavePath(Path(dict.value(u"save_path"_qs).toString())); - rule.setCategory(dict.value(u"category_assigned"_qs).toString()); - rule.setAddPaused(addPausedLegacyToOptionalBool(dict.value(u"add_paused"_qs).toInt())); rule.setLastMatch(dict.value(u"last_match"_qs).toDateTime()); rule.setIgnoreDays(dict.value(u"ignore_days"_qs).toInt()); + rule.setAddTorrentParams(addTorrentParams); return rule; } @@ -622,44 +645,14 @@ void AutoDownloadRule::setName(const QString &name) m_dataPtr->name = name; } -Path AutoDownloadRule::savePath() const -{ - return m_dataPtr->savePath; -} - -void AutoDownloadRule::setSavePath(const Path &savePath) -{ - m_dataPtr->savePath = savePath; -} - -std::optional AutoDownloadRule::addPaused() const -{ - return m_dataPtr->addPaused; -} - -void AutoDownloadRule::setAddPaused(const std::optional addPaused) -{ - m_dataPtr->addPaused = addPaused; -} - -std::optional AutoDownloadRule::torrentContentLayout() const -{ - return m_dataPtr->contentLayout; -} - -void AutoDownloadRule::setTorrentContentLayout(const std::optional contentLayout) -{ - m_dataPtr->contentLayout = contentLayout; -} - -QString AutoDownloadRule::assignedCategory() const +BitTorrent::AddTorrentParams AutoDownloadRule::addTorrentParams() const { - return m_dataPtr->category; + return m_dataPtr->addTorrentParams; } -void AutoDownloadRule::setCategory(const QString &category) +void AutoDownloadRule::setAddTorrentParams(BitTorrent::AddTorrentParams addTorrentParams) { - m_dataPtr->category = category; + m_dataPtr->addTorrentParams = std::move(addTorrentParams); } bool AutoDownloadRule::isEnabled() const diff --git a/src/base/rss/rss_autodownloadrule.h b/src/base/rss/rss_autodownloadrule.h index a37d14f91..92050468b 100644 --- a/src/base/rss/rss_autodownloadrule.h +++ b/src/base/rss/rss_autodownloadrule.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2017 Vladimir Golovnev + * Copyright (C) 2017-2023 Vladimir Golovnev * Copyright (C) 2010 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -35,7 +35,7 @@ #include #include "base/global.h" -#include "base/bittorrent/torrentcontentlayout.h" +#include "base/bittorrent/addtorrentparams.h" #include "base/pathfwd.h" class QDateTime; @@ -81,14 +81,8 @@ namespace RSS QStringList previouslyMatchedEpisodes() const; void setPreviouslyMatchedEpisodes(const QStringList &previouslyMatchedEpisodes); - Path savePath() const; - void setSavePath(const Path &savePath); - std::optional addPaused() const; - void setAddPaused(std::optional addPaused); - std::optional torrentContentLayout() const; - void setTorrentContentLayout(std::optional contentLayout); - QString assignedCategory() const; - void setCategory(const QString &category); + BitTorrent::AddTorrentParams addTorrentParams() const; + void setAddTorrentParams(BitTorrent::AddTorrentParams addTorrentParams); bool matches(const QVariantHash &articleData) const; bool accepts(const QVariantHash &articleData); diff --git a/src/base/torrentfileswatcher.cpp b/src/base/torrentfileswatcher.cpp index d47ea5742..37262d165 100644 --- a/src/base/torrentfileswatcher.cpp +++ b/src/base/torrentfileswatcher.cpp @@ -36,10 +36,8 @@ #include #include #include -#include #include #include -#include #include #include #include @@ -70,124 +68,12 @@ const QString CONF_FILE_NAME = u"watched_folders.json"_qs; const QString OPTION_ADDTORRENTPARAMS = u"add_torrent_params"_qs; const QString OPTION_RECURSIVE = u"recursive"_qs; -const QString PARAM_CATEGORY = u"category"_qs; -const QString PARAM_TAGS = u"tags"_qs; -const QString PARAM_SAVEPATH = u"save_path"_qs; -const QString PARAM_USEDOWNLOADPATH = u"use_download_path"_qs; -const QString PARAM_DOWNLOADPATH = u"download_path"_qs; -const QString PARAM_OPERATINGMODE = u"operating_mode"_qs; -const QString PARAM_QUEUETOP = u"add_to_top_of_queue"_qs; -const QString PARAM_STOPPED = u"stopped"_qs; -const QString PARAM_SKIPCHECKING = u"skip_checking"_qs; -const QString PARAM_CONTENTLAYOUT = u"content_layout"_qs; -const QString PARAM_AUTOTMM = u"use_auto_tmm"_qs; -const QString PARAM_UPLOADLIMIT = u"upload_limit"_qs; -const QString PARAM_DOWNLOADLIMIT = u"download_limit"_qs; -const QString PARAM_SEEDINGTIMELIMIT = u"seeding_time_limit"_qs; -const QString PARAM_RATIOLIMIT = u"ratio_limit"_qs; - namespace { - TagSet parseTagSet(const QJsonArray &jsonArr) - { - TagSet tags; - for (const QJsonValue &jsonVal : jsonArr) - tags.insert(jsonVal.toString()); - - return tags; - } - - QJsonArray serializeTagSet(const TagSet &tags) - { - QJsonArray arr; - for (const QString &tag : tags) - arr.append(tag); - - return arr; - } - - std::optional getOptionalBool(const QJsonObject &jsonObj, const QString &key) - { - const QJsonValue jsonVal = jsonObj.value(key); - if (jsonVal.isUndefined() || jsonVal.isNull()) - return std::nullopt; - - return jsonVal.toBool(); - } - - template - std::optional getOptionalEnum(const QJsonObject &jsonObj, const QString &key) - { - const QJsonValue jsonVal = jsonObj.value(key); - if (jsonVal.isUndefined() || jsonVal.isNull()) - return std::nullopt; - - return Utils::String::toEnum(jsonVal.toString(), {}); - } - - template - Enum getEnum(const QJsonObject &jsonObj, const QString &key) - { - const QJsonValue jsonVal = jsonObj.value(key); - return Utils::String::toEnum(jsonVal.toString(), {}); - } - - BitTorrent::AddTorrentParams parseAddTorrentParams(const QJsonObject &jsonObj) - { - BitTorrent::AddTorrentParams params; - params.category = jsonObj.value(PARAM_CATEGORY).toString(); - params.tags = parseTagSet(jsonObj.value(PARAM_TAGS).toArray()); - params.savePath = Path(jsonObj.value(PARAM_SAVEPATH).toString()); - params.useDownloadPath = getOptionalBool(jsonObj, PARAM_USEDOWNLOADPATH); - params.downloadPath = Path(jsonObj.value(PARAM_DOWNLOADPATH).toString()); - params.addForced = (getEnum(jsonObj, PARAM_OPERATINGMODE) == BitTorrent::TorrentOperatingMode::Forced); - params.addToQueueTop = getOptionalBool(jsonObj, PARAM_QUEUETOP); - params.addPaused = getOptionalBool(jsonObj, PARAM_STOPPED); - params.skipChecking = jsonObj.value(PARAM_SKIPCHECKING).toBool(); - params.contentLayout = getOptionalEnum(jsonObj, PARAM_CONTENTLAYOUT); - params.useAutoTMM = getOptionalBool(jsonObj, PARAM_AUTOTMM); - params.uploadLimit = jsonObj.value(PARAM_UPLOADLIMIT).toInt(-1); - params.downloadLimit = jsonObj.value(PARAM_DOWNLOADLIMIT).toInt(-1); - params.seedingTimeLimit = jsonObj.value(PARAM_SEEDINGTIMELIMIT).toInt(BitTorrent::Torrent::USE_GLOBAL_SEEDING_TIME); - params.ratioLimit = jsonObj.value(PARAM_RATIOLIMIT).toDouble(BitTorrent::Torrent::USE_GLOBAL_RATIO); - - return params; - } - - QJsonObject serializeAddTorrentParams(const BitTorrent::AddTorrentParams ¶ms) - { - QJsonObject jsonObj { - {PARAM_CATEGORY, params.category}, - {PARAM_TAGS, serializeTagSet(params.tags)}, - {PARAM_SAVEPATH, params.savePath.data()}, - {PARAM_DOWNLOADPATH, params.downloadPath.data()}, - {PARAM_OPERATINGMODE, Utils::String::fromEnum(params.addForced - ? BitTorrent::TorrentOperatingMode::Forced : BitTorrent::TorrentOperatingMode::AutoManaged)}, - {PARAM_SKIPCHECKING, params.skipChecking}, - {PARAM_UPLOADLIMIT, params.uploadLimit}, - {PARAM_DOWNLOADLIMIT, params.downloadLimit}, - {PARAM_SEEDINGTIMELIMIT, params.seedingTimeLimit}, - {PARAM_RATIOLIMIT, params.ratioLimit} - }; - - if (params.addToQueueTop) - jsonObj[PARAM_QUEUETOP] = *params.addToQueueTop; - if (params.addPaused) - jsonObj[PARAM_STOPPED] = *params.addPaused; - if (params.contentLayout) - jsonObj[PARAM_CONTENTLAYOUT] = Utils::String::fromEnum(*params.contentLayout); - if (params.useAutoTMM) - jsonObj[PARAM_AUTOTMM] = *params.useAutoTMM; - if (params.useDownloadPath) - jsonObj[PARAM_USEDOWNLOADPATH] = *params.useDownloadPath; - - return jsonObj; - } - TorrentFilesWatcher::WatchedFolderOptions parseWatchedFolderOptions(const QJsonObject &jsonObj) { TorrentFilesWatcher::WatchedFolderOptions options; - options.addTorrentParams = parseAddTorrentParams(jsonObj.value(OPTION_ADDTORRENTPARAMS).toObject()); + options.addTorrentParams = BitTorrent::parseAddTorrentParams(jsonObj.value(OPTION_ADDTORRENTPARAMS).toObject()); options.recursive = jsonObj.value(OPTION_RECURSIVE).toBool(); return options; @@ -195,10 +81,8 @@ namespace QJsonObject serializeWatchedFolderOptions(const TorrentFilesWatcher::WatchedFolderOptions &options) { - return { - {OPTION_ADDTORRENTPARAMS, serializeAddTorrentParams(options.addTorrentParams)}, - {OPTION_RECURSIVE, options.recursive} - }; + return {{OPTION_ADDTORRENTPARAMS, BitTorrent::serializeAddTorrentParams(options.addTorrentParams)}, + {OPTION_RECURSIVE, options.recursive}}; } } diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 52657d84a..78ed703da 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -11,6 +11,7 @@ endif() qt_wrap_ui(UI_HEADERS aboutdialog.ui addnewtorrentdialog.ui + addtorrentparamswidget.ui autoexpandabledialog.ui banlistoptionsdialog.ui cookiesdialog.ui @@ -46,6 +47,7 @@ add_library(qbt_gui STATIC # headers aboutdialog.h addnewtorrentdialog.h + addtorrentparamswidget.h advancedsettings.h autoexpandabledialog.h banlistoptionsdialog.h @@ -140,6 +142,7 @@ add_library(qbt_gui STATIC # sources aboutdialog.cpp addnewtorrentdialog.cpp + addtorrentparamswidget.cpp advancedsettings.cpp autoexpandabledialog.cpp banlistoptionsdialog.cpp diff --git a/src/gui/addtorrentparamswidget.cpp b/src/gui/addtorrentparamswidget.cpp new file mode 100644 index 000000000..0d83701d9 --- /dev/null +++ b/src/gui/addtorrentparamswidget.cpp @@ -0,0 +1,392 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 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 "addtorrentparamswidget.h" + +#include + +#include "base/bittorrent/session.h" +#include "base/bittorrent/torrent.h" +#include "base/utils/compare.h" +#include "flowlayout.h" +#include "fspathedit.h" +#include "torrenttagsdialog.h" +#include "ui_addtorrentparamswidget.h" + +namespace +{ + std::optional toOptionalBool(const QVariant &data) + { + if (!data.isValid()) + return std::nullopt; + + Q_ASSERT(data.userType() == QMetaType::Bool); + return data.toBool(); + } +} + +AddTorrentParamsWidget::AddTorrentParamsWidget(BitTorrent::AddTorrentParams addTorrentParams, QWidget *parent) + : QWidget(parent) + , m_ui {new Ui::AddTorrentParamsWidget} + , m_addTorrentParams {std::move(addTorrentParams)} +{ + m_ui->setupUi(this); + + m_ui->savePathEdit->setMode(FileSystemPathEdit::Mode::DirectorySave); + m_ui->savePathEdit->setDialogCaption(tr("Choose save path")); + + m_ui->downloadPathEdit->setMode(FileSystemPathEdit::Mode::DirectorySave); + m_ui->downloadPathEdit->setDialogCaption(tr("Choose save path")); + + m_ui->useDownloadPathComboBox->addItem(tr("Default")); + m_ui->useDownloadPathComboBox->addItem(tr("Yes"), true); + m_ui->useDownloadPathComboBox->addItem(tr("No"), false); + + m_ui->comboTTM->addItem(tr("Default")); + m_ui->comboTTM->addItem(tr("Manual"), false); + m_ui->comboTTM->addItem(tr("Automatic"), true); + + m_ui->contentLayoutComboBox->addItem(tr("Default")); + m_ui->contentLayoutComboBox->addItem(tr("Original"), QVariant::fromValue(BitTorrent::TorrentContentLayout::Original)); + m_ui->contentLayoutComboBox->addItem(tr("Create subfolder"), QVariant::fromValue(BitTorrent::TorrentContentLayout::Subfolder)); + m_ui->contentLayoutComboBox->addItem(tr("Don't create subfolder"), QVariant::fromValue(BitTorrent::TorrentContentLayout::NoSubfolder)); + + m_ui->stopConditionComboBox->addItem(tr("Default")); + m_ui->stopConditionComboBox->addItem(tr("None"), QVariant::fromValue(BitTorrent::Torrent::StopCondition::None)); + m_ui->stopConditionComboBox->addItem(tr("Metadata received"), QVariant::fromValue(BitTorrent::Torrent::StopCondition::MetadataReceived)); + m_ui->stopConditionComboBox->addItem(tr("Files checked"), QVariant::fromValue(BitTorrent::Torrent::StopCondition::FilesChecked)); + + m_ui->startTorrentComboBox->addItem(tr("Default")); + m_ui->startTorrentComboBox->addItem(tr("Yes"), true); + m_ui->startTorrentComboBox->addItem(tr("No"), false); + + m_ui->addToQueueTopComboBox->addItem(tr("Default")); + m_ui->addToQueueTopComboBox->addItem(tr("Yes"), true); + m_ui->addToQueueTopComboBox->addItem(tr("No"), false); + + connect(m_ui->tagsEditButton, &QAbstractButton::clicked, this, [this] + { + auto *dlg = new TorrentTagsDialog(m_addTorrentParams.tags, this); + dlg->setAttribute(Qt::WA_DeleteOnClose); + connect(dlg, &TorrentTagsDialog::accepted, this, [this, dlg] + { + m_addTorrentParams.tags = dlg->tags(); + m_ui->tagsLineEdit->setText(m_addTorrentParams.tags.join(u", "_qs)); + }); + dlg->open(); + }); + + auto *miscParamsLayout = new FlowLayout(m_ui->miscParamsWidget); + miscParamsLayout->setContentsMargins(0, 0, 0, 0); + miscParamsLayout->addWidget(m_ui->contentLayoutWidget); + miscParamsLayout->addWidget(m_ui->skipCheckingCheckBox); + miscParamsLayout->setAlignment(m_ui->skipCheckingCheckBox, Qt::AlignVCenter); + miscParamsLayout->addWidget(m_ui->startTorrentWidget); + miscParamsLayout->addWidget(m_ui->stopConditionWidget); + miscParamsLayout->addWidget(m_ui->addToQueueTopWidget); + + populate(); +} + +AddTorrentParamsWidget::~AddTorrentParamsWidget() +{ + delete m_ui; +} + +void AddTorrentParamsWidget::setAddTorrentParams(BitTorrent::AddTorrentParams addTorrentParams) +{ + m_addTorrentParams = std::move(addTorrentParams); + populate(); +} + +BitTorrent::AddTorrentParams AddTorrentParamsWidget::addTorrentParams() const +{ + return m_addTorrentParams; +} + +void AddTorrentParamsWidget::populate() +{ + m_ui->comboTTM->disconnect(this); + m_ui->comboTTM->setCurrentIndex(m_addTorrentParams.useAutoTMM + ? m_ui->comboTTM->findData(*m_addTorrentParams.useAutoTMM) : 0); + connect(m_ui->comboTTM, &QComboBox::currentIndexChanged, this, [this] + { + m_addTorrentParams.useAutoTMM = toOptionalBool(m_ui->comboTTM->currentData()); + + populateSavePathOptions(); + }); + + m_ui->categoryComboBox->disconnect(this); + m_ui->categoryComboBox->clear(); + QStringList categories = BitTorrent::Session::instance()->categories(); + std::sort(categories.begin(), categories.end(), Utils::Compare::NaturalLessThan()); + if (!m_addTorrentParams.category.isEmpty()) + m_ui->categoryComboBox->addItem(m_addTorrentParams.category); + m_ui->categoryComboBox->addItem(u""_qs); + for (const QString &category : asConst(categories)) + { + if (category != m_addTorrentParams.category) + m_ui->categoryComboBox->addItem(category); + } + connect(m_ui->categoryComboBox, &QComboBox::currentIndexChanged, this, [this] + { + m_addTorrentParams.category = m_ui->categoryComboBox->currentText(); + + const auto *btSession = BitTorrent::Session::instance(); + const bool useAutoTMM = m_addTorrentParams.useAutoTMM.value_or(!btSession->isAutoTMMDisabledByDefault()); + if (useAutoTMM) + { + const auto downloadPathOption = btSession->categoryOptions(m_addTorrentParams.category).downloadPath; + m_ui->useDownloadPathComboBox->setCurrentIndex(downloadPathOption.has_value() + ? m_ui->useDownloadPathComboBox->findData(downloadPathOption->enabled) : 0); + } + + populateDefaultPaths(); + }); + + m_ui->savePathEdit->disconnect(this); + m_ui->downloadPathEdit->disconnect(this); + m_ui->useDownloadPathComboBox->disconnect(this); + + populateSavePathOptions(); + + connect(m_ui->savePathEdit, &FileSystemPathLineEdit::selectedPathChanged, this, [this] + { + m_addTorrentParams.savePath = m_ui->savePathEdit->selectedPath(); + }); + connect(m_ui->downloadPathEdit, &FileSystemPathLineEdit::selectedPathChanged, this, [this] + { + m_addTorrentParams.downloadPath = m_ui->downloadPathEdit->selectedPath(); + }); + connect(m_ui->useDownloadPathComboBox, &QComboBox::currentIndexChanged, this, [this] + { + m_addTorrentParams.useDownloadPath = toOptionalBool(m_ui->useDownloadPathComboBox->currentData()); + + loadCustomDownloadPath(); + }); + + m_ui->contentLayoutComboBox->disconnect(this); + m_ui->contentLayoutComboBox->setCurrentIndex(m_addTorrentParams.contentLayout + ? m_ui->contentLayoutComboBox->findData(QVariant::fromValue(*m_addTorrentParams.contentLayout)) : 0); + connect(m_ui->contentLayoutComboBox, &QComboBox::currentIndexChanged, this, [this] + { + const QVariant data = m_ui->contentLayoutComboBox->currentData(); + if (!data.isValid()) + m_addTorrentParams.contentLayout = std::nullopt; + else + m_addTorrentParams.contentLayout = data.value(); + }); + + m_ui->stopConditionComboBox->disconnect(this); + m_ui->stopConditionComboBox->setCurrentIndex(m_addTorrentParams.stopCondition + ? m_ui->stopConditionComboBox->findData(QVariant::fromValue(*m_addTorrentParams.stopCondition)) : 0); + connect(m_ui->stopConditionComboBox, &QComboBox::currentIndexChanged, this, [this] + { + const QVariant data = m_ui->stopConditionComboBox->currentData(); + if (!data.isValid()) + m_addTorrentParams.stopCondition = std::nullopt; + else + m_addTorrentParams.stopCondition = data.value(); + }); + + m_ui->tagsLineEdit->setText(m_addTorrentParams.tags.join(u", "_qs)); + + m_ui->startTorrentComboBox->disconnect(this); + m_ui->startTorrentComboBox->setCurrentIndex(m_addTorrentParams.addPaused + ? m_ui->startTorrentComboBox->findData(!*m_addTorrentParams.addPaused) : 0); + connect(m_ui->startTorrentComboBox, &QComboBox::currentIndexChanged, this, [this] + { + const QVariant data = m_ui->startTorrentComboBox->currentData(); + if (!data.isValid()) + m_addTorrentParams.addPaused = std::nullopt; + else + m_addTorrentParams.addPaused = !data.toBool(); + }); + + m_ui->skipCheckingCheckBox->disconnect(this); + m_ui->skipCheckingCheckBox->setChecked(m_addTorrentParams.skipChecking); + connect(m_ui->skipCheckingCheckBox, &QCheckBox::toggled, this, [this] + { + m_addTorrentParams.skipChecking = m_ui->skipCheckingCheckBox->isChecked(); + }); + + m_ui->addToQueueTopComboBox->disconnect(this); + m_ui->addToQueueTopComboBox->setCurrentIndex(m_addTorrentParams.addToQueueTop + ? m_ui->addToQueueTopComboBox->findData(*m_addTorrentParams.addToQueueTop) : 0); + connect(m_ui->addToQueueTopComboBox, &QComboBox::currentIndexChanged, this, [this] + { + const QVariant data = m_ui->addToQueueTopComboBox->currentData(); + if (!data.isValid()) + m_addTorrentParams.addToQueueTop = std::nullopt; + else + m_addTorrentParams.addToQueueTop = data.toBool(); + }); +} + +void AddTorrentParamsWidget::loadCustomSavePathOptions() +{ + [[maybe_unused]] const auto *btSession = BitTorrent::Session::instance(); + Q_ASSERT(!m_addTorrentParams.useAutoTMM.value_or(!btSession->isAutoTMMDisabledByDefault())); + + m_ui->savePathEdit->setSelectedPath(m_addTorrentParams.savePath); + + m_ui->useDownloadPathComboBox->setCurrentIndex(m_addTorrentParams.useDownloadPath + ? m_ui->useDownloadPathComboBox->findData(*m_addTorrentParams.useDownloadPath) : 0); + + loadCustomDownloadPath(); +} + +void AddTorrentParamsWidget::loadCustomDownloadPath() +{ + populateDefaultDownloadPath(); + + if (!m_addTorrentParams.useDownloadPath.has_value()) + { + // Default "Download path" settings + + m_ui->downloadPathEdit->setEnabled(false); + m_ui->downloadPathEdit->blockSignals(true); + m_ui->downloadPathEdit->setSelectedPath(Path()); + } + else + { + // Overridden "Download path" settings + + const bool useDownloadPath = m_addTorrentParams.useDownloadPath.value(); + if (useDownloadPath) + { + m_ui->downloadPathEdit->setSelectedPath(m_addTorrentParams.downloadPath); + + m_ui->downloadPathEdit->blockSignals(false); + m_ui->downloadPathEdit->setEnabled(true); + } + else + { + m_ui->downloadPathEdit->setEnabled(false); + m_ui->downloadPathEdit->blockSignals(true); + + m_ui->downloadPathEdit->setSelectedPath(Path()); + } + } +} + +void AddTorrentParamsWidget::loadCategorySavePathOptions() +{ + const auto *btSession = BitTorrent::Session::instance(); + Q_ASSERT(m_addTorrentParams.useAutoTMM.value_or(!btSession->isAutoTMMDisabledByDefault())); + + const auto downloadPathOption = btSession->categoryOptions(m_addTorrentParams.category).downloadPath; + m_ui->useDownloadPathComboBox->setCurrentIndex(downloadPathOption.has_value() + ? m_ui->useDownloadPathComboBox->findData(downloadPathOption->enabled) : 0); +} + +void AddTorrentParamsWidget::populateDefaultPaths() +{ + const auto *btSession = BitTorrent::Session::instance(); + + const Path defaultSavePath = btSession->suggestedSavePath( + m_ui->categoryComboBox->currentText(), toOptionalBool(m_ui->comboTTM->currentData())); + m_ui->savePathEdit->setPlaceholder(defaultSavePath); + + populateDefaultDownloadPath(); +} + +void AddTorrentParamsWidget::populateDefaultDownloadPath() +{ + const auto *btSession = BitTorrent::Session::instance(); + + const std::optional useDownloadPath = toOptionalBool(m_ui->useDownloadPathComboBox->currentData()); + if (useDownloadPath.value_or(btSession->isDownloadPathEnabled())) + { + const Path defaultDownloadPath = btSession->suggestedDownloadPath( + m_ui->categoryComboBox->currentText(), toOptionalBool(m_ui->comboTTM->currentData())); + m_ui->downloadPathEdit->setPlaceholder(defaultDownloadPath); + } + else + { + m_ui->downloadPathEdit->setPlaceholder(Path()); + } +} + +void AddTorrentParamsWidget::populateSavePathOptions() +{ + if (!m_addTorrentParams.useAutoTMM.has_value()) + { + // Default TMM settings + + m_ui->groupBoxSavePath->setEnabled(false); + m_ui->defaultsNoteLabel->setVisible(true); + m_ui->savePathEdit->blockSignals(true); + m_ui->savePathEdit->setSelectedPath(Path()); + m_ui->downloadPathEdit->blockSignals(true); + m_ui->useDownloadPathComboBox->blockSignals(true); + m_ui->downloadPathEdit->setSelectedPath(Path()); + + const auto *btSession = BitTorrent::Session::instance(); + const bool useAutoTMM = !btSession->isAutoTMMDisabledByDefault(); + + if (useAutoTMM) + { + loadCategorySavePathOptions(); + } + else + { + m_ui->useDownloadPathComboBox->setCurrentIndex( + m_ui->useDownloadPathComboBox->findData(btSession->isDownloadPathEnabled())); + } + } + else + { + // Overridden TMM settings + + const bool useAutoTMM = m_addTorrentParams.useAutoTMM.value(); + if (useAutoTMM) + { + m_ui->groupBoxSavePath->setEnabled(false); + m_ui->defaultsNoteLabel->setVisible(true); + m_ui->savePathEdit->blockSignals(true); + m_ui->savePathEdit->setSelectedPath(Path()); + m_ui->downloadPathEdit->blockSignals(true); + m_ui->useDownloadPathComboBox->blockSignals(true); + m_ui->downloadPathEdit->setSelectedPath(Path()); + + loadCategorySavePathOptions(); + } + else + { + loadCustomSavePathOptions(); + + m_ui->groupBoxSavePath->setEnabled(true); + m_ui->defaultsNoteLabel->setVisible(false); + m_ui->savePathEdit->blockSignals(false); + m_ui->useDownloadPathComboBox->blockSignals(false); + } + } + + populateDefaultPaths(); +} diff --git a/src/gui/addtorrentparamswidget.h b/src/gui/addtorrentparamswidget.h new file mode 100644 index 000000000..3f61e55ae --- /dev/null +++ b/src/gui/addtorrentparamswidget.h @@ -0,0 +1,64 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 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 + +#include "base/bittorrent/addtorrentparams.h" + +namespace Ui +{ + class AddTorrentParamsWidget; +} + +class AddTorrentParamsWidget final : public QWidget +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(AddTorrentParamsWidget) + +public: + explicit AddTorrentParamsWidget(BitTorrent::AddTorrentParams addTorrentParams = {}, QWidget *parent = nullptr); + ~AddTorrentParamsWidget() override; + + void setAddTorrentParams(BitTorrent::AddTorrentParams addTorrentParams); + BitTorrent::AddTorrentParams addTorrentParams() const; + +private: + void populate(); + void loadCustomSavePathOptions(); + void loadCustomDownloadPath(); + void loadCategorySavePathOptions(); + void populateDefaultPaths(); + void populateDefaultDownloadPath(); + void populateSavePathOptions(); + + + Ui::AddTorrentParamsWidget *m_ui; + BitTorrent::AddTorrentParams m_addTorrentParams; +}; diff --git a/src/gui/addtorrentparamswidget.ui b/src/gui/addtorrentparamswidget.ui new file mode 100644 index 000000000..e43d88169 --- /dev/null +++ b/src/gui/addtorrentparamswidget.ui @@ -0,0 +1,363 @@ + + + AddTorrentParamsWidget + + + + 0 + 0 + 503 + 366 + + + + Form + + + + + + + + Torrent Management Mode: + + + + + + + Automatic mode means that various torrent properties(eg save path) will be decided by the associated category + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + + + Save at + + + + + + + true + + + + Note: the current defaults are displayed for reference. + + + + + + + + + + + + Use another path for incomplete torrents: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + Category: + + + + + + + + 0 + 0 + + + + true + + + QComboBox::InsertAtTop + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Tags: + + + + + + + + 0 + 0 + + + + true + + + Click [...] button to add/remove tags. + + + false + + + + + + + Add/remove tags + + + ... + + + + + + + + + + + 9 + 65 + 143 + 22 + + + + + 0 + + + 0 + + + 2 + + + 0 + + + + + Start torrent: + + + + + + + -1 + + + + + + + + + 9 + 9 + 159 + 22 + + + + + 0 + + + 0 + + + 2 + + + 0 + + + + + Content layout: + + + + + + + -1 + + + + + + + + + 9 + 93 + 158 + 22 + + + + + 0 + + + 0 + + + 2 + + + 0 + + + + + Stop condition: + + + + + + + -1 + + + + + + + + + 9 + 121 + 187 + 22 + + + + + 0 + + + 0 + + + 2 + + + 0 + + + + + Add to top of queue: + + + + + + + -1 + + + + + + + + + 10 + 30 + 83 + 21 + + + + Skip hash check + + + + + + + + + FileSystemPathLineEdit + QWidget +
gui/fspathedit.h
+ 1 +
+
+ + +
diff --git a/src/gui/flowlayout.cpp b/src/gui/flowlayout.cpp index 6cd3bfeab..29eaceed5 100644 --- a/src/gui/flowlayout.cpp +++ b/src/gui/flowlayout.cpp @@ -29,6 +29,7 @@ #include "flowlayout.h" +#include #include #include "base/global.h" @@ -142,6 +143,7 @@ int FlowLayout::doLayout(const QRect &rect, const bool testOnly) const int y = effectiveRect.y(); int lineHeight = 0; + QHash lineItems; for (QLayoutItem *item : asConst(m_itemList)) { const QWidget *wid = item->widget(); @@ -163,6 +165,12 @@ int FlowLayout::doLayout(const QRect &rect, const bool testOnly) const int nextX = x + item->sizeHint().width() + spaceX; if (((nextX - spaceX) > effectiveRect.right()) && (lineHeight > 0)) { + if (!testOnly) + { + applyItemsGeometry(lineItems, lineHeight); + lineItems.clear(); + } + x = effectiveRect.x(); y = y + lineHeight + spaceY; nextX = x + item->sizeHint().width() + spaceX; @@ -170,12 +178,15 @@ int FlowLayout::doLayout(const QRect &rect, const bool testOnly) const } if (!testOnly) - item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); + lineItems[item] = QPoint(x, y); x = nextX; lineHeight = std::max(lineHeight, item->sizeHint().height()); } + if (!testOnly) + applyItemsGeometry(lineItems, lineHeight); + return y + lineHeight - rect.y() + bottom; } @@ -193,3 +204,20 @@ int FlowLayout::smartSpacing(const QStyle::PixelMetric pm) const return static_cast(parent)->spacing(); } + +void FlowLayout::applyItemsGeometry(const QHash &items, const int lineHeight) const +{ + for (auto it = items.cbegin(); it != items.cend(); ++it) + { + QLayoutItem *item = it.key(); + QPoint point = it.value(); + + const auto alignment = item->alignment(); + const int vSpace = lineHeight - item->sizeHint().height(); + if (alignment & Qt::AlignVCenter) + point.ry() += vSpace / 2; + else if (alignment & Qt::AlignBottom) + point.ry() += vSpace; + item->setGeometry(QRect(point, item->sizeHint())); + } +} diff --git a/src/gui/flowlayout.h b/src/gui/flowlayout.h index 888e8262f..926d07531 100644 --- a/src/gui/flowlayout.h +++ b/src/gui/flowlayout.h @@ -56,6 +56,7 @@ public: private: int doLayout(const QRect &rect, bool testOnly) const; int smartSpacing(QStyle::PixelMetric pm) const; + void applyItemsGeometry(const QHash &items, int lineHeight) const; QList m_itemList; int m_hSpace; diff --git a/src/gui/fspathedit.cpp b/src/gui/fspathedit.cpp index 94ac6741d..563bc6a1b 100644 --- a/src/gui/fspathedit.cpp +++ b/src/gui/fspathedit.cpp @@ -165,7 +165,7 @@ void FileSystemPathEdit::FileSystemPathEditPrivate::modeChanged() { m_editor->completeDirectoriesOnly((m_mode == FileSystemPathEdit::Mode::DirectoryOpen) || (m_mode == FileSystemPathEdit::Mode::DirectorySave)); - m_validator->setExistingOnly(m_mode != FileSystemPathEdit::Mode::FileSave); + m_validator->setExistingOnly((m_mode == FileSystemPathEdit::Mode::FileOpen) || (m_mode == FileSystemPathEdit::Mode::DirectoryOpen)); m_validator->setDirectoriesOnly((m_mode == FileSystemPathEdit::Mode::DirectoryOpen) || (m_mode == FileSystemPathEdit::Mode::DirectorySave)); m_validator->setCheckReadPermission((m_mode == FileSystemPathEdit::Mode::FileOpen) || (m_mode == FileSystemPathEdit::Mode::DirectoryOpen)); m_validator->setCheckWritePermission((m_mode == FileSystemPathEdit::Mode::FileSave) || (m_mode == FileSystemPathEdit::Mode::DirectorySave)); diff --git a/src/gui/fspathedit_p.cpp b/src/gui/fspathedit_p.cpp index db4498925..bd7b39787 100644 --- a/src/gui/fspathedit_p.cpp +++ b/src/gui/fspathedit_p.cpp @@ -102,8 +102,8 @@ Private::FileSystemPathValidator::testPath(const Path &path) const // `QFileInfo` will cache the query results and avoid exessive querying to filesystem const QFileInfo info {path.data()}; - if (existingOnly() && !info.exists()) - return TestResult::DoesNotExist; + if (!info.exists()) + return existingOnly() ? TestResult::DoesNotExist : TestResult::OK; if (directoriesOnly()) { diff --git a/src/gui/gui.pri b/src/gui/gui.pri index c07cdfa06..29c5a429c 100644 --- a/src/gui/gui.pri +++ b/src/gui/gui.pri @@ -3,6 +3,7 @@ INCLUDEPATH += $$PWD HEADERS += \ $$PWD/aboutdialog.h \ $$PWD/addnewtorrentdialog.h \ + $$PWD/addtorrentparamswidget.h \ $$PWD/advancedsettings.h \ $$PWD/autoexpandabledialog.h \ $$PWD/banlistoptionsdialog.h \ @@ -97,6 +98,7 @@ HEADERS += \ SOURCES += \ $$PWD/aboutdialog.cpp \ $$PWD/addnewtorrentdialog.cpp \ + $$PWD/addtorrentparamswidget.cpp \ $$PWD/advancedsettings.cpp \ $$PWD/autoexpandabledialog.cpp \ $$PWD/banlistoptionsdialog.cpp \ @@ -187,6 +189,7 @@ SOURCES += \ FORMS += \ $$PWD/aboutdialog.ui \ $$PWD/addnewtorrentdialog.ui \ + $$PWD/addtorrentparamswidget.ui \ $$PWD/autoexpandabledialog.ui \ $$PWD/banlistoptionsdialog.ui \ $$PWD/cookiesdialog.ui \ diff --git a/src/gui/rss/automatedrssdownloader.cpp b/src/gui/rss/automatedrssdownloader.cpp index c5add9633..9f35afd6b 100644 --- a/src/gui/rss/automatedrssdownloader.cpp +++ b/src/gui/rss/automatedrssdownloader.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2017 Vladimir Golovnev + * Copyright (C) 2017, 2023 Vladimir Golovnev * Copyright (C) 2010 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -50,6 +50,7 @@ #include "base/utils/compare.h" #include "base/utils/io.h" #include "base/utils/string.h" +#include "gui/addtorrentparamswidget.h" #include "gui/autoexpandabledialog.h" #include "gui/torrentcategorydialog.h" #include "gui/uithememanager.h" @@ -61,37 +62,44 @@ const QString EXT_LEGACY = u".rssrules"_qs; AutomatedRssDownloader::AutomatedRssDownloader(QWidget *parent) : QDialog(parent) - , m_formatFilterJSON(u"%1 (*%2)"_qs.arg(tr("Rules"), EXT_JSON)) - , m_formatFilterLegacy(u"%1 (*%2)"_qs.arg(tr("Rules (legacy)"), EXT_LEGACY)) - , m_ui(new Ui::AutomatedRssDownloader) + , m_formatFilterJSON {u"%1 (*%2)"_qs.arg(tr("Rules"), EXT_JSON)} + , m_formatFilterLegacy {u"%1 (*%2)"_qs.arg(tr("Rules (legacy)"), EXT_LEGACY)} + , m_ui {new Ui::AutomatedRssDownloader} + , m_addTorrentParamsWidget {new AddTorrentParamsWidget} , m_storeDialogSize {u"RssFeedDownloader/geometrySize"_qs} #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - , m_storeHSplitterSize {u"GUI/Qt6/RSSFeedDownloader/HSplitterSizes"_qs} + , m_storeMainSplitterState {u"GUI/Qt6/RSSFeedDownloader/HSplitterSizes"_qs} + , m_storeRuleDefSplitterState {u"GUI/Qt6/RSSFeedDownloader/RuleDefSplitterState"_qs} #else - , m_storeHSplitterSize {u"RssFeedDownloader/qt5/hsplitterSizes"_qs} + , m_storeMainSplitterState {u"RssFeedDownloader/qt5/hsplitterSizes"_qs} + , m_storeRuleDefSplitterState {u"RssFeedDownloader/qt5/RuleDefSplitterState"_qs} #endif - { m_ui->setupUi(this); + m_ui->torrentParametersGroupBox->layout()->addWidget(m_addTorrentParamsWidget); + + connect(m_ui->addRuleBtn, &QPushButton::clicked, this, &AutomatedRssDownloader::onAddRuleBtnClicked); + connect(m_ui->removeRuleBtn, &QPushButton::clicked, this, &AutomatedRssDownloader::onRemoveRuleBtnClicked); + connect(m_ui->exportBtn, &QPushButton::clicked, this, &AutomatedRssDownloader::onExportBtnClicked); + connect(m_ui->importBtn, &QPushButton::clicked, this, &AutomatedRssDownloader::onImportBtnClicked); + connect(m_ui->renameRuleBtn, &QPushButton::clicked, this, &AutomatedRssDownloader::onRenameRuleBtnClicked); + // Icons m_ui->renameRuleBtn->setIcon(UIThemeManager::instance()->getIcon(u"edit-rename"_qs)); m_ui->removeRuleBtn->setIcon(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs)); m_ui->addRuleBtn->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_qs)); - m_ui->addCategoryBtn->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_qs)); // Ui Settings - m_ui->listRules->setSortingEnabled(true); - m_ui->listRules->setSelectionMode(QAbstractItemView::ExtendedSelection); - m_ui->treeMatchingArticles->setSortingEnabled(true); - m_ui->treeMatchingArticles->sortByColumn(0, Qt::AscendingOrder); - m_ui->hsplitter->setCollapsible(0, false); - m_ui->hsplitter->setCollapsible(1, false); - m_ui->hsplitter->setCollapsible(2, true); // Only the preview list is collapsible - m_ui->lineSavePath->setDialogCaption(tr("Destination directory")); - m_ui->lineSavePath->setMode(FileSystemPathEdit::Mode::DirectorySave); + m_ui->ruleList->setSortingEnabled(true); + m_ui->ruleList->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_ui->matchingArticlesTree->setSortingEnabled(true); + m_ui->matchingArticlesTree->sortByColumn(0, Qt::AscendingOrder); + m_ui->mainSplitter->setCollapsible(0, false); + m_ui->mainSplitter->setCollapsible(1, false); + m_ui->mainSplitter->setCollapsible(2, true); // Only the preview list is collapsible connect(m_ui->checkRegex, &QAbstractButton::toggled, this, &AutomatedRssDownloader::updateFieldsToolTips); - connect(m_ui->listRules, &QWidget::customContextMenuRequested, this, &AutomatedRssDownloader::displayRulesListMenu); + connect(m_ui->ruleList, &QWidget::customContextMenuRequested, this, &AutomatedRssDownloader::displayRulesListMenu); m_episodeRegex = new QRegularExpression(u"^(^\\d{1,4}x(\\d{1,4}(-(\\d{1,4})?)?;){1,}){1,1}"_qs , QRegularExpression::CaseInsensitiveOption); @@ -106,7 +114,6 @@ AutomatedRssDownloader::AutomatedRssDownloader(QWidget *parent) + u"
  • " + tr("Infinite range: 1x25-; matches episodes 25 and upward of season one, and all episodes of later seasons") + u"
  • " + u""; m_ui->lineEFilter->setToolTip(tip); - initCategoryCombobox(); loadSettings(); connect(RSS::AutoDownloader::instance(), &RSS::AutoDownloader::ruleAdded, this, &AutomatedRssDownloader::handleRuleAdded); @@ -130,27 +137,28 @@ AutomatedRssDownloader::AutomatedRssDownloader(QWidget *parent) connect(m_ui->listFeeds, &QListWidget::itemChanged, this, &AutomatedRssDownloader::handleFeedCheckStateChange); - connect(m_ui->listRules, &QListWidget::itemSelectionChanged, this, &AutomatedRssDownloader::updateRuleDefinitionBox); - connect(m_ui->listRules, &QListWidget::itemChanged, this, &AutomatedRssDownloader::handleRuleCheckStateChange); + connect(m_ui->ruleList, &QListWidget::itemSelectionChanged, this, &AutomatedRssDownloader::updateRuleDefinitionBox); + connect(m_ui->ruleList, &QListWidget::itemChanged, this, &AutomatedRssDownloader::handleRuleCheckStateChange); - const auto *editHotkey = new QShortcut(Qt::Key_F2, m_ui->listRules, nullptr, nullptr, Qt::WidgetShortcut); + const auto *editHotkey = new QShortcut(Qt::Key_F2, m_ui->ruleList, nullptr, nullptr, Qt::WidgetShortcut); connect(editHotkey, &QShortcut::activated, this, &AutomatedRssDownloader::renameSelectedRule); - const auto *deleteHotkey = new QShortcut(QKeySequence::Delete, m_ui->listRules, nullptr, nullptr, Qt::WidgetShortcut); - connect(deleteHotkey, &QShortcut::activated, this, &AutomatedRssDownloader::on_removeRuleBtn_clicked); + const auto *deleteHotkey = new QShortcut(QKeySequence::Delete, m_ui->ruleList, nullptr, nullptr, Qt::WidgetShortcut); + connect(deleteHotkey, &QShortcut::activated, this, &AutomatedRssDownloader::onRemoveRuleBtnClicked); - connect(m_ui->listRules, &QAbstractItemView::doubleClicked, this, &AutomatedRssDownloader::renameSelectedRule); + connect(m_ui->ruleList, &QAbstractItemView::doubleClicked, this, &AutomatedRssDownloader::renameSelectedRule); loadFeedList(); - m_ui->listRules->blockSignals(true); + m_ui->ruleList->blockSignals(true); for (const RSS::AutoDownloadRule &rule : asConst(RSS::AutoDownloader::instance()->rules())) createRuleItem(rule); - m_ui->listRules->blockSignals(false); + m_ui->ruleList->blockSignals(false); updateRuleDefinitionBox(); if (RSS::AutoDownloader::instance()->isProcessingEnabled()) m_ui->labelWarn->hide(); + connect(RSS::AutoDownloader::instance(), &RSS::AutoDownloader::processingStateChanged , this, &AutomatedRssDownloader::handleProcessingStateChanged); } @@ -170,19 +178,23 @@ void AutomatedRssDownloader::loadSettings() if (const QSize dialogSize = m_storeDialogSize; dialogSize.isValid()) resize(dialogSize); - if (const QByteArray hSplitterSize = m_storeHSplitterSize; !hSplitterSize.isEmpty()) - m_ui->hsplitter->restoreState(hSplitterSize); + if (const QByteArray mainSplitterSize = m_storeMainSplitterState; !mainSplitterSize.isEmpty()) + m_ui->mainSplitter->restoreState(mainSplitterSize); + + if (const QByteArray ruleDefSplitterSize = m_storeRuleDefSplitterState; !ruleDefSplitterSize.isEmpty()) + m_ui->ruleDefSplitter->restoreState(ruleDefSplitterSize); } void AutomatedRssDownloader::saveSettings() { m_storeDialogSize = size(); - m_storeHSplitterSize = m_ui->hsplitter->saveState(); + m_storeMainSplitterState = m_ui->mainSplitter->saveState(); + m_storeRuleDefSplitterState = m_ui->ruleDefSplitter->saveState(); } void AutomatedRssDownloader::createRuleItem(const RSS::AutoDownloadRule &rule) { - QListWidgetItem *item = new QListWidgetItem(rule.name(), m_ui->listRules); + QListWidgetItem *item = new QListWidgetItem(rule.name(), m_ui->ruleList); m_itemsByRuleName.insert(rule.name(), item); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(rule.isEnabled() ? Qt::Checked : Qt::Unchecked); @@ -211,7 +223,7 @@ void AutomatedRssDownloader::updateFeedList() if (m_currentRuleItem) selection << m_currentRuleItem; else - selection = m_ui->listRules->selectedItems(); + selection = m_ui->ruleList->selectedItems(); bool enable = !selection.isEmpty(); @@ -248,7 +260,7 @@ void AutomatedRssDownloader::updateFeedList() void AutomatedRssDownloader::updateRuleDefinitionBox() { - const QList selection = m_ui->listRules->selectedItems(); + const QList selection = m_ui->ruleList->selectedItems(); QListWidgetItem *currentRuleItem = ((selection.count() == 1) ? selection.first() : nullptr); // Enable the edit rule button but only if we have 1 rule selected @@ -261,7 +273,7 @@ void AutomatedRssDownloader::updateRuleDefinitionBox() { saveEditedRule(); // Save previous rule first m_currentRuleItem = currentRuleItem; - //m_ui->listRules->setCurrentItem(m_currentRuleItem); + //m_ui->ruleList->setCurrentItem(m_currentRuleItem); } // Update rule definition box @@ -269,31 +281,20 @@ void AutomatedRssDownloader::updateRuleDefinitionBox() { m_currentRule = RSS::AutoDownloader::instance()->ruleByName(m_currentRuleItem->text()); + m_addTorrentParamsWidget->setAddTorrentParams(m_currentRule.addTorrentParams()); + m_ui->lineContains->setText(m_currentRule.mustContain()); m_ui->lineNotContains->setText(m_currentRule.mustNotContain()); if (!m_currentRule.episodeFilter().isEmpty()) m_ui->lineEFilter->setText(m_currentRule.episodeFilter()); else m_ui->lineEFilter->clear(); - m_ui->checkBoxSaveDiffDir->setChecked(!m_currentRule.savePath().isEmpty()); - m_ui->lineSavePath->setSelectedPath(m_currentRule.savePath()); m_ui->checkRegex->blockSignals(true); m_ui->checkRegex->setChecked(m_currentRule.useRegex()); m_ui->checkRegex->blockSignals(false); m_ui->checkSmart->blockSignals(true); m_ui->checkSmart->setChecked(m_currentRule.useSmartFilter()); m_ui->checkSmart->blockSignals(false); - m_ui->comboCategory->setCurrentIndex(m_ui->comboCategory->findText(m_currentRule.assignedCategory())); - if (m_currentRule.assignedCategory().isEmpty()) - m_ui->comboCategory->clearEditText(); - int index = 0; - if (m_currentRule.addPaused().has_value()) - index = (*m_currentRule.addPaused() ? 1 : 2); - m_ui->comboAddPaused->setCurrentIndex(index); - index = 0; - if (m_currentRule.torrentContentLayout()) - index = static_cast(*m_currentRule.torrentContentLayout()) + 1; - m_ui->comboContentLayout->setCurrentIndex(index); m_ui->spinIgnorePeriod->setValue(m_currentRule.ignoreDays()); QDateTime dateTime = m_currentRule.lastMatch(); QString lMatch; @@ -307,13 +308,13 @@ void AutomatedRssDownloader::updateRuleDefinitionBox() updateEpisodeFilterValidity(); updateFieldsToolTips(m_ui->checkRegex->isChecked()); - m_ui->ruleDefBox->setEnabled(true); + m_ui->ruleScrollArea->setEnabled(true); } else { m_currentRule = RSS::AutoDownloadRule(); clearRuleDefinitionBox(); - m_ui->ruleDefBox->setEnabled(false); + m_ui->ruleScrollArea->setEnabled(false); } updateFeedList(); @@ -322,38 +323,23 @@ void AutomatedRssDownloader::updateRuleDefinitionBox() void AutomatedRssDownloader::clearRuleDefinitionBox() { + m_addTorrentParamsWidget->setAddTorrentParams({}); m_ui->lineContains->clear(); m_ui->lineNotContains->clear(); m_ui->lineEFilter->clear(); - m_ui->checkBoxSaveDiffDir->setChecked(false); - m_ui->lineSavePath->clear(); - m_ui->comboCategory->clearEditText(); - m_ui->comboCategory->setCurrentIndex(-1); m_ui->checkRegex->setChecked(false); m_ui->checkSmart->setChecked(false); m_ui->spinIgnorePeriod->setValue(0); - m_ui->comboAddPaused->clearEditText(); - m_ui->comboAddPaused->setCurrentIndex(-1); - m_ui->comboContentLayout->clearEditText(); - m_ui->comboContentLayout->setCurrentIndex(-1); updateFieldsToolTips(m_ui->checkRegex->isChecked()); updateMustLineValidity(); updateMustNotLineValidity(); updateEpisodeFilterValidity(); } -void AutomatedRssDownloader::initCategoryCombobox() -{ - // Load torrent categories - QStringList categories = BitTorrent::Session::instance()->categories(); - std::sort(categories.begin(), categories.end(), Utils::Compare::NaturalLessThan()); - m_ui->comboCategory->addItem(u""_qs); - m_ui->comboCategory->addItems(categories); -} - void AutomatedRssDownloader::updateEditedRule() { - if (!m_currentRuleItem || !m_ui->ruleDefBox->isEnabled()) return; + if (!m_currentRuleItem || !m_ui->ruleScrollArea->isEnabled()) + return; m_currentRule.setEnabled(m_currentRuleItem->checkState() != Qt::Unchecked); m_currentRule.setUseRegex(m_ui->checkRegex->isChecked()); @@ -361,32 +347,20 @@ void AutomatedRssDownloader::updateEditedRule() m_currentRule.setMustContain(m_ui->lineContains->text()); m_currentRule.setMustNotContain(m_ui->lineNotContains->text()); m_currentRule.setEpisodeFilter(m_ui->lineEFilter->text()); - m_currentRule.setSavePath(m_ui->checkBoxSaveDiffDir->isChecked() ? m_ui->lineSavePath->selectedPath() : Path()); - m_currentRule.setCategory(m_ui->comboCategory->currentText()); - std::optional addPaused; - if (m_ui->comboAddPaused->currentIndex() == 1) - addPaused = true; - else if (m_ui->comboAddPaused->currentIndex() == 2) - addPaused = false; - m_currentRule.setAddPaused(addPaused); - - std::optional contentLayout; - if (m_ui->comboContentLayout->currentIndex() > 0) - contentLayout = static_cast(m_ui->comboContentLayout->currentIndex() - 1); - m_currentRule.setTorrentContentLayout(contentLayout); - m_currentRule.setIgnoreDays(m_ui->spinIgnorePeriod->value()); + + m_currentRule.setAddTorrentParams(m_addTorrentParamsWidget->addTorrentParams()); } void AutomatedRssDownloader::saveEditedRule() { - if (!m_currentRuleItem || !m_ui->ruleDefBox->isEnabled()) return; + if (!m_currentRuleItem || !m_ui->ruleScrollArea->isEnabled()) return; updateEditedRule(); RSS::AutoDownloader::instance()->insertRule(m_currentRule); } -void AutomatedRssDownloader::on_addRuleBtn_clicked() +void AutomatedRssDownloader::onAddRuleBtnClicked() { // saveEditedRule(); @@ -406,9 +380,9 @@ void AutomatedRssDownloader::on_addRuleBtn_clicked() RSS::AutoDownloader::instance()->insertRule(RSS::AutoDownloadRule(ruleName)); } -void AutomatedRssDownloader::on_removeRuleBtn_clicked() +void AutomatedRssDownloader::onRemoveRuleBtnClicked() { - const QList selection = m_ui->listRules->selectedItems(); + const QList selection = m_ui->ruleList->selectedItems(); if (selection.isEmpty()) return; // Ask for confirmation @@ -423,23 +397,12 @@ void AutomatedRssDownloader::on_removeRuleBtn_clicked() RSS::AutoDownloader::instance()->removeRule(item->text()); } -void AutomatedRssDownloader::on_addCategoryBtn_clicked() -{ - const QString newCategoryName = TorrentCategoryDialog::createCategory(this); - - if (!newCategoryName.isEmpty()) - { - m_ui->comboCategory->addItem(newCategoryName); - m_ui->comboCategory->setCurrentText(newCategoryName); - } -} - -void AutomatedRssDownloader::on_renameRuleBtn_clicked() +void AutomatedRssDownloader::onRenameRuleBtnClicked() { renameSelectedRule(); } -void AutomatedRssDownloader::on_exportBtn_clicked() +void AutomatedRssDownloader::onExportBtnClicked() { if (RSS::AutoDownloader::instance()->rules().isEmpty()) { @@ -482,7 +445,7 @@ void AutomatedRssDownloader::on_exportBtn_clicked() } } -void AutomatedRssDownloader::on_importBtn_clicked() +void AutomatedRssDownloader::onImportBtnClicked() { QString selectedFilter {m_formatFilterJSON}; const Path path {QFileDialog::getOpenFileName( @@ -525,16 +488,16 @@ void AutomatedRssDownloader::displayRulesListMenu() menu->setAttribute(Qt::WA_DeleteOnClose); menu->addAction(UIThemeManager::instance()->getIcon(u"list-add"_qs), tr("Add new rule...") - , this, &AutomatedRssDownloader::on_addRuleBtn_clicked); + , this, &AutomatedRssDownloader::onAddRuleBtnClicked); - const QList selection = m_ui->listRules->selectedItems(); + const QList selection = m_ui->ruleList->selectedItems(); if (!selection.isEmpty()) { if (selection.count() == 1) { menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs), tr("Delete rule") - , this, &AutomatedRssDownloader::on_removeRuleBtn_clicked); + , this, &AutomatedRssDownloader::onRemoveRuleBtnClicked); menu->addSeparator(); menu->addAction(UIThemeManager::instance()->getIcon(u"edit-rename"_qs), tr("Rename rule...") , this, &AutomatedRssDownloader::renameSelectedRule); @@ -542,7 +505,7 @@ void AutomatedRssDownloader::displayRulesListMenu() else { menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs), tr("Delete selected rules") - , this, &AutomatedRssDownloader::on_removeRuleBtn_clicked); + , this, &AutomatedRssDownloader::onRemoveRuleBtnClicked); } menu->addSeparator(); @@ -555,7 +518,7 @@ void AutomatedRssDownloader::displayRulesListMenu() void AutomatedRssDownloader::renameSelectedRule() { - const QList selection = m_ui->listRules->selectedItems(); + const QList selection = m_ui->ruleList->selectedItems(); if (selection.isEmpty()) return; QListWidgetItem *item = selection.first(); @@ -583,7 +546,7 @@ void AutomatedRssDownloader::renameSelectedRule() void AutomatedRssDownloader::handleRuleCheckStateChange(QListWidgetItem *ruleItem) { - m_ui->listRules->setCurrentItem(ruleItem); + m_ui->ruleList->setCurrentItem(ruleItem); } void AutomatedRssDownloader::clearSelectedRuleDownloadedEpisodeList() @@ -604,7 +567,7 @@ void AutomatedRssDownloader::clearSelectedRuleDownloadedEpisodeList() void AutomatedRssDownloader::handleFeedCheckStateChange(QListWidgetItem *feedItem) { const QString feedURL = feedItem->data(Qt::UserRole).toString(); - for (QListWidgetItem *ruleItem : asConst(m_ui->listRules->selectedItems())) + for (QListWidgetItem *ruleItem : asConst(m_ui->ruleList->selectedItems())) { RSS::AutoDownloadRule rule = (ruleItem == m_currentRuleItem ? m_currentRule @@ -627,9 +590,9 @@ void AutomatedRssDownloader::handleFeedCheckStateChange(QListWidgetItem *feedIte void AutomatedRssDownloader::updateMatchingArticles() { - m_ui->treeMatchingArticles->clear(); + m_ui->matchingArticlesTree->clear(); - for (const QListWidgetItem *ruleItem : asConst(m_ui->listRules->selectedItems())) + for (const QListWidgetItem *ruleItem : asConst(m_ui->ruleList->selectedItems())) { RSS::AutoDownloadRule rule = (ruleItem == m_currentRuleItem ? m_currentRule @@ -654,13 +617,13 @@ void AutomatedRssDownloader::updateMatchingArticles() void AutomatedRssDownloader::addFeedArticlesToTree(RSS::Feed *feed, const QStringList &articles) { // Turn off sorting while inserting - m_ui->treeMatchingArticles->setSortingEnabled(false); + m_ui->matchingArticlesTree->setSortingEnabled(false); // Check if this feed is already in the tree QTreeWidgetItem *treeFeedItem = nullptr; - for (int i = 0; i < m_ui->treeMatchingArticles->topLevelItemCount(); ++i) + for (int i = 0; i < m_ui->matchingArticlesTree->topLevelItemCount(); ++i) { - QTreeWidgetItem *item = m_ui->treeMatchingArticles->topLevelItem(i); + QTreeWidgetItem *item = m_ui->matchingArticlesTree->topLevelItem(i); if (item->data(0, Qt::UserRole).toString() == feed->url()) { treeFeedItem = item; @@ -678,7 +641,7 @@ void AutomatedRssDownloader::addFeedArticlesToTree(RSS::Feed *feed, const QStrin treeFeedItem->setFont(0, f); treeFeedItem->setData(0, Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"directory"_qs)); treeFeedItem->setData(0, Qt::UserRole, feed->url()); - m_ui->treeMatchingArticles->addTopLevelItem(treeFeedItem); + m_ui->matchingArticlesTree->addTopLevelItem(treeFeedItem); } // Insert the articles @@ -695,9 +658,9 @@ void AutomatedRssDownloader::addFeedArticlesToTree(RSS::Feed *feed, const QStrin } } - m_ui->treeMatchingArticles->expandItem(treeFeedItem); - m_ui->treeMatchingArticles->sortItems(0, Qt::AscendingOrder); - m_ui->treeMatchingArticles->setSortingEnabled(true); + m_ui->matchingArticlesTree->expandItem(treeFeedItem); + m_ui->matchingArticlesTree->sortItems(0, Qt::AscendingOrder); + m_ui->matchingArticlesTree->setSortingEnabled(true); } void AutomatedRssDownloader::updateFieldsToolTips(bool regex) diff --git a/src/gui/rss/automatedrssdownloader.h b/src/gui/rss/automatedrssdownloader.h index acf588e17..7c95bcb65 100644 --- a/src/gui/rss/automatedrssdownloader.h +++ b/src/gui/rss/automatedrssdownloader.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2017 Vladimir Golovnev + * Copyright (C) 2017, 2023 Vladimir Golovnev * Copyright (C) 2010 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -51,6 +51,8 @@ namespace Ui class AutomatedRssDownloader; } +class AddTorrentParamsWidget; + class AutomatedRssDownloader : public QDialog { Q_OBJECT @@ -61,13 +63,11 @@ public: ~AutomatedRssDownloader() override; private slots: - void on_addRuleBtn_clicked(); - void on_removeRuleBtn_clicked(); - void on_addCategoryBtn_clicked(); - void on_exportBtn_clicked(); - void on_importBtn_clicked(); - void on_renameRuleBtn_clicked(); - + void onAddRuleBtnClicked(); + void onRemoveRuleBtnClicked(); + void onExportBtnClicked(); + void onImportBtnClicked(); + void onRenameRuleBtnClicked(); void handleRuleCheckStateChange(QListWidgetItem *ruleItem); void handleFeedCheckStateChange(QListWidgetItem *feedItem); void displayRulesListMenu(); @@ -90,7 +90,6 @@ private: void loadSettings(); void saveSettings(); void createRuleItem(const RSS::AutoDownloadRule &rule); - void initCategoryCombobox(); void clearRuleDefinitionBox(); void updateEditedRule(); void updateMatchingArticles(); @@ -103,6 +102,7 @@ private: const QString m_formatFilterLegacy; Ui::AutomatedRssDownloader *m_ui = nullptr; + AddTorrentParamsWidget *m_addTorrentParamsWidget = nullptr; QListWidgetItem *m_currentRuleItem = nullptr; QSet> m_treeListEntries; RSS::AutoDownloadRule m_currentRule; @@ -110,5 +110,6 @@ private: QRegularExpression *m_episodeRegex = nullptr; SettingValue m_storeDialogSize; - SettingValue m_storeHSplitterSize; + SettingValue m_storeMainSplitterState; + SettingValue m_storeRuleDefSplitterState; }; diff --git a/src/gui/rss/automatedrssdownloader.ui b/src/gui/rss/automatedrssdownloader.ui index b54ab5841..aa0792237 100644 --- a/src/gui/rss/automatedrssdownloader.ui +++ b/src/gui/rss/automatedrssdownloader.ui @@ -7,13 +7,13 @@ 0 0 818 - 571 + 588 RSS Downloader - + @@ -33,7 +33,7 @@ - + 0 @@ -43,15 +43,14 @@ Qt::Horizontal - - + + - + - + - 75 true @@ -99,7 +98,7 @@ - + Qt::CustomContextMenu @@ -107,313 +106,240 @@ - - - - - - Rule Definition - - - - - - Use Regular Expressions - - - - - - - - - Must Contain: - - - - - - - Must Not Contain: - - - - - - - Episode Filter: - - - - - - - - 18 - 18 - - - - - - - - - 18 - 18 - - - - - - - - - - - - 18 - 18 - - - - - - - - - 18 - 18 - - - - - - - - - - - - - - - - Smart Episode Filter will check the episode number to prevent downloading of duplicates. + + + Qt::Vertical + + + + + 0 + 0 + + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustToContentsOnFirstShow + + + true + + + + + 0 + 0 + 329 + 243 + + + + + + + Use Regular Expressions + + + + + + + + + Must Contain: + + + + + + + Must Not Contain: + + + + + + + Episode Filter: + + + + + + + + 18 + 18 + + + + + + + + + 18 + 18 + + + + + + + + + + + + 18 + 18 + + + + + + + + + 18 + 18 + + + + + + + + + + + + + + + + Smart Episode Filter will check the episode number to prevent downloading of duplicates. Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also support - as a separator) + + + Use Smart Episode Filter + + + + + + + + + Ignore Subsequent Matches for (0 to Disable) + + + + + + + true + + + Disabled + + + days + + + 0 + + + 365 + + + + + + + + + true + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Torrent parameters + + + true + + + + 0 - - Use Smart Episode Filter - - - - - - - Qt::Horizontal - - - - - - - - - - 0 - 0 - - - - Category: - - - - - - - false - - - - - - - - - - - - Save to a Different Directory + + 0 - - - - - - - - false - - - Save to: - - - - - - - - - - - - - - Ignore Subsequent Matches for (0 to Disable) - - - - - - - true - - - Disabled - - - days - - - 0 - - - 365 - - - - - - - - - true + + 0 - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 0 - - - - - - - - - 0 - 0 - - - - Add Paused: - - - - - - - - Use global settings - - - - - Always - - - - - Never - - - - - - - - - - - - - 0 - 0 - - - - Torrent content layout: - - - - - - - - Use global settings - - - - - Original - - - - - Create subfolder - - - - - Don't create subfolder - - - - - - - - - - - - - - - 50 - false - - - - Apply Rule to Feeds: - - + + + Qt::Vertical + + + + 20 + 0 + + + - - + + + + + + QLayout::SetDefaultConstraint + + + + + + false + + + + Apply Rule to Feeds: + + + + + + + + - - + + - + - 75 true @@ -423,7 +349,7 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also - + false @@ -439,7 +365,7 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also - + @@ -474,31 +400,17 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also - - - FileSystemPathLineEdit - QWidget -
    gui/fspathedit.h
    -
    -
    renameRuleBtn removeRuleBtn addRuleBtn - listRules - checkRegex - checkSmart + ruleList lineContains lineNotContains lineEFilter - comboCategory - checkBoxSaveDiffDir - lineSavePath spinIgnorePeriod - comboAddPaused - comboContentLayout listFeeds - treeMatchingArticles + matchingArticlesTree importBtn exportBtn @@ -536,37 +448,5 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also - - checkBoxSaveDiffDir - toggled(bool) - label_6 - setEnabled(bool) - - - 304 - 171 - - - 377 - 205 - - - - - checkBoxSaveDiffDir - toggled(bool) - lineSavePath - setEnabled(bool) - - - 474 - 174 - - - 476 - 204 - - - diff --git a/src/gui/watchedfolderoptionsdialog.cpp b/src/gui/watchedfolderoptionsdialog.cpp index 30b513c68..25610887d 100644 --- a/src/gui/watchedfolderoptionsdialog.cpp +++ b/src/gui/watchedfolderoptionsdialog.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 @@ -28,14 +28,9 @@ #include "watchedfolderoptionsdialog.h" -#include -#include - -#include "base/bittorrent/session.h" #include "base/global.h" -#include "base/utils/fs.h" +#include "addtorrentparamswidget.h" #include "ui_watchedfolderoptionsdialog.h" -#include "utils.h" #define SETTINGS_KEY(name) u"WatchedFolderOptionsDialog/" name @@ -43,56 +38,13 @@ WatchedFolderOptionsDialog::WatchedFolderOptionsDialog( const TorrentFilesWatcher::WatchedFolderOptions &watchedFolderOptions, QWidget *parent) : QDialog {parent} , m_ui {new Ui::WatchedFolderOptionsDialog} - , m_savePath {watchedFolderOptions.addTorrentParams.savePath} - , m_downloadPath {watchedFolderOptions.addTorrentParams.downloadPath} + , m_addTorrentParamsWidget {new AddTorrentParamsWidget(watchedFolderOptions.addTorrentParams)} , m_storeDialogSize {SETTINGS_KEY(u"DialogSize"_qs)} { m_ui->setupUi(this); - - m_ui->savePath->setMode(FileSystemPathEdit::Mode::DirectorySave); - m_ui->savePath->setDialogCaption(tr("Choose save path")); - - m_ui->downloadPath->setMode(FileSystemPathEdit::Mode::DirectorySave); - m_ui->downloadPath->setDialogCaption(tr("Choose save path")); - - const auto *session = BitTorrent::Session::instance(); - m_useDownloadPath = watchedFolderOptions.addTorrentParams.useDownloadPath.value_or(session->isDownloadPathEnabled()); - - connect(m_ui->comboTTM, qOverload(&QComboBox::currentIndexChanged), this, &WatchedFolderOptionsDialog::onTMMChanged); - connect(m_ui->categoryComboBox, qOverload(&QComboBox::currentIndexChanged), this, &WatchedFolderOptionsDialog::onCategoryChanged); - - m_ui->checkBoxRecursive->setChecked(watchedFolderOptions.recursive); - populateSavePaths(); - - const BitTorrent::AddTorrentParams &torrentParams = watchedFolderOptions.addTorrentParams; - m_ui->addToQueueTopCheckBox->setChecked(torrentParams.addToQueueTop.value_or(session->isAddTorrentToQueueTop())); - m_ui->startTorrentCheckBox->setChecked(!torrentParams.addPaused.value_or(session->isAddTorrentPaused())); - m_ui->skipCheckingCheckBox->setChecked(torrentParams.skipChecking); - m_ui->comboTTM->setCurrentIndex(torrentParams.useAutoTMM.value_or(!session->isAutoTMMDisabledByDefault())); - m_ui->contentLayoutComboBox->setCurrentIndex( - static_cast(torrentParams.contentLayout.value_or(session->torrentContentLayout()))); - - // Load categories - QStringList categories = session->categories(); - std::sort(categories.begin(), categories.end(), Utils::Compare::NaturalLessThan()); - - if (!torrentParams.category.isEmpty()) - m_ui->categoryComboBox->addItem(torrentParams.category); - m_ui->categoryComboBox->addItem(u""_qs); - - for (const QString &category : asConst(categories)) - { - if (category != torrentParams.category) - m_ui->categoryComboBox->addItem(category); - } + m_ui->groupBoxParameters->layout()->addWidget(m_addTorrentParamsWidget); loadState(); - - // Default focus - if (m_ui->comboTTM->currentIndex() == 0) // 0 is Manual mode - m_ui->savePath->setFocus(); - else - m_ui->categoryComboBox->setFocus(); } WatchedFolderOptionsDialog::~WatchedFolderOptionsDialog() @@ -105,22 +57,7 @@ TorrentFilesWatcher::WatchedFolderOptions WatchedFolderOptionsDialog::watchedFol { TorrentFilesWatcher::WatchedFolderOptions watchedFolderOptions; watchedFolderOptions.recursive = m_ui->checkBoxRecursive->isChecked(); - - BitTorrent::AddTorrentParams ¶ms = watchedFolderOptions.addTorrentParams; - const bool useAutoTMM = (m_ui->comboTTM->currentIndex() == 1); - if (!useAutoTMM) - { - params.savePath = m_ui->savePath->selectedPath(); - params.useDownloadPath = m_ui->groupBoxDownloadPath->isChecked(); - if (params.useDownloadPath) - params.downloadPath = m_ui->downloadPath->selectedPath(); - } - params.useAutoTMM = useAutoTMM; - params.category = m_ui->categoryComboBox->currentText(); - params.addToQueueTop = m_ui->addToQueueTopCheckBox->isChecked(); - params.addPaused = !m_ui->startTorrentCheckBox->isChecked(); - params.skipChecking = m_ui->skipCheckingCheckBox->isChecked(); - params.contentLayout = static_cast(m_ui->contentLayoutComboBox->currentIndex()); + watchedFolderOptions.addTorrentParams = m_addTorrentParamsWidget->addTorrentParams(); return watchedFolderOptions; } @@ -135,65 +72,3 @@ void WatchedFolderOptionsDialog::saveState() { m_storeDialogSize = size(); } - -void WatchedFolderOptionsDialog::onCategoryChanged(const int index) -{ - Q_UNUSED(index); - - if (m_ui->comboTTM->currentIndex() == 1) - { - const auto *btSession = BitTorrent::Session::instance(); - const QString categoryName = m_ui->categoryComboBox->currentText(); - - const Path savePath = btSession->categorySavePath(categoryName); - m_ui->savePath->setSelectedPath(savePath); - - const Path downloadPath = btSession->categoryDownloadPath(categoryName); - m_ui->downloadPath->setSelectedPath(downloadPath); - - m_ui->groupBoxDownloadPath->setChecked(!downloadPath.isEmpty()); - } -} - -void WatchedFolderOptionsDialog::populateSavePaths() -{ - const auto *btSession = BitTorrent::Session::instance(); - - const Path defaultSavePath {btSession->savePath()}; - m_ui->savePath->setSelectedPath(!m_savePath.isEmpty() ? m_savePath : defaultSavePath); - - const Path defaultDownloadPath {btSession->downloadPath()}; - m_ui->downloadPath->setSelectedPath(!m_downloadPath.isEmpty() ? m_downloadPath : defaultDownloadPath); - - m_ui->groupBoxDownloadPath->setChecked(m_useDownloadPath); -} - -void WatchedFolderOptionsDialog::onTMMChanged(const int index) -{ - if (index != 1) - { // 0 is Manual mode and 1 is Automatic mode. Handle all non 1 values as manual mode. - populateSavePaths(); - m_ui->groupBoxSavePath->setEnabled(true); - m_ui->savePath->blockSignals(false); - m_ui->downloadPath->blockSignals(false); - } - else - { - m_ui->groupBoxSavePath->setEnabled(false); - - const auto *btSession = BitTorrent::Session::instance(); - - m_ui->savePath->blockSignals(true); - m_savePath = m_ui->savePath->selectedPath(); - const Path savePath = btSession->categorySavePath(m_ui->categoryComboBox->currentText()); - m_ui->savePath->setSelectedPath(savePath); - - m_ui->downloadPath->blockSignals(true); - m_downloadPath = m_ui->downloadPath->selectedPath(); - const Path downloadPath = btSession->categoryDownloadPath(m_ui->categoryComboBox->currentText()); - m_ui->downloadPath->setSelectedPath(downloadPath); - - m_useDownloadPath = m_ui->groupBoxDownloadPath->isChecked(); - m_ui->groupBoxDownloadPath->setChecked(!downloadPath.isEmpty()); - } -} diff --git a/src/gui/watchedfolderoptionsdialog.h b/src/gui/watchedfolderoptionsdialog.h index 4292b5001..2029e5d03 100644 --- a/src/gui/watchedfolderoptionsdialog.h +++ b/src/gui/watchedfolderoptionsdialog.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 @@ -30,7 +30,6 @@ #include -#include "base/path.h" #include "base/settingvalue.h" #include "base/torrentfileswatcher.h" @@ -39,6 +38,8 @@ namespace Ui class WatchedFolderOptionsDialog; } +class AddTorrentParamsWidget; + class WatchedFolderOptionsDialog final : public QDialog { Q_OBJECT @@ -58,8 +59,6 @@ private: void onCategoryChanged(int index); Ui::WatchedFolderOptionsDialog *m_ui = nullptr; - Path m_savePath; - Path m_downloadPath; - bool m_useDownloadPath = false; + AddTorrentParamsWidget *m_addTorrentParamsWidget = nullptr; SettingValue m_storeDialogSize; }; diff --git a/src/gui/watchedfolderoptionsdialog.ui b/src/gui/watchedfolderoptionsdialog.ui index 1e14ae385..1633ea342 100644 --- a/src/gui/watchedfolderoptionsdialog.ui +++ b/src/gui/watchedfolderoptionsdialog.ui @@ -7,7 +7,7 @@ 0 0 462 - 364 + 392
    @@ -42,267 +42,24 @@
    - - - Qt::Horizontal - - - false + + + Torrent parameters - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Torrent parameters - - - - - - - - Torrent Management Mode: - - - - - - - Automatic mode means that various torrent properties(eg save path) will be decided by the associated category - - - - Manual - - - - - Automatic - - - - - - - - Qt::Horizontal - - - - 20 - 20 - - - - - - - - - - Save at - - - - - - - - - Use another path for incomplete torrents - - - true - - - false - - - - - - - - - - - - - - - - - Category: - - - - - - - - 0 - 0 - - - - true - - - QComboBox::InsertAtTop - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Start torrent - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Add to top of queue - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Skip hash check - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Content layout: - - - - - - - 0 - - - - Original - - - - - Create subfolder - - - - - Don't create subfolder - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - + + + 0 + + + 0 + + + 0 + + + 0 + + @@ -331,20 +88,6 @@
    - - - FileSystemPathLineEdit - QWidget -
    gui/fspathedit.h
    - 1 -
    - - FileSystemPathLineEdit - QWidget -
    gui/fspathedit.h
    - 1 -
    -