Browse Source

Allow to add root folder to torrent content

adaptive-webui-19844
Vladimir Golovnev (Glassez) 4 years ago
parent
commit
1d5dc283fe
No known key found for this signature in database
GPG Key ID: 52A2C7DEE2DFA6F7
  1. 24
      src/app/upgrade.cpp
  2. 1
      src/base/CMakeLists.txt
  3. 1
      src/base/base.pri
  4. 5
      src/base/bittorrent/addtorrentparams.h
  5. 39
      src/base/bittorrent/session.cpp
  6. 9
      src/base/bittorrent/session.h
  7. 51
      src/base/bittorrent/torrentcontentlayout.h
  8. 2
      src/base/bittorrent/torrenthandle.h
  9. 27
      src/base/bittorrent/torrenthandleimpl.cpp
  10. 7
      src/base/bittorrent/torrenthandleimpl.h
  11. 87
      src/base/bittorrent/torrentinfo.cpp
  12. 7
      src/base/bittorrent/torrentinfo.h
  13. 2
      src/base/rss/rss_autodownloader.cpp
  14. 64
      src/base/rss/rss_autodownloadrule.cpp
  15. 8
      src/base/rss/rss_autodownloadrule.h
  16. 12
      src/base/settingvalue.h
  17. 19
      src/base/utils/string.h
  18. 12
      src/gui/addnewtorrentdialog.cpp
  19. 57
      src/gui/addnewtorrentdialog.ui
  20. 6
      src/gui/optionsdialog.cpp
  21. 52
      src/gui/optionsdialog.ui
  22. 24
      src/gui/rss/automatedrssdownloader.cpp
  23. 17
      src/gui/rss/automatedrssdownloader.ui
  24. 7
      src/webui/api/appcontroller.cpp
  25. 8
      src/webui/api/torrentscontroller.cpp
  26. 2
      src/webui/webapplication.h
  27. 9
      src/webui/www/private/download.html
  28. 10
      src/webui/www/private/scripts/download.js
  29. 9
      src/webui/www/private/upload.html
  30. 40
      src/webui/www/private/views/rssDownloader.html

24
src/app/upgrade.cpp

@ -29,12 +29,15 @@
#include "upgrade.h" #include "upgrade.h"
#include <QFile> #include <QFile>
#include <QMetaEnum>
#include <QVector> #include <QVector>
#include "base/bittorrent/torrentcontentlayout.h"
#include "base/logger.h" #include "base/logger.h"
#include "base/profile.h" #include "base/profile.h"
#include "base/settingsstorage.h" #include "base/settingsstorage.h"
#include "base/utils/fs.h" #include "base/utils/fs.h"
#include "base/utils/string.h"
namespace namespace
{ {
@ -79,11 +82,32 @@ namespace
, QLatin1String("Preferences/WebUI/HTTPS/KeyPath") , QLatin1String("Preferences/WebUI/HTTPS/KeyPath")
, Utils::Fs::toNativePath(configPath + QLatin1String("WebUIPrivateKey.pem"))); , 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*/) bool upgrade(const bool /*ask*/)
{ {
exportWebUIHttpsFiles(); exportWebUIHttpsFiles();
upgradeTorrentContentLayout();
return true; return true;
} }

1
src/base/CMakeLists.txt

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

1
src/base/base.pri

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

5
src/base/bittorrent/addtorrentparams.h

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

39
src/base/bittorrent/session.cpp

