Browse Source

Merge pull request #13955 from glassez/content-policy

Improve content root folder handling
adaptive-webui-19844
Vladimir Golovnev 4 years ago committed by GitHub
parent
commit
70b242f190
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  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. 43
      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. 43
      src/base/bittorrent/torrenthandleimpl.cpp
  10. 7
      src/base/bittorrent/torrenthandleimpl.h
  11. 89
      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. 20
      src/base/settingvalue.h
  17. 23
      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 @@ @@ -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 @@ -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;
}

1
src/base/CMakeLists.txt

@ -24,6 +24,7 @@ add_library(qbt_base STATIC @@ -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

1
src/base/base.pri

@ -23,6 +23,7 @@ HEADERS += \ @@ -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 \

5
src/base/bittorrent/addtorrentparams.h

@ -28,12 +28,15 @@ @@ -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 @@ -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;

43
src/base/bittorrent/session.cpp

@ -396,7 +396,7 @@ Session::Session(QObject *parent) @@ -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 @@ -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 @@ -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 @@ -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 @@ -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) @@ -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

9
src/base/bittorrent/session.h

@ -114,7 +114,7 @@ namespace BitTorrent @@ -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 @@ -160,7 +160,6 @@ namespace BitTorrent
Q_ENUM_NS(OSMemoryPriority)
#endif
}
using namespace SessionSettingsEnums;
struct SessionMetricIndices
{
@ -276,8 +275,8 @@ namespace BitTorrent @@ -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 @@ -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

@ -0,0 +1,51 @@ @@ -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 @@ -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;

43
src/base/bittorrent/torrenthandleimpl.cpp

@ -115,8 +115,8 @@ TorrentHandleImpl::TorrentHandleImpl(Session *session, lt::session *nativeSessio @@ -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 @@ -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 @@ -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 @@ -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 @@ -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) @@ -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 @@ -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 @@ -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);

7
src/base/bittorrent/torrenthandleimpl.h

@ -62,12 +62,13 @@ namespace BitTorrent @@ -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 @@ -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 @@ -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;

89
src/base/bittorrent/torrentinfo.cpp

@ -52,6 +52,29 @@ @@ -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 @@ -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 @@ -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 @@ -436,10 +443,26 @@ bool TorrentInfo::hasRootFolder() const
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();
// Solution for case of renamed root folder
@ -456,6 +479,32 @@ void TorrentInfo::stripRootFolder() @@ -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;

7
src/base/bittorrent/torrentinfo.h

@ -34,6 +34,7 @@ @@ -34,6 +34,7 @@
#include <QtContainerFwd>
#include "base/indexrange.h"
#include "torrentcontentlayout.h"
class QByteArray;
class QDateTime;
@ -94,13 +95,17 @@ namespace BitTorrent @@ -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;
};
}

2
src/base/rss/rss_autodownloader.cpp

@ -396,7 +396,7 @@ void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job) @@ -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();

64
src/base/rss/rss_autodownloadrule.cpp

@ -40,11 +40,11 @@ @@ -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 @@ -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")); @@ -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 @@ -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 @@ -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 @@ -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 @@ -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) @@ -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

8
src/base/rss/rss_autodownloadrule.h

@ -29,9 +29,13 @@ @@ -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 @@ -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);

20
src/base/settingvalue.h

@ -34,6 +34,7 @@ @@ -34,6 +34,7 @@
#include <QString>
#include "settingsstorage.h"
#include "utils/string.h"
template <typename T>
class CachedSettingValue
@ -77,13 +78,13 @@ public: @@ -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: @@ -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;

23
src/base/utils/string.h

@ -30,6 +30,7 @@ @@ -30,6 +30,7 @@
#pragma once
#include <QChar>
#include <QMetaEnum>
#include <QString>
#include <Qt>
#include <QtContainerFwd>
@ -71,5 +72,27 @@ namespace Utils @@ -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);
}
}
}

12
src/gui/addnewtorrentdialog.cpp

@ -123,12 +123,8 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP @@ -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() @@ -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() @@ -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 @@ -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)

57
src/gui/addnewtorrentdialog.ui

@ -135,7 +135,7 @@ @@ -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 &quot;Download&quot; page of the options dialog</string>
@ -152,7 +152,7 @@ @@ -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 @@ @@ -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 @@ @@ -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>

6
src/gui/optionsdialog.cpp

@ -358,7 +358,7 @@ OptionsDialog::OptionsDialog(QWidget *parent) @@ -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() @@ -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() @@ -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);

52
src/gui/optionsdialog.ui

@ -769,14 +769,50 @@ @@ -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">

24
src/gui/rss/automatedrssdownloader.cpp

@ -275,11 +275,9 @@ void AutomatedRssDownloader::updateRuleDefinitionBox() @@ -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() @@ -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() @@ -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());
}

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

7
src/webui/api/appcontroller.cpp

@ -56,6 +56,7 @@ @@ -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() @@ -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() @@ -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"))

8
src/webui/api/torrentscontroller.cpp

@ -606,7 +606,6 @@ void TorrentsController::addAction() @@ -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() @@ -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() @@ -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;

2
src/webui/webapplication.h

@ -43,7 +43,7 @@ @@ -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;

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

@ -89,11 +89,14 @@ @@ -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>

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

@ -82,6 +82,16 @@ window.qBittorrent.Download = (function() { @@ -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();
};

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

@ -77,11 +77,14 @@ @@ -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>

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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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…
Cancel
Save