Allow to add root folder to torrent content

This commit is contained in:
Vladimir Golovnev (Glassez)
2020-12-10 09:54:27 +03:00
committed by sledgehammer999
parent cd0b6d9a43
commit c08ec1ac5e
30 changed files with 452 additions and 172 deletions

View File

@@ -24,6 +24,7 @@ add_library(qbt_base STATIC
bittorrent/sessionstatus.h
bittorrent/speedmonitor.h
bittorrent/statistics.h
bittorrent/torrentcontentlayout.h
bittorrent/torrentcreatorthread.h
bittorrent/torrenthandle.h
bittorrent/torrenthandleimpl.h

View File

@@ -23,6 +23,7 @@ HEADERS += \
$$PWD/bittorrent/sessionstatus.h \
$$PWD/bittorrent/speedmonitor.h \
$$PWD/bittorrent/statistics.h \
$$PWD/bittorrent/torrentcontentlayout.h \
$$PWD/bittorrent/torrentcreatorthread.h \
$$PWD/bittorrent/torrenthandle.h \
$$PWD/bittorrent/torrenthandleimpl.h \

View File

@@ -28,12 +28,15 @@
#pragma once
#include <boost/optional.hpp>
#include <QSet>
#include <QString>
#include <QVector>
#include "base/tristatebool.h"
#include "torrenthandle.h"
#include "torrentcontentlayout.h"
namespace BitTorrent
{
@@ -52,7 +55,7 @@ namespace BitTorrent
TriStateBool addPaused;
QVector<DownloadPriority> filePriorities; // used if TorrentInfo is set
bool skipChecking = false;
TriStateBool createSubfolder;
boost::optional<BitTorrent::TorrentContentLayout> contentLayout;
TriStateBool useAutoTMM;
int uploadLimit = -1;
int downloadLimit = -1;

View File

@@ -396,7 +396,7 @@ Session::Session(QObject *parent)
, m_globalMaxRatio(BITTORRENT_SESSION_KEY("GlobalMaxRatio"), -1, [](qreal r) { return r < 0 ? -1. : r;})
, m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY("GlobalMaxSeedingMinutes"), -1, lowerLimited(-1))
, m_isAddTorrentPaused(BITTORRENT_SESSION_KEY("AddTorrentPaused"), false)
, m_isKeepTorrentTopLevelFolder(BITTORRENT_SESSION_KEY("CreateTorrentSubfolder"), true)
, m_torrentContentLayout(BITTORRENT_SESSION_KEY("TorrentContentLayout"), TorrentContentLayout::Original)
, m_isAppendExtensionEnabled(BITTORRENT_SESSION_KEY("AddExtensionToIncompleteFiles"), false)
, m_refreshInterval(BITTORRENT_SESSION_KEY("RefreshInterval"), 1500)
, m_isPreallocationEnabled(BITTORRENT_SESSION_KEY("Preallocation"), false)
@@ -2058,9 +2058,9 @@ LoadTorrentParams Session::initLoadTorrentParams(const AddTorrentParams &addTorr
loadTorrentParams.tags = addTorrentParams.tags;
loadTorrentParams.firstLastPiecePriority = addTorrentParams.firstLastPiecePriority;
loadTorrentParams.hasSeedStatus = addTorrentParams.skipChecking; // do not react on 'torrent_finished_alert' when skipping
loadTorrentParams.hasRootFolder = ((addTorrentParams.createSubfolder == TriStateBool::Undefined)
? isKeepTorrentTopLevelFolder()
: (addTorrentParams.createSubfolder == TriStateBool::True));
loadTorrentParams.contentLayout = (addTorrentParams.contentLayout
? *addTorrentParams.contentLayout
: torrentContentLayout());
loadTorrentParams.forced = (addTorrentParams.addForced == TriStateBool::True);
loadTorrentParams.paused = ((addTorrentParams.addPaused == TriStateBool::Undefined)
? isAddTorrentPaused()
@@ -2125,8 +2125,7 @@ bool Session::addTorrent_impl(const AddTorrentParams &addTorrentParams, const Ma
const QString actualSavePath = loadTorrentParams.savePath.isEmpty() ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath;
if (hasMetadata)
{
if (!loadTorrentParams.hasRootFolder)
metadata.stripRootFolder();
metadata.setContentLayout(loadTorrentParams.contentLayout);
if (!loadTorrentParams.hasSeedStatus)
{
@@ -4195,9 +4194,27 @@ bool Session::loadTorrentResumeData(const QByteArray &data, const TorrentInfo &m
Utils::Fs::toUniformPath(fromLTString(root.dict_find_string_value("qBt-savePath"))));
torrentParams.hasSeedStatus = root.dict_find_int_value("qBt-seedStatus");
torrentParams.firstLastPiecePriority = root.dict_find_int_value("qBt-firstLastPiecePriority");
torrentParams.hasRootFolder = root.dict_find_int_value("qBt-hasRootFolder");
torrentParams.seedingTimeLimit = root.dict_find_int_value("qBt-seedingTimeLimit", TorrentHandle::USE_GLOBAL_SEEDING_TIME);
// TODO: The following code is deprecated. Replace with the commented one after several releases in 4.4.x.
// === BEGIN DEPRECATED CODE === //
const lt::bdecode_node contentLayoutNode = root.dict_find("qBt-contentLayout");
if (contentLayoutNode.type() == lt::bdecode_node::string_t)
{
const QString contentLayoutStr = fromLTString(contentLayoutNode.string_value());
torrentParams.contentLayout = Utils::String::toEnum(contentLayoutStr, TorrentContentLayout::Original);
}
else
{
const bool hasRootFolder = root.dict_find_int_value("qBt-hasRootFolder");
torrentParams.contentLayout = (hasRootFolder ? TorrentContentLayout::Original : TorrentContentLayout::NoSubfolder);
}
// === END DEPRECATED CODE === //
// === BEGIN REPLACEMENT CODE === //
// torrentParams.contentLayout = Utils::String::parse(
// fromLTString(root.dict_find_string_value("qBt-contentLayout")), TorrentContentLayout::Default);
// === END REPLACEMENT CODE === //
const lt::string_view ratioLimitString = root.dict_find_string_value("qBt-ratioLimit");
if (ratioLimitString.empty())
torrentParams.ratioLimit = root.dict_find_int_value("qBt-ratioLimit", TorrentHandle::USE_GLOBAL_RATIO * 1000) / 1000.0;
@@ -4428,14 +4445,14 @@ std::vector<lt::alert *> Session::getPendingAlerts(const lt::time_duration time)
return alerts;
}
bool Session::isKeepTorrentTopLevelFolder() const
TorrentContentLayout Session::torrentContentLayout() const
{
return m_isKeepTorrentTopLevelFolder;
return m_torrentContentLayout;
}
void Session::setKeepTorrentTopLevelFolder(const bool value)
void Session::setTorrentContentLayout(const TorrentContentLayout value)
{
m_isKeepTorrentTopLevelFolder = value;
m_torrentContentLayout = value;
}
// Read alerts sent by the BitTorrent session

View File

@@ -114,7 +114,7 @@ namespace BitTorrent
// Using `Q_ENUM_NS()` without a wrapper namespace in our case is not advised
// since `Q_NAMESPACE` cannot be used when the same namespace resides at different files.
// https://www.kdab.com/new-qt-5-8-meta-object-support-namespaces/#comment-143779
namespace SessionSettingsEnums
inline namespace SessionSettingsEnums
{
Q_NAMESPACE
@@ -160,7 +160,6 @@ namespace BitTorrent
Q_ENUM_NS(OSMemoryPriority)
#endif
}
using namespace SessionSettingsEnums;
struct SessionMetricIndices
{
@@ -276,8 +275,8 @@ namespace BitTorrent
void setPeXEnabled(bool enabled);
bool isAddTorrentPaused() const;
void setAddTorrentPaused(bool value);
bool isKeepTorrentTopLevelFolder() const;
void setKeepTorrentTopLevelFolder(bool value);
TorrentContentLayout torrentContentLayout() const;
void setTorrentContentLayout(TorrentContentLayout value);
bool isTrackerEnabled() const;
void setTrackerEnabled(bool enabled);
bool isAppendExtensionEnabled() const;
@@ -704,7 +703,7 @@ namespace BitTorrent
CachedSettingValue<qreal> m_globalMaxRatio;
CachedSettingValue<int> m_globalMaxSeedingMinutes;
CachedSettingValue<bool> m_isAddTorrentPaused;
CachedSettingValue<bool> m_isKeepTorrentTopLevelFolder;
CachedSettingValue<TorrentContentLayout> m_torrentContentLayout;
CachedSettingValue<bool> m_isAppendExtensionEnabled;
CachedSettingValue<int> m_refreshInterval;
CachedSettingValue<bool> m_isPreallocationEnabled;

View File

@@ -0,0 +1,51 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2020 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#pragma once
#include <QMetaEnum>
namespace BitTorrent
{
// Using `Q_ENUM_NS()` without a wrapper namespace in our case is not advised
// since `Q_NAMESPACE` cannot be used when the same namespace resides at different files.
// https://www.kdab.com/new-qt-5-8-meta-object-support-namespaces/#comment-143779
inline namespace TorrentContentLayoutNS
{
Q_NAMESPACE
enum class TorrentContentLayout
{
Original,
Subfolder,
NoSubfolder
};
Q_ENUM_NS(TorrentContentLayout)
}
}

View File

@@ -177,8 +177,6 @@ namespace BitTorrent
virtual bool removeTag(const QString &tag) = 0;
virtual void removeAllTags() = 0;
virtual bool hasRootFolder() const = 0;
virtual int filesCount() const = 0;
virtual int piecesCount() const = 0;
virtual int piecesHave() const = 0;

View File

@@ -115,8 +115,8 @@ TorrentHandleImpl::TorrentHandleImpl(Session *session, lt::session *nativeSessio
, m_ratioLimit(params.ratioLimit)
, m_seedingTimeLimit(params.seedingTimeLimit)
, m_operatingMode(params.forced ? TorrentOperatingMode::Forced : TorrentOperatingMode::AutoManaged)
, m_contentLayout(params.contentLayout)
, m_hasSeedStatus(params.hasSeedStatus)
, m_hasRootFolder(params.hasRootFolder)
, m_hasFirstLastPiecePriority(params.firstLastPiecePriority)
, m_useAutoTMM(params.savePath.isEmpty())
, m_isStopped(params.paused)
@@ -136,16 +136,8 @@ TorrentHandleImpl::TorrentHandleImpl(Session *session, lt::session *nativeSessio
updateStatus();
if (hasMetadata())
{
applyFirstLastPiecePriority(m_hasFirstLastPiecePriority);
if (!params.restored)
{
if (filesCount() == 1)
m_hasRootFolder = false;
}
}
// TODO: Remove the following upgrade code in v.4.4
// == BEGIN UPGRADE CODE ==
const QString spath = actualStorageLocation();
@@ -259,7 +251,7 @@ QString TorrentHandleImpl::savePath(bool actual) const
QString TorrentHandleImpl::rootPath(bool actual) const
{
if ((filesCount() > 1) && !hasRootFolder())
if (!hasMetadata())
return {};
const QString firstFilePath = filePath(0);
@@ -272,10 +264,13 @@ QString TorrentHandleImpl::rootPath(bool actual) const
QString TorrentHandleImpl::contentPath(const bool actual) const
{
if (!hasMetadata())
return {};
if (filesCount() == 1)
return QDir(savePath(actual)).absoluteFilePath(filePath(0));
if (hasRootFolder())
if (m_torrentInfo.hasRootFolder())
return rootPath(actual);
return savePath(actual);
@@ -297,11 +292,6 @@ void TorrentHandleImpl::setAutoTMMEnabled(bool enabled)
move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite);
}
bool TorrentHandleImpl::hasRootFolder() const
{
return m_hasRootFolder;
}
QString TorrentHandleImpl::actualStorageLocation() const
{
return QString::fromStdString(m_nativeStatus.save_path);
@@ -1581,8 +1571,7 @@ void TorrentHandleImpl::handleSaveResumeDataAlert(const lt::save_resume_data_ale
m_ltAddTorrentParams.verified_pieces.clear();
TorrentInfo metadata = TorrentInfo {m_nativeHandle.torrent_file()};
if (!m_hasRootFolder)
metadata.stripRootFolder();
metadata.setContentLayout(m_contentLayout);
m_session->findIncompleteFiles(metadata, m_savePath);
}
@@ -1614,7 +1603,7 @@ void TorrentHandleImpl::handleSaveResumeDataAlert(const lt::save_resume_data_ale
resumeData["qBt-tags"] = setToEntryList(m_tags);
resumeData["qBt-name"] = m_name.toStdString();
resumeData["qBt-seedStatus"] = m_hasSeedStatus;
resumeData["qBt-hasRootFolder"] = m_hasRootFolder;
resumeData["qBt-contentLayout"] = Utils::String::fromEnum(m_contentLayout).toStdString();
resumeData["qBt-firstLastPiecePriority"] = m_hasFirstLastPiecePriority;
m_session->handleTorrentResumeDataReady(this, resumeDataPtr);

View File

@@ -62,12 +62,13 @@ namespace BitTorrent
QString category;
QSet<QString> tags;
QString savePath;
TorrentContentLayout contentLayout = TorrentContentLayout::Original;
bool firstLastPiecePriority = false;
bool hasSeedStatus = false;
bool hasRootFolder = true;
bool forced = false;
bool paused = false;
qreal ratioLimit = TorrentHandle::USE_GLOBAL_RATIO;
int seedingTimeLimit = TorrentHandle::USE_GLOBAL_SEEDING_TIME;
@@ -129,8 +130,6 @@ namespace BitTorrent
bool removeTag(const QString &tag) override;
void removeAllTags() override;
bool hasRootFolder() const override;
int filesCount() const override;
int piecesCount() const override;
int piecesHave() const override;
@@ -319,10 +318,10 @@ namespace BitTorrent
qreal m_ratioLimit;
int m_seedingTimeLimit;
TorrentOperatingMode m_operatingMode;
TorrentContentLayout m_contentLayout;
bool m_hasSeedStatus;
bool m_fastresumeDataRejected = false;
bool m_hasMissingFiles = false;
bool m_hasRootFolder;
bool m_hasFirstLastPiecePriority = false;
bool m_useAutoTMM;
bool m_isStopped;

View File

@@ -52,6 +52,29 @@
using namespace BitTorrent;
namespace
{
QString getRootFolder(const QStringList &filePaths)
{
QString rootFolder;
for (const QString &filePath : filePaths)
{
if (QDir::isAbsolutePath(filePath)) continue;
const auto filePathElements = filePath.splitRef('/');
// if at least one file has no root folder, no common root folder exists
if (filePathElements.count() <= 1) return {};
if (rootFolder.isEmpty())
rootFolder = filePathElements.at(0).toString();
else if (rootFolder != filePathElements.at(0))
return {};
}
return rootFolder;
}
}
TorrentInfo::TorrentInfo(std::shared_ptr<const lt::torrent_info> nativeInfo)
{
m_nativeInfo = std::const_pointer_cast<lt::torrent_info>(nativeInfo);
@@ -412,23 +435,7 @@ int TorrentInfo::fileIndex(const QString &fileName) const
QString TorrentInfo::rootFolder() const
{
QString rootFolder;
for (int i = 0; i < filesCount(); ++i)
{
const QString filePath = this->filePath(i);
if (QDir::isAbsolutePath(filePath)) continue;
const auto filePathElements = filePath.splitRef('/');
// if at least one file has no root folder, no common root folder exists
if (filePathElements.count() <= 1) return "";
if (rootFolder.isEmpty())
rootFolder = filePathElements.at(0).toString();
else if (rootFolder != filePathElements.at(0))
return "";
}
return rootFolder;
return getRootFolder(filePaths());
}
bool TorrentInfo::hasRootFolder() const
@@ -436,10 +443,26 @@ bool TorrentInfo::hasRootFolder() const
return !rootFolder().isEmpty();
}
void TorrentInfo::setContentLayout(const TorrentContentLayout layout)
{
switch (layout)
{
case TorrentContentLayout::Original:
setContentLayout(defaultContentLayout());
break;
case TorrentContentLayout::Subfolder:
if (rootFolder().isEmpty())
addRootFolder();
break;
case TorrentContentLayout::NoSubfolder:
if (!rootFolder().isEmpty())
stripRootFolder();
break;
}
}
void TorrentInfo::stripRootFolder()
{
if (!hasRootFolder()) return;
lt::file_storage files = m_nativeInfo->files();
// Solution for case of renamed root folder
@@ -456,6 +479,32 @@ void TorrentInfo::stripRootFolder()
m_nativeInfo->remap_files(files);
}
void TorrentInfo::addRootFolder()
{
const QString rootFolder = name();
Q_ASSERT(!rootFolder.isEmpty());
const std::string rootPrefix = Utils::Fs::toNativePath(rootFolder + QLatin1Char {'/'}).toStdString();
lt::file_storage files = m_nativeInfo->files();
files.set_name(rootFolder.toStdString());
for (int i = 0; i < files.num_files(); ++i)
files.rename_file(lt::file_index_t {i}, rootPrefix + files.file_path(lt::file_index_t {i}));
m_nativeInfo->remap_files(files);
}
TorrentContentLayout TorrentInfo::defaultContentLayout() const
{
QStringList origFilePaths;
origFilePaths.reserve(filesCount());
for (int i = 0; i < filesCount(); ++i)
origFilePaths << origFilePath(i);
const QString origRootFolder = getRootFolder(origFilePaths);
return (origRootFolder.isEmpty()
? TorrentContentLayout::NoSubfolder
: TorrentContentLayout::Subfolder);
}
std::shared_ptr<lt::torrent_info> TorrentInfo::nativeInfo() const
{
return m_nativeInfo;

View File

@@ -34,6 +34,7 @@
#include <QtContainerFwd>
#include "base/indexrange.h"
#include "torrentcontentlayout.h"
class QByteArray;
class QDateTime;
@@ -94,13 +95,17 @@ namespace BitTorrent
QString rootFolder() const;
bool hasRootFolder() const;
void stripRootFolder();
void setContentLayout(TorrentContentLayout layout);
std::shared_ptr<lt::torrent_info> nativeInfo() const;
private:
// returns file index or -1 if fileName is not found
int fileIndex(const QString &fileName) const;
void stripRootFolder();
void addRootFolder();
TorrentContentLayout defaultContentLayout() const;
std::shared_ptr<lt::torrent_info> m_nativeInfo;
};
}

View File

@@ -396,7 +396,7 @@ void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job)
params.savePath = rule.savePath();
params.category = rule.assignedCategory();
params.addPaused = rule.addPaused();
params.createSubfolder = rule.createSubfolder();
params.contentLayout = rule.torrentContentLayout();
if (!rule.savePath().isEmpty())
params.useAutoTMM = TriStateBool::False;
const auto torrentURL = job->articleData.value(Article::KeyTorrentURL).toString();

View File

@@ -40,11 +40,11 @@
#include <QString>
#include <QStringList>
#include "../global.h"
#include "../preferences.h"
#include "../tristatebool.h"
#include "../utils/fs.h"
#include "../utils/string.h"
#include "base/global.h"
#include "base/preferences.h"
#include "base/tristatebool.h"
#include "base/utils/fs.h"
#include "base/utils/string.h"
#include "rss_article.h"
#include "rss_autodownloader.h"
#include "rss_feed.h"
@@ -91,6 +91,21 @@ namespace
default: return 0; // default
}
}
boost::optional<BitTorrent::TorrentContentLayout> jsonValueToContentLayout(const QJsonValue &jsonVal)
{
const QString str = jsonVal.toString();
if (str.isEmpty())
return {};
return Utils::String::toEnum(str, BitTorrent::TorrentContentLayout::Original);
}
QJsonValue contentLayoutToJsonValue(const boost::optional<BitTorrent::TorrentContentLayout> contentLayout)
{
if (!contentLayout)
return {};
return Utils::String::fromEnum(*contentLayout);
}
}
const QString Str_Name(QStringLiteral("name"));
@@ -106,6 +121,7 @@ const QString Str_LastMatch(QStringLiteral("lastMatch"));
const QString Str_IgnoreDays(QStringLiteral("ignoreDays"));
const QString Str_AddPaused(QStringLiteral("addPaused"));
const QString Str_CreateSubfolder(QStringLiteral("createSubfolder"));
const QString Str_ContentLayout(QStringLiteral("torrentContentLayout"));
const QString Str_SmartFilter(QStringLiteral("smartFilter"));
const QString Str_PreviouslyMatched(QStringLiteral("previouslyMatchedEpisodes"));
@@ -127,7 +143,7 @@ namespace RSS
QString savePath;
QString category;
TriStateBool addPaused = TriStateBool::Undefined;
TriStateBool createSubfolder = TriStateBool::Undefined;
boost::optional<BitTorrent::TorrentContentLayout> contentLayout;
bool smartFilter = false;
QStringList previouslyMatchedEpisodes;
@@ -149,7 +165,7 @@ namespace RSS
&& (savePath == other.savePath)
&& (category == other.category)
&& (addPaused == other.addPaused)
&& (createSubfolder == other.createSubfolder)
&& (contentLayout == other.contentLayout)
&& (smartFilter == other.smartFilter);
}
};
@@ -462,7 +478,7 @@ QJsonObject AutoDownloadRule::toJsonObject() const
, {Str_LastMatch, lastMatch().toString(Qt::RFC2822Date)}
, {Str_IgnoreDays, ignoreDays()}
, {Str_AddPaused, triStateBoolToJsonValue(addPaused())}
, {Str_CreateSubfolder, triStateBoolToJsonValue(createSubfolder())}
, {Str_ContentLayout, contentLayoutToJsonValue(torrentContentLayout())}
, {Str_SmartFilter, useSmartFilter()}
, {Str_PreviouslyMatched, QJsonArray::fromStringList(previouslyMatchedEpisodes())}};
}
@@ -479,7 +495,29 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
rule.setSavePath(jsonObj.value(Str_SavePath).toString());
rule.setCategory(jsonObj.value(Str_AssignedCategory).toString());
rule.setAddPaused(jsonValueToTriStateBool(jsonObj.value(Str_AddPaused)));
rule.setCreateSubfolder(jsonValueToTriStateBool(jsonObj.value(Str_CreateSubfolder)));
// 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 TriStateBool createSubfolder = jsonValueToTriStateBool(jsonObj.value(Str_CreateSubfolder));
boost::optional<BitTorrent::TorrentContentLayout> contentLayout;
if (createSubfolder == TriStateBool::True)
contentLayout = BitTorrent::TorrentContentLayout::Original;
else if (createSubfolder == TriStateBool::False)
contentLayout = BitTorrent::TorrentContentLayout::NoSubfolder;
rule.setTorrentContentLayout(contentLayout);
}
// === END DEPRECATED CODE === //
// === BEGIN REPLACEMENT CODE === //
// rule.setTorrentContentLayout(jsonValueToContentLayout(jsonObj.value(Str_ContentLayout)));
// === END REPLACEMENT CODE === //
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));
@@ -611,14 +649,14 @@ void AutoDownloadRule::setAddPaused(const TriStateBool addPaused)
m_dataPtr->addPaused = addPaused;
}
TriStateBool AutoDownloadRule::createSubfolder() const
boost::optional<BitTorrent::TorrentContentLayout> AutoDownloadRule::torrentContentLayout() const
{
return m_dataPtr->createSubfolder;
return m_dataPtr->contentLayout;
}
void AutoDownloadRule::setCreateSubfolder(const TriStateBool createSubfolder)
void AutoDownloadRule::setTorrentContentLayout(const boost::optional<BitTorrent::TorrentContentLayout> contentLayout)
{
m_dataPtr->createSubfolder = createSubfolder;
m_dataPtr->contentLayout = contentLayout;
}
QString AutoDownloadRule::assignedCategory() const