@ -396,7 +396,7 @@ Session::Session(QObject *parent)
, m_globalMaxRatio(BITTORRENT_SESSION_KEY("GlobalMaxRatio"), -1, [](qreal r) { return r < 0 ? -1. : r;}) , m_globalMaxRatio(BITTORRENT_SESSION_KEY("GlobalMaxRatio"), -1, [](qreal r) { return r < 0 ? -1. : r;})
, m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY("GlobalMaxSeedingMinutes"), -1, lowerLimited(-1)) , m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY("GlobalMaxSeedingMinutes"), -1, lowerLimited(-1))
, m_isAddTorrentPaused(BITTORRENT_SESSION_KEY("AddTorrentPaused"), false) , 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_isAppendExtensionEnabled(BITTORRENT_SESSION_KEY("AddExtensionToIncompleteFiles"), false)
, m_refreshInterval(BITTORRENT_SESSION_KEY("RefreshInterval"), 1500) , m_refreshInterval(BITTORRENT_SESSION_KEY("RefreshInterval"), 1500)
, m_isPreallocationEnabled(BITTORRENT_SESSION_KEY("Preallocation"), false) , m_isPreallocationEnabled(BITTORRENT_SESSION_KEY("Preallocation"), false)
@ -2094,9 +2094,9 @@ LoadTorrentParams Session::initLoadTorrentParams(const AddTorrentParams &addTorr
loadTorrentParams.tags = addTorrentParams.tags; loadTorrentParams.tags = addTorrentParams.tags;
loadTorrentParams.firstLastPiecePriority = addTorrentParams.firstLastPiecePriority; loadTorrentParams.firstLastPiecePriority = addTorrentParams.firstLastPiecePriority;
loadTorrentParams.hasSeedStatus = addTorrentParams.skipChecking; // do not react on 'torrent_finished_alert' when skipping loadTorrentParams.hasSeedStatus = addTorrentParams.skipChecking; // do not react on 'torrent_finished_alert' when skipping
loadTorrentParams.hasRootFolder = ((addTorrentParams.createSubfolder == TriStateBool::Undefined) loadTorrentParams.contentLayout = (addTorrentParams.contentLayout
? isKeepTorrentTopLevelFolder() ? *addTorrentParams.contentLayout
: (addTorrentParams.createSubfolder == TriStateBool::True)); : torrentContentLayout());
loadTorrentParams.forced = (addTorrentParams.addForced == TriStateBool::True); loadTorrentParams.forced = (addTorrentParams.addForced == TriStateBool::True);
loadTorrentParams.paused = ((addTorrentParams.addPaused == TriStateBool::Undefined) loadTorrentParams.paused = ((addTorrentParams.addPaused == TriStateBool::Undefined)
? isAddTorrentPaused() ? isAddTorrentPaused()
@ -2161,8 +2161,7 @@ bool Session::addTorrent_impl(const AddTorrentParams &addTorrentParams, const Ma
const QString actualSavePath = loadTorrentParams.savePath.isEmpty() ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath; const QString actualSavePath = loadTorrentParams.savePath.isEmpty() ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath;
if (hasMetadata) if (hasMetadata)
{ {
if (!loadTorrentParams.hasRootFolder) metadata.setContentLayout(loadTorrentParams.contentLayout);
metadata.stripRootFolder();
if (!loadTorrentParams.hasSeedStatus) if (!loadTorrentParams.hasSeedStatus)
{ {
@ -4234,9 +4233,27 @@ bool Session::loadTorrentResumeData(const QByteArray &data, const TorrentInfo &m
Utils::Fs::toUniformPath(fromLTString(root.dict_find_string_value("qBt-savePath")))); Utils::Fs::toUniformPath(fromLTString(root.dict_find_string_value("qBt-savePath"))));
torrentParams.hasSeedStatus = root.dict_find_int_value("qBt-seedStatus"); torrentParams.hasSeedStatus = root.dict_find_int_value("qBt-seedStatus");
torrentParams.firstLastPiecePriority = root.dict_find_int_value("qBt-firstLastPiecePriority"); 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); 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"); const lt::string_view ratioLimitString = root.dict_find_string_value("qBt-ratioLimit");
if (ratioLimitString.empty()) if (ratioLimitString.empty())
torrentParams.ratioLimit = root.dict_find_int_value("qBt-ratioLimit", TorrentHandle::USE_GLOBAL_RATIO * 1000) / 1000.0; torrentParams.ratioLimit = root.dict_find_int_value("qBt-ratioLimit", TorrentHandle::USE_GLOBAL_RATIO * 1000) / 1000.0;
@ -4467,14 +4484,14 @@ std::vector<lt::alert *> Session::getPendingAlerts(const lt::time_duration time)
return alerts; 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 // Read alerts sent by the BitTorrent session

9
src/base/bittorrent/session.h

@ -114,7 +114,7 @@ namespace BitTorrent
// Using `Q_ENUM_NS()` without a wrapper namespace in our case is not advised // 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. // 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 // https://www.kdab.com/new-qt-5-8-meta-object-support-namespaces/#comment-143779
namespace SessionSettingsEnums inline namespace SessionSettingsEnums
{ {
Q_NAMESPACE Q_NAMESPACE
@ -160,7 +160,6 @@ namespace BitTorrent
Q_ENUM_NS(OSMemoryPriority) Q_ENUM_NS(OSMemoryPriority)
#endif #endif
} }
using namespace SessionSettingsEnums;
struct SessionMetricIndices struct SessionMetricIndices
{ {
@ -274,8 +273,8 @@ namespace BitTorrent
void setPeXEnabled(bool enabled); void setPeXEnabled(bool enabled);
bool isAddTorrentPaused() const; bool isAddTorrentPaused() const;
void setAddTorrentPaused(bool value); void setAddTorrentPaused(bool value);
bool isKeepTorrentTopLevelFolder() const; TorrentContentLayout torrentContentLayout() const;
void setKeepTorrentTopLevelFolder(bool value); void setTorrentContentLayout(TorrentContentLayout value);
bool isTrackerEnabled() const; bool isTrackerEnabled() const;
void setTrackerEnabled(bool enabled); void setTrackerEnabled(bool enabled);
bool isAppendExtensionEnabled() const; bool isAppendExtensionEnabled() const;
@ -702,7 +701,7 @@ namespace BitTorrent
CachedSettingValue<qreal> m_globalMaxRatio; CachedSettingValue<qreal> m_globalMaxRatio;
CachedSettingValue<int> m_globalMaxSeedingMinutes; CachedSettingValue<int> m_globalMaxSeedingMinutes;
CachedSettingValue<bool> m_isAddTorrentPaused; CachedSettingValue<bool> m_isAddTorrentPaused;
CachedSettingValue<bool> m_isKeepTorrentTopLevelFolder; CachedSettingValue<TorrentContentLayout> m_torrentContentLayout;
CachedSettingValue<bool> m_isAppendExtensionEnabled; CachedSettingValue<bool> m_isAppendExtensionEnabled;
CachedSettingValue<int> m_refreshInterval; CachedSettingValue<int> m_refreshInterval;
CachedSettingValue<bool> m_isPreallocationEnabled; CachedSettingValue<bool> m_isPreallocationEnabled;

51
src/base/bittorrent/torrentcontentlayout.h

@ -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)
}
}

2
src/base/bittorrent/torrenthandle.h

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

27
src/base/bittorrent/torrenthandleimpl.cpp

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

7
src/base/bittorrent/torrenthandleimpl.h

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

87
src/base/bittorrent/torrentinfo.cpp

@ -52,6 +52,29 @@
using namespace BitTorrent; 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) TorrentInfo::TorrentInfo(std::shared_ptr<const lt::torrent_info> nativeInfo)
{ {
m_nativeInfo = std::const_pointer_cast<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 TorrentInfo::rootFolder() const
{ {
QString rootFolder; return getRootFolder(filePaths());
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;
} }
bool TorrentInfo::hasRootFolder() const bool TorrentInfo::hasRootFolder() const
@ -436,10 +443,26 @@ bool TorrentInfo::hasRootFolder() const
return !rootFolder().isEmpty(); return !rootFolder().isEmpty();
} }
void TorrentInfo::stripRootFolder() void TorrentInfo::setContentLayout(const TorrentContentLayout layout)
{ {
if (!hasRootFolder()) return; 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()
{
lt::file_storage files = m_nativeInfo->files(); lt::file_storage files = m_nativeInfo->files();
// Solution for case of renamed root folder // Solution for case of renamed root folder
@ -456,6 +479,32 @@ void TorrentInfo::stripRootFolder()
m_nativeInfo->remap_files(files); 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 std::shared_ptr<lt::torrent_info> TorrentInfo::nativeInfo() const
{ {
return m_nativeInfo; return m_nativeInfo;

7
src/base/bittorrent/torrentinfo.h

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

2
src/base/rss/rss_autodownloader.cpp

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

64
src/base/rss/rss_autodownloadrule.cpp

@ -40,11 +40,11 @@
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
#include "../global.h" #include "base/global.h"
#include "../preferences.h" #include "base/preferences.h"
#include "../tristatebool.h" #include "base/tristatebool.h"
#include "../utils/fs.h" #include "base/utils/fs.h"
#include "../utils/string.h" #include "base/utils/string.h"
#include "rss_article.h" #include "rss_article.h"
#include "rss_autodownloader.h" #include "rss_autodownloader.h"
#include "rss_feed.h" #include "rss_feed.h"
@ -91,6 +91,21 @@ namespace
default: return 0; // default 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")); 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_IgnoreDays(QStringLiteral("ignoreDays"));
const QString Str_AddPaused(QStringLiteral("addPaused")); const QString Str_AddPaused(QStringLiteral("addPaused"));
const QString Str_CreateSubfolder(QStringLiteral("createSubfolder")); const QString Str_CreateSubfolder(QStringLiteral("createSubfolder"));
const QString Str_ContentLayout(QStringLiteral("torrentContentLayout"));
const QString Str_SmartFilter(QStringLiteral("smartFilter")); const QString Str_SmartFilter(QStringLiteral("smartFilter"));
const QString Str_PreviouslyMatched(QStringLiteral("previouslyMatchedEpisodes")); const QString Str_PreviouslyMatched(QStringLiteral("previouslyMatchedEpisodes"));
@ -127,7 +143,7 @@ namespace RSS
QString savePath; QString savePath;
QString category; QString category;
TriStateBool addPaused = TriStateBool::Undefined; TriStateBool addPaused = TriStateBool::Undefined;
TriStateBool createSubfolder = TriStateBool::Undefined; boost::optional<BitTorrent::TorrentContentLayout> contentLayout;
bool smartFilter = false; bool smartFilter = false;
QStringList previouslyMatchedEpisodes; QStringList previouslyMatchedEpisodes;
@ -149,7 +165,7 @@ namespace RSS
&& (savePath == other.savePath) && (savePath == other.savePath)
&& (category == other.category) && (category == other.category)
&& (addPaused == other.addPaused) && (addPaused == other.addPaused)
&& (createSubfolder == other.createSubfolder) && (contentLayout == other.contentLayout)
&& (smartFilter == other.smartFilter); && (smartFilter == other.smartFilter);
} }
}; };
@ -462,7 +478,7 @@ QJsonObject AutoDownloadRule::toJsonObject() const
, {Str_LastMatch, lastMatch().toString(Qt::RFC2822Date)} , {Str_LastMatch, lastMatch().toString(Qt::RFC2822Date)}
, {Str_IgnoreDays, ignoreDays()} , {Str_IgnoreDays, ignoreDays()}
, {Str_AddPaused, triStateBoolToJsonValue(addPaused())} , {Str_AddPaused, triStateBoolToJsonValue(addPaused())}
, {Str_CreateSubfolder, triStateBoolToJsonValue(createSubfolder())} , {Str_ContentLayout, contentLayoutToJsonValue(torrentContentLayout())}
, {Str_SmartFilter, useSmartFilter()} , {Str_SmartFilter, useSmartFilter()}
, {Str_PreviouslyMatched, QJsonArray::fromStringList(previouslyMatchedEpisodes())}}; , {Str_PreviouslyMatched, QJsonArray::fromStringList(previouslyMatchedEpisodes())}};
} }
@ -479,7 +495,29 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
rule.setSavePath(jsonObj.value(Str_SavePath).toString()); rule.setSavePath(jsonObj.value(Str_SavePath).toString());
rule.setCategory(jsonObj.value(Str_AssignedCategory).toString()); rule.setCategory(jsonObj.value(Str_AssignedCategory).toString());
rule.setAddPaused(jsonValueToTriStateBool(jsonObj.value(Str_AddPaused))); 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.setLastMatch(QDateTime::fromString(jsonObj.value(Str_LastMatch).toString(), Qt::RFC2822Date));
rule.setIgnoreDays(jsonObj.value(Str_IgnoreDays).toInt()); rule.setIgnoreDays(jsonObj.value(Str_IgnoreDays).toInt());
rule.setUseSmartFilter(jsonObj.value(Str_SmartFilter).toBool(false)); rule.setUseSmartFilter(jsonObj.value(Str_SmartFilter).toBool(false));
@ -611,14 +649,14 @@ void AutoDownloadRule::setAddPaused(const TriStateBool addPaused)
m_dataPtr->addPaused = 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 QString AutoDownloadRule::assignedCategory() const

8
src/base/rss/rss_autodownloadrule.h

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

12
src/base/settingvalue.h

@ -78,13 +78,13 @@ public:
private: private:
// regular load/save pair // 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) U loadValue(const U &defaultValue)
{ {
return SettingsStorage::instance()->loadValue(m_keyName, defaultValue).template value<T>(); 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) void storeValue(const U &value)
{ {
SettingsStorage::instance()->storeValue(m_keyName, value); SettingsStorage::instance()->storeValue(m_keyName, value);
@ -92,16 +92,16 @@ private:
// load/save pair for an enum // load/save pair for an enum
// saves literal value of the enum constant, obtained from QMetaEnum // 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) 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) 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; const QString m_keyName;

19
src/base/utils/string.h

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

12
src/gui/addnewtorrentdialog.cpp

@ -123,12 +123,8 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
const bool rememberLastSavePath = settings()->loadValue(KEY_REMEMBERLASTSAVEPATH, false).toBool(); const bool rememberLastSavePath = settings()->loadValue(KEY_REMEMBERLASTSAVEPATH, false).toBool();
m_ui->checkBoxRememberLastSavePath->setChecked(rememberLastSavePath); m_ui->checkBoxRememberLastSavePath->setChecked(rememberLastSavePath);
if (m_torrentParams.createSubfolder == TriStateBool::True) m_ui->contentLayoutComboBox->setCurrentIndex(
m_ui->keepTopLevelFolderCheckBox->setChecked(true); static_cast<int>(m_torrentParams.contentLayout ? *m_torrentParams.contentLayout : session->torrentContentLayout()));
else if (m_torrentParams.createSubfolder == TriStateBool::False)
m_ui->keepTopLevelFolderCheckBox->setChecked(false);
else
m_ui->keepTopLevelFolderCheckBox->setChecked(session->isKeepTorrentTopLevelFolder());
m_ui->sequentialCheckBox->setChecked(m_torrentParams.sequential); m_ui->sequentialCheckBox->setChecked(m_torrentParams.sequential);
m_ui->firstLastCheckBox->setChecked(m_torrentParams.firstLastPiecePriority); m_ui->firstLastCheckBox->setChecked(m_torrentParams.firstLastPiecePriority);
@ -308,7 +304,6 @@ bool AddNewTorrentDialog::loadTorrentImpl()
m_ui->labelHashData->setText(infoHash); m_ui->labelHashData->setText(infoHash);
setupTreeview(); setupTreeview();
TMMChanged(m_ui->comboTTM->currentIndex()); TMMChanged(m_ui->comboTTM->currentIndex());
m_ui->keepTopLevelFolderCheckBox->setEnabled(m_torrentInfo.hasRootFolder());
return true; return true;
} }
@ -579,7 +574,7 @@ void AddNewTorrentDialog::accept()
m_torrentParams.filePriorities = m_contentModel->model()->getFilePriorities(); m_torrentParams.filePriorities = m_contentModel->model()->getFilePriorities();
m_torrentParams.addPaused = TriStateBool(!m_ui->startTorrentCheckBox->isChecked()); 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.sequential = m_ui->sequentialCheckBox->isChecked();
m_torrentParams.firstLastPiecePriority = m_ui->firstLastCheckBox->isChecked(); m_torrentParams.firstLastPiecePriority = m_ui->firstLastCheckBox->isChecked();
@ -640,7 +635,6 @@ void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata
// Update UI // Update UI
setupTreeview(); setupTreeview();
setMetadataProgressIndicator(false, tr("Metadata retrieval complete")); setMetadataProgressIndicator(false, tr("Metadata retrieval complete"));
m_ui->keepTopLevelFolderCheckBox->setEnabled(m_torrentInfo.hasRootFolder());
} }
void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText) void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText)

