mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2024-11-26 19:26:59 +03:00
Merge pull request #13955 from glassez/content-policy
Improve content root folder handling
This commit is contained in:
commit
70b242f190
30 changed files with 470 additions and 182 deletions
|
@ -29,12 +29,15 @@
|
|||
#include "upgrade.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QMetaEnum>
|
||||
#include <QVector>
|
||||
|
||||
#include "base/bittorrent/torrentcontentlayout.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/profile.h"
|
||||
#include "base/settingsstorage.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/string.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -79,11 +82,32 @@ namespace
|
|||
, QLatin1String("Preferences/WebUI/HTTPS/KeyPath")
|
||||
, Utils::Fs::toNativePath(configPath + QLatin1String("WebUIPrivateKey.pem")));
|
||||
}
|
||||
|
||||
void upgradeTorrentContentLayout()
|
||||
{
|
||||
const QString oldKey {QLatin1String {"BitTorrent/Session/CreateTorrentSubfolder"}};
|
||||
const QString newKey {QLatin1String {"BitTorrent/Session/TorrentContentLayout"}};
|
||||
|
||||
SettingsStorage *settingsStorage {SettingsStorage::instance()};
|
||||
const QVariant oldData {settingsStorage->loadValue(oldKey)};
|
||||
const QString newData {settingsStorage->loadValue(newKey).toString()};
|
||||
|
||||
if (!newData.isEmpty() || !oldData.isValid())
|
||||
return;
|
||||
|
||||
const bool createSubfolder = oldData.toBool();
|
||||
const BitTorrent::TorrentContentLayout torrentContentLayout =
|
||||
(createSubfolder ? BitTorrent::TorrentContentLayout::Original : BitTorrent::TorrentContentLayout::NoSubfolder);
|
||||
|
||||
settingsStorage->storeValue(newKey, Utils::String::fromEnum(torrentContentLayout));
|
||||
settingsStorage->removeValue(oldKey);
|
||||
}
|
||||
}
|
||||
|
||||
bool upgrade(const bool /*ask*/)
|
||||
{
|
||||
exportWebUIHttpsFiles();
|
||||
upgradeTorrentContentLayout();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
@ -661,9 +661,7 @@ QString Session::tempPath() const
|
|||
QString Session::torrentTempPath(const TorrentInfo &torrentInfo) const
|
||||
{
|
||||
if ((torrentInfo.filesCount() > 1) && !torrentInfo.hasRootFolder())
|
||||
return tempPath()
|
||||
+ QString::fromStdString(torrentInfo.nativeInfo()->orig_files().name())
|
||||
+ '/';
|
||||
return tempPath() + torrentInfo.name() + '/';
|
||||
|
||||
return tempPath();
|
||||
}
|
||||
|
@ -2060,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()
|
||||
|
@ -2127,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)
|
||||
{
|
||||
|
@ -4197,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;
|
||||
|
@ -4430,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();
|
||||
|
@ -185,17 +177,15 @@ InfoHash TorrentHandleImpl::hash() const
|
|||
|
||||
QString TorrentHandleImpl::name() const
|
||||
{
|
||||
QString name = m_name;
|
||||
if (!name.isEmpty()) return name;
|
||||
|
||||
name = QString::fromStdString(m_nativeStatus.name);
|
||||
if (!name.isEmpty()) return name;
|
||||
if (!m_name.isEmpty())
|
||||
return m_name;
|
||||
|
||||
if (hasMetadata())
|
||||
{
|
||||
name = QString::fromStdString(m_torrentInfo.nativeInfo()->orig_files().name());
|
||||
if (!name.isEmpty()) return name;
|
||||
}
|
||||
return m_torrentInfo.name();
|
||||
|
||||
const QString name = QString::fromStdString(m_nativeStatus.name);
|
||||
if (!name.isEmpty())
|
||||
return name;
|
||||
|
||||
return m_hash;
|
||||
}
|
||||
|
@ -261,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);
|
||||
|
@ -274,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);
|
||||
|
@ -299,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);
|
||||
|
@ -1583,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);
|
||||
}
|
||||
|
@ -1616,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);
|
||||
|
@ -170,7 +193,7 @@ InfoHash TorrentInfo::hash() const
|
|||
QString TorrentInfo::name() const
|
||||
{
|
||||
if (!isValid()) return {};
|
||||
return QString::fromStdString(m_nativeInfo->name());
|
||||
return QString::fromStdString(m_nativeInfo->orig_files().name());
|
||||
}
|
||||
|
||||
QDateTime TorrentInfo::creationDate() const
|
||||
|
@ -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);
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include <QString>
|
||||
|
||||
#include "settingsstorage.h"
|
||||
#include "utils/string.h"
|
||||
|
||||
template <typename T>
|
||||
class CachedSettingValue
|
||||
|
@ -77,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);
|
||||
|
@ -91,23 +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)
|
||||
{
|
||||
static_assert(std::is_same<int, typename std::underlying_type<U>::type>::value,
|
||||
"Enumeration underlying type has to be int");
|
||||
|
||||
bool ok = false;
|
||||
const U res = static_cast<U>(QMetaEnum::fromType<U>().keyToValue(
|
||||
SettingsStorage::instance()->loadValue(m_keyName).toString().toLatin1().constData(), &ok));
|
||||
return ok ? res : 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,
|
||||
QString::fromLatin1(QMetaEnum::fromType<U>().valueToKey(static_cast<int>(value))));
|
||||
SettingsStorage::instance()->storeValue(m_keyName, Utils::String::fromEnum(value));
|
||||
}
|
||||
|
||||
const QString m_keyName;
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <QChar>
|
||||
#include <QMetaEnum>
|
||||
#include <QString>
|
||||
#include <Qt>
|
||||
#include <QtContainerFwd>
|
||||
|
@ -71,5 +72,27 @@ namespace Utils
|
|||
TriStateBool parseTriStateBool(const QString &string);
|
||||
|
||||
QString join(const QVector<QStringRef> &strings, const QString &separator);
|
||||
|
||||
template <typename T, typename std::enable_if_t<std::is_enum<T>::value, int> = 0>
|
||||
QString fromEnum(const T &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 T value = static_cast<T>(metaEnum.keyToValue(serializedValue.toLatin1().constData(), &ok));
|
||||
return (ok ? value : defaultValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,12 +123,8 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
|
|||
const bool rememberLastSavePath = settings()->loadValue(KEY_REMEMBERLASTSAVEPATH, false).toBool();
|
||||
m_ui->checkBoxRememberLastSavePath->setChecked(rememberLastSavePath);
|
||||
|
||||
if (m_torrentParams.createSubfolder == TriStateBool::True)
|
||||
m_ui->keepTopLevelFolderCheckBox->setChecked(true);
|
||||
else if (m_torrentParams.createSubfolder == TriStateBool::False)
|
||||
m_ui->keepTopLevelFolderCheckBox->setChecked(false);
|
||||
else
|
||||
m_ui->keepTopLevelFolderCheckBox->setChecked(session->isKeepTorrentTopLevelFolder());
|
||||
m_ui->contentLayoutComboBox->setCurrentIndex(
|
||||
static_cast<int>(m_torrentParams.contentLayout ? *m_torrentParams.contentLayout : session->torrentContentLayout()));
|
||||
|
||||
m_ui->sequentialCheckBox->setChecked(m_torrentParams.sequential);
|
||||
m_ui->firstLastCheckBox->setChecked(m_torrentParams.firstLastPiecePriority);
|
||||
|
@ -308,7 +304,6 @@ bool AddNewTorrentDialog::loadTorrentImpl()
|
|||
m_ui->labelHashData->setText(infoHash);
|
||||
setupTreeview();
|
||||
TMMChanged(m_ui->comboTTM->currentIndex());
|
||||
m_ui->keepTopLevelFolderCheckBox->setEnabled(m_torrentInfo.hasRootFolder());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -579,7 +574,7 @@ void AddNewTorrentDialog::accept()
|
|||
m_torrentParams.filePriorities = m_contentModel->model()->getFilePriorities();
|
||||
|
||||
m_torrentParams.addPaused = TriStateBool(!m_ui->startTorrentCheckBox->isChecked());
|
||||
m_torrentParams.createSubfolder = TriStateBool(m_ui->keepTopLevelFolderCheckBox->isChecked());
|
||||
m_torrentParams.contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
|
||||
|
||||
m_torrentParams.sequential = m_ui->sequentialCheckBox->isChecked();
|
||||
m_torrentParams.firstLastPiecePriority = m_ui->firstLastCheckBox->isChecked();
|
||||
|
@ -640,7 +635,6 @@ void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata
|
|||
// Update UI
|
||||
setupTreeview();
|
||||
setMetadataProgressIndicator(false, tr("Metadata retrieval complete"));
|
||||
m_ui->keepTopLevelFolderCheckBox->setEnabled(m_torrentInfo.hasRootFolder());
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText)
|
||||
|
|
|
@ -135,7 +135,7 @@
|
|||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="3" column="0">
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="doNotDeleteTorrentCheckBox">
|
||||
<property name="toolTip">
|
||||
<string>When checked, the .torrent file will not be deleted despite the settings at the "Download" page of the options dialog</string>
|
||||
|
@ -152,7 +152,7 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="skipCheckingCheckBox">
|
||||
<property name="text">
|
||||
<string>Skip hash check</string>
|
||||
|
@ -166,13 +166,6 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="keepTopLevelFolderCheckBox">
|
||||
<property name="text">
|
||||
<string>Keep top-level folder</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="startTorrentCheckBox">
|
||||
<property name="text">
|
||||
|
@ -195,6 +188,52 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="contentLayoutLabel">
|
||||
<property name="text">
|
||||
<string>Content layout:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="contentLayoutComboBox">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Original</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Create subfolder</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Don't create subfolder</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -358,7 +358,7 @@ OptionsDialog::OptionsDialog(QWidget *parent)
|
|||
connect(m_ui->checkAdditionDialog, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkAdditionDialogFront, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkStartPaused, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkKeepTopLevelFolder, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->contentLayoutComboBox, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->deleteTorrentBox, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->deleteCancelledTorrentBox, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkExportDir, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
|
@ -752,7 +752,7 @@ void OptionsDialog::saveOptions()
|
|||
AddNewTorrentDialog::setEnabled(useAdditionDialog());
|
||||
AddNewTorrentDialog::setTopLevel(m_ui->checkAdditionDialogFront->isChecked());
|
||||
session->setAddTorrentPaused(addTorrentsInPause());
|
||||
session->setKeepTorrentTopLevelFolder(m_ui->checkKeepTopLevelFolder->isChecked());
|
||||
session->setTorrentContentLayout(static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex()));
|
||||
ScanFoldersModel::instance()->removeFromFSWatcher(m_removedScanDirs);
|
||||
ScanFoldersModel::instance()->addToFSWatcher(m_addedScanDirs);
|
||||
ScanFoldersModel::instance()->makePersistent();
|
||||
|
@ -1000,7 +1000,7 @@ void OptionsDialog::loadOptions()
|
|||
m_ui->checkAdditionDialog->setChecked(AddNewTorrentDialog::isEnabled());
|
||||
m_ui->checkAdditionDialogFront->setChecked(AddNewTorrentDialog::isTopLevel());
|
||||
m_ui->checkStartPaused->setChecked(session->isAddTorrentPaused());
|
||||
m_ui->checkKeepTopLevelFolder->setChecked(session->isKeepTorrentTopLevelFolder());
|
||||
m_ui->contentLayoutComboBox->setCurrentIndex(static_cast<int>(session->torrentContentLayout()));
|
||||
const TorrentFileGuard::AutoDeleteMode autoDeleteMode = TorrentFileGuard::autoDeleteMode();
|
||||
m_ui->deleteTorrentBox->setChecked(autoDeleteMode != TorrentFileGuard::Never);
|
||||
m_ui->deleteCancelledTorrentBox->setChecked(autoDeleteMode == TorrentFileGuard::Always);
|
||||
|
|
|
@ -769,14 +769,50 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkKeepTopLevelFolder">
|
||||
<property name="text">
|
||||
<string>Keep top-level folder</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_19">
|
||||
<item>
|
||||
<widget class="QLabel" name="contentLayoutLabel">
|
||||
<property name="text">
|
||||
<string>Torrent content layout:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="contentLayoutComboBox">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Original</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Create subfolder</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Don't create subfolder</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_20">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkStartPaused">
|
||||
|
|
|
@ -275,11 +275,9 @@ void AutomatedRssDownloader::updateRuleDefinitionBox()
|
|||
index = 2;
|
||||
m_ui->comboAddPaused->setCurrentIndex(index);
|
||||
index = 0;
|
||||
if (m_currentRule.createSubfolder() == TriStateBool::True)
|
||||
index = 1;
|
||||
else if (m_currentRule.createSubfolder() == TriStateBool::False)
|
||||
index = 2;
|
||||
m_ui->comboCreateSubfolder->setCurrentIndex(index);
|
||||
if (m_currentRule.torrentContentLayout())
|
||||
index = static_cast<int>(*m_currentRule.torrentContentLayout()) + 1;
|
||||
m_ui->comboContentLayout->setCurrentIndex(index);
|
||||
m_ui->spinIgnorePeriod->setValue(m_currentRule.ignoreDays());
|
||||
QDateTime dateTime = m_currentRule.lastMatch();
|
||||
QString lMatch;
|
||||
|
@ -320,8 +318,8 @@ void AutomatedRssDownloader::clearRuleDefinitionBox()
|
|||
m_ui->spinIgnorePeriod->setValue(0);
|
||||
m_ui->comboAddPaused->clearEditText();
|
||||
m_ui->comboAddPaused->setCurrentIndex(-1);
|
||||
m_ui->comboCreateSubfolder->clearEditText();
|
||||
m_ui->comboCreateSubfolder->setCurrentIndex(-1);
|
||||
m_ui->comboContentLayout->clearEditText();
|
||||
m_ui->comboContentLayout->setCurrentIndex(-1);
|
||||
updateFieldsToolTips(m_ui->checkRegex->isChecked());
|
||||
updateMustLineValidity();
|
||||
updateMustNotLineValidity();
|
||||
|
@ -355,12 +353,12 @@ void AutomatedRssDownloader::updateEditedRule()
|
|||
else if (m_ui->comboAddPaused->currentIndex() == 2)
|
||||
addPaused = TriStateBool::False;
|
||||
m_currentRule.setAddPaused(addPaused);
|
||||
TriStateBool createSubfolder; // Undefined by default
|
||||
if (m_ui->comboCreateSubfolder->currentIndex() == 1)
|
||||
createSubfolder = TriStateBool::True;
|
||||
else if (m_ui->comboCreateSubfolder->currentIndex() == 2)
|
||||
createSubfolder = TriStateBool::False;
|
||||
m_currentRule.setCreateSubfolder(createSubfolder);
|
||||
|
||||
boost::optional<BitTorrent::TorrentContentLayout> contentLayout;
|
||||
if (m_ui->comboContentLayout->currentIndex() > 0)
|
||||
contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->comboContentLayout->currentIndex() - 1);
|
||||
m_currentRule.setTorrentContentLayout(contentLayout);
|
||||
|
||||
m_currentRule.setIgnoreDays(m_ui->spinIgnorePeriod->value());
|
||||
}
|
||||
|
||||
|
|
|
@ -325,7 +325,7 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
|
|||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_10">
|
||||
<item>
|
||||
<widget class="QLabel" name="lblKeepTopLevelFolder">
|
||||
<widget class="QLabel" name="lblContentLayout">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
|
@ -333,12 +333,12 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
|
|||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Keep top-level folder:</string>
|
||||
<string>Torrent content layout:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboCreateSubfolder">
|
||||
<widget class="QComboBox" name="comboContentLayout">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Use global settings</string>
|
||||
|
@ -346,12 +346,17 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
|
|||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Always</string>
|
||||
<string>Original</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Never</string>
|
||||
<string>Create subfolder</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Don't create subfolder</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
|
@ -471,7 +476,7 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
|
|||
<tabstop>lineSavePath</tabstop>
|
||||
<tabstop>spinIgnorePeriod</tabstop>
|
||||
<tabstop>comboAddPaused</tabstop>
|
||||
<tabstop>comboCreateSubfolder</tabstop>
|
||||
<tabstop>comboContentLayout</tabstop>
|
||||
<tabstop>listFeeds</tabstop>
|
||||
<tabstop>treeMatchingArticles</tabstop>
|
||||
<tabstop>importBtn</tabstop>
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
#include "base/utils/misc.h"
|
||||
#include "base/utils/net.h"
|
||||
#include "base/utils/password.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "../webapplication.h"
|
||||
|
||||
void AppController::webapiVersionAction()
|
||||
|
@ -100,7 +101,7 @@ void AppController::preferencesAction()
|
|||
|
||||
// Downloads
|
||||
// When adding a torrent
|
||||
data["create_subfolder_enabled"] = session->isKeepTorrentTopLevelFolder();
|
||||
data["torrent_content_layout"] = Utils::String::fromEnum(session->torrentContentLayout());
|
||||
data["start_paused_enabled"] = session->isAddTorrentPaused();
|
||||
data["auto_delete_mode"] = static_cast<int>(TorrentFileGuard::autoDeleteMode());
|
||||
data["preallocate_all"] = session->isPreallocationEnabled();
|
||||
|
@ -355,8 +356,8 @@ void AppController::setPreferencesAction()
|
|||
|
||||
// Downloads
|
||||
// When adding a torrent
|
||||
if (hasKey("create_subfolder_enabled"))
|
||||
session->setKeepTorrentTopLevelFolder(it.value().toBool());
|
||||
if (hasKey("torrent_content_layout"))
|
||||
session->setTorrentContentLayout(Utils::String::toEnum(it.value().toString(), BitTorrent::TorrentContentLayout::Original));
|
||||
if (hasKey("start_paused_enabled"))
|
||||
session->setAddTorrentPaused(it.value().toBool());
|
||||
if (hasKey("auto_delete_mode"))
|
||||
|
|
|
@ -606,7 +606,6 @@ void TorrentsController::addAction()
|
|||
const bool seqDownload = parseBool(params()["sequentialDownload"], false);
|
||||
const bool firstLastPiece = parseBool(params()["firstLastPiecePrio"], false);
|
||||
const TriStateBool addPaused = parseTriStateBool(params()["paused"]);
|
||||
const TriStateBool rootFolder = parseTriStateBool(params()["root_folder"]);
|
||||
const QString savepath = params()["savepath"].trimmed();
|
||||
const QString category = params()["category"];
|
||||
const QSet<QString> tags = List::toSet(params()["tags"].split(',', QString::SkipEmptyParts));
|
||||
|
@ -616,6 +615,11 @@ void TorrentsController::addAction()
|
|||
const int dlLimit = params()["dlLimit"].toInt();
|
||||
const TriStateBool autoTMM = parseTriStateBool(params()["autoTMM"]);
|
||||
|
||||
const QString contentLayoutParam = params()["contentLayout"];
|
||||
const boost::optional<BitTorrent::TorrentContentLayout> contentLayout = (!contentLayoutParam.isEmpty()
|
||||
? Utils::String::toEnum(contentLayoutParam, BitTorrent::TorrentContentLayout::Original)
|
||||
: boost::optional<BitTorrent::TorrentContentLayout> {});
|
||||
|
||||
QList<QNetworkCookie> cookies;
|
||||
if (!cookie.isEmpty())
|
||||
{
|
||||
|
@ -639,7 +643,7 @@ void TorrentsController::addAction()
|
|||
params.sequential = seqDownload;
|
||||
params.firstLastPiecePriority = firstLastPiece;
|
||||
params.addPaused = addPaused;
|
||||
params.createSubfolder = rootFolder;
|
||||
params.contentLayout = contentLayout;
|
||||
params.savePath = savepath;
|
||||
params.category = category;
|
||||
params.tags = tags;
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
#include "base/utils/net.h"
|
||||
#include "base/utils/version.h"
|
||||
|
||||
constexpr Utils::Version<int, 3, 2> API_VERSION {2, 6, 2};
|
||||
constexpr Utils::Version<int, 3, 2> API_VERSION {2, 7, 0};
|
||||
|
||||
class APIController;
|
||||
class WebApplication;
|
||||
|
|
|
@ -89,11 +89,14 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="rootFolder">QBT_TR(Keep top-level folder)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
|
||||
<label for="contentLayout">QBT_TR(Content layout:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="hidden" id="rootFolderHidden" name="root_folder" />
|
||||
<input type="checkbox" id="rootFolder" />
|
||||
<select id="contentLayout" name="contentLayout">
|
||||
<option selected value="Original">QBT_TR(Original)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
|
||||
<option value="Subfolder">QBT_TR(Create subfolder)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
|
||||
<option value="NoSubfolder">QBT_TR(Don't create subfolder)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
@ -82,6 +82,16 @@ window.qBittorrent.Download = (function() {
|
|||
else {
|
||||
$('autoTMM').selectedIndex = 0;
|
||||
}
|
||||
|
||||
if (pref.torrent_content_layout === "Subfolder") {
|
||||
$('contentLayout').selectedIndex = 1;
|
||||
}
|
||||
else if (pref.torrent_content_layout === "NoSubfolder") {
|
||||
$('contentLayout').selectedIndex = 2;
|
||||
}
|
||||
else {
|
||||
$('contentLayout').selectedIndex = 0;
|
||||
}
|
||||
}
|
||||
}).send();
|
||||
};
|
||||
|
|
|
@ -77,11 +77,14 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="rootFolder">QBT_TR(Keep top-level folder)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
|
||||
<label for="contentLayout">QBT_TR(Content layout:)QBT_TR[CONTEXT=AddNewTorrentDialog]</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="hidden" id="rootFolderHidden" name="root_folder" />
|
||||
<input type="checkbox" id="rootFolder" />
|
||||
<select id="contentLayout" name="contentLayout">
|
||||
<option selected value="Original">QBT_TR(Original)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
|
||||
<option value="Subfolder">QBT_TR(Create subfolder)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
|
||||
<option value="NoSubfolder">QBT_TR(Don't create subfolder)QBT_TR[CONTEXT=AddNewTorrentDialog]</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
@ -248,13 +248,14 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
|
|||
<table class="fullWidth">
|
||||
<tr>
|
||||
<td>
|
||||
<label class="noWrap">QBT_TR(Create Subfolder:)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
|
||||
<label class="noWrap">QBT_TR(Torrent content layout:)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
|
||||
</td>
|
||||
<td class="fullWidth">
|
||||
<select disabled id="creatSubfolderCombobox" class="fullWidth">
|
||||
<option value="default">QBT_TR(Use global settings)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
|
||||
<option value="always">QBT_TR(Always)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
|
||||
<option value="never">QBT_TR(Never)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
|
||||
<select disabled id="contentLayoutCombobox" class="fullWidth">
|
||||
<option value="Default">QBT_TR(Use global settings)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
|
||||
<option value="Original">QBT_TR(Original)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
|
||||
<option value="Subfolder">QBT_TR(Create subfolder)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
|
||||
<option value="NoSubfolder">QBT_TR(Don't create subfolder)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -586,15 +587,18 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
|
|||
break;
|
||||
}
|
||||
|
||||
switch ($('creatSubfolderCombobox').value) {
|
||||
case 'default':
|
||||
rulesList[rule].createSubfolder = null;
|
||||
switch ($('contentLayoutCombobox').value) {
|
||||
case 'Default':
|
||||
rulesList[rule].torrentContentLayout = null;
|
||||
break;
|
||||
case 'always':
|
||||
rulesList[rule].createSubfolder = true;
|
||||
case 'Original':
|
||||
rulesList[rule].torrentContentLayout = 'Original';
|
||||
break;
|
||||
case 'never':
|
||||
rulesList[rule].createSubfolder = false;
|
||||
case 'Subfolder':
|
||||
rulesList[rule].torrentContentLayout = 'Subfolder';
|
||||
break;
|
||||
case 'NoSubfolder':
|
||||
rulesList[rule].torrentContentLayout = 'NoSubfolder';
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -660,7 +664,7 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
|
|||
$('saveToText').disabled = true;
|
||||
$('ignoreDaysValue').disabled = true;
|
||||
$('addPausedCombobox').disabled = true;
|
||||
$('creatSubfolderCombobox').disabled = true;
|
||||
$('contentLayoutCombobox').disabled = true;
|
||||
|
||||
// reset all boxes
|
||||
$('useRegEx').checked = false;
|
||||
|
@ -674,7 +678,7 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
|
|||
$('ignoreDaysValue').value = 0;
|
||||
$('lastMatchText').innerHTML = 'QBT_TR(Last Match: Unknown)QBT_TR[CONTEXT=AutomatedRssDownloader]';
|
||||
$('addPausedCombobox').value = 'default';
|
||||
$('creatSubfolderCombobox').value = 'default';
|
||||
$('contentLayoutCombobox').value = 'Default';
|
||||
rssDownloaderFeedSelectionTable.clear();
|
||||
rssDownloaderArticlesTable.clear();
|
||||
|
||||
|
@ -696,7 +700,7 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
|
|||
$('saveToText').disabled = rulesList[ruleName].savePath ? false : true;
|
||||
$('ignoreDaysValue').disabled = false;
|
||||
$('addPausedCombobox').disabled = false;
|
||||
$('creatSubfolderCombobox').disabled = false;
|
||||
$('contentLayoutCombobox').disabled = false;
|
||||
|
||||
// load rule settings
|
||||
$('useRegEx').checked = rulesList[ruleName].useRegex;
|
||||
|
@ -725,10 +729,10 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
|
|||
else
|
||||
$('addPausedCombobox').value = rulesList[ruleName].addPaused ? 'always' : 'never';
|
||||
|
||||
if (rulesList[ruleName].createSubfolder === null)
|
||||
$('creatSubfolderCombobox').value = 'default';
|
||||
if (rulesList[ruleName].torrentContentLayout === null)
|
||||
$('contentLayoutCombobox').value = 'Default';
|
||||
else
|
||||
$('creatSubfolderCombobox').value = rulesList[ruleName].createSubfolder ? 'always' : 'never';
|
||||
$('contentLayoutCombobox').value = rulesList[ruleName].torrentContentLayout;
|
||||
|
||||
setElementTitles();
|
||||
|
||||
|
|
Loading…
Reference in a new issue