mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2026-01-01 13:18:06 -06:00
Allow to add root folder to torrent content
This commit is contained in:
committed by
sledgehammer999
parent
cd0b6d9a43
commit
c08ec1ac5e
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
51
src/base/bittorrent/torrentcontentlayout.h
Normal file
51
src/base/bittorrent/torrentcontentlayout.h
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user