57
src/gui/addnewtorrentdialog.ui

@ -135,7 +135,7 @@
</item> </item>
<item> <item>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="3" column="0"> <item row="2" column="0">
<widget class="QCheckBox" name="doNotDeleteTorrentCheckBox"> <widget class="QCheckBox" name="doNotDeleteTorrentCheckBox">
<property name="toolTip"> <property name="toolTip">
<string>When checked, the .torrent file will not be deleted despite the settings at the &quot;Download&quot; page of the options dialog</string> <string>When checked, the .torrent file will not be deleted despite the settings at the &quot;Download&quot; page of the options dialog</string>
@ -152,7 +152,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="1" column="0">
<widget class="QCheckBox" name="skipCheckingCheckBox"> <widget class="QCheckBox" name="skipCheckingCheckBox">
<property name="text"> <property name="text">
<string>Skip hash check</string> <string>Skip hash check</string>
@ -166,13 +166,6 @@
</property> </property>
</widget> </widget>
</item> </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"> <item row="0" column="0">
<widget class="QCheckBox" name="startTorrentCheckBox"> <widget class="QCheckBox" name="startTorrentCheckBox">
<property name="text"> <property name="text">
@ -195,6 +188,52 @@
</item> </item>
</layout> </layout>
</item> </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> </layout>
</widget> </widget>
</item> </item>