View File

@@ -29,9 +29,13 @@
#pragma once
#include <boost/optional.hpp>
#include <QSharedDataPointer>
#include <QVariant>
#include "base/bittorrent/torrentcontentlayout.h"
class QDateTime;
class QJsonObject;
class QRegularExpression;
@@ -79,8 +83,8 @@ namespace RSS
void setSavePath(const QString &savePath);
TriStateBool addPaused() const;
void setAddPaused(TriStateBool addPaused);
TriStateBool createSubfolder() const;
void setCreateSubfolder(TriStateBool createSubfolder);
boost::optional<BitTorrent::TorrentContentLayout> torrentContentLayout() const;
void setTorrentContentLayout(boost::optional<BitTorrent::TorrentContentLayout> contentLayout);
QString assignedCategory() const;
void setCategory(const QString &category);

View File

@@ -78,13 +78,13 @@ public:
private:
// regular load/save pair
template <typename U, typename std::enable_if<!std::is_enum<U>::value, int>::type = 0>
template <typename U, typename std::enable_if_t<!std::is_enum<U>::value, int> = 0>
U loadValue(const U &defaultValue)
{
return SettingsStorage::instance()->loadValue(m_keyName, defaultValue).template value<T>();
}
template <typename U, typename std::enable_if<!std::is_enum<U>::value, int>::type = 0>
template <typename U, typename std::enable_if_t<!std::is_enum<U>::value, int> = 0>
void storeValue(const U &value)
{
SettingsStorage::instance()->storeValue(m_keyName, value);
@@ -92,16 +92,16 @@ private:
// load/save pair for an enum
// saves literal value of the enum constant, obtained from QMetaEnum
template <typename U, typename std::enable_if<std::is_enum<U>::value, int>::type = 0>
template <typename U, typename std::enable_if_t<std::is_enum<U>::value, int> = 0>
U loadValue(const U &defaultValue)
{
return Utils::String::parse(SettingsStorage::instance()->loadValue(m_keyName).toString(), defaultValue);
return Utils::String::toEnum(SettingsStorage::instance()->loadValue(m_keyName).toString(), defaultValue);
}
template <typename U, typename std::enable_if<std::is_enum<U>::value, int>::type = 0>
template <typename U, typename std::enable_if_t<std::is_enum<U>::value, int> = 0>
void storeValue(const U &value)
{
SettingsStorage::instance()->storeValue(m_keyName, Utils::String::serialize(value));
SettingsStorage::instance()->storeValue(m_keyName, Utils::String::fromEnum(value));
}
const QString m_keyName;

View File

@@ -73,20 +73,25 @@ namespace Utils
QString join(const QVector<QStringRef> &strings, const QString &separator);
template <typename U, typename std::enable_if<std::is_enum<U>::value, int>::type = 0>
QString serialize(const U &value)
template <typename T, typename std::enable_if_t<std::is_enum<T>::value, int> = 0>
QString fromEnum(const T &value)
{
return QString::fromLatin1(QMetaEnum::fromType<U>().valueToKey(static_cast<int>(value)));
}
template <typename U, typename std::enable_if<std::is_enum<U>::value, int>::type = 0>
U parse(const QString &serializedValue, const U &defaultValue)
{
static_assert(std::is_same<int, typename std::underlying_type<U>::type>::value,
static_assert(std::is_same<int, typename std::underlying_type_t<T>>::value,
"Enumeration underlying type has to be int.");
const auto metaEnum = QMetaEnum::fromType<T>();
return QString::fromLatin1(metaEnum.valueToKey(static_cast<int>(value)));
}
template <typename T, typename std::enable_if_t<std::is_enum<T>::value, int> = 0>
T toEnum(const QString &serializedValue, const T &defaultValue)
{
static_assert(std::is_same<int, typename std::underlying_type_t<T>>::value,
"Enumeration underlying type has to be int.");
const auto metaEnum = QMetaEnum::fromType<T>();
bool ok = false;
const U value = static_cast<U>(QMetaEnum::fromType<U>().keyToValue(serializedValue.toLatin1().constData(), &ok));
const T value = static_cast<T>(metaEnum.keyToValue(serializedValue.toLatin1().constData(), &ok));
return (ok ? value : defaultValue);
}
}