6
src/gui/optionsdialog.cpp

@ -358,7 +358,7 @@ OptionsDialog::OptionsDialog(QWidget *parent)
connect(m_ui->checkAdditionDialog, &QGroupBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkAdditionDialog, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkAdditionDialogFront, &QAbstractButton::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->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->deleteTorrentBox, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->deleteCancelledTorrentBox, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->deleteCancelledTorrentBox, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkExportDir, &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::setEnabled(useAdditionDialog());
AddNewTorrentDialog::setTopLevel(m_ui->checkAdditionDialogFront->isChecked()); AddNewTorrentDialog::setTopLevel(m_ui->checkAdditionDialogFront->isChecked());
session->setAddTorrentPaused(addTorrentsInPause()); 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()->removeFromFSWatcher(m_removedScanDirs);
ScanFoldersModel::instance()->addToFSWatcher(m_addedScanDirs); ScanFoldersModel::instance()->addToFSWatcher(m_addedScanDirs);
ScanFoldersModel::instance()->makePersistent(); ScanFoldersModel::instance()->makePersistent();
@ -1000,7 +1000,7 @@ void OptionsDialog::loadOptions()
m_ui->checkAdditionDialog->setChecked(AddNewTorrentDialog::isEnabled()); m_ui->checkAdditionDialog->setChecked(AddNewTorrentDialog::isEnabled());
m_ui->checkAdditionDialogFront->setChecked(AddNewTorrentDialog::isTopLevel()); m_ui->checkAdditionDialogFront->setChecked(AddNewTorrentDialog::isTopLevel());
m_ui->checkStartPaused->setChecked(session->isAddTorrentPaused()); 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(); const TorrentFileGuard::AutoDeleteMode autoDeleteMode = TorrentFileGuard::autoDeleteMode();
m_ui->deleteTorrentBox->setChecked(autoDeleteMode != TorrentFileGuard::Never); m_ui->deleteTorrentBox->setChecked(autoDeleteMode != TorrentFileGuard::Never);
m_ui->deleteCancelledTorrentBox->setChecked(autoDeleteMode == TorrentFileGuard::Always); m_ui->deleteCancelledTorrentBox->setChecked(autoDeleteMode == TorrentFileGuard::Always);

52
src/gui/optionsdialog.ui

@ -769,14 +769,50 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="checkKeepTopLevelFolder"> <layout class="QHBoxLayout" name="horizontalLayout_19">
<property name="text"> <item>
<string>Keep top-level folder</string> <widget class="QLabel" name="contentLayoutLabel">
</property> <property name="text">
<property name="checked"> <string>Torrent content layout:</string>
<bool>true</bool> </property>
</property> </widget>
</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>
<item> <item>
<widget class="QCheckBox" name="checkStartPaused"> <widget class="QCheckBox" name="checkStartPaused">

24
src/gui/rss/automatedrssdownloader.cpp

@ -275,11 +275,9 @@ void AutomatedRssDownloader::updateRuleDefinitionBox()
index = 2; index = 2;
m_ui->comboAddPaused->setCurrentIndex(index); m_ui->comboAddPaused->setCurrentIndex(index);
index = 0; index = 0;
if (m_currentRule.createSubfolder() == TriStateBool::True) if (m_currentRule.torrentContentLayout())
index = 1; index = static_cast<int>(*m_currentRule.torrentContentLayout()) + 1;
else if (m_currentRule.createSubfolder() == TriStateBool::False) m_ui->comboContentLayout->setCurrentIndex(index);
index = 2;
m_ui->comboCreateSubfolder->setCurrentIndex(index);
m_ui->spinIgnorePeriod->setValue(m_currentRule.ignoreDays()); m_ui->spinIgnorePeriod->setValue(m_currentRule.ignoreDays());
QDateTime dateTime = m_currentRule.lastMatch(); QDateTime dateTime = m_currentRule.lastMatch();
QString lMatch; QString lMatch;
@ -320,8 +318,8 @@ void AutomatedRssDownloader::clearRuleDefinitionBox()
m_ui->spinIgnorePeriod->setValue(0); m_ui->spinIgnorePeriod->setValue(0);
m_ui->comboAddPaused->clearEditText(); m_ui->comboAddPaused->clearEditText();
m_ui->comboAddPaused->setCurrentIndex(-1); m_ui->comboAddPaused->setCurrentIndex(-1);
m_ui->comboCreateSubfolder->clearEditText(); m_ui->comboContentLayout->clearEditText();
m_ui->comboCreateSubfolder->setCurrentIndex(-1); m_ui->comboContentLayout->setCurrentIndex(-1);
updateFieldsToolTips(m_ui->checkRegex->isChecked()); updateFieldsToolTips(m_ui->checkRegex->isChecked());
updateMustLineValidity(); updateMustLineValidity();
updateMustNotLineValidity(); updateMustNotLineValidity();
@ -355,12 +353,12 @@ void AutomatedRssDownloader::updateEditedRule()
else if (m_ui->comboAddPaused->currentIndex() == 2) else if (m_ui->comboAddPaused->currentIndex() == 2)
addPaused = TriStateBool::False; addPaused = TriStateBool::False;
m_currentRule.setAddPaused(addPaused); m_currentRule.setAddPaused(addPaused);
TriStateBool createSubfolder; // Undefined by default
if (m_ui->comboCreateSubfolder->currentIndex() == 1) boost::optional<BitTorrent::TorrentContentLayout> contentLayout;
createSubfolder = TriStateBool::True; if (m_ui->comboContentLayout->currentIndex() > 0)
else if (m_ui->comboCreateSubfolder->currentIndex() == 2) contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->comboContentLayout->currentIndex() - 1);
createSubfolder = TriStateBool::False; m_currentRule.setTorrentContentLayout(contentLayout);
m_currentRule.setCreateSubfolder(createSubfolder);
m_currentRule.setIgnoreDays(m_ui->spinIgnorePeriod->value()); m_currentRule.setIgnoreDays(m_ui->spinIgnorePeriod->value());
} }

17
src/gui/rss/automatedrssdownloader.ui

@ -325,7 +325,7 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_10"> <layout class="QHBoxLayout" name="horizontalLayout_10">
<item> <item>
<widget class="QLabel" name="lblKeepTopLevelFolder"> <widget class="QLabel" name="lblContentLayout">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred"> <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@ -333,12 +333,12 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>Keep top-level folder:</string> <string>Torrent content layout:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QComboBox" name="comboCreateSubfolder"> <widget class="QComboBox" name="comboContentLayout">
<item> <item>
<property name="text"> <property name="text">
<string>Use global settings</string> <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>
<item> <item>
<property name="text"> <property name="text">
<string>Always</string> <string>Original</string>
</property> </property>
</item> </item>
<item> <item>
<property name="text"> <property name="text">
<string>Never</string> <string>Create subfolder</string>
</property>
</item>
<item>
<property name="text">
<string>Don't create subfolder</string>
</property> </property>
</item> </item>
</widget> </widget>
@ -471,7 +476,7 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
<tabstop>lineSavePath</tabstop> <tabstop>lineSavePath</tabstop>
<tabstop>spinIgnorePeriod</tabstop> <tabstop>spinIgnorePeriod</tabstop>
<tabstop>comboAddPaused</tabstop> <tabstop>comboAddPaused</tabstop>
<tabstop>comboCreateSubfolder</tabstop> <tabstop>comboContentLayout</tabstop>
<tabstop>listFeeds</tabstop> <tabstop>listFeeds</tabstop>
<tabstop>treeMatchingArticles</tabstop> <tabstop>treeMatchingArticles</tabstop>
<tabstop>importBtn</tabstop> <tabstop>importBtn</tabstop>

7
src/webui/api/appcontroller.cpp

@ -56,6 +56,7 @@
#include "base/utils/misc.h" #include "base/utils/misc.h"
#include "base/utils/net.h" #include "base/utils/net.h"
#include "base/utils/password.h" #include "base/utils/password.h"
#include "base/utils/string.h"
#include "../webapplication.h" #include "../webapplication.h"
void AppController::webapiVersionAction() void AppController::webapiVersionAction()
@ -100,7 +101,7 @@ void AppController::preferencesAction()
// Downloads // Downloads
// When adding a torrent // 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["start_paused_enabled"] = session->isAddTorrentPaused();
data["auto_delete_mode"] = static_cast<int>(TorrentFileGuard::autoDeleteMode()); data["auto_delete_mode"] = static_cast<int>(TorrentFileGuard::autoDeleteMode());
data["preallocate_all"] = session->isPreallocationEnabled(); data["preallocate_all"] = session->isPreallocationEnabled();
@ -355,8 +356,8 @@ void AppController::setPreferencesAction()
// Downloads // Downloads
// When adding a torrent // When adding a torrent
if (hasKey("create_subfolder_enabled")) if (hasKey("torrent_content_layout"))
session->setKeepTorrentTopLevelFolder(it.value().toBool()); session->setTorrentContentLayout(Utils::String::toEnum(it.value().toString(), BitTorrent::TorrentContentLayout::Original));
if (hasKey("start_paused_enabled")) if (hasKey("start_paused_enabled"))
session->setAddTorrentPaused(it.value().toBool()); session->setAddTorrentPaused(it.value().toBool());
if (hasKey("auto_delete_mode")) if (hasKey("auto_delete_mode"))

8
src/webui/api/torrentscontroller.cpp

@ -568,7 +568,6 @@ void TorrentsController::addAction()
const bool seqDownload = parseBool(params()["sequentialDownload"], false); const bool seqDownload = parseBool(params()["sequentialDownload"], false);
const bool firstLastPiece = parseBool(params()["firstLastPiecePrio"], false); const bool firstLastPiece = parseBool(params()["firstLastPiecePrio"], false);
const TriStateBool addPaused = parseTriStateBool(params()["paused"]); const TriStateBool addPaused = parseTriStateBool(params()["paused"]);
const TriStateBool rootFolder = parseTriStateBool(params()["root_folder"]);
const QString savepath = params()["savepath"].trimmed(); const QString savepath = params()["savepath"].trimmed();
const QString category = params()["category"]; const QString category = params()["category"];
const QSet<QString> tags = List::toSet(params()["tags"].split(',', QString::SkipEmptyParts)); const QSet<QString> tags = List::toSet(params()["tags"].split(',', QString::SkipEmptyParts));
@ -578,6 +577,11 @@ void TorrentsController::addAction()
const int dlLimit = params()["dlLimit"].toInt(); const int dlLimit = params()["dlLimit"].toInt();
const TriStateBool autoTMM = parseTriStateBool(params()["autoTMM"]); 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; QList<QNetworkCookie> cookies;
if (!cookie.isEmpty()) if (!cookie.isEmpty())
{ {
@ -601,7 +605,7 @@ void TorrentsController::addAction()
params.sequential = seqDownload; params.sequential = seqDownload;
params.firstLastPiecePriority = firstLastPiece; params.firstLastPiecePriority = firstLastPiece;
params.addPaused = addPaused; params.addPaused = addPaused;
params.createSubfolder = rootFolder; params.contentLayout = contentLayout;
params.savePath = savepath; params.savePath = savepath;
params.category = category; params.category = category;
params.tags = tags; params.tags = tags;

2
src/webui/webapplication.h

@ -43,7 +43,7 @@
#include "base/utils/net.h" #include "base/utils/net.h"
#include "base/utils/version.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 APIController;
class WebApplication; class WebApplication;

9
src/webui/www/private/download.html

@ -89,11 +89,14 @@
</tr> </tr>
<tr> <tr>
<td> <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>
<td> <td>
<input type="hidden" id="rootFolderHidden" name="root_folder" /> <select id="contentLayout" name="contentLayout">
<input type="checkbox" id="rootFolder" /> <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> </td>
</tr> </tr>
<tr> <tr>

10
src/webui/www/private/scripts/download.js

@ -82,6 +82,16 @@ window.qBittorrent.Download = (function() {
else { else {
$('autoTMM').selectedIndex = 0; $('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(); }).send();
}; };

9
src/webui/www/private/upload.html

@ -77,11 +77,14 @@
</tr> </tr>
<tr> <tr>
<td> <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>
<td> <td>
<input type="hidden" id="rootFolderHidden" name="root_folder" /> <select id="contentLayout" name="contentLayout">
<input type="checkbox" id="rootFolder" /> <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> </td>
</tr> </tr>
<tr> <tr>

40
src/webui/www/private/views/rssDownloader.html

@ -248,13 +248,14 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
<table class="fullWidth"> <table class="fullWidth">
<tr> <tr>
<td> <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>
<td class="fullWidth"> <td class="fullWidth">
<select disabled id="creatSubfolderCombobox" class="fullWidth"> <select disabled id="contentLayoutCombobox" class="fullWidth">
<option value="default">QBT_TR(Use global settings)QBT_TR[CONTEXT=AutomatedRssDownloader]</option> <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="Original">QBT_TR(Original)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
<option value="never">QBT_TR(Never)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> </select>
</td> </td>
</tr> </tr>
@ -586,15 +587,18 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
break; break;
} }
switch ($('creatSubfolderCombobox').value) { switch ($('contentLayoutCombobox').value) {
case 'default': case 'Default':
rulesList[rule].createSubfolder = null; rulesList[rule].torrentContentLayout = null;
break; break;
case 'always': case 'Original':
rulesList[rule].createSubfolder = true; rulesList[rule].torrentContentLayout = 'Original';
break; break;
case 'never': case 'Subfolder':
rulesList[rule].createSubfolder = false; rulesList[rule].torrentContentLayout = 'Subfolder';
break;
case 'NoSubfolder':
rulesList[rule].torrentContentLayout = 'NoSubfolder';
break; break;
} }
@ -660,7 +664,7 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
$('saveToText').disabled = true; $('saveToText').disabled = true;
$('ignoreDaysValue').disabled = true; $('ignoreDaysValue').disabled = true;
$('addPausedCombobox').disabled = true; $('addPausedCombobox').disabled = true;
$('creatSubfolderCombobox').disabled = true; $('contentLayoutCombobox').disabled = true;
// reset all boxes // reset all boxes
$('useRegEx').checked = false; $('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; $('ignoreDaysValue').value = 0;
$('lastMatchText').innerHTML = 'QBT_TR(Last Match: Unknown)QBT_TR[CONTEXT=AutomatedRssDownloader]'; $('lastMatchText').innerHTML = 'QBT_TR(Last Match: Unknown)QBT_TR[CONTEXT=AutomatedRssDownloader]';
$('addPausedCombobox').value = 'default'; $('addPausedCombobox').value = 'default';
$('creatSubfolderCombobox').value = 'default'; $('contentLayoutCombobox').value = 'Default';
rssDownloaderFeedSelectionTable.clear(); rssDownloaderFeedSelectionTable.clear();
rssDownloaderArticlesTable.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; $('saveToText').disabled = rulesList[ruleName].savePath ? false : true;
$('ignoreDaysValue').disabled = false; $('ignoreDaysValue').disabled = false;
$('addPausedCombobox').disabled = false; $('addPausedCombobox').disabled = false;
$('creatSubfolderCombobox').disabled = false; $('contentLayoutCombobox').disabled = false;
// load rule settings // load rule settings
$('useRegEx').checked = rulesList[ruleName].useRegex; $('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 else
$('addPausedCombobox').value = rulesList[ruleName].addPaused ? 'always' : 'never'; $('addPausedCombobox').value = rulesList[ruleName].addPaused ? 'always' : 'never';
if (rulesList[ruleName].createSubfolder === null) if (rulesList[ruleName].torrentContentLayout === null)
$('creatSubfolderCombobox').value = 'default'; $('contentLayoutCombobox').value = 'Default';
else else
$('creatSubfolderCombobox').value = rulesList[ruleName].createSubfolder ? 'always' : 'never'; $('contentLayoutCombobox').value = rulesList[ruleName].torrentContentLayout;
setElementTitles(); setElementTitles();

Loading…
Cancel
Save