Browse Source

Merge pull request #4784 from glassez/asm

Implement Advanced Saving Management subsystem
adaptive-webui-19844
sledgehammer999 9 years ago
parent
commit
6ff929e567
  1. 7
      src/app/application.cpp
  2. 4
      src/app/upgrade.h
  3. 2
      src/base/base.pri
  4. 34
      src/base/bittorrent/private/statistics.cpp
  5. 560
      src/base/bittorrent/session.cpp
  6. 79
      src/base/bittorrent/session.h
  7. 91
      src/base/bittorrent/torrenthandle.cpp
  8. 16
      src/base/bittorrent/torrenthandle.h
  9. 4
      src/base/bittorrent/torrentinfo.cpp
  10. 2
      src/base/bittorrent/torrentinfo.h
  11. 2
      src/base/http/types.h
  12. 407
      src/base/preferences.cpp
  13. 64
      src/base/preferences.h
  14. 17
      src/base/rss/rssdownloadrule.cpp
  15. 6
      src/base/rss/rssdownloadrule.h
  16. 2
      src/base/rss/rssfeed.cpp
  17. 244
      src/base/settingsstorage.cpp
  18. 65
      src/base/settingsstorage.h
  19. 29
      src/base/torrentfilter.cpp
  20. 14
      src/base/torrentfilter.h
  21. 32
      src/base/types.h
  22. 17
      src/base/utils/fs.cpp
  23. 4
      src/base/utils/fs.h
  24. 1
      src/base/utils/misc.h
  25. 338
      src/gui/addnewtorrentdialog.cpp
  26. 22
      src/gui/addnewtorrentdialog.h
  27. 138
      src/gui/addnewtorrentdialog.ui
  28. 6
      src/gui/mainwindow.cpp
  29. 379
      src/gui/options.ui
  30. 78
      src/gui/options_imp.cpp
  31. 3
      src/gui/options_imp.h
  32. 40
      src/gui/rss/automatedrssdownloader.cpp
  33. 2
      src/gui/rss/automatedrssdownloader.h
  34. 6
      src/gui/rss/automatedrssdownloader.ui
  35. 2
      src/gui/rss/rss_imp.cpp
  36. 4
      src/gui/scanfoldersdelegate.cpp
  37. 2
      src/gui/search/searchwidget.cpp
  38. 12
      src/gui/torrentmodel.cpp
  39. 2
      src/gui/torrentmodel.h
  40. 293
      src/gui/transferlistfilterswidget.cpp
  41. 27
      src/gui/transferlistfilterswidget.h
  42. 8
      src/gui/transferlistsortmodel.cpp
  43. 4
      src/gui/transferlistsortmodel.h
  44. 128
      src/gui/transferlistwidget.cpp
  45. 8
      src/gui/transferlistwidget.h
  46. 2
      src/webui/abstractwebapplication.cpp
  47. 23
      src/webui/btjson.cpp
  48. 2
      src/webui/btjson.h
  49. 2
      src/webui/extra_translations.h
  50. 30
      src/webui/prefjson.cpp
  51. 42
      src/webui/webapplication.cpp
  52. 2
      src/webui/webapplication.h
  53. 82
      src/webui/webui.qrc
  54. 4
      src/webui/www/private/index.html
  55. 34
      src/webui/www/public/download.html
  56. 4
      src/webui/www/public/filters.html
  57. 30
      src/webui/www/public/newcategory.html
  58. 14
      src/webui/www/public/preferences_content.html
  59. 166
      src/webui/www/public/scripts/client.js
  60. 14
      src/webui/www/public/scripts/dynamicTable.js
  61. 20
      src/webui/www/public/scripts/mocha-init.js
  62. 2
      src/webui/www/public/upload.html

7
src/app/application.cpp

@ -59,6 +59,7 @@ @@ -59,6 +59,7 @@
#include "application.h"
#include "base/logger.h"
#include "base/settingsstorage.h"
#include "base/preferences.h"
#include "base/utils/fs.h"
#include "base/utils/misc.h"
@ -80,6 +81,7 @@ Application::Application(const QString &id, int &argc, char **argv) @@ -80,6 +81,7 @@ Application::Application(const QString &id, int &argc, char **argv)
#endif
{
Logger::initInstance();
SettingsStorage::initInstance();
Preferences::initInstance();
#if defined(Q_OS_MACX) && !defined(DISABLE_GUI)
@ -146,7 +148,7 @@ void Application::torrentFinished(BitTorrent::TorrentHandle *const torrent) @@ -146,7 +148,7 @@ void Application::torrentFinished(BitTorrent::TorrentHandle *const torrent)
QString program = pref->getAutoRunProgram();
program.replace("%N", torrent->name());
program.replace("%L", torrent->label());
program.replace("%L", torrent->category());
program.replace("%F", Utils::Fs::toNativePath(torrent->contentPath()));
program.replace("%R", Utils::Fs::toNativePath(torrent->rootPath()));
program.replace("%D", Utils::Fs::toNativePath(torrent->savePath()));
@ -227,7 +229,7 @@ void Application::processParams(const QStringList &params) @@ -227,7 +229,7 @@ void Application::processParams(const QStringList &params)
foreach (QString param, params) {
param = param.trimmed();
#ifndef DISABLE_GUI
if (Preferences::instance()->useAdditionDialog())
if (AddNewTorrentDialog::isEnabled())
AddNewTorrentDialog::show(param, m_window);
else
#endif
@ -466,6 +468,7 @@ void Application::cleanup() @@ -466,6 +468,7 @@ void Application::cleanup()
#endif
Net::DownloadManager::freeInstance();
Preferences::freeInstance();
SettingsStorage::freeInstance();
Logger::freeInstance();
IconProvider::freeInstance();
#ifndef DISABLE_GUI

4
src/app/upgrade.h

@ -129,8 +129,8 @@ bool upgradeResumeFile(const QString &filepath, const QVariantHash &oldTorrent = @@ -129,8 +129,8 @@ bool upgradeResumeFile(const QString &filepath, const QVariantHash &oldTorrent =
bool upgrade(bool ask = true)
{
// Move RSS cookies to common storage
Preferences::instance()->moveRSSCookies();
// Upgrade preferences
Preferences::instance()->upgrade();
QString backupFolderPath = Utils::Fs::expandPathAbs(Utils::Fs::QDesktopServicesDataLocation() + "BT_backup");
QDir backupFolderDir(backupFolderPath);

2
src/base/base.pri

@ -4,6 +4,7 @@ HEADERS += \ @@ -4,6 +4,7 @@ HEADERS += \
$$PWD/filesystemwatcher.h \
$$PWD/qinisettings.h \
$$PWD/logger.h \
$$PWD/settingsstorage.h \
$$PWD/preferences.h \
$$PWD/iconprovider.h \
$$PWD/http/irequesthandler.h \
@ -58,6 +59,7 @@ SOURCES += \ @@ -58,6 +59,7 @@ SOURCES += \
$$PWD/tristatebool.cpp \
$$PWD/filesystemwatcher.cpp \
$$PWD/logger.cpp \
$$PWD/settingsstorage.cpp \
$$PWD/preferences.cpp \
$$PWD/iconprovider.cpp \
$$PWD/http/connection.cpp \

34
src/base/bittorrent/private/statistics.cpp

@ -3,7 +3,6 @@ @@ -3,7 +3,6 @@
#include <libtorrent/session.hpp>
#include "base/qinisettings.h"
#include "base/preferences.h"
#include "base/bittorrent/sessionstatus.h"
#include "base/bittorrent/session.h"
#include "statistics.h"
@ -76,40 +75,9 @@ void Statistics::save() const @@ -76,40 +75,9 @@ void Statistics::save() const
void Statistics::load()
{
// Temp code. Versions v3.1.4 and v3.1.5 saved the data in the qbittorrent.ini file.
// This code reads the data from there, writes it to the new file, and removes the keys
// from the old file. This code should be removed after some time has passed.
// e.g. When we reach v3.3.0
// Don't forget to remove:
// 1. Preferences::getStats()
// 2. Preferences::removeStats()
// 3. #include "base/preferences.h"
Preferences* const pref = Preferences::instance();
QIniSettings s("qBittorrent", "qBittorrent-data");
QVariantHash v = pref->getStats();
// Let's test if the qbittorrent.ini holds the key
if (!v.isEmpty()) {
m_dirty = true;
// If the user has used qbt > 3.1.5 and then reinstalled/used
// qbt < 3.1.6, there will be stats in qbittorrent-data.ini too
// so we need to merge those 2.
if (s.contains("Stats/AllStats")) {
QVariantHash tmp = s.value("Stats/AllStats").toHash();
v["AlltimeDL"] = v["AlltimeDL"].toULongLong() + tmp["AlltimeDL"].toULongLong();
v["AlltimeUL"] = v["AlltimeUL"].toULongLong() + tmp["AlltimeUL"].toULongLong();
}
}
else {
v = s.value("Stats/AllStats").toHash();
}
QVariantHash v = s.value("Stats/AllStats").toHash();
m_alltimeDL = v["AlltimeDL"].toULongLong();
m_alltimeUL = v["AlltimeUL"].toULongLong();
if (m_dirty) {
save();
pref->removeStats();
}
}

560
src/base/bittorrent/session.cpp

@ -28,10 +28,6 @@ @@ -28,10 +28,6 @@
* exception statement from your version.
*/
#include "session.h"
using namespace BitTorrent;
#include <QDebug>
#include <QDir>
#include <QDateTime>
@ -44,6 +40,7 @@ using namespace BitTorrent; @@ -44,6 +40,7 @@ using namespace BitTorrent;
#include <QProcess>
#include <QCoreApplication>
#include <QThread>
#include <QRegExp>
#include <queue>
#include <vector>
@ -70,6 +67,7 @@ using namespace BitTorrent; @@ -70,6 +67,7 @@ using namespace BitTorrent;
#include "base/utils/string.h"
#include "base/unicodestrings.h"
#include "base/logger.h"
#include "base/settingsstorage.h"
#include "base/preferences.h"
#include "base/torrentfilter.h"
#include "base/net/downloadmanager.h"
@ -94,20 +92,73 @@ static const char RESUME_FOLDER[] = "BT_backup"; @@ -94,20 +92,73 @@ static const char RESUME_FOLDER[] = "BT_backup";
namespace libt = libtorrent;
using namespace BitTorrent;
static bool readFile(const QString &path, QByteArray &buf);
static bool loadTorrentResumeData(const QByteArray &data, AddTorrentData &out, int &prio, MagnetUri &magnetUri);
#define SETTINGS_KEY(name) "BitTorrent/Session/" name
const QString KEY_CATEGORIES = SETTINGS_KEY("Categories");
const QString KEY_MAXRATIOACTION = SETTINGS_KEY("MaxRatioAction");
const QString KEY_DEFAULTSAVEPATH = SETTINGS_KEY("DefaultSavePath");
const QString KEY_TEMPPATH = SETTINGS_KEY("TempPath");
const QString KEY_SUBCATEGORIESENABLED = SETTINGS_KEY("SubcategoriesEnabled");
const QString KEY_TEMPPATHENABLED = SETTINGS_KEY("TempPathEnabled");
const QString KEY_DISABLEASMBYDEFAULT = SETTINGS_KEY("DisableASMByDefault");
const QString KEY_DISABLEASMONCATEGORYCHANGED = SETTINGS_KEY("DisableASMTriggers/CategoryChanged");
const QString KEY_DISABLEASMONDEFAULTSAVEPATHCHANGED = SETTINGS_KEY("DisableASMTriggers/DefaultSavePathChanged");
const QString KEY_DISABLEASMONCATEGORYSAVEPATHCHANGED = SETTINGS_KEY("DisableASMTriggers/CategorySavePathChanged");
const QString KEY_ADDTORRENTPAUSED = SETTINGS_KEY("AddTorrentPaused");
namespace
{
bool readFile(const QString &path, QByteArray &buf);
bool loadTorrentResumeData(const QByteArray &data, AddTorrentData &torrentData, int &prio, MagnetUri &magnetUri);
void torrentQueuePositionUp(const libt::torrent_handle &handle);
void torrentQueuePositionDown(const libt::torrent_handle &handle);
void torrentQueuePositionTop(const libt::torrent_handle &handle);
void torrentQueuePositionBottom(const libt::torrent_handle &handle);
QStringMap map_cast(const QVariantMap &map)
{
QStringMap result;
foreach (const QString &key, map.keys())
result[key] = map.value(key).toString();
return result;
}
QVariantMap map_cast(const QStringMap &map)
{
QVariantMap result;
foreach (const QString &key, map.keys())
result[key] = map.value(key);
return result;
}
QString normalizePath(QString path, const QString &defaultPath = Utils::Fs::QDesktopServicesDownloadLocation())
{
path = Utils::Fs::fromNativePath(path.trimmed());
return !path.isEmpty() ? path : defaultPath;
}
QStringMap expandCategories(const QStringMap &categories)
{
QStringMap expanded = categories;
static void torrentQueuePositionUp(const libt::torrent_handle &handle);
static void torrentQueuePositionDown(const libt::torrent_handle &handle);
static void torrentQueuePositionTop(const libt::torrent_handle &handle);
static void torrentQueuePositionBottom(const libt::torrent_handle &handle);
foreach (const QString &category, categories.keys()) {
foreach (const QString &subcat, Session::expandCategory(category)) {
if (!expanded.contains(subcat))
expanded[subcat] = "";
}
}
return expanded;
}
}
// Session
Session *Session::m_instance = 0;
Session *Session::m_instance = nullptr;
Session::Session(QObject *parent)
: QObject(parent)
, m_settings(SettingsStorage::instance())
, m_LSDEnabled(false)
, m_DHTEnabled(false)
, m_PeXEnabled(false)
@ -118,10 +169,8 @@ Session::Session(QObject *parent) @@ -118,10 +169,8 @@ Session::Session(QObject *parent)
, m_globalMaxRatio(-1)
, m_numResumeData(0)
, m_extraLimit(0)
, m_appendLabelToSavePath(false)
, m_appendExtension(false)
, m_refreshInterval(0)
, m_highRatioAction(MaxRatioAction::Pause)
{
Preferences* const pref = Preferences::instance();
Logger* const logger = Logger::instance();
@ -174,6 +223,13 @@ Session::Session(QObject *parent) @@ -174,6 +223,13 @@ Session::Session(QObject *parent)
m_nativeSession->add_extension(&libt::create_ut_pex_plugin);
m_nativeSession->add_extension(&libt::create_smart_ban_plugin);
m_categories = map_cast(m_settings->loadValue(KEY_CATEGORIES).toMap());
if (isSubcategoriesEnabled()) {
// if subcategories support changed manually
m_categories = expandCategories(m_categories);
m_settings->storeValue(KEY_CATEGORIES, map_cast(m_categories));
}
m_refreshTimer = new QTimer(this);
m_refreshTimer->setInterval(2000);
connect(m_refreshTimer, SIGNAL(timeout()), SLOT(refresh()));
@ -185,6 +241,10 @@ Session::Session(QObject *parent) @@ -185,6 +241,10 @@ Session::Session(QObject *parent)
m_statistics = new Statistics(this);
m_maxRatioAction = static_cast<MaxRatioAction>(m_settings->loadValue(KEY_MAXRATIOACTION, Pause).toInt());
m_defaultSavePath = normalizePath(m_settings->loadValue(KEY_DEFAULTSAVEPATH).toString());
m_tempPath = normalizePath(m_settings->loadValue(KEY_TEMPPATH).toString(), m_defaultSavePath + "/temp");
// Apply user settings to BitTorrent session
configure();
connect(pref, SIGNAL(changed()), SLOT(configure()));
@ -231,17 +291,19 @@ bool Session::isQueueingEnabled() const @@ -231,17 +291,19 @@ bool Session::isQueueingEnabled() const
bool Session::isTempPathEnabled() const
{
return !m_tempPath.isEmpty();
return m_settings->loadValue(KEY_TEMPPATHENABLED, false).toBool();
}
bool Session::isAppendExtensionEnabled() const
void Session::setTempPathEnabled(bool enabled)
{
return m_appendExtension;
m_settings->storeValue(KEY_TEMPPATHENABLED, enabled);
foreach (TorrentHandle *const torrent, m_torrents)
torrent->handleTempPathChanged();
}
bool Session::useAppendLabelToSavePath() const
bool Session::isAppendExtensionEnabled() const
{
return m_appendLabelToSavePath;
return m_appendExtension;
}
QString Session::defaultSavePath() const
@ -254,6 +316,200 @@ QString Session::tempPath() const @@ -254,6 +316,200 @@ QString Session::tempPath() const
return m_tempPath;
}
bool Session::isValidCategoryName(const QString &name)
{
QRegExp re(R"#(^([^\\\/]|[^\\\/]([^\\\/]|\/(?=[^\/]))*[^\\\/])$)#");
if (!name.isEmpty() && (re.indexIn(name) != 0)) {
qDebug() << "Incorrect category name:" << name;
return false;
}
return true;
}
QStringList Session::expandCategory(const QString &category)
{
QStringList result;
if (!isValidCategoryName(category))
return result;
int index = 0;
while ((index = category.indexOf('/', index)) >= 0) {
result << category.left(index);
++index;
}
result << category;
return result;
}
QStringList Session::categories() const
{
return m_categories.keys();
}
QString Session::categorySavePath(const QString &categoryName) const
{
QString basePath = m_defaultSavePath;
QString path = m_categories.value(categoryName);
if (categoryName.isEmpty()) return basePath;
if (path.isEmpty()) // use implicit save path
path = Utils::Fs::toValidFileSystemName(categoryName, true);
if (!QDir::isAbsolutePath(path))
path = QString("%1/%2").arg(basePath).arg(path);
return path;
}
bool Session::addCategory(const QString &name, const QString &savePath)
{
if (name.isEmpty()) return false;
if (!isValidCategoryName(name) || m_categories.contains(name))
return false;
if (isSubcategoriesEnabled()) {
foreach (const QString &parent, expandCategory(name)) {
if ((parent != name) && !m_categories.contains(parent)) {
m_categories[parent] = "";
emit categoryAdded(parent);
}
}
}
m_categories[name] = savePath;
m_settings->storeValue(KEY_CATEGORIES, map_cast(m_categories));
emit categoryAdded(name);
return true;
}
bool Session::editCategory(const QString &name, const QString &savePath)
{
if (!m_categories.contains(name)) return false;
if (categorySavePath(name) == savePath) return false;
m_categories[name] = savePath;
if (isDisableASMWhenCategorySavePathChanged()) {
foreach (TorrentHandle *const torrent, torrents())
if (torrent->category() == name)
torrent->setASMEnabled(false);
}
else {
foreach (TorrentHandle *const torrent, torrents())
if (torrent->category() == name)
torrent->handleCategorySavePathChanged();
}
return true;
}
bool Session::removeCategory(const QString &name)
{
foreach (TorrentHandle *const torrent, torrents())
if (torrent->belongsToCategory(name))
torrent->setCategory("");
// remove stored category and its subcategories if exist
bool result = false;
if (isSubcategoriesEnabled()) {
// remove subcategories
QString test = name + "/";
foreach (const QString &category, m_categories.keys()) {
if (category.startsWith(test)) {
m_categories.remove(category);
result = true;
emit categoryRemoved(category);
}
}
}
result = (m_categories.remove(name) > 0) || result;
if (result) {
// update stored categories
m_settings->storeValue(KEY_CATEGORIES, map_cast(m_categories));
emit categoryRemoved(name);
}
return result;
}
bool Session::isSubcategoriesEnabled() const
{
return m_settings->loadValue(KEY_SUBCATEGORIESENABLED, false).toBool();
}
void Session::setSubcategoriesEnabled(bool value)
{
if (isSubcategoriesEnabled() == value) return;
if (value) {
// expand categories to include all parent categories
m_categories = expandCategories(m_categories);
// update stored categories
m_settings->storeValue(KEY_CATEGORIES, map_cast(m_categories));
}
else {
// reload categories
m_categories = map_cast(m_settings->loadValue(KEY_CATEGORIES).toMap());
}
m_settings->storeValue(KEY_SUBCATEGORIESENABLED, value);
emit subcategoriesSupportChanged();
}
bool Session::isASMDisabledByDefault() const
{
return m_settings->loadValue(KEY_DISABLEASMBYDEFAULT, true).toBool();
}
void Session::setASMDisabledByDefault(bool value)
{
m_settings->storeValue(KEY_DISABLEASMBYDEFAULT, value);
}
bool Session::isDisableASMWhenCategoryChanged() const
{
return m_settings->loadValue(KEY_DISABLEASMONCATEGORYCHANGED, false).toBool();
}
void Session::setDisableASMWhenCategoryChanged(bool value)
{
m_settings->storeValue(KEY_DISABLEASMONCATEGORYCHANGED, value);
}
bool Session::isDisableASMWhenDefaultSavePathChanged() const
{
return m_settings->loadValue(KEY_DISABLEASMONDEFAULTSAVEPATHCHANGED, true).toBool();
}
void Session::setDisableASMWhenDefaultSavePathChanged(bool value)
{
m_settings->storeValue(KEY_DISABLEASMONDEFAULTSAVEPATHCHANGED, value);
}
bool Session::isDisableASMWhenCategorySavePathChanged() const
{
return m_settings->loadValue(KEY_DISABLEASMONCATEGORYSAVEPATHCHANGED, true).toBool();
}
void Session::setDisableASMWhenCategorySavePathChanged(bool value)
{
m_settings->storeValue(KEY_DISABLEASMONCATEGORYSAVEPATHCHANGED, value);
}
bool Session::isAddTorrentPaused() const
{
return m_settings->loadValue(KEY_ADDTORRENTPAUSED, false).toBool();
}
void Session::setAddTorrentPaused(bool value)
{
m_settings->storeValue(KEY_ADDTORRENTPAUSED, value);
}
qreal Session::globalMaxRatio() const
{
return m_globalMaxRatio;
@ -442,22 +698,12 @@ void Session::configure() @@ -442,22 +698,12 @@ void Session::configure()
setListeningPort();
}
// * Save path
setDefaultSavePath(pref->getSavePath());
// * Temp path
if (pref->isTempPathEnabled())
setDefaultTempPath(pref->getTempPath());
else
setDefaultTempPath();
uint newRefreshInterval = pref->getRefreshInterval();
if (newRefreshInterval != m_refreshInterval) {
m_refreshInterval = newRefreshInterval;
m_refreshTimer->setInterval(m_refreshInterval);
}
setAppendLabelToSavePath(pref->appendTorrentLabel());
setAppendExtension(pref->useIncompleteFilesExtension());
preAllocateAllFiles(pref->preAllocateAllFiles());
@ -587,7 +833,6 @@ void Session::configure() @@ -587,7 +833,6 @@ void Session::configure()
}
// * Maximum ratio
m_highRatioAction = pref->getMaxRatioAction();
setGlobalMaxRatio(pref->getGlobalMaxRatio());
// Ip Filter
@ -690,7 +935,7 @@ void Session::processBigRatios() @@ -690,7 +935,7 @@ void Session::processBigRatios()
if ((ratio <= TorrentHandle::MAX_RATIO) && (ratio >= ratioLimit)) {
Logger* const logger = Logger::instance();
if (m_highRatioAction == MaxRatioAction::Remove) {
if (m_maxRatioAction == Remove) {
logger->addMessage(tr("'%1' reached the maximum ratio you set. Removing...").arg(torrent->name()));
deleteTorrent(torrent->hash());
}
@ -974,18 +1219,19 @@ bool Session::addTorrent_impl(AddTorrentData addData, const MagnetUri &magnetUri @@ -974,18 +1219,19 @@ bool Session::addTorrent_impl(AddTorrentData addData, const MagnetUri &magnetUri
const TorrentInfo &torrentInfo, const QByteArray &fastresumeData)
{
if (!addData.resumed) {
// manage save path
QString defaultSavePath = this->defaultSavePath();
if (addData.savePath.isEmpty())
addData.savePath = defaultSavePath;
if (!addData.savePath.endsWith("/"))
addData.savePath += "/";
if (useAppendLabelToSavePath()) {
if ((addData.savePath == defaultSavePath) && !addData.label.isEmpty())
addData.savePath += QString("%1/").arg(addData.label);
if (addData.savePath.isEmpty() && isASMDisabledByDefault())
addData.savePath = m_defaultSavePath;
}
if (!addData.category.isEmpty()) {
if (!m_categories.contains(addData.category) && !addCategory(addData.category)) {
qWarning() << "Couldn't create category" << addData.category;
addData.category = "";
}
}
addData.savePath = Utils::Fs::fromNativePath(addData.savePath);
libt::add_torrent_params p;
InfoHash hash;
std::vector<char> buf(fastresumeData.constData(), fastresumeData.constData() + fastresumeData.size());
@ -1082,7 +1328,9 @@ bool Session::addTorrent_impl(AddTorrentData addData, const MagnetUri &magnetUri @@ -1082,7 +1328,9 @@ bool Session::addTorrent_impl(AddTorrentData addData, const MagnetUri &magnetUri
// Set actual save path (e.g. temporary folder)
if (isTempPathEnabled() && !addData.disableTempPath && !addData.hasSeedStatus)
savePath = m_tempPath;
else
else if (addData.savePath.isEmpty()) // using Advanced mode
savePath = categorySavePath(addData.category);
else // using Simple mode
savePath = addData.savePath;
p.save_path = Utils::String::toStdString(Utils::Fs::toNativePath(savePath));
@ -1341,51 +1589,32 @@ void Session::saveResumeData() @@ -1341,51 +1589,32 @@ void Session::saveResumeData()
}
}
void Session::setDefaultSavePath(const QString &path)
void Session::setDefaultSavePath(QString path)
{
if (path.isEmpty()) return;
path = normalizePath(path);
if (m_defaultSavePath == path) return;
m_defaultSavePath = Utils::Fs::fromNativePath(path);
if (!m_defaultSavePath.endsWith("/"))
m_defaultSavePath += "/";
}
m_defaultSavePath = path;
m_settings->storeValue(KEY_DEFAULTSAVEPATH, m_defaultSavePath);
void Session::setDefaultTempPath(const QString &path)
{
QString tempPath;
if (!path.isEmpty()) {
tempPath = Utils::Fs::fromNativePath(path);
if (!tempPath.endsWith("/"))
tempPath += "/";
}
if (m_tempPath != tempPath) {
m_tempPath = tempPath;
foreach (TorrentHandle *const torrent, m_torrents)
torrent->handleTempPathChanged();
}
if (isDisableASMWhenDefaultSavePathChanged())
foreach (TorrentHandle *const torrent, torrents())
torrent->setASMEnabled(false);
else
foreach (TorrentHandle *const torrent, torrents())
torrent->handleCategorySavePathChanged();
}
void Session::setAppendLabelToSavePath(bool append)
void Session::setTempPath(QString path)
{
if (m_appendLabelToSavePath != append) {
m_appendLabelToSavePath = append;
foreach (TorrentHandle *const torrent, m_torrents) {
QString label = torrent->label();
if (label.isEmpty()) continue;
path = normalizePath(path, m_defaultSavePath + "/temp");
if (m_tempPath == path) return;
QString testedOldSavePath = m_defaultSavePath;
QString newSavePath = m_defaultSavePath;
if (!m_appendLabelToSavePath)
testedOldSavePath += QString("%1/").arg(label);
else
newSavePath += QString("%1/").arg(label);
m_tempPath = path;
m_settings->storeValue(KEY_TEMPPATH, m_tempPath);
if (torrent->savePath() == testedOldSavePath)
torrent->move(newSavePath);
}
}
foreach (TorrentHandle *const torrent, m_torrents)
torrent->handleTempPathChanged();
}
void Session::setAppendExtension(bool append)
@ -1536,6 +1765,19 @@ bool Session::isListening() const @@ -1536,6 +1765,19 @@ bool Session::isListening() const
return m_nativeSession->is_listening();
}
MaxRatioAction Session::maxRatioAction() const
{
return m_maxRatioAction;
}
void Session::setMaxRatioAction(MaxRatioAction act)
{
if (m_maxRatioAction != act) {
m_maxRatioAction = act;
m_settings->storeValue(KEY_MAXRATIOACTION, act);
}
}
// Torrents will a ratio superior to the given value will
// be automatically deleted
void Session::setGlobalMaxRatio(qreal ratio)
@ -1586,22 +1828,14 @@ void Session::handleTorrentSavePathChanged(TorrentHandle *const torrent) @@ -1586,22 +1828,14 @@ void Session::handleTorrentSavePathChanged(TorrentHandle *const torrent)
emit torrentSavePathChanged(torrent);
}
void Session::handleTorrentLabelChanged(TorrentHandle *const torrent, const QString &oldLabel)
void Session::handleTorrentCategoryChanged(TorrentHandle *const torrent, const QString &oldCategory)
{
if (m_appendLabelToSavePath) {
QString testedOldSavePath = m_defaultSavePath;
if (!oldLabel.isEmpty())
testedOldSavePath += QString("%1/").arg(oldLabel);
QString newLabel = torrent->label();
if (torrent->savePath() == testedOldSavePath) {
QString newSavePath = m_defaultSavePath;
if (!newLabel.isEmpty())
newSavePath += QString("%1/").arg(newLabel);
torrent->move(newSavePath);
}
}
emit torrentCategoryChanged(torrent, oldCategory);
}
emit torrentLabelChanged(torrent, oldLabel);
void Session::handleTorrentSavingModeChanged(TorrentHandle * const torrent)
{
emit torrentSavingModeChanged(torrent);
}
void Session::handleTorrentTrackersAdded(TorrentHandle *const torrent, const QList<TrackerEntry> &newTrackers)
@ -2138,7 +2372,7 @@ void Session::createTorrentHandle(const libt::torrent_handle &nativeHandle) @@ -2138,7 +2372,7 @@ void Session::createTorrentHandle(const libt::torrent_handle &nativeHandle)
bool addPaused = data.addPaused;
if (data.addPaused == TriStateBool::Undefined)
addPaused = pref->addTorrentsInPause();
addPaused = isAddTorrentPaused();
// Start torrent because it was added in paused state
if (!addPaused)
@ -2351,86 +2585,90 @@ void Session::handleStateUpdateAlert(libt::state_update_alert *p) @@ -2351,86 +2585,90 @@ void Session::handleStateUpdateAlert(libt::state_update_alert *p)
emit torrentsUpdated();
}
bool readFile(const QString &path, QByteArray &buf)
{
QFile file(path);
if (!file.open(QIODevice::ReadOnly)) {
qDebug("Cannot read file %s: %s", qPrintable(path), qPrintable(file.errorString()));
return false;
}
buf = file.readAll();
return true;
}
bool loadTorrentResumeData(const QByteArray &data, AddTorrentData &out, int &prio, MagnetUri &magnetUri)
namespace
{
out = AddTorrentData();
out.resumed = true;
out.skipChecking = false;
libt::lazy_entry fast;
libt::error_code ec;
libt::lazy_bdecode(data.constData(), data.constData() + data.size(), fast, ec);
if (ec || (fast.type() != libt::lazy_entry::dict_t)) return false;
bool readFile(const QString &path, QByteArray &buf)
{
QFile file(path);
if (!file.open(QIODevice::ReadOnly)) {
qDebug("Cannot read file %s: %s", qPrintable(path), qPrintable(file.errorString()));
return false;
}
out.savePath = Utils::Fs::fromNativePath(Utils::String::fromStdString(fast.dict_find_string_value("qBt-savePath")));
if (out.savePath.isEmpty()) {
Logger::instance()->addMessage("Empty torrent save path was loaded from .fastresume file! Using default one...", Log::WARNING);
out.savePath = Preferences::instance()->getSavePath();
buf = file.readAll();
return true;
}
out.ratioLimit = Utils::String::fromStdString(fast.dict_find_string_value("qBt-ratioLimit")).toDouble();
out.label = Utils::String::fromStdString(fast.dict_find_string_value("qBt-label"));
out.name = Utils::String::fromStdString(fast.dict_find_string_value("qBt-name"));
out.hasSeedStatus = fast.dict_find_int_value("qBt-seedStatus");
out.disableTempPath = fast.dict_find_int_value("qBt-tempPathDisabled");
magnetUri = MagnetUri(Utils::String::fromStdString(fast.dict_find_string_value("qBt-magnetUri")));
out.addPaused = fast.dict_find_int_value("qBt-paused");
out.addForced = fast.dict_find_int_value("qBt-forced");
prio = fast.dict_find_int_value("qBt-queuePosition");
return true;
}
bool loadTorrentResumeData(const QByteArray &data, AddTorrentData &torrentData, int &prio, MagnetUri &magnetUri)
{
torrentData = AddTorrentData();
torrentData.resumed = true;
torrentData.skipChecking = false;
libt::lazy_entry fast;
libt::error_code ec;
libt::lazy_bdecode(data.constData(), data.constData() + data.size(), fast, ec);
if (ec || (fast.type() != libt::lazy_entry::dict_t)) return false;
torrentData.savePath = Utils::Fs::fromNativePath(Utils::String::fromStdString(fast.dict_find_string_value("qBt-savePath")));
torrentData.ratioLimit = Utils::String::fromStdString(fast.dict_find_string_value("qBt-ratioLimit")).toDouble();
// **************************************************************************************
// Workaround to convert legacy label to category
// TODO: Should be removed in future
torrentData.category = Utils::String::fromStdString(fast.dict_find_string_value("qBt-label"));
if (torrentData.category.isEmpty())
// **************************************************************************************
torrentData.category = Utils::String::fromStdString(fast.dict_find_string_value("qBt-category"));
torrentData.name = Utils::String::fromStdString(fast.dict_find_string_value("qBt-name"));
torrentData.hasSeedStatus = fast.dict_find_int_value("qBt-seedStatus");
torrentData.disableTempPath = fast.dict_find_int_value("qBt-tempPathDisabled");
magnetUri = MagnetUri(Utils::String::fromStdString(fast.dict_find_string_value("qBt-magnetUri")));
torrentData.addPaused = fast.dict_find_int_value("qBt-paused");
torrentData.addForced = fast.dict_find_int_value("qBt-forced");
prio = fast.dict_find_int_value("qBt-queuePosition");
void torrentQueuePositionUp(const libt::torrent_handle &handle)
{
try {
handle.queue_position_up();
}
catch (std::exception &exc) {
qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
return true;
}
}
void torrentQueuePositionDown(const libt::torrent_handle &handle)
{
try {
handle.queue_position_down();
}
catch (std::exception &exc) {
qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
void torrentQueuePositionUp(const libt::torrent_handle &handle)
{
try {
handle.queue_position_up();
}
catch (std::exception &exc) {
qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
}
}
}
void torrentQueuePositionTop(const libt::torrent_handle &handle)
{
try {
handle.queue_position_top();
}
catch (std::exception &exc) {
qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
void torrentQueuePositionDown(const libt::torrent_handle &handle)
{
try {
handle.queue_position_down();
}
catch (std::exception &exc) {
qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
}
}
}
void torrentQueuePositionBottom(const libt::torrent_handle &handle)
{
try {
handle.queue_position_bottom();
void torrentQueuePositionTop(const libt::torrent_handle &handle)
{
try {
handle.queue_position_top();
}
catch (std::exception &exc) {
qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
}
}
catch (std::exception &exc) {
qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
void torrentQueuePositionBottom(const libt::torrent_handle &handle)
{
try {
handle.queue_position_bottom();
}
catch (std::exception &exc) {
qDebug() << Q_FUNC_INFO << " fails: " << exc.what();
}
}
}

79
src/base/bittorrent/session.h

@ -32,6 +32,7 @@ @@ -32,6 +32,7 @@
#include <QFile>
#include <QHash>
#include <QMap>
#include <QPointer>
#include <QVector>
#include <QMutex>
@ -110,8 +111,19 @@ class FilterParserThread; @@ -110,8 +111,19 @@ class FilterParserThread;
class BandwidthScheduler;
class Statistics;
class ResumeDataSavingManager;
class SettingsStorage;
typedef QPair<QString, QString> QStringPair;
enum MaxRatioAction
{
Pause,
Remove
};
enum TorrentExportFolder
{
Regular,
Finished
};
namespace BitTorrent
{
@ -127,7 +139,7 @@ namespace BitTorrent @@ -127,7 +139,7 @@ namespace BitTorrent
struct AddTorrentParams
{
QString name;
QString label;
QString category;
QString savePath;
bool disableTempPath = false; // e.g. for imported torrents
bool sequential = false;
@ -165,11 +177,49 @@ namespace BitTorrent @@ -165,11 +177,49 @@ namespace BitTorrent
bool isPexEnabled() const;
bool isQueueingEnabled() const;
qreal globalMaxRatio() const;
bool isTempPathEnabled() const;
bool isAppendExtensionEnabled() const;
bool useAppendLabelToSavePath() const;
QString defaultSavePath() const;
void setDefaultSavePath(QString path);
QString tempPath() const;
void setTempPath(QString path);
bool isTempPathEnabled() const;
void setTempPathEnabled(bool enabled);
static bool isValidCategoryName(const QString &name);
// returns category itself and all top level categories
static QStringList expandCategory(const QString &category);
QStringList categories() const;
QString categorySavePath(const QString &categoryName) const;
bool addCategory(const QString &name, const QString &savePath = "");
bool editCategory(const QString &name, const QString &savePath);
bool removeCategory(const QString &name);
bool isSubcategoriesEnabled() const;
void setSubcategoriesEnabled(bool value);
// Advanced Saving Management subsystem (ASM)
//
// Each torrent can be either in Simple mode or in Advanced mode
// In Simple mode torrent has explicit save path
// In Advanced Mode torrent has implicit save path (based on Default
// save path and Category save path)
// In Advanced Mode torrent save path can be changed in following cases:
// 1. Default save path changed
// 2. Torrent category save path changed
// 3. Torrent category changed
// (unless otherwise is specified)
bool isASMDisabledByDefault() const;
void setASMDisabledByDefault(bool value);
bool isDisableASMWhenCategoryChanged() const;
void setDisableASMWhenCategoryChanged(bool value);
bool isDisableASMWhenDefaultSavePathChanged() const;
void setDisableASMWhenDefaultSavePathChanged(bool value);
bool isDisableASMWhenCategorySavePathChanged() const;
void setDisableASMWhenCategorySavePathChanged(bool value);
bool isAddTorrentPaused() const;
void setAddTorrentPaused(bool value);
TorrentHandle *findTorrent(const InfoHash &hash) const;
QHash<InfoHash, TorrentHandle *> torrents() const;
@ -184,6 +234,9 @@ namespace BitTorrent @@ -184,6 +234,9 @@ namespace BitTorrent
int uploadRateLimit() const;
bool isListening() const;
MaxRatioAction maxRatioAction() const;
void setMaxRatioAction(MaxRatioAction act);
void changeSpeedLimitMode(bool alternative);
void setDownloadRateLimit(int rate);
void setUploadRateLimit(int rate);
@ -208,7 +261,8 @@ namespace BitTorrent @@ -208,7 +261,8 @@ namespace BitTorrent
// TorrentHandle interface
void handleTorrentRatioLimitChanged(TorrentHandle *const torrent);
void handleTorrentSavePathChanged(TorrentHandle *const torrent);
void handleTorrentLabelChanged(TorrentHandle *const torrent, const QString &oldLabel);
void handleTorrentCategoryChanged(TorrentHandle *const torrent, const QString &oldCategory);
void handleTorrentSavingModeChanged(TorrentHandle *const torrent);
void handleTorrentMetadataReceived(TorrentHandle *const torrent);
void handleTorrentPaused(TorrentHandle *const torrent);
void handleTorrentResumed(TorrentHandle *const torrent);
@ -236,7 +290,8 @@ namespace BitTorrent @@ -236,7 +290,8 @@ namespace BitTorrent
void torrentFinished(BitTorrent::TorrentHandle *const torrent);
void torrentFinishedChecking(BitTorrent::TorrentHandle *const torrent);
void torrentSavePathChanged(BitTorrent::TorrentHandle *const torrent);
void torrentLabelChanged(BitTorrent::TorrentHandle *const torrent, const QString &oldLabel);
void torrentCategoryChanged(BitTorrent::TorrentHandle *const torrent, const QString &oldCategory);
void torrentSavingModeChanged(BitTorrent::TorrentHandle *const torrent);
void allTorrentsFinished();
void metadataLoaded(const BitTorrent::TorrentInfo &info);
void torrentMetadataLoaded(BitTorrent::TorrentHandle *const torrent);
@ -254,6 +309,9 @@ namespace BitTorrent @@ -254,6 +309,9 @@ namespace BitTorrent
void trackerlessStateChanged(BitTorrent::TorrentHandle *const torrent, bool trackerless);
void downloadFromUrlFailed(const QString &url, const QString &reason);
void downloadFromUrlFinished(const QString &url);
void categoryAdded(const QString &categoryName);
void categoryRemoved(const QString &categoryName);
void subcategoriesSupportChanged();
private slots:
void configure();
@ -287,8 +345,6 @@ namespace BitTorrent @@ -287,8 +345,6 @@ namespace BitTorrent
void adjustLimits(libtorrent::session_settings &sessionSettings);
const QStringList getListeningIPs();
void setListeningPort();
void setDefaultSavePath(const QString &path);
void setDefaultTempPath(const QString &path = QString());
void preAllocateAllFiles(bool b);
void setMaxConnectionsPerTorrent(int max);
void setMaxUploadsPerTorrent(int max);
@ -296,7 +352,6 @@ namespace BitTorrent @@ -296,7 +352,6 @@ namespace BitTorrent
void enableDHT(bool enable);
void changeSpeedLimitMode_impl(bool alternative);
void setAppendLabelToSavePath(bool append);
void setAppendExtension(bool append);
void startUpTorrents();
@ -333,6 +388,8 @@ namespace BitTorrent @@ -333,6 +388,8 @@ namespace BitTorrent
void dispatchAlerts(std::auto_ptr<libtorrent::alert> alertPtr);
void getPendingAlerts(QVector<libtorrent::alert *> &out, ulong time = 0);
SettingsStorage *m_settings;
// BitTorrent
libtorrent::session *m_nativeSession;
@ -346,10 +403,9 @@ namespace BitTorrent @@ -346,10 +403,9 @@ namespace BitTorrent
qreal m_globalMaxRatio;
int m_numResumeData;
int m_extraLimit;
bool m_appendLabelToSavePath;
bool m_appendExtension;
uint m_refreshInterval;
MaxRatioAction m_highRatioAction;
MaxRatioAction m_maxRatioAction;
QList<BitTorrent::TrackerEntry> m_additionalTrackers;
QString m_defaultSavePath;
QString m_tempPath;
@ -376,6 +432,7 @@ namespace BitTorrent @@ -376,6 +432,7 @@ namespace BitTorrent
QHash<InfoHash, AddTorrentData> m_addingTorrents;
QHash<QString, AddTorrentParams> m_downloadedTorrents;
TorrentStatusReport m_torrentStatusReport;
QStringMap m_categories;
QMutex m_alertsMutex;
QWaitCondition m_alertsWaitCondition;

91
src/base/bittorrent/torrenthandle.cpp

@ -80,7 +80,7 @@ AddTorrentData::AddTorrentData() @@ -80,7 +80,7 @@ AddTorrentData::AddTorrentData()
AddTorrentData::AddTorrentData(const AddTorrentParams &params)
: resumed(false)
, name(params.name)
, label(params.label)
, category(params.category)
, savePath(params.savePath)
, disableTempPath(params.disableTempPath)
, sequential(params.sequential)
@ -201,7 +201,8 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle @@ -201,7 +201,8 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle
, m_renameCount(0)
, m_name(data.name)
, m_savePath(Utils::Fs::toNativePath(data.savePath))
, m_label(data.label)
, m_category(data.category)
, m_useASM(data.savePath.isEmpty())
, m_hasSeedStatus(data.hasSeedStatus)
, m_ratioLimit(data.ratioLimit)
, m_tempPathDisabled(data.disableTempPath)
@ -209,7 +210,8 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle @@ -209,7 +210,8 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle
, m_pauseAfterRecheck(false)
, m_needSaveResumeData(false)
{
Q_ASSERT(!m_savePath.isEmpty());
if (m_useASM)
m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category));
updateStatus();
m_hash = InfoHash(m_nativeStatus.info_hash);
@ -330,6 +332,22 @@ QString TorrentHandle::contentPath(bool actual) const @@ -330,6 +332,22 @@ QString TorrentHandle::contentPath(bool actual) const
return rootPath(actual);
}
bool TorrentHandle::isASMEnabled() const
{
return m_useASM;
}
void TorrentHandle::setASMEnabled(bool enabled)
{
if (m_useASM == enabled) return;
m_useASM = enabled;
m_session->handleTorrentSavingModeChanged(this);
if (m_useASM)
move_impl(m_session->categorySavePath(m_category));
}
QString TorrentHandle::nativeActualSavePath() const
{
return Utils::String::fromStdString(m_nativeStatus.save_path);
@ -507,9 +525,22 @@ qreal TorrentHandle::progress() const @@ -507,9 +525,22 @@ qreal TorrentHandle::progress() const
return progress;
}
QString TorrentHandle::label() const
QString TorrentHandle::category() const
{
return m_label;
return m_category;
}
bool TorrentHandle::belongsToCategory(const QString &category) const
{
if (m_category.isEmpty()) return category.isEmpty();
if (!Session::isValidCategoryName(category)) return false;
if (m_category == category) return true;
if (m_session->isSubcategoriesEnabled() && m_category.startsWith(category + "/"))
return true;
return false;
}
QDateTime TorrentHandle::addedTime() const
@ -1110,17 +1141,41 @@ void TorrentHandle::setName(const QString &name) @@ -1110,17 +1141,41 @@ void TorrentHandle::setName(const QString &name)
}
}
void TorrentHandle::setLabel(const QString &label)
bool TorrentHandle::setCategory(const QString &category)
{
if (m_label != label) {
QString oldLabel = m_label;
m_label = label;
if (m_category != category) {
if (!category.isEmpty()) {
if (!Session::isValidCategoryName(category)) return false;
if (!m_session->categories().contains(category))
if (!m_session->addCategory(category))
return false;
}
QString oldCategory = m_category;
m_category = category;
m_needSaveResumeData = true;
m_session->handleTorrentLabelChanged(this, oldLabel);
m_session->handleTorrentCategoryChanged(this, oldCategory);
if (m_useASM) {
if (!m_session->isDisableASMWhenCategoryChanged())
move_impl(m_session->categorySavePath(m_category));
else
setASMEnabled(false);
}
}
return true;
}
void TorrentHandle::move(QString path)
{
m_useASM = false;
m_session->handleTorrentSavingModeChanged(this);
move_impl(path);
}
void TorrentHandle::move_impl(QString path)
{
path = Utils::Fs::toNativePath(path);
if (path == savePath()) return;
@ -1469,9 +1524,9 @@ void TorrentHandle::handleSaveResumeDataAlert(libtorrent::save_resume_data_alert @@ -1469,9 +1524,9 @@ void TorrentHandle::handleSaveResumeDataAlert(libtorrent::save_resume_data_alert
resumeData["qBt-paused"] = isPaused();
resumeData["qBt-forced"] = isForced();
}
resumeData["qBt-savePath"] = Utils::String::toStdString(m_savePath);
resumeData["qBt-savePath"] = m_useASM ? "" : Utils::String::toStdString(m_savePath);
resumeData["qBt-ratioLimit"] = Utils::String::toStdString(QString::number(m_ratioLimit));
resumeData["qBt-label"] = Utils::String::toStdString(m_label);
resumeData["qBt-category"] = Utils::String::toStdString(m_category);
resumeData["qBt-name"] = Utils::String::toStdString(m_name);
resumeData["qBt-seedStatus"] = m_hasSeedStatus;
resumeData["qBt-tempPathDisabled"] = m_tempPathDisabled;
@ -1532,12 +1587,6 @@ void TorrentHandle::handleFileRenamedAlert(libtorrent::file_renamed_alert *p) @@ -1532,12 +1587,6 @@ void TorrentHandle::handleFileRenamedAlert(libtorrent::file_renamed_alert *p)
updateStatus();
if (filesCount() == 1) {
// Single-file torrent
// Renaming a file corresponds to changing the save path
m_session->handleTorrentSavePathChanged(this);
}
--m_renameCount;
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
m_moveFinishedTriggers.takeFirst()();
@ -1598,6 +1647,12 @@ void TorrentHandle::handleTempPathChanged() @@ -1598,6 +1647,12 @@ void TorrentHandle::handleTempPathChanged()
adjustActualSavePath();
}
void TorrentHandle::handleCategorySavePathChanged()
{
if (m_useASM)
move_impl(m_session->categorySavePath(m_category));
}
void TorrentHandle::handleAppendExtensionToggled()
{
if (!hasMetadata()) return;

16
src/base/bittorrent/torrenthandle.h

@ -90,7 +90,7 @@ namespace BitTorrent @@ -90,7 +90,7 @@ namespace BitTorrent
bool resumed;
// for both new and resumed torrents
QString name;
QString label;
QString category;
QString savePath;
bool disableTempPath;
bool sequential;
@ -227,11 +227,16 @@ namespace BitTorrent @@ -227,11 +227,16 @@ namespace BitTorrent
QString rootPath(bool actual = false) const;
QString contentPath(bool actual = false) const;
bool isASMEnabled() const;
void setASMEnabled(bool enabled);
QString category() const;
bool belongsToCategory(const QString &category) const;
bool setCategory(const QString &category);
int filesCount() const;
int piecesCount() const;
int piecesHave() const;
qreal progress() const;
QString label() const;
QDateTime addedTime() const;
qreal ratioLimit() const;
@ -307,7 +312,6 @@ namespace BitTorrent @@ -307,7 +312,6 @@ namespace BitTorrent
qlonglong nextAnnounce() const;
void setName(const QString &name);
void setLabel(const QString &label);
void setSequentialDownload(bool b);
void toggleSequentialDownload();
void setFirstLastPiecePriority(bool b);
@ -344,6 +348,7 @@ namespace BitTorrent @@ -344,6 +348,7 @@ namespace BitTorrent
void handleAlert(libtorrent::alert *a);
void handleStateUpdate(const libtorrent::torrent_status &nativeStatus);
void handleTempPathChanged();
void handleCategorySavePathChanged();
void handleAppendExtensionToggled();
void saveResumeData();
@ -379,6 +384,7 @@ namespace BitTorrent @@ -379,6 +384,7 @@ namespace BitTorrent
void adjustActualSavePath();
void adjustActualSavePath_impl();
void move_impl(QString path);
void moveStorage(const QString &newPath);
void appendExtensionsToIncompleteFiles();
void removeExtensionsFromIncompleteFiles();
@ -405,10 +411,12 @@ namespace BitTorrent @@ -405,10 +411,12 @@ namespace BitTorrent
QQueue<EventTrigger> m_moveFinishedTriggers;
int m_renameCount;
bool m_useASM;
// Persistent data
QString m_name;
QString m_savePath;
QString m_label;
QString m_category;
bool m_hasSeedStatus;
qreal m_ratioLimit;
bool m_tempPathDisabled;

4
src/base/bittorrent/torrentinfo.cpp

@ -44,8 +44,8 @@ namespace libt = libtorrent; @@ -44,8 +44,8 @@ namespace libt = libtorrent;
using namespace BitTorrent;
TorrentInfo::TorrentInfo(NativeConstPtr nativeInfo)
: m_nativeInfo(nativeInfo)
{
m_nativeInfo = boost::const_pointer_cast<libt::torrent_info>(nativeInfo);
}
TorrentInfo::TorrentInfo(const TorrentInfo &other)
@ -219,5 +219,5 @@ void TorrentInfo::renameFile(uint index, const QString &newPath) @@ -219,5 +219,5 @@ void TorrentInfo::renameFile(uint index, const QString &newPath)
TorrentInfo::NativePtr TorrentInfo::nativeInfo() const
{
return *reinterpret_cast<const NativePtr *>(&m_nativeInfo);
return m_nativeInfo;
}

2
src/base/bittorrent/torrentinfo.h

@ -91,7 +91,7 @@ namespace BitTorrent @@ -91,7 +91,7 @@ namespace BitTorrent
NativePtr nativeInfo() const;
private:
NativeConstPtr m_nativeInfo;
NativePtr m_nativeInfo;
};
}

2
src/base/http/types.h

@ -34,7 +34,7 @@ @@ -34,7 +34,7 @@
#include <QHostAddress>
#include <QVector>
typedef QMap<QString, QString> QStringMap;
#include "base/types.h"
namespace Http
{

407
src/base/preferences.cpp

@ -30,15 +30,10 @@ @@ -30,15 +30,10 @@
* Contact : hammered999@gmail.com
*/
#include "preferences.h"
#include "qinisettings.h"
#include "logger.h"
#include <QCryptographicHash>
#include <QPair>
#include <QDir>
#include <QReadLocker>
#include <QWriteLocker>
#include <QSettings>
#ifndef DISABLE_GUI
#include <QApplication>
@ -56,74 +51,18 @@ @@ -56,74 +51,18 @@
#endif
#include <cstdlib>
#include "base/utils/fs.h"
#include "base/utils/misc.h"
#include "utils/fs.h"
#include "utils/misc.h"
#include "settingsstorage.h"
#include "logger.h"
#include "preferences.h"
Preferences* Preferences::m_instance = 0;
Preferences::Preferences()
: m_randomPort(rand() % 64512 + 1024)
, dirty(false)
, lock(QReadWriteLock::Recursive)
{
qRegisterMetaTypeStreamOperators<MaxRatioAction>("MaxRatioAction");
QIniSettings *settings = new QIniSettings;
#ifndef Q_OS_MAC
QIniSettings *settings_new = new QIniSettings("qBittorrent", "qBittorrent_new");
QStringList keys = settings_new->allKeys();
bool use_new = false;
// This means that the PC closed either due to power outage
// or because the disk was full. In any case the settings weren't transfered
// in their final position. So assume that qbittorrent_new.ini/qbittorrent_new.conf
// contains the most recent settings.
if (!keys.isEmpty()) {
Logger::instance()->addMessage(tr("Detected unclean program exit. Using fallback file to restore settings."), Log::WARNING);
use_new = true;
dirty = true;
}
else {
keys = settings->allKeys();
}
#else
QStringList keys = settings->allKeys();
#endif
// Copy everything into memory. This means even keys inserted in the file manually
// or that we don't touch directly in this code(eg disabled by ifdef). This ensures
// that they will be copied over when save our settings to disk.
for (QStringList::const_iterator i = keys.begin(), e = keys.end(); i != e; ++i) {
#ifndef Q_OS_MAC
if (!use_new)
m_data[*i] = settings->value(*i);
else
m_data[*i] = settings_new->value(*i);
#else
m_data[*i] = settings->value(*i);
#endif
}
//Ensures sync to disk before we attempt to manipulate the files from save().
delete settings;
#ifndef Q_OS_MAC
QString new_path = settings_new->fileName();
delete settings_new;
Utils::Fs::forceRemove(new_path);
if (use_new)
save();
#endif
timer.setSingleShot(true);
timer.setInterval(5 * 1000);
connect(&timer, SIGNAL(timeout()), SLOT(save()));
}
Preferences::~Preferences()
{
save();
}
Preferences *Preferences::instance()
@ -145,71 +84,14 @@ void Preferences::freeInstance() @@ -145,71 +84,14 @@ void Preferences::freeInstance()
}
}
bool Preferences::save()
{
QWriteLocker locker(&lock);
if (!dirty) return false;
#ifndef Q_OS_MAC
// QSettings delete the file before writing it out. This can result in problems
// if the disk is full or a power outage occurs. Those events might occur
// between deleting the file and recreating it. This is a safety measure.
// Write everything to qBittorrent_new.ini/qBittorrent_new.conf and if it succeeds
// replace qBittorrent_new.ini/qBittorrent.conf with it.
QIniSettings *settings = new QIniSettings("qBittorrent", "qBittorrent_new");
#else
QIniSettings *settings = new QIniSettings;
#endif
for (QHash<QString, QVariant>::const_iterator i = m_data.begin(), e = m_data.end(); i != e; ++i)
settings->setValue(i.key(), i.value());
dirty = false;
locker.unlock();
#ifndef Q_OS_MAC
settings->sync(); // Important to get error status
QString new_path = settings->fileName();
QSettings::Status status = settings->status();
if (status != QSettings::NoError) {
if (status == QSettings::AccessError)
Logger::instance()->addMessage(tr("An access error occurred while trying to write the configuration file."), Log::CRITICAL);
else
Logger::instance()->addMessage(tr("A format error occurred while trying to write the configuration file."), Log::CRITICAL);
delete settings;
Utils::Fs::forceRemove(new_path);
return false;
}
delete settings;
QString final_path = new_path;
int index = final_path.lastIndexOf("_new", -1, Qt::CaseInsensitive);
final_path.remove(index, 4);
Utils::Fs::forceRemove(final_path);
QFile::rename(new_path, final_path);
#else
delete settings;
#endif
return true;
}
const QVariant Preferences::value(const QString &key, const QVariant &defaultValue) const
{
QReadLocker locker(&lock);
return m_data.value(key, defaultValue);
return SettingsStorage::instance()->loadValue(key, defaultValue);
}
void Preferences::setValue(const QString &key, const QVariant &value)
{
QWriteLocker locker(&lock);
if (m_data.value(key) == value)
return;
dirty = true;
timer.start();
m_data.insert(key, value);
SettingsStorage::instance()->storeValue(key, value);
}
// General options
@ -395,40 +277,6 @@ void Preferences::setWinStartup(bool b) @@ -395,40 +277,6 @@ void Preferences::setWinStartup(bool b)
#endif
// Downloads
QString Preferences::getSavePath() const
{
QString save_path = value("Preferences/Downloads/SavePath").toString();
if (!save_path.isEmpty())
return Utils::Fs::fromNativePath(save_path);
return Utils::Fs::QDesktopServicesDownloadLocation();
}
void Preferences::setSavePath(const QString &save_path)
{
setValue("Preferences/Downloads/SavePath", Utils::Fs::fromNativePath(save_path));
}
bool Preferences::isTempPathEnabled() const
{
return value("Preferences/Downloads/TempPathEnabled", false).toBool();
}
void Preferences::setTempPathEnabled(bool enabled)
{
setValue("Preferences/Downloads/TempPathEnabled", enabled);
}
QString Preferences::getTempPath() const
{
const QString temp = QDir(getSavePath()).absoluteFilePath("temp");
return Utils::Fs::fromNativePath(value("Preferences/Downloads/TempPath", temp).toString());
}
void Preferences::setTempPath(const QString &path)
{
setValue("Preferences/Downloads/TempPath", Utils::Fs::fromNativePath(path));
}
bool Preferences::useIncompleteFilesExtension() const
{
return value("Preferences/Downloads/UseIncompleteExtension", false).toBool();
@ -439,16 +287,6 @@ void Preferences::useIncompleteFilesExtension(bool enabled) @@ -439,16 +287,6 @@ void Preferences::useIncompleteFilesExtension(bool enabled)
setValue("Preferences/Downloads/UseIncompleteExtension", enabled);
}
bool Preferences::appendTorrentLabel() const
{
return value("Preferences/Downloads/AppendLabel", false).toBool();
}
void Preferences::setAppendTorrentLabel(bool b)
{
setValue("Preferences/Downloads/AppendLabel", b);
}
QString Preferences::lastLocationPath() const
{
return Utils::Fs::fromNativePath(value("Preferences/Downloads/LastLocationPath").toString());
@ -469,36 +307,6 @@ void Preferences::preAllocateAllFiles(bool enabled) @@ -469,36 +307,6 @@ void Preferences::preAllocateAllFiles(bool enabled)
return setValue("Preferences/Downloads/PreAllocation", enabled);
}
bool Preferences::useAdditionDialog() const
{
return value("Preferences/Downloads/NewAdditionDialog", true).toBool();
}
void Preferences::useAdditionDialog(bool b)
{
setValue("Preferences/Downloads/NewAdditionDialog", b);
}
bool Preferences::additionDialogFront() const
{
return value("Preferences/Downloads/NewAdditionDialogFront", true).toBool();
}
void Preferences::additionDialogFront(bool b)
{
setValue("Preferences/Downloads/NewAdditionDialogFront", b);
}
bool Preferences::addTorrentsInPause() const
{
return value("Preferences/Downloads/StartInPause", false).toBool();
}
void Preferences::addTorrentsInPause(bool b)
{
setValue("Preferences/Downloads/StartInPause", b);
}
QVariantHash Preferences::getScanDirs() const
{
return value("Preferences/Downloads/ScanDirsV2").toHash();
@ -643,7 +451,6 @@ void Preferences::setActionOnDblClOnTorrentFn(int act) @@ -643,7 +451,6 @@ void Preferences::setActionOnDblClOnTorrentFn(int act)
// Connection options
int Preferences::getSessionPort() const
{
QReadLocker locker(&lock);
if (useRandomPort())
return m_randomPort;
return value("Preferences/Connection/PortRangeMin", 8999).toInt();
@ -997,16 +804,6 @@ void Preferences::setGlobalMaxRatio(qreal ratio) @@ -997,16 +804,6 @@ void Preferences::setGlobalMaxRatio(qreal ratio)
setValue("Preferences/Bittorrent/MaxRatio", ratio);
}
MaxRatioAction Preferences::getMaxRatioAction() const
{
return value("Preferences/Bittorrent/MaxRatioAction", QVariant::fromValue(MaxRatioAction::Pause)).value<MaxRatioAction>();
}
void Preferences::setMaxRatioAction(MaxRatioAction act)
{
setValue("Preferences/Bittorrent/MaxRatioAction", QVariant::fromValue(act));
}
// IP Filter
bool Preferences::isFilteringEnabled() const
{
@ -1620,51 +1417,6 @@ void Preferences::useSystemIconTheme(bool enabled) @@ -1620,51 +1417,6 @@ void Preferences::useSystemIconTheme(bool enabled)
}
#endif
QStringList Preferences::getTorrentLabels() const
{
return value("TransferListFilters/customLabels").toStringList();
}
void Preferences::setTorrentLabels(const QStringList& labels)
{
setValue("TransferListFilters/customLabels", labels);
}
void Preferences::addTorrentLabelExternal(const QString &label)
{
addTorrentLabel(label);
QString toEmit = label;
emit externalLabelAdded(toEmit);
}
void Preferences::addTorrentLabel(const QString& label)
{
QStringList labels = value("TransferListFilters/customLabels").toStringList();
if (labels.contains(label))
return;
labels << label;
setValue("TransferListFilters/customLabels", labels);
}
void Preferences::removeTorrentLabel(const QString& label)
{
QStringList labels = value("TransferListFilters/customLabels").toStringList();
if (!labels.contains(label))
return;
labels.removeOne(label);
setValue("TransferListFilters/customLabels", labels);
}
QString Preferences::getDefaultLabel() const
{
return value("Preferences/Downloads/DefaultLabel").toString();
}
void Preferences::setDefaultLabel(const QString &defaultLabel)
{
setValue("Preferences/Downloads/DefaultLabel", defaultLabel);
}
bool Preferences::recursiveDownloadDisabled() const
{
return value("Preferences/Advanced/DisableRecursiveDownload", false).toBool();
@ -2023,63 +1775,6 @@ void Preferences::setTrayIconStyle(TrayIcon::Style style) @@ -2023,63 +1775,6 @@ void Preferences::setTrayIconStyle(TrayIcon::Style style)
// Stuff that don't appear in the Options GUI but are saved
// in the same file.
QByteArray Preferences::getAddNewTorrentDialogState() const
{
#ifdef QBT_USES_QT5
return value("AddNewTorrentDialog/qt5/treeHeaderState").toByteArray();
#else
return value("AddNewTorrentDialog/treeHeaderState").toByteArray();
#endif
}
void Preferences::setAddNewTorrentDialogState(const QByteArray &state)
{
#ifdef QBT_USES_QT5
setValue("AddNewTorrentDialog/qt5/treeHeaderState", state);
#else
setValue("AddNewTorrentDialog/treeHeaderState", state);
#endif
}
int Preferences::getAddNewTorrentDialogPos() const
{
return value("AddNewTorrentDialog/y", -1).toInt();
}
void Preferences::setAddNewTorrentDialogPos(const int &pos)
{
setValue("AddNewTorrentDialog/y", pos);
}
int Preferences::getAddNewTorrentDialogWidth() const
{
return value("AddNewTorrentDialog/width", -1).toInt();
}
void Preferences::setAddNewTorrentDialogWidth(const int &width)
{
setValue("AddNewTorrentDialog/width", width);
}
bool Preferences::getAddNewTorrentDialogExpanded() const
{
return value("AddNewTorrentDialog/expanded", false).toBool();
}
void Preferences::setAddNewTorrentDialogExpanded(const bool expanded)
{
setValue("AddNewTorrentDialog/expanded", expanded);
}
QStringList Preferences::getAddNewTorrentDialogPathHistory() const
{
return value("TorrentAdditionDlg/save_path_history").toStringList();
}
void Preferences::setAddNewTorrentDialogPathHistory(const QStringList &history)
{
setValue("TorrentAdditionDlg/save_path_history", history);
}
QDateTime Preferences::getDNSLastUpd() const
{
@ -2439,14 +2134,14 @@ void Preferences::setStatusFilterState(const bool checked) @@ -2439,14 +2134,14 @@ void Preferences::setStatusFilterState(const bool checked)
setValue("TransferListFilters/statusFilterState", checked);
}
bool Preferences::getLabelFilterState() const
bool Preferences::getCategoryFilterState() const
{
return value("TransferListFilters/labelFilterState", true).toBool();
return value("TransferListFilters/CategoryFilterState", true).toBool();
}
void Preferences::setLabelFilterState(const bool checked)
void Preferences::setCategoryFilterState(const bool checked)
{
setValue("TransferListFilters/labelFilterState", checked);
setValue("TransferListFilters/CategoryFilterState", checked);
}
bool Preferences::getTrackerFilterState() const
@ -2487,22 +2182,6 @@ void Preferences::setTransHeaderState(const QByteArray &state) @@ -2487,22 +2182,6 @@ void Preferences::setTransHeaderState(const QByteArray &state)
#endif
}
// Temp code.
// See TorrentStatistics::loadStats() for details.
QVariantHash Preferences::getStats() const
{
return value("Stats/AllStats").toHash();
}
void Preferences::removeStats()
{
QWriteLocker locker(&lock);
dirty = true;
if (!timer.isActive())
timer.start();
m_data.remove("Stats/AllStats");
}
//From old RssSettings class
bool Preferences::isRSSEnabled() const
{
@ -2574,31 +2253,6 @@ void Preferences::setToolbarTextPosition(const int position) @@ -2574,31 +2253,6 @@ void Preferences::setToolbarTextPosition(const int position)
setValue("Toolbar/textPosition", position);
}
void Preferences::moveRSSCookies()
{
QList<QNetworkCookie> cookies = getNetworkCookies();
QVariantMap hostsTable = value("Rss/hosts_cookies").toMap();
foreach (const QString &key, hostsTable.keys()) {
QVariant value = hostsTable[key];
QList<QByteArray> rawCookies = value.toByteArray().split(':');
foreach (const QByteArray &rawCookie, rawCookies) {
foreach (QNetworkCookie cookie, QNetworkCookie::parseCookies(rawCookie)) {
cookie.setDomain(key);
cookie.setPath("/");
cookie.setExpirationDate(QDateTime::currentDateTime().addYears(10));
cookies << cookie;
}
}
}
setNetworkCookies(cookies);
QWriteLocker locker(&lock);
dirty = true;
timer.start();
m_data.remove("Rss/hosts_cookies");
}
QList<QNetworkCookie> Preferences::getNetworkCookies() const
{
QList<QNetworkCookie> cookies;
@ -2639,8 +2293,43 @@ void Preferences::setSpeedWidgetGraphEnable(int id, const bool enable) @@ -2639,8 +2293,43 @@ void Preferences::setSpeedWidgetGraphEnable(int id, const bool enable)
setValue("SpeedWidget/graph_enable_" + QString::number(id), enable);
}
void Preferences::upgrade()
{
// Move RSS cookies to global storage
QList<QNetworkCookie> cookies = getNetworkCookies();
QVariantMap hostsTable = value("Rss/hosts_cookies").toMap();
foreach (const QString &key, hostsTable.keys()) {
QVariant value = hostsTable[key];
QList<QByteArray> rawCookies = value.toByteArray().split(':');
foreach (const QByteArray &rawCookie, rawCookies) {
foreach (QNetworkCookie cookie, QNetworkCookie::parseCookies(rawCookie)) {
cookie.setDomain(key);
cookie.setPath("/");
cookie.setExpirationDate(QDateTime::currentDateTime().addYears(10));
cookies << cookie;
}
}
}
setNetworkCookies(cookies);
QStringList labels = value("TransferListFilters/customLabels").toStringList();
if (!labels.isEmpty()) {
QVariantMap categories = value("BitTorrent/Session/Categories").toMap();
foreach (const QString &label, labels) {
if (!categories.contains(label))
categories[label] = "";
}
setValue("BitTorrent/Session/Categories", categories);
SettingsStorage::instance()->removeValue("TransferListFilters/customLabels");
}
SettingsStorage::instance()->removeValue("Rss/hosts_cookies");
SettingsStorage::instance()->removeValue("Preferences/Downloads/AppendLabel");
}
void Preferences::apply()
{
if (save())
if (SettingsStorage::instance()->save())
emit changed();
}

64
src/base/preferences.h

@ -41,7 +41,7 @@ @@ -41,7 +41,7 @@
#include <QNetworkCookie>
#include <QVariant>
#include "base/types.h"
#include "types.h"
enum scheduler_days
{
@ -89,30 +89,23 @@ namespace DNS @@ -89,30 +89,23 @@ namespace DNS
};
}
class SettingsStorage;
class Preferences: public QObject
{
Q_OBJECT
Q_DISABLE_COPY(Preferences)
private:
Preferences();
~Preferences();
static Preferences* m_instance;
QHash<QString, QVariant> m_data;
int m_randomPort;
bool dirty;
QTimer timer;
mutable QReadWriteLock lock;
const QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
void setValue(const QString &key, const QVariant &value);
private slots:
bool save();
static Preferences* m_instance;
int m_randomPort;
signals:
void changed();
void externalLabelAdded(QString&);
public:
static void initInstance();
@ -158,28 +151,12 @@ public: @@ -158,28 +151,12 @@ public:
#endif
// Downloads
QString getSavePath() const;
void setSavePath(const QString &save_path);
bool isTempPathEnabled() const;
void setTempPathEnabled(bool enabled);
QString getTempPath() const;
void setTempPath(const QString &path);
QString getDefaultLabel() const;
void setDefaultLabel(const QString &defaultLabel);
bool useIncompleteFilesExtension() const;
void useIncompleteFilesExtension(bool enabled);
bool appendTorrentLabel() const;
void setAppendTorrentLabel(bool b);
QString lastLocationPath() const;
void setLastLocationPath(const QString &path);
bool preAllocateAllFiles() const;
void preAllocateAllFiles(bool enabled);
bool useAdditionDialog() const;
void useAdditionDialog(bool b);
bool additionDialogFront() const;
void additionDialogFront(bool b);
bool addTorrentsInPause() const;
void addTorrentsInPause(bool b);
QVariantHash getScanDirs() const;
void setScanDirs(const QVariantHash &dirs);
QString getScanDirsLastPath() const;
@ -281,8 +258,6 @@ public: @@ -281,8 +258,6 @@ public:
void setTrackersList(const QString &val);
qreal getGlobalMaxRatio() const;
void setGlobalMaxRatio(qreal ratio);
MaxRatioAction getMaxRatioAction() const;
void setMaxRatioAction(MaxRatioAction act);
// IP Filter
bool isFilteringEnabled() const;
@ -406,11 +381,6 @@ public: @@ -406,11 +381,6 @@ public:
bool useSystemIconTheme() const;
void useSystemIconTheme(bool enabled);
#endif
QStringList getTorrentLabels() const;
void setTorrentLabels(const QStringList& labels);
void addTorrentLabelExternal(const QString &label);
void addTorrentLabel(const QString& label);
void removeTorrentLabel(const QString& label);
bool recursiveDownloadDisabled() const;
void disableRecursiveDownload(bool disable = true);
#ifdef Q_OS_WIN
@ -443,19 +413,8 @@ public: @@ -443,19 +413,8 @@ public:
TrayIcon::Style trayIconStyle() const;
void setTrayIconStyle(TrayIcon::Style style);
// Stuff that don't appear in the Options GUI but are saved
// in the same file.
QByteArray getAddNewTorrentDialogState() const;
void setAddNewTorrentDialogState(const QByteArray &state);
int getAddNewTorrentDialogPos() const;
void setAddNewTorrentDialogPos(const int &pos);
int getAddNewTorrentDialogWidth() const;
void setAddNewTorrentDialogWidth(const int &width);
bool getAddNewTorrentDialogExpanded() const;
void setAddNewTorrentDialogExpanded(const bool expanded);
QStringList getAddNewTorrentDialogPathHistory() const;
void setAddNewTorrentDialogPathHistory(const QStringList &history);
QDateTime getDNSLastUpd() const;
void setDNSLastUpd(const QDateTime &date);
QString getDNSLastIP() const;
@ -517,7 +476,7 @@ public: @@ -517,7 +476,7 @@ public:
QByteArray getTorImportGeometry() const;
void setTorImportGeometry(const QByteArray &geometry);
bool getStatusFilterState() const;
bool getLabelFilterState() const;
bool getCategoryFilterState() const;
bool getTrackerFilterState() const;
int getTransSelFilter() const;
void setTransSelFilter(const int &index);
@ -526,11 +485,6 @@ public: @@ -526,11 +485,6 @@ public:
int getToolbarTextPosition() const;
void setToolbarTextPosition(const int position);
// Temp code.
// See TorrentStatistics::loadStats() for details.
QVariantHash getStats() const;
void removeStats();
//From old RssSettings class
bool isRSSEnabled() const;
void setRSSEnabled(const bool enabled);
@ -548,8 +502,6 @@ public: @@ -548,8 +502,6 @@ public:
// Network
QList<QNetworkCookie> getNetworkCookies() const;
void setNetworkCookies(const QList<QNetworkCookie> &cookies);
// Temporary method for upgrade purposes
void moveRSSCookies();
// SpeedWidget
int getSpeedWidgetPeriod() const;
@ -557,9 +509,11 @@ public: @@ -557,9 +509,11 @@ public:
bool getSpeedWidgetGraphEnable(int id) const;
void setSpeedWidgetGraphEnable(int id, const bool enable);
void upgrade();
public slots:
void setStatusFilterState(bool checked);
void setLabelFilterState(bool checked);
void setCategoryFilterState(bool checked);
void setTrackerFilterState(bool checked);
void apply();

17
src/base/rss/rssdownloadrule.cpp

@ -178,7 +178,7 @@ DownloadRulePtr DownloadRule::fromVariantHash(const QVariantHash &ruleHash) @@ -178,7 +178,7 @@ DownloadRulePtr DownloadRule::fromVariantHash(const QVariantHash &ruleHash)
rule->setRssFeeds(ruleHash.value("affected_feeds").toStringList());
rule->setEnabled(ruleHash.value("enabled", false).toBool());
rule->setSavePath(ruleHash.value("save_path").toString());
rule->setLabel(ruleHash.value("label_assigned").toString());
rule->setCategory(ruleHash.value("category_assigned").toString());
rule->setAddPaused(AddPausedState(ruleHash.value("add_paused").toUInt()));
rule->setLastMatch(ruleHash.value("last_match").toDateTime());
rule->setIgnoreDays(ruleHash.value("ignore_days").toInt());
@ -194,7 +194,7 @@ QVariantHash DownloadRule::toVariantHash() const @@ -194,7 +194,7 @@ QVariantHash DownloadRule::toVariantHash() const
hash["save_path"] = m_savePath;
hash["affected_feeds"] = m_rssFeeds;
hash["enabled"] = m_enabled;
hash["label_assigned"] = m_label;
hash["category_assigned"] = m_category;
hash["use_regex"] = m_useRegex;
hash["add_paused"] = m_apstate;
hash["episode_filter"] = m_episodeFilter;
@ -210,10 +210,7 @@ bool DownloadRule::operator==(const DownloadRule &other) const @@ -210,10 +210,7 @@ bool DownloadRule::operator==(const DownloadRule &other) const
void DownloadRule::setSavePath(const QString &savePath)
{
if (!savePath.isEmpty() && (QDir(savePath) != QDir(Preferences::instance()->getSavePath())))
m_savePath = Utils::Fs::fromNativePath(savePath);
else
m_savePath = QString();
m_savePath = Utils::Fs::fromNativePath(savePath);
}
DownloadRule::AddPausedState DownloadRule::addPaused() const
@ -226,14 +223,14 @@ void DownloadRule::setAddPaused(const DownloadRule::AddPausedState &aps) @@ -226,14 +223,14 @@ void DownloadRule::setAddPaused(const DownloadRule::AddPausedState &aps)
m_apstate = aps;
}
QString DownloadRule::label() const
QString DownloadRule::category() const
{
return m_label;
return m_category;
}
void DownloadRule::setLabel(const QString &label)
void DownloadRule::setCategory(const QString &category)
{
m_label = label;
m_category = category;
}
bool DownloadRule::isEnabled() const

6
src/base/rss/rssdownloadrule.h

@ -69,8 +69,8 @@ namespace Rss @@ -69,8 +69,8 @@ namespace Rss
void setSavePath(const QString &savePath);
AddPausedState addPaused() const;
void setAddPaused(const AddPausedState &aps);
QString label() const;
void setLabel(const QString &label);
QString category() const;
void setCategory(const QString &category);
bool isEnabled() const;
void setEnabled(bool enable);
void setLastMatch(const QDateTime &d);
@ -93,7 +93,7 @@ namespace Rss @@ -93,7 +93,7 @@ namespace Rss
QStringList m_mustNotContain;
QString m_episodeFilter;
QString m_savePath;
QString m_label;
QString m_category;
bool m_enabled;
QStringList m_rssFeeds;
bool m_useRegex;

2
src/base/rss/rssfeed.cpp

@ -391,7 +391,7 @@ void Feed::downloadArticleTorrentIfMatching(const ArticlePtr &article) @@ -391,7 +391,7 @@ void Feed::downloadArticleTorrentIfMatching(const ArticlePtr &article)
BitTorrent::AddTorrentParams params;
params.savePath = matchingRule->savePath();
params.label = matchingRule->label();
params.category = matchingRule->category();
if (matchingRule->addPaused() == DownloadRule::ALWAYS_PAUSED)
params.addPaused = TriStateBool::True;
else if (matchingRule->addPaused() == DownloadRule::NEVER_PAUSED)

244
src/base/settingsstorage.cpp

@ -0,0 +1,244 @@ @@ -0,0 +1,244 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2016 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2014 sledgehammer999 <hammered999@gmail.com>
*
* 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.
*/
#include <QFile>
#include <QHash>
#include <QStringList>
#include <QSettings>
#include "logger.h"
#include "utils/fs.h"
#include "settingsstorage.h"
namespace
{
inline QSettings *createSettings(const QString &name)
{
#ifdef Q_OS_WIN
return new QSettings(QSettings::IniFormat, QSettings::UserScope, "qBittorrent", name);
#else
return new QSettings("qBittorrent", name);
#endif
}
#ifdef QBT_USES_QT5
typedef QHash<QString, QString> MappingTable;
#else
class MappingTable: public QHash<QString, QString>
{
public:
MappingTable(std::initializer_list<std::pair<QString, QString>> list)
{
reserve(static_cast<int>(list.size()));
for (std::initializer_list<std::pair<QString, QString>>::const_iterator it = list.begin(); it != list.end(); ++it)
insert(it->first, it->second);
}
};
#endif
QString mapKey(const QString &key)
{
static const MappingTable keyMapping = {
{ "BitTorrent/Session/MaxRatioAction", "Preferences/Bittorrent/MaxRatioAction" },
{ "BitTorrent/Session/DefaultSavePath", "Preferences/Downloads/SavePath" },
{ "BitTorrent/Session/TempPath", "Preferences/Downloads/TempPath" },
{ "BitTorrent/Session/TempPathEnabled", "Preferences/Downloads/TempPathEnabled" },
{ "BitTorrent/Session/AddTorrentPaused", "Preferences/Downloads/StartInPause" },
#ifdef QBT_USES_QT5
{ "AddNewTorrentDialog/TreeHeaderState", "AddNewTorrentDialog/qt5/treeHeaderState" },
#else
{ "AddNewTorrentDialog/TreeHeaderState", "AddNewTorrentDialog/treeHeaderState" },
#endif
{ "AddNewTorrentDialog/Width", "AddNewTorrentDialog/width" },
{ "AddNewTorrentDialog/Position", "AddNewTorrentDialog/y" },
{ "AddNewTorrentDialog/Expanded", "AddNewTorrentDialog/expanded" },
{ "AddNewTorrentDialog/SavePathHistory", "TorrentAdditionDlg/save_path_history" },
{ "AddNewTorrentDialog/Enabled", "Preferences/Downloads/NewAdditionDialog" },
{ "AddNewTorrentDialog/TopLevel", "Preferences/Downloads/NewAdditionDialogFront" }
};
return keyMapping.value(key, key);
}
}
SettingsStorage *SettingsStorage::m_instance = nullptr;
SettingsStorage::SettingsStorage()
: m_dirty(false)
, m_lock(QReadWriteLock::Recursive)
{
QSettings *settings;
QStringList keys;
#ifdef Q_OS_MAC
settings = createSettings("qBittorrent");
#else
settings = createSettings("qBittorrent_new");
QString newPath = settings->fileName();
// This means that the PC closed either due to power outage
// or because the disk was full. In any case the settings weren't transfered
// in their final position. So assume that qbittorrent_new.ini/qbittorrent_new.conf
// contains the most recent settings.
if (!settings->allKeys().isEmpty()) {
Logger::instance()->addMessage(tr("Detected unclean program exit. Using fallback file to restore settings."), Log::WARNING);
m_dirty = true;
}
else {
delete settings;
settings = createSettings("qBittorrent");
}
#endif
keys = settings->allKeys();
// Copy everything into memory. This means even keys inserted in the file manually
// or that we don't touch directly in this code(eg disabled by ifdef). This ensures
// that they will be copied over when save our settings to disk.
foreach (const QString &key, keys)
m_data[key] = settings->value(key);
//Ensures sync to disk before we attempt to manipulate the files from save().
delete settings;
#ifndef Q_OS_MAC
Utils::Fs::forceRemove(newPath);
if (m_dirty)
save();
#endif
m_timer.setSingleShot(true);
m_timer.setInterval(5 * 1000);
connect(&m_timer, SIGNAL(timeout()), SLOT(save()));
}
SettingsStorage::~SettingsStorage()
{
save();
}
void SettingsStorage::initInstance()
{
if (!m_instance)
m_instance = new SettingsStorage;
}
void SettingsStorage::freeInstance()
{
delete m_instance;
m_instance = nullptr;
}
SettingsStorage *SettingsStorage::instance()
{
return m_instance;
}
bool SettingsStorage::save()
{
QWriteLocker locker(&m_lock);
if (!m_dirty) return false;
#ifndef Q_OS_MAC
// QSettings delete the file before writing it out. This can result in problems
// if the disk is full or a power outage occurs. Those events might occur
// between deleting the file and recreating it. This is a safety measure.
// Write everything to qBittorrent_new.ini/qBittorrent_new.conf and if it succeeds
// replace qBittorrent_new.ini/qBittorrent.conf with it.
QSettings *settings = createSettings("qBittorrent_new");
#else
QSettings *settings = createSettings("qBittorrent");
#endif
foreach (const QString &key, m_data.keys())
settings->setValue(key, m_data[key]);
m_dirty = false;
locker.unlock();
#ifndef Q_OS_MAC
settings->sync(); // Important to get error status
QString newPath = settings->fileName();
QSettings::Status status = settings->status();
if (status != QSettings::NoError) {
if (status == QSettings::AccessError)
Logger::instance()->addMessage(tr("An access error occurred while trying to write the configuration file."), Log::CRITICAL);
else
Logger::instance()->addMessage(tr("A format error occurred while trying to write the configuration file."), Log::CRITICAL);
delete settings;
Utils::Fs::forceRemove(newPath);
return false;
}
delete settings;
QString finalPath = newPath;
int index = finalPath.lastIndexOf("_new", -1, Qt::CaseInsensitive);
finalPath.remove(index, 4);
Utils::Fs::forceRemove(finalPath);
QFile::rename(newPath, finalPath);
#else
delete settings;
#endif
return true;
}
QVariant SettingsStorage::loadValue(const QString &key, const QVariant &defaultValue) const
{
QReadLocker locker(&m_lock);
return m_data.value(mapKey(key), defaultValue);
}
void SettingsStorage::storeValue(const QString &key, const QVariant &value)
{
QString realKey = mapKey(key);
QWriteLocker locker(&m_lock);
if (m_data.value(realKey) != value) {
m_dirty = true;
m_timer.start();
m_data.insert(realKey, value);
}
}
void SettingsStorage::removeValue(const QString &key)
{
QString realKey = mapKey(key);
QWriteLocker locker(&m_lock);
if (m_data.contains(realKey)) {
m_dirty = true;
m_timer.start();
m_data.remove(realKey);
}
}

65
src/base/settingsstorage.h

@ -0,0 +1,65 @@ @@ -0,0 +1,65 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2016 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2014 sledgehammer999 <hammered999@gmail.com>
*
* 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.
*/
#ifndef SETTINGSSTORAGE_H
#define SETTINGSSTORAGE_H
#include <QObject>
#include <QVariantHash>
#include <QTimer>
#include <QReadWriteLock>
class SettingsStorage: public QObject
{
Q_OBJECT
SettingsStorage();
~SettingsStorage();
public:
static void initInstance();
static void freeInstance();
static SettingsStorage* instance();
QVariant loadValue(const QString &key, const QVariant &defaultValue = QVariant()) const;
void storeValue(const QString &key, const QVariant &value);
void removeValue(const QString &key);
public slots:
bool save();
private:
static SettingsStorage *m_instance;
QVariantHash m_data;
bool m_dirty;
QTimer m_timer;
mutable QReadWriteLock m_lock;
};
#endif // SETTINGSSTORAGE_H

29
src/base/torrentfilter.cpp

@ -29,7 +29,7 @@ @@ -29,7 +29,7 @@
#include "bittorrent/torrenthandle.h"
#include "torrentfilter.h"
const QString TorrentFilter::AnyLabel;
const QString TorrentFilter::AnyCategory;
const QStringSet TorrentFilter::AnyHash = (QStringSet() << QString());
const TorrentFilter TorrentFilter::DownloadingTorrent(TorrentFilter::Downloading);
@ -49,16 +49,16 @@ TorrentFilter::TorrentFilter() @@ -49,16 +49,16 @@ TorrentFilter::TorrentFilter()
{
}
TorrentFilter::TorrentFilter(Type type, QStringSet hashSet, QString label)
TorrentFilter::TorrentFilter(Type type, QStringSet hashSet, QString category)
: m_type(type)
, m_label(label)
, m_category(category)
, m_hashSet(hashSet)
{
}
TorrentFilter::TorrentFilter(QString filter, QStringSet hashSet, QString label)
TorrentFilter::TorrentFilter(QString filter, QStringSet hashSet, QString category)
: m_type(All)
, m_label(label)
, m_category(category)
, m_hashSet(hashSet)
{
setTypeByName(filter);
@ -108,13 +108,13 @@ bool TorrentFilter::setHashSet(const QStringSet &hashSet) @@ -108,13 +108,13 @@ bool TorrentFilter::setHashSet(const QStringSet &hashSet)
return false;
}
bool TorrentFilter::setLabel(const QString &label)
bool TorrentFilter::setCategory(const QString &category)
{
// QString::operator==() doesn't distinguish between empty and null strings.
if ((m_label != label)
|| (m_label.isNull() && !label.isNull())
|| (!m_label.isNull() && label.isNull())) {
m_label = label;
if ((m_category != category)
|| (m_category.isNull() && !category.isNull())
|| (!m_category.isNull() && category.isNull())) {
m_category = category;
return true;
}
@ -125,7 +125,7 @@ bool TorrentFilter::match(TorrentHandle *const torrent) const @@ -125,7 +125,7 @@ bool TorrentFilter::match(TorrentHandle *const torrent) const
{
if (!torrent) return false;
return (matchState(torrent) && matchHash(torrent) && matchLabel(torrent));
return (matchState(torrent) && matchHash(torrent) && matchCategory(torrent));
}
bool TorrentFilter::matchState(BitTorrent::TorrentHandle *const torrent) const
@ -160,9 +160,8 @@ bool TorrentFilter::matchHash(BitTorrent::TorrentHandle *const torrent) const @@ -160,9 +160,8 @@ bool TorrentFilter::matchHash(BitTorrent::TorrentHandle *const torrent) const
else return m_hashSet.contains(torrent->hash());
}
bool TorrentFilter::matchLabel(BitTorrent::TorrentHandle *const torrent) const
bool TorrentFilter::matchCategory(BitTorrent::TorrentHandle *const torrent) const
{
if (m_label.isNull()) return true;
else if (m_label.isEmpty()) return torrent->label().isEmpty();
else return (torrent->label() == m_label);
if (m_category.isNull()) return true;
else return (torrent->belongsToCategory(m_category));
}

14
src/base/torrentfilter.h

@ -58,7 +58,7 @@ public: @@ -58,7 +58,7 @@ public:
Errored
};
static const QString AnyLabel;
static const QString AnyCategory;
static const QStringSet AnyHash;
static const TorrentFilter DownloadingTorrent;
@ -71,24 +71,24 @@ public: @@ -71,24 +71,24 @@ public:
static const TorrentFilter ErroredTorrent;
TorrentFilter();
// label: pass empty string for "no label" or null string (QString()) for "any label"
TorrentFilter(Type type, QStringSet hashSet = AnyHash, QString label = AnyLabel);
TorrentFilter(QString filter, QStringSet hashSet = AnyHash, QString label = AnyLabel);
// category: pass empty string for "no category" or null string (QString()) for "any category"
TorrentFilter(Type type, QStringSet hashSet = AnyHash, QString category = AnyCategory);
TorrentFilter(QString filter, QStringSet hashSet = AnyHash, QString category = AnyCategory);
bool setType(Type type);
bool setTypeByName(const QString &filter);
bool setHashSet(const QStringSet &hashSet);
bool setLabel(const QString &label);
bool setCategory(const QString &category);
bool match(BitTorrent::TorrentHandle *const torrent) const;
private:
bool matchState(BitTorrent::TorrentHandle *const torrent) const;
bool matchHash(BitTorrent::TorrentHandle *const torrent) const;
bool matchLabel(BitTorrent::TorrentHandle *const torrent) const;
bool matchCategory(BitTorrent::TorrentHandle *const torrent) const;
Type m_type;
QString m_label;
QString m_category;
QStringSet m_hashSet;
};

32
src/base/types.h

@ -29,25 +29,10 @@ @@ -29,25 +29,10 @@
#ifndef TYPES_H
#define TYPES_H
#include <QVariant>
#include <QDataStream>
#include <QMap>
const qlonglong MAX_ETA = 8640000;
enum class MaxRatioAction
{
Pause,
Remove
};
Q_DECLARE_METATYPE(MaxRatioAction)
enum class TorrentExportFolder
{
Regular,
Finished
};
enum class ShutdownAction
{
None,
@ -56,19 +41,6 @@ enum class ShutdownAction @@ -56,19 +41,6 @@ enum class ShutdownAction
Hibernate
};
template<typename T>
inline QDataStream &operator<<(QDataStream &out, const T &val)
{
return (out << static_cast<int>(val));
}
template<typename T>
inline QDataStream &operator>>(QDataStream &in, T &val)
{
int tmp;
in >> tmp;
val = static_cast<T>(tmp);
return in;
}
typedef QMap<QString, QString> QStringMap;
#endif // TYPES_H

17
src/base/utils/fs.cpp

@ -250,22 +250,23 @@ bool Utils::Fs::sameFiles(const QString& path1, const QString& path2) @@ -250,22 +250,23 @@ bool Utils::Fs::sameFiles(const QString& path1, const QString& path2)
return same;
}
QString Utils::Fs::toValidFileSystemName(const QString &filename)
QString Utils::Fs::toValidFileSystemName(const QString &name, bool allowSeparators)
{
static const QRegExp regex("[\\\\/:?\"*<>|]");
QRegExp regex(allowSeparators ? "[:?\"*<>|]+" : "[\\\\/:?\"*<>|]+");
QString validName = filename.trimmed();
QString validName = name.trimmed();
validName.replace(regex, " ");
qDebug() << "toValidFileSystemName:" << filename << "=>" << validName;
qDebug() << "toValidFileSystemName:" << name << "=>" << validName;
return validName;
}
bool Utils::Fs::isValidFileSystemName(const QString& filename)
bool Utils::Fs::isValidFileSystemName(const QString &name, bool allowSeparators)
{
if (filename.isEmpty()) return false;
const QRegExp regex("[\\\\/:?\"*<>|]");
return !filename.contains(regex);
if (name.isEmpty()) return false;
QRegExp regex(allowSeparators ? "[:?\"*<>|]" : "[\\\\/:?\"*<>|]");
return !name.contains(regex);
}
qlonglong Utils::Fs::freeDiskSpaceOnPath(QString path)

4
src/base/utils/fs.h

@ -48,8 +48,8 @@ namespace Utils @@ -48,8 +48,8 @@ namespace Utils
QString folderName(const QString& file_path);
qint64 computePathSize(const QString& path);
bool sameFiles(const QString& path1, const QString& path2);
QString toValidFileSystemName(const QString &filename);
bool isValidFileSystemName(const QString& filename);
QString toValidFileSystemName(const QString &name, bool allowSeparators = false);
bool isValidFileSystemName(const QString& name, bool allowSeparators = false);
qlonglong freeDiskSpaceOnPath(QString path);
QString branchPath(const QString& file_path, QString* removed = 0);
bool sameFileNames(const QString& first, const QString& second);

1
src/base/utils/misc.h

@ -39,6 +39,7 @@ @@ -39,6 +39,7 @@
#include <QFile>
#include <QDir>
#include <QUrl>
#include <QSize>
#include "base/types.h"
/* Miscellaneous functions that can be useful */

338
src/gui/addnewtorrentdialog.cpp

@ -35,7 +35,7 @@ @@ -35,7 +35,7 @@
#include <QMenu>
#include <QFileDialog>
#include "base/preferences.h"
#include "base/settingsstorage.h"
#include "base/net/downloadmanager.h"
#include "base/net/downloadhandler.h"
#include "base/bittorrent/session.h"
@ -44,16 +44,34 @@ @@ -44,16 +44,34 @@
#include "base/bittorrent/torrenthandle.h"
#include "base/utils/fs.h"
#include "base/utils/misc.h"
#include "base/utils/string.h"
#include "base/unicodestrings.h"
#include "guiiconprovider.h"
#include "autoexpandabledialog.h"
#include "messageboxraised.h"
#include "ui_addnewtorrentdialog.h"
#include "proplistdelegate.h"
#include "torrentcontentmodel.h"
#include "torrentcontentfiltermodel.h"
#include "ui_addnewtorrentdialog.h"
#include "addnewtorrentdialog.h"
#define SETTINGS_KEY(name) "AddNewTorrentDialog/" name
const QString KEY_ENABLED = SETTINGS_KEY("Enabled");
const QString KEY_DEFAULTSAVEPATH = SETTINGS_KEY("DefaultSavePath");
const QString KEY_DEFAULTCATEGORY = SETTINGS_KEY("DefaultCategory");
const QString KEY_TREEHEADERSTATE = SETTINGS_KEY("TreeHeaderState");
const QString KEY_WIDTH = SETTINGS_KEY("Width");
const QString KEY_EXPANDED = SETTINGS_KEY("Expanded");
const QString KEY_POSITION = SETTINGS_KEY("Position");
const QString KEY_TOPLEVEL = SETTINGS_KEY("TopLevel");
const QString KEY_SAVEPATHHISTORY = SETTINGS_KEY("SavePathHistory");
namespace
{
//just a shortcut
inline SettingsStorage *settings() { return SettingsStorage::instance(); }
}
AddNewTorrentDialog::AddNewTorrentDialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::AddNewTorrentDialog)
@ -67,34 +85,36 @@ AddNewTorrentDialog::AddNewTorrentDialog(QWidget *parent) @@ -67,34 +85,36 @@ AddNewTorrentDialog::AddNewTorrentDialog(QWidget *parent)
ui->lblMetaLoading->setVisible(false);
ui->progMetaLoading->setVisible(false);
Preferences* const pref = Preferences::instance();
ui->start_torrent_cb->setChecked(!pref->addTorrentsInPause());
ui->save_path_combo->addItem(Utils::Fs::toNativePath(pref->getSavePath()), pref->getSavePath());
loadSavePathHistory();
connect(ui->save_path_combo, SIGNAL(currentIndexChanged(int)), SLOT(onSavePathChanged(int)));
connect(ui->browse_button, SIGNAL(clicked()), SLOT(browseButton_clicked()));
ui->default_save_path_cb->setVisible(false); // Default path is selected by default
auto session = BitTorrent::Session::instance();
ui->startTorrentCheckBox->setChecked(!session->isAddTorrentPaused());
(session->isASMDisabledByDefault() ? ui->simpleModeRadioButton : ui->advancedModeRadioButton)->setChecked(true);
populateSavePathComboBox();
connect(ui->savePathComboBox, SIGNAL(currentIndexChanged(int)), SLOT(onSavePathChanged(int)));
connect(ui->browseButton, SIGNAL(clicked()), SLOT(browseButton_clicked()));
ui->defaultSavePathCheckBox->setVisible(false); // Default path is selected by default
// Load labels
const QStringList customLabels = pref->getTorrentLabels();
const QString defaultLabel = pref->getDefaultLabel();
// Load categories
QStringList categories = session->categories();
std::sort(categories.begin(), categories.end(), Utils::String::NaturalCompare());
QString defaultCategory = settings()->loadValue(KEY_DEFAULTCATEGORY).toString();
if (!defaultLabel.isEmpty())
ui->label_combo->addItem(defaultLabel);
ui->label_combo->addItem("");
if (!defaultCategory.isEmpty())
ui->categoryComboBox->addItem(defaultCategory);
ui->categoryComboBox->addItem("");
foreach (const QString& label, customLabels)
if (label != defaultLabel)
ui->label_combo->addItem(label);
foreach (const QString &category, categories)
if (category != defaultCategory)
ui->categoryComboBox->addItem(category);
ui->label_combo->model()->sort(0);
ui->content_tree->header()->setSortIndicator(0, Qt::AscendingOrder);
ui->categoryComboBox->model()->sort(0);
ui->contentTreeView->header()->setSortIndicator(0, Qt::AscendingOrder);
loadState();
// Signal / slots
connect(ui->adv_button, SIGNAL(clicked(bool)), SLOT(showAdvancedSettings(bool)));
editHotkey = new QShortcut(QKeySequence("F2"), ui->content_tree, 0, 0, Qt::WidgetShortcut);
editHotkey = new QShortcut(QKeySequence("F2"), ui->contentTreeView, 0, 0, Qt::WidgetShortcut);
connect(editHotkey, SIGNAL(activated()), SLOT(renameSelectedFile()));
connect(ui->content_tree, SIGNAL(doubleClicked(QModelIndex)), SLOT(renameSelectedFile()));
connect(ui->contentTreeView, SIGNAL(doubleClicked(QModelIndex)), SLOT(renameSelectedFile()));
ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus();
}
@ -108,27 +128,45 @@ AddNewTorrentDialog::~AddNewTorrentDialog() @@ -108,27 +128,45 @@ AddNewTorrentDialog::~AddNewTorrentDialog()
delete editHotkey;
}
bool AddNewTorrentDialog::isEnabled()
{
return SettingsStorage::instance()->loadValue(KEY_ENABLED, true).toBool();
}
void AddNewTorrentDialog::setEnabled(bool value)
{
SettingsStorage::instance()->storeValue(KEY_ENABLED, value);
}
bool AddNewTorrentDialog::isTopLevel()
{
return SettingsStorage::instance()->loadValue(KEY_TOPLEVEL, true).toBool();
}
void AddNewTorrentDialog::setTopLevel(bool value)
{
SettingsStorage::instance()->storeValue(KEY_TOPLEVEL, value);
}
void AddNewTorrentDialog::loadState()
{
const Preferences* const pref = Preferences::instance();
m_headerState = pref->getAddNewTorrentDialogState();
int width = pref->getAddNewTorrentDialogWidth();
m_headerState = settings()->loadValue(KEY_TREEHEADERSTATE).toByteArray();
int width = settings()->loadValue(KEY_WIDTH, -1).toInt();
if (width >= 0) {
QRect geo = geometry();
geo.setWidth(width);
setGeometry(geo);
}
ui->adv_button->setChecked(pref->getAddNewTorrentDialogExpanded());
ui->adv_button->setChecked(settings()->loadValue(KEY_EXPANDED).toBool());
}
void AddNewTorrentDialog::saveState()
{
Preferences* const pref = Preferences::instance();
if (m_contentModel)
pref->setAddNewTorrentDialogState(ui->content_tree->header()->saveState());
pref->setAddNewTorrentDialogPos(pos().y());
pref->setAddNewTorrentDialogWidth(width());
pref->setAddNewTorrentDialogExpanded(ui->adv_button->isChecked());
settings()->storeValue(KEY_TREEHEADERSTATE, ui->contentTreeView->header()->saveState());
settings()->storeValue(KEY_POSITION, pos().y());
settings()->storeValue(KEY_WIDTH, width());
settings()->storeValue(KEY_EXPANDED, ui->adv_button->isChecked());
}
void AddNewTorrentDialog::show(QString source, QWidget *parent)
@ -256,9 +294,8 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri) @@ -256,9 +294,8 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri)
void AddNewTorrentDialog::showEvent(QShowEvent *event)
{
QDialog::showEvent(event);
Preferences* const pref = Preferences::instance();
if (!pref->additionDialogFront())
return;
if (!isTopLevel()) return;
activateWindow();
raise();
}
@ -272,12 +309,7 @@ void AddNewTorrentDialog::showAdvancedSettings(bool show) @@ -272,12 +309,7 @@ void AddNewTorrentDialog::showAdvancedSettings(bool show)
ui->adv_button->setText(QString::fromUtf8(C_UP));
ui->settings_group->setVisible(true);
ui->infoGroup->setVisible(true);
if (m_hasMetadata && (m_torrentInfo.filesCount() > 1)) {
ui->content_tree->setVisible(true);
}
else {
ui->content_tree->setVisible(false);
}
ui->contentTreeView->setVisible(m_hasMetadata);
static_cast<QVBoxLayout*>(layout())->insertWidget(layout()->indexOf(ui->never_show_cb) + 1, ui->adv_button);
}
else {
@ -292,21 +324,20 @@ void AddNewTorrentDialog::showAdvancedSettings(bool show) @@ -292,21 +324,20 @@ void AddNewTorrentDialog::showAdvancedSettings(bool show)
void AddNewTorrentDialog::saveSavePathHistory() const
{
QDir selected_save_path(ui->save_path_combo->itemData(ui->save_path_combo->currentIndex()).toString());
Preferences* const pref = Preferences::instance();
QDir selectedSavePath(ui->savePathComboBox->itemData(ui->savePathComboBox->currentIndex()).toString());
// Get current history
QStringList history = pref->getAddNewTorrentDialogPathHistory();
QList<QDir> history_dirs;
QStringList history = settings()->loadValue(KEY_SAVEPATHHISTORY).toStringList();
QList<QDir> historyDirs;
foreach(const QString dir, history)
history_dirs << QDir(dir);
if (!history_dirs.contains(selected_save_path)) {
historyDirs << QDir(dir);
if (!historyDirs.contains(selectedSavePath)) {
// Add save path to history
history.push_front(selected_save_path.absolutePath());
history.push_front(selectedSavePath.absolutePath());
// Limit list size
if (history.size() > 8)
history.pop_back();
// Save history
pref->setAddNewTorrentDialogPathHistory(history);
settings()->storeValue(KEY_SAVEPATHHISTORY, history);
}
}
@ -314,20 +345,12 @@ void AddNewTorrentDialog::saveSavePathHistory() const @@ -314,20 +345,12 @@ void AddNewTorrentDialog::saveSavePathHistory() const
int AddNewTorrentDialog::indexOfSavePath(const QString &save_path)
{
QDir saveDir(save_path);
for(int i = 0; i < ui->save_path_combo->count(); ++i)
if (QDir(ui->save_path_combo->itemData(i).toString()) == saveDir)
for (int i = 0; i < ui->savePathComboBox->count(); ++i)
if (QDir(ui->savePathComboBox->itemData(i).toString()) == saveDir)
return i;
return -1;
}
void AddNewTorrentDialog::updateFileNameInSavePaths(const QString &new_filename)
{
for(int i = 0; i < ui->save_path_combo->count(); ++i) {
const QDir folder(ui->save_path_combo->itemData(i).toString());
ui->save_path_combo->setItemText(i, Utils::Fs::toNativePath(folder.absoluteFilePath(new_filename)));
}
}
void AddNewTorrentDialog::updateDiskSpaceLabel()
{
// Determine torrent size
@ -349,8 +372,8 @@ void AddNewTorrentDialog::updateDiskSpaceLabel() @@ -349,8 +372,8 @@ void AddNewTorrentDialog::updateDiskSpaceLabel()
QString size_string = torrent_size ? Utils::Misc::friendlyUnit(torrent_size) : QString(tr("Not Available", "This size is unavailable."));
size_string += " (";
size_string += tr("Free space on disk: %1").arg(Utils::Misc::friendlyUnit(Utils::Fs::freeDiskSpaceOnPath(
ui->save_path_combo->itemData(
ui->save_path_combo->currentIndex()).toString())));
ui->savePathComboBox->itemData(
ui->savePathComboBox->currentIndex()).toString())));
size_string += ")";
ui->size_lbl->setText(size_string);
}
@ -358,8 +381,10 @@ void AddNewTorrentDialog::updateDiskSpaceLabel() @@ -358,8 +381,10 @@ void AddNewTorrentDialog::updateDiskSpaceLabel()
void AddNewTorrentDialog::onSavePathChanged(int index)
{
// Toggle default save path setting checkbox visibility
ui->default_save_path_cb->setChecked(false);
ui->default_save_path_cb->setVisible(QDir(ui->save_path_combo->itemData(ui->save_path_combo->currentIndex()).toString()) != QDir(Preferences::instance()->getSavePath()));
ui->defaultSavePathCheckBox->setChecked(false);
ui->defaultSavePathCheckBox->setVisible(
QDir(ui->savePathComboBox->itemData(ui->savePathComboBox->currentIndex()).toString())
!= QDir(defaultSavePath()));
// Remember index
m_oldIndex = index;
@ -367,58 +392,54 @@ void AddNewTorrentDialog::onSavePathChanged(int index) @@ -367,58 +392,54 @@ void AddNewTorrentDialog::onSavePathChanged(int index)
updateDiskSpaceLabel();
}
void AddNewTorrentDialog::categoryChanged(int index)
{
Q_UNUSED(index);
if (ui->advancedModeRadioButton->isChecked()) {
QString savePath = BitTorrent::Session::instance()->categorySavePath(ui->categoryComboBox->currentText());
ui->savePathComboBox->setItemText(0, Utils::Fs::toNativePath(savePath));
ui->savePathComboBox->setItemData(0, savePath);
}
}
void AddNewTorrentDialog::browseButton_clicked()
{
disconnect(ui->save_path_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(onSavePathChanged(int)));
disconnect(ui->savePathComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onSavePathChanged(int)));
// User is asking for a new save path
QString cur_save_path = ui->save_path_combo->itemText(m_oldIndex);
QString new_path, old_filename, new_filename;
if (m_torrentInfo.isValid() && (m_torrentInfo.filesCount() == 1)) {
old_filename = Utils::Fs::fileName(cur_save_path);
new_path = QFileDialog::getSaveFileName(this, tr("Choose save path"), cur_save_path, QString(), 0, QFileDialog::DontConfirmOverwrite);
if (!new_path.isEmpty())
new_path = Utils::Fs::branchPath(new_path, &new_filename);
qDebug() << "new_path: " << new_path;
qDebug() << "new_filename: " << new_filename;
}
else {
if (!cur_save_path.isEmpty() && QDir(cur_save_path).exists())
new_path = QFileDialog::getExistingDirectory(this, tr("Choose save path"), cur_save_path);
else
new_path = QFileDialog::getExistingDirectory(this, tr("Choose save path"), QDir::homePath());
}
if (!new_path.isEmpty()) {
const int existing_index = indexOfSavePath(new_path);
if (existing_index >= 0) {
ui->save_path_combo->setCurrentIndex(existing_index);
QString curSavePath = ui->savePathComboBox->itemText(m_oldIndex);
QString newPath;
if (!curSavePath.isEmpty() && QDir(curSavePath).exists())
newPath = QFileDialog::getExistingDirectory(this, tr("Choose save path"), curSavePath);
else
newPath = QFileDialog::getExistingDirectory(this, tr("Choose save path"), QDir::homePath());
if (!newPath.isEmpty()) {
const int existingIndex = indexOfSavePath(newPath);
if (existingIndex >= 0) {
ui->savePathComboBox->setCurrentIndex(existingIndex);
}
else {
// New path, prepend to combo box
if (!new_filename.isEmpty())
ui->save_path_combo->insertItem(0, Utils::Fs::toNativePath(QDir(new_path).absoluteFilePath(new_filename)), new_path);
else
ui->save_path_combo->insertItem(0, Utils::Fs::toNativePath(new_path), new_path);
ui->save_path_combo->setCurrentIndex(0);
}
// Update file name in all save_paths
if (!new_filename.isEmpty() && !Utils::Fs::sameFileNames(old_filename, new_filename)) {
m_torrentInfo.renameFile(0, new_filename);
updateFileNameInSavePaths(new_filename);
ui->savePathComboBox->insertItem(0, Utils::Fs::toNativePath(newPath), newPath);
ui->savePathComboBox->setCurrentIndex(0);
}
onSavePathChanged(0);
}
else {
// Restore index
ui->save_path_combo->setCurrentIndex(m_oldIndex);
ui->savePathComboBox->setCurrentIndex(m_oldIndex);
}
connect(ui->save_path_combo, SIGNAL(currentIndexChanged(int)), SLOT(onSavePathChanged(int)));
connect(ui->savePathComboBox, SIGNAL(currentIndexChanged(int)), SLOT(onSavePathChanged(int)));
}
void AddNewTorrentDialog::renameSelectedFile()
{
const QModelIndexList selectedIndexes = ui->content_tree->selectionModel()->selectedRows(0);
const QModelIndexList selectedIndexes = ui->contentTreeView->selectionModel()->selectedRows(0);
if (selectedIndexes.size() != 1)
return;
const QModelIndex &index = selectedIndexes.first();
@ -518,7 +539,7 @@ void AddNewTorrentDialog::setdialogPosition() @@ -518,7 +539,7 @@ void AddNewTorrentDialog::setdialogPosition()
qApp->processEvents();
QPoint center(Utils::Misc::screenCenter(this));
// Adjust y
int y = Preferences::instance()->getAddNewTorrentDialogPos();
int y = settings()->loadValue(KEY_POSITION, -1).toInt();
if (y >= 0) {
center.setY(y);
}
@ -530,22 +551,25 @@ void AddNewTorrentDialog::setdialogPosition() @@ -530,22 +551,25 @@ void AddNewTorrentDialog::setdialogPosition()
move(center);
}
void AddNewTorrentDialog::loadSavePathHistory()
void AddNewTorrentDialog::populateSavePathComboBox()
{
QDir default_save_path(Preferences::instance()->getSavePath());
QString defSavePath = defaultSavePath();
ui->savePathComboBox->clear();
ui->savePathComboBox->addItem(Utils::Fs::toNativePath(defSavePath), defSavePath);
QDir defaultSaveDir(defSavePath);
// Load save path history
QStringList raw_path_history = Preferences::instance()->getAddNewTorrentDialogPathHistory();
foreach (const QString &sp, raw_path_history)
if (QDir(sp) != default_save_path)
ui->save_path_combo->addItem(Utils::Fs::toNativePath(sp), sp);
foreach (const QString &savePath, settings()->loadValue(KEY_SAVEPATHHISTORY).toStringList())
if (QDir(savePath) != defaultSaveDir)
ui->savePathComboBox->addItem(Utils::Fs::toNativePath(savePath), savePath);
}
void AddNewTorrentDialog::displayContentTreeMenu(const QPoint&)
{
QMenu myFilesLlistMenu;
const QModelIndexList selectedRows = ui->content_tree->selectionModel()->selectedRows(0);
const QModelIndexList selectedRows = ui->contentTreeView->selectionModel()->selectedRows(0);
QAction *actRename = 0;
if ((selectedRows.size() == 1) && (m_torrentInfo.filesCount() > 1)) {
if (selectedRows.size() == 1) {
actRename = myFilesLlistMenu.addAction(GuiIconProvider::instance()->getIcon("edit-rename"), tr("Rename..."));
myFilesLlistMenu.addSeparator();
}
@ -585,39 +609,34 @@ void AddNewTorrentDialog::accept() @@ -585,39 +609,34 @@ void AddNewTorrentDialog::accept()
if (!m_hasMetadata)
disconnect(this, SLOT(updateMetadata(const BitTorrent::TorrentInfo &)));
Preferences *const pref = Preferences::instance();
BitTorrent::AddTorrentParams params;
if (ui->skip_check_cb->isChecked())
// TODO: Check if destination actually exists
params.skipChecking = true;
// Label
params.label = ui->label_combo->currentText();
// Category
params.category = ui->categoryComboBox->currentText();
if (ui->defaultLabel->isChecked())
pref->setDefaultLabel(params.label);
if (ui->defaultCategoryCheckbox->isChecked())
settings()->storeValue(KEY_DEFAULTCATEGORY, params.category);
// Save file priorities
if (m_contentModel)
params.filePriorities = m_contentModel->model()->getFilePriorities();
params.addPaused = !ui->start_torrent_cb->isChecked();
saveSavePathHistory();
pref->useAdditionDialog(!ui->never_show_cb->isChecked());
params.addPaused = !ui->startTorrentCheckBox->isChecked();
QString savePath = ui->save_path_combo->itemData(ui->save_path_combo->currentIndex()).toString();
if (ui->default_save_path_cb->isChecked()) {
pref->setSavePath(savePath);
pref->apply();
}
else {
// if we don't use default save path...
if (QDir(savePath) != QDir(pref->getSavePath()))
params.savePath = savePath;
QString savePath = ui->savePathComboBox->itemData(ui->savePathComboBox->currentIndex()).toString();
if (ui->simpleModeRadioButton->isChecked()) {
params.savePath = savePath;
saveSavePathHistory();
if (ui->defaultSavePathCheckBox->isChecked())
settings()->storeValue(KEY_DEFAULTSAVEPATH, savePath);
}
setEnabled(!ui->never_show_cb->isChecked());
// Add torrent
if (!m_hasMetadata)
BitTorrent::Session::instance()->addTorrent(m_hash, params);
@ -682,38 +701,37 @@ void AddNewTorrentDialog::setupTreeview() @@ -682,38 +701,37 @@ void AddNewTorrentDialog::setupTreeview()
ui->date_lbl->setText(!m_torrentInfo.creationDate().isNull() ? m_torrentInfo.creationDate().toString(Qt::DefaultLocaleShortDate) : tr("Not available"));
// Prepare content tree
if (m_torrentInfo.filesCount() > 1) {
m_contentModel = new TorrentContentFilterModel(this);
connect(m_contentModel->model(), SIGNAL(filteredFilesChanged()), SLOT(updateDiskSpaceLabel()));
ui->content_tree->setModel(m_contentModel);
ui->content_tree->hideColumn(PROGRESS);
m_contentDelegate = new PropListDelegate();
ui->content_tree->setItemDelegate(m_contentDelegate);
connect(ui->content_tree, SIGNAL(clicked(const QModelIndex &)), ui->content_tree, SLOT(edit(const QModelIndex &)));
connect(ui->content_tree, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(displayContentTreeMenu(const QPoint &)));
// List files in torrent
m_contentModel->model()->setupModelData(m_torrentInfo);
if (!m_headerState.isEmpty())
ui->content_tree->header()->restoreState(m_headerState);
// Expand root folder
ui->content_tree->setExpanded(m_contentModel->index(0, 0), true);
}
else {
// Update save paths (append file name to them)
QString single_file_relpath = m_torrentInfo.filePath(0);
for (int i = 0; i < ui->save_path_combo->count(); ++i)
ui->save_path_combo->setItemText(i, Utils::Fs::toNativePath(QDir(ui->save_path_combo->itemText(i)).absoluteFilePath(single_file_relpath)));
}
m_contentModel = new TorrentContentFilterModel(this);
connect(m_contentModel->model(), SIGNAL(filteredFilesChanged()), SLOT(updateDiskSpaceLabel()));
ui->contentTreeView->setModel(m_contentModel);
ui->contentTreeView->hideColumn(PROGRESS);
m_contentDelegate = new PropListDelegate();
ui->contentTreeView->setItemDelegate(m_contentDelegate);
connect(ui->contentTreeView, SIGNAL(clicked(const QModelIndex &)), ui->contentTreeView, SLOT(edit(const QModelIndex &)));
connect(ui->contentTreeView, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(displayContentTreeMenu(const QPoint &)));
// List files in torrent
m_contentModel->model()->setupModelData(m_torrentInfo);
if (!m_headerState.isEmpty())
ui->contentTreeView->header()->restoreState(m_headerState);
// Expand root folder
ui->contentTreeView->setExpanded(m_contentModel->index(0, 0), true);
}
updateDiskSpaceLabel();
showAdvancedSettings(Preferences::instance()->getAddNewTorrentDialogExpanded());
showAdvancedSettings(settings()->loadValue(KEY_EXPANDED, false).toBool());
// Set dialog position
setdialogPosition();
}
QString AddNewTorrentDialog::defaultSavePath() const
{
return Utils::Fs::fromNativePath(
settings()->loadValue(KEY_DEFAULTSAVEPATH,
BitTorrent::Session::instance()->defaultSavePath()).toString());
}
void AddNewTorrentDialog::handleDownloadFailed(const QString &url, const QString &reason)
{
MessageBoxRaised::critical(0, tr("Download Error"), QString("Cannot download '%1': %2").arg(url).arg(reason));
@ -737,3 +755,25 @@ void AddNewTorrentDialog::handleDownloadFinished(const QString &url, const QStri @@ -737,3 +755,25 @@ void AddNewTorrentDialog::handleDownloadFinished(const QString &url, const QStri
else
this->deleteLater();
}
void AddNewTorrentDialog::savingModeChanged(bool enabled)
{
if (!enabled) return;
if (ui->simpleModeRadioButton->isChecked()) {
populateSavePathComboBox();
ui->savePathComboBox->setEnabled(true);
ui->browseButton->setEnabled(true);
ui->savePathComboBox->blockSignals(false);
ui->savePathComboBox->setCurrentIndex(m_oldIndex < ui->savePathComboBox->count() ? m_oldIndex : ui->savePathComboBox->count() - 1);
}
else {
ui->savePathComboBox->blockSignals(true);
ui->savePathComboBox->clear();
QString savePath = BitTorrent::Session::instance()->categorySavePath(ui->categoryComboBox->currentText());
ui->savePathComboBox->addItem(Utils::Fs::toNativePath(savePath), savePath);
ui->savePathComboBox->setEnabled(false);
ui->browseButton->setEnabled(false);
ui->defaultSavePathCheckBox->setVisible(false);
}
}

22
src/gui/addnewtorrentdialog.h

@ -58,10 +58,12 @@ class AddNewTorrentDialog: public QDialog @@ -58,10 +58,12 @@ class AddNewTorrentDialog: public QDialog
public:
~AddNewTorrentDialog();
static void show(QString source, QWidget *parent = 0);
static bool isEnabled();
static void setEnabled(bool value);
static bool isTopLevel();
static void setTopLevel(bool value);
protected:
void showEvent(QShowEvent *event);
static void show(QString source, QWidget *parent = 0);
private slots:
void showAdvancedSettings(bool show);
@ -75,25 +77,27 @@ private slots: @@ -75,25 +77,27 @@ private slots:
void handleDownloadFailed(const QString &url, const QString &reason);
void handleRedirectedToMagnet(const QString &url, const QString &magnetUri);
void handleDownloadFinished(const QString &url, const QString &filePath);
void savingModeChanged(bool enabled);
void categoryChanged(int index);
protected slots:
virtual void accept();
virtual void reject();
void accept() override;
void reject() override;
private:
explicit AddNewTorrentDialog(QWidget *parent = 0);
bool loadTorrent(const QString &torrentPath);
bool loadMagnet(const BitTorrent::MagnetUri &magnetUri);
void loadSavePathHistory();
void populateSavePathComboBox();
void saveSavePathHistory() const;
int indexOfSavePath(const QString& save_path);
void updateFileNameInSavePaths(const QString& new_filename);
void loadState();
void saveState();
void setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText = QString());
void setupTreeview();
QString defaultSavePath() const;
void showEvent(QShowEvent *event) override;
private:
Ui::AddNewTorrentDialog *ui;
TorrentContentFilterModel *m_contentModel;
PropListDelegate *m_contentDelegate;

138
src/gui/addnewtorrentdialog.ui

@ -17,10 +17,54 @@ @@ -17,10 +17,54 @@
<string>Save at</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>20</number>
</property>
<item>
<widget class="QLabel" name="savingModeLabel">
<property name="text">
<string>Saving Management:</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="simpleModeRadioButton">
<property name="text">
<string>Simple</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="advancedModeRadioButton">
<property name="text">
<string>Advanced</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<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>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QComboBox" name="save_path_combo">
<widget class="QComboBox" name="savePathComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -33,7 +77,7 @@ @@ -33,7 +77,7 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="browse_button">
<widget class="QPushButton" name="browseButton">
<property name="text">
<string>Browse...</string>
</property>
@ -42,7 +86,7 @@ @@ -42,7 +86,7 @@
</layout>
</item>
<item>
<widget class="QCheckBox" name="default_save_path_cb">
<widget class="QCheckBox" name="defaultSavePathCheckBox">
<property name="text">
<string>Set as default save path</string>
</property>
@ -75,14 +119,14 @@ @@ -75,14 +119,14 @@
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="2">
<widget class="QCheckBox" name="defaultLabel">
<widget class="QCheckBox" name="defaultCategoryCheckbox">
<property name="text">
<string>Set as default label</string>
<string>Set as default category</string>
</property>
</widget>
</item>
<item row="0" column="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<layout class="QHBoxLayout" name="horizontalLayout_1">
<item>
<widget class="QLabel" name="label_5">
<property name="sizePolicy">
@ -92,12 +136,12 @@ @@ -92,12 +136,12 @@
</sizepolicy>
</property>
<property name="text">
<string>Set label:</string>
<string>Category:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="label_combo">
<widget class="QComboBox" name="categoryComboBox">
<property name="minimumSize">
<size>
<width>140</width>
@ -115,7 +159,7 @@ @@ -115,7 +159,7 @@
</layout>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="start_torrent_cb">
<widget class="QCheckBox" name="startTorrentCheckBox">
<property name="text">
<string>Start torrent</string>
</property>
@ -231,7 +275,7 @@ @@ -231,7 +275,7 @@
</layout>
</item>
<item>
<widget class="TorrentContentTreeView" name="content_tree">
<widget class="TorrentContentTreeView" name="contentTreeView">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
@ -311,6 +355,20 @@ @@ -311,6 +355,20 @@
<header>torrentcontenttreeview.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>savePathComboBox</tabstop>
<tabstop>browseButton</tabstop>
<tabstop>simpleModeRadioButton</tabstop>
<tabstop>advancedModeRadioButton</tabstop>
<tabstop>defaultSavePathCheckBox</tabstop>
<tabstop>never_show_cb</tabstop>
<tabstop>adv_button</tabstop>
<tabstop>startTorrentCheckBox</tabstop>
<tabstop>categoryComboBox</tabstop>
<tabstop>defaultCategoryCheckbox</tabstop>
<tabstop>skip_check_cb</tabstop>
<tabstop>contentTreeView</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
@ -320,8 +378,8 @@ @@ -320,8 +378,8 @@
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
<x>403</x>
<y>579</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
@ -336,8 +394,8 @@ @@ -336,8 +394,8 @@
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
<x>403</x>
<y>579</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
@ -345,5 +403,57 @@ @@ -345,5 +403,57 @@
</hint>
</hints>
</connection>
<connection>
<sender>simpleModeRadioButton</sender>
<signal>toggled(bool)</signal>
<receiver>AddNewTorrentDialog</receiver>
<slot>savingModeChanged(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>154</x>
<y>39</y>
</hint>
<hint type="destinationlabel">
<x>122</x>
<y>6</y>
</hint>
</hints>
</connection>
<connection>
<sender>advancedModeRadioButton</sender>
<signal>toggled(bool)</signal>
<receiver>AddNewTorrentDialog</receiver>
<slot>savingModeChanged(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>218</x>
<y>44</y>
</hint>
<hint type="destinationlabel">
<x>209</x>
<y>7</y>
</hint>
</hints>
</connection>
<connection>
<sender>categoryComboBox</sender>
<signal>currentIndexChanged(int)</signal>
<receiver>AddNewTorrentDialog</receiver>
<slot>categoryChanged(int)</slot>
<hints>
<hint type="sourcelabel">
<x>337</x>
<y>205</y>
</hint>
<hint type="destinationlabel">
<x>403</x>
<y>160</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>savingModeChanged(bool)</slot>
<slot>categoryChanged(int)</slot>
</slots>
</ui>

6
src/gui/mainwindow.cpp

@ -1004,7 +1004,7 @@ void MainWindow::dropEvent(QDropEvent *event) @@ -1004,7 +1004,7 @@ void MainWindow::dropEvent(QDropEvent *event)
}
// Add file to download list
const bool useTorrentAdditionDialog = Preferences::instance()->useAdditionDialog();
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
foreach (QString file, files) {
qDebug("Dropped file %s on download list", qPrintable(file));
if (useTorrentAdditionDialog)
@ -1039,7 +1039,7 @@ void MainWindow::on_actionOpen_triggered() @@ -1039,7 +1039,7 @@ void MainWindow::on_actionOpen_triggered()
const QStringList pathsList =
QFileDialog::getOpenFileNames(0, tr("Open Torrent Files"), pref->getMainLastDir(),
tr("Torrent Files") + QString::fromUtf8(" (*.torrent)"));
const bool useTorrentAdditionDialog = Preferences::instance()->useAdditionDialog();
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
if (!pathsList.isEmpty()) {
foreach (QString file, pathsList) {
qDebug("Dropped file %s on download list", qPrintable(file));
@ -1249,7 +1249,7 @@ void MainWindow::showNotificationBaloon(QString title, QString msg) const @@ -1249,7 +1249,7 @@ void MainWindow::showNotificationBaloon(QString title, QString msg) const
void MainWindow::downloadFromURLList(const QStringList& url_list)
{
const bool useTorrentAdditionDialog = Preferences::instance()->useAdditionDialog();
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
foreach (QString url, url_list) {
if ((url.size() == 40 && !url.contains(QRegExp("[^0-9A-Fa-f]")))
|| (url.size() == 32 && !url.contains(QRegExp("[^2-7A-Za-z]"))))

379
src/gui/options.ui

@ -546,8 +546,8 @@ @@ -546,8 +546,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>487</width>
<height>1040</height>
<width>454</width>
<height>1212</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@ -599,7 +599,7 @@ @@ -599,7 +599,7 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="fileSystemBox">
<widget class="QGroupBox" name="groupSavingManagement">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
@ -607,13 +607,57 @@ @@ -607,13 +607,57 @@
</sizepolicy>
</property>
<property name="title">
<string>Hard Disk</string>
<string>Saving Management</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_25">
<item>
<widget class="QGroupBox" name="groupBox_3">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<property name="spacing">
<number>10</number>
</property>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Default Saving Mode:</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioBtnSimpleMode">
<property name="text">
<string>Simple</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioBtnAdvancedMode">
<property name="text">
<string>Advanced</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_16">
<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="QGroupBox" name="groupDefaultSavePath">
<property name="title">
<string>Save files to location:</string>
<string>Default Save Path</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_26">
<property name="bottomMargin">
@ -658,10 +702,134 @@ @@ -658,10 +702,134 @@
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="checkAppendLabel">
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<property name="spacing">
<number>10</number>
</property>
<item>
<widget class="QLabel" name="labelEnableSubcategories">
<property name="text">
<string>Enable Subcategories:</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioBtnEnableSubcategories">
<property name="text">
<string>Yes</string>
</property>
<attribute name="buttonGroup">
<string notr="true">buttonGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioBtnDisableSubcategories">
<property name="text">
<string>No</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">buttonGroup</string>
</attribute>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_17">
<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="QGroupBox" name="groupCategoryChanged">
<property name="title">
<string>When Torrent Category changed</string>
</property>
<layout class="QFormLayout" name="formLayout_6">
<item row="0" column="0">
<widget class="QRadioButton" name="radioBtnRelocateOnCategoryChanged">
<property name="text">
<string>Relocate torrent</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="radioBtnDisableASMOnCategoryChanged">
<property name="text">
<string>Switch torrent to Simple Mode</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupDefaultSavePathChanged">
<property name="title">
<string>When Default Save Path changed</string>
</property>
<layout class="QFormLayout" name="formLayout_7">
<item row="0" column="0">
<widget class="QRadioButton" name="radioBtnRelocateOnDefaultSavePathChanged">
<property name="text">
<string>Relocate affected torrents</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="radioBtnDisableASMOnDefaultSavePathChanged">
<property name="text">
<string>Switch affected torrents to Simple Mode</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupCategorySavePathChanged">
<property name="title">
<string>When Category changed</string>
</property>
<layout class="QFormLayout" name="formLayout_8">
<item row="0" column="0">
<widget class="QRadioButton" name="radioBtnRelocateOnCategorySavePathChanged">
<property name="text">
<string>Append the label of the torrent to the save path</string>
<string>Relocate affected torrents</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="radioBtnDisableASMOnCategorySavePathChanged">
<property name="text">
<string>Switch affected torrents to Simple Mode</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
@ -728,93 +896,6 @@ @@ -728,93 +896,6 @@
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Automatically add torrents from:</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_38">
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_16">
<item>
<widget class="QTreeView" name="scanFoldersView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>250</width>
<height>150</height>
</size>
</property>
<property name="editTriggers">
<set>QAbstractItemView::AllEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="textElideMode">
<enum>Qt::ElideNone</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<attribute name="headerDefaultSectionSize">
<number>80</number>
</attribute>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_37">
<item>
<widget class="QPushButton" name="addScanFolderButton">
<property name="text">
<string>Add folder...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeScanFolderButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Remove folder</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="checkExportDir">
<property name="sizePolicy">
@ -926,6 +1007,93 @@ @@ -926,6 +1007,93 @@
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Automatically add torrents from:</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_38">
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_16">
<item>
<widget class="QTreeView" name="scanFoldersView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>250</width>
<height>150</height>
</size>
</property>
<property name="editTriggers">
<set>QAbstractItemView::AllEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="textElideMode">
<enum>Qt::ElideNone</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<attribute name="headerDefaultSectionSize">
<number>80</number>
</attribute>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_37">
<item>
<widget class="QPushButton" name="addScanFolderButton">
<property name="text">
<string>Add folder...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeScanFolderButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Remove folder</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupMailNotification">
<property name="title">
@ -1062,8 +1230,8 @@ @@ -1062,8 +1230,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>450</width>
<height>658</height>
<width>361</width>
<height>586</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_20">
@ -1592,8 +1760,8 @@ @@ -1592,8 +1760,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>376</width>
<height>444</height>
<width>275</width>
<height>401</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
@ -1979,8 +2147,8 @@ @@ -1979,8 +2147,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>555</width>
<height>527</height>
<width>440</width>
<height>481</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
@ -2376,8 +2544,8 @@ @@ -2376,8 +2544,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>419</width>
<height>537</height>
<width>332</width>
<height>480</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_23">
@ -2950,4 +3118,7 @@ @@ -2950,4 +3118,7 @@
</hints>
</connection>
</connections>
<buttongroups>
<buttongroup name="buttonGroup"/>
</buttongroups>
</ui>

78
src/gui/options_imp.cpp

@ -51,6 +51,7 @@ @@ -51,6 +51,7 @@
#include "advancedsettings.h"
#include "guiiconprovider.h"
#include "scanfoldersdelegate.h"
#include "addnewtorrentdialog.h"
#include "options_imp.h"
#ifndef QT_NO_OPENSSL
@ -168,8 +169,12 @@ options_imp::options_imp(QWidget *parent) @@ -168,8 +169,12 @@ options_imp::options_imp(QWidget *parent)
#endif
// Downloads tab
connect(textSavePath, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton()));
connect(radioBtnEnableSubcategories, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton()));
connect(radioBtnAdvancedMode, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton()));
connect(radioBtnRelocateOnCategoryChanged, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton()));
connect(radioBtnRelocateOnCategorySavePathChanged, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton()));
connect(radioBtnRelocateOnDefaultSavePathChanged, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton()));
connect(textTempPath, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton()));
connect(checkAppendLabel, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton()));
connect(checkAppendqB, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton()));
connect(checkPreallocateAll, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton()));
connect(checkAdditionDialog, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton()));
@ -197,7 +202,7 @@ options_imp::options_imp(QWidget *parent) @@ -197,7 +202,7 @@ options_imp::options_imp(QWidget *parent)
autoRun_param->setText(QString::fromUtf8("%1\n %2\n %3\n %4\n %5\n %6\n %7\n %8\n %9\n %10")
.arg(tr("Supported parameters (case sensitive):"))
.arg(tr("%N: Torrent name"))
.arg(tr("%L: Label"))
.arg(tr("%L: Category"))
.arg(tr("%F: Content path (same as root path for multifile torrent)"))
.arg(tr("%R: Root path (first torrent subdirectory path)"))
.arg(tr("%D: Save path"))
@ -429,16 +434,22 @@ void options_imp::saveOptions() @@ -429,16 +434,22 @@ void options_imp::saveOptions()
#endif
// End General preferences
auto session = BitTorrent::Session::instance();
// Downloads preferences
pref->setSavePath(getSavePath());
pref->setTempPathEnabled(isTempPathEnabled());
pref->setTempPath(getTempPath());
pref->setAppendTorrentLabel(checkAppendLabel->isChecked());
session->setDefaultSavePath(Utils::Fs::expandPathAbs(textSavePath->text()));
session->setSubcategoriesEnabled(radioBtnEnableSubcategories->isChecked());
session->setASMDisabledByDefault(radioBtnSimpleMode->isChecked());
session->setDisableASMWhenCategoryChanged(radioBtnDisableASMOnCategoryChanged->isChecked());
session->setDisableASMWhenCategorySavePathChanged(radioBtnDisableASMOnCategorySavePathChanged->isChecked());
session->setDisableASMWhenDefaultSavePathChanged(radioBtnDisableASMOnDefaultSavePathChanged->isChecked());
session->setTempPathEnabled(checkTempFolder->isChecked());
session->setTempPath(Utils::Fs::expandPathAbs(textTempPath->text()));
pref->useIncompleteFilesExtension(checkAppendqB->isChecked());
pref->preAllocateAllFiles(preAllocateAllFiles());
pref->useAdditionDialog(useAdditionDialog());
pref->additionDialogFront(checkAdditionDialogFront->isChecked());
pref->addTorrentsInPause(addTorrentsInPause());
AddNewTorrentDialog::setEnabled(useAdditionDialog());
AddNewTorrentDialog::setTopLevel(checkAdditionDialogFront->isChecked());
session->setAddTorrentPaused(addTorrentsInPause());
ScanFoldersModel::instance()->removeFromFSWatcher(removedScanDirs);
ScanFoldersModel::instance()->addToFSWatcher(addedScanDirs);
ScanFoldersModel::instance()->makePersistent();
@ -500,7 +511,7 @@ void options_imp::saveOptions() @@ -500,7 +511,7 @@ void options_imp::saveOptions()
pref->setAddTrackersEnabled(checkEnableAddTrackers->isChecked());
pref->setTrackersList(textTrackers->toPlainText());
pref->setGlobalMaxRatio(getMaxRatio());
pref->setMaxRatioAction(static_cast<MaxRatioAction>(comboRatioLimitAct->currentIndex()));
session->setMaxRatioAction(static_cast<MaxRatioAction>(comboRatioLimitAct->currentIndex()));
// End Bittorrent preferences
// Misc preferences
// * IPFilter
@ -610,18 +621,21 @@ void options_imp::loadOptions() @@ -610,18 +621,21 @@ void options_imp::loadOptions()
#endif
// End General preferences
// Downloads preferences
checkAdditionDialog->setChecked(pref->useAdditionDialog());
checkAdditionDialogFront->setChecked(pref->additionDialogFront());
checkStartPaused->setChecked(pref->addTorrentsInPause());
auto session = BitTorrent::Session::instance();
textSavePath->setText(Utils::Fs::toNativePath(pref->getSavePath()));
if (pref->isTempPathEnabled())
checkTempFolder->setChecked(true);
else
checkTempFolder->setChecked(false);
textTempPath->setText(Utils::Fs::toNativePath(pref->getTempPath()));
checkAppendLabel->setChecked(pref->appendTorrentLabel());
// Downloads preferences
checkAdditionDialog->setChecked(AddNewTorrentDialog::isEnabled());
checkAdditionDialogFront->setChecked(AddNewTorrentDialog::isTopLevel());
checkStartPaused->setChecked(session->isAddTorrentPaused());
textSavePath->setText(Utils::Fs::toNativePath(session->defaultSavePath()));
(session->isSubcategoriesEnabled() ? radioBtnEnableSubcategories : radioBtnDisableSubcategories)->setChecked(true);
(session->isASMDisabledByDefault() ? radioBtnSimpleMode : radioBtnAdvancedMode)->setChecked(true);
(session->isDisableASMWhenCategoryChanged() ? radioBtnDisableASMOnCategoryChanged : radioBtnRelocateOnCategoryChanged)->setChecked(true);
(session->isDisableASMWhenCategorySavePathChanged() ? radioBtnDisableASMOnCategorySavePathChanged : radioBtnRelocateOnCategorySavePathChanged)->setChecked(true);
(session->isDisableASMWhenDefaultSavePathChanged() ? radioBtnDisableASMOnDefaultSavePathChanged : radioBtnRelocateOnDefaultSavePathChanged)->setChecked(true);
checkTempFolder->setChecked(session->isTempPathEnabled());
textTempPath->setText(Utils::Fs::toNativePath(session->tempPath()));
checkAppendqB->setChecked(pref->useIncompleteFilesExtension());
checkPreallocateAll->setChecked(pref->preAllocateAllFiles());
@ -844,7 +858,7 @@ void options_imp::loadOptions() @@ -844,7 +858,7 @@ void options_imp::loadOptions()
spinMaxRatio->setEnabled(false);
comboRatioLimitAct->setEnabled(false);
}
comboRatioLimitAct->setCurrentIndex(static_cast<int>(pref->getMaxRatioAction()));
comboRatioLimitAct->setCurrentIndex(session->maxRatioAction());
// End Bittorrent preferences
// Web UI preferences
@ -974,26 +988,6 @@ qreal options_imp::getMaxRatio() const @@ -974,26 +988,6 @@ qreal options_imp::getMaxRatio() const
return -1;
}
// Return Save Path
QString options_imp::getSavePath() const
{
if (textSavePath->text().trimmed().isEmpty()) {
QString save_path = Preferences::instance()->getSavePath();
textSavePath->setText(Utils::Fs::toNativePath(save_path));
}
return Utils::Fs::expandPathAbs(textSavePath->text());
}
QString options_imp::getTempPath() const
{
return Utils::Fs::expandPathAbs(textTempPath->text());
}
bool options_imp::isTempPathEnabled() const
{
return checkTempFolder->isChecked();
}
// Return max connections number
int options_imp::getMaxConnecs() const
{

3
src/gui/options_imp.h

@ -114,9 +114,6 @@ private: @@ -114,9 +114,6 @@ private:
bool WinStartup() const;
#endif
// Downloads
QString getSavePath() const;
bool isTempPathEnabled() const;
QString getTempPath() const;
bool preAllocateAllFiles() const;
bool useAdditionDialog() const;
bool addTorrentsInPause() const;

40
src/gui/rss/automatedrssdownloader.cpp

@ -34,17 +34,18 @@ @@ -34,17 +34,18 @@
#include <QMenu>
#include <QCursor>
#include "automatedrssdownloader.h"
#include "ui_automatedrssdownloader.h"
#include "base/rss/rssdownloadrulelist.h"
#include "base/preferences.h"
#include "base/bittorrent/session.h"
#include "base/rss/rssdownloadrulelist.h"
#include "base/rss/rssmanager.h"
#include "base/rss/rssfolder.h"
#include "base/rss/rssfeed.h"
#include "guiiconprovider.h"
#include "autoexpandabledialog.h"
#include "base/utils/fs.h"
#include "base/utils/string.h"
#include "guiiconprovider.h"
#include "autoexpandabledialog.h"
#include "ui_automatedrssdownloader.h"
#include "automatedrssdownloader.h"
AutomatedRssDownloader::AutomatedRssDownloader(const QWeakPointer<Rss::Manager>& manager, QWidget *parent) :
QDialog(parent),
@ -85,7 +86,7 @@ AutomatedRssDownloader::AutomatedRssDownloader(const QWeakPointer<Rss::Manager>& @@ -85,7 +86,7 @@ AutomatedRssDownloader::AutomatedRssDownloader(const QWeakPointer<Rss::Manager>&
"<li>" + tr("Normal range: <b>1x25-40;</b> matches episodes 25 through 40 of season one") + "</li>" +
"<li>" + tr("Infinite range: <b>1x25-;</b> matches episodes 25 and upward of season one") + "</li>" + "</ul></li></ul>";
ui->lineEFilter->setToolTip(tip);
initLabelCombobox();
initCategoryCombobox();
loadFeedList();
loadSettings();
ok = connect(ui->listRules, SIGNAL(itemSelectionChanged()), SLOT(updateRuleDefinitionBox()));
@ -253,11 +254,11 @@ void AutomatedRssDownloader::updateRuleDefinitionBox() @@ -253,11 +254,11 @@ void AutomatedRssDownloader::updateRuleDefinitionBox()
ui->checkRegex->blockSignals(true);
ui->checkRegex->setChecked(rule->useRegex());
ui->checkRegex->blockSignals(false);
if (rule->label().isEmpty()) {
ui->comboLabel->setCurrentIndex(-1);
ui->comboLabel->clearEditText();
if (rule->category().isEmpty()) {
ui->comboCategory->setCurrentIndex(-1);
ui->comboCategory->clearEditText();
} else {
ui->comboLabel->setCurrentIndex(ui->comboLabel->findText(rule->label()));
ui->comboCategory->setCurrentIndex(ui->comboCategory->findText(rule->category()));
}
ui->comboAddPaused->setCurrentIndex(rule->addPaused());
ui->spinIgnorePeriod->setValue(rule->ignoreDays());
@ -293,7 +294,7 @@ void AutomatedRssDownloader::clearRuleDefinitionBox() @@ -293,7 +294,7 @@ void AutomatedRssDownloader::clearRuleDefinitionBox()
ui->lineNotContains->clear();
ui->saveDiffDir_check->setChecked(false);
ui->lineSavePath->clear();
ui->comboLabel->clearEditText();
ui->comboCategory->clearEditText();
ui->checkRegex->setChecked(false);
ui->spinIgnorePeriod->setValue(0);
updateFieldsToolTips(ui->checkRegex->isChecked());
@ -309,13 +310,12 @@ Rss::DownloadRulePtr AutomatedRssDownloader::getCurrentRule() const @@ -309,13 +310,12 @@ Rss::DownloadRulePtr AutomatedRssDownloader::getCurrentRule() const
return Rss::DownloadRulePtr();
}
void AutomatedRssDownloader::initLabelCombobox()
void AutomatedRssDownloader::initCategoryCombobox()
{
// Load custom labels
QStringList customLabels = Preferences::instance()->getTorrentLabels();
std::sort(customLabels.begin(), customLabels.end(), Utils::String::NaturalCompare());
foreach (const QString& l, customLabels)
ui->comboLabel->addItem(l);
// Load torrent categories
QStringList categories = BitTorrent::Session::instance()->categories();
std::sort(categories.begin(), categories.end(), Utils::String::NaturalCompare());
ui->comboCategory->addItems(categories);
}
void AutomatedRssDownloader::saveEditedRule()
@ -344,11 +344,9 @@ void AutomatedRssDownloader::saveEditedRule() @@ -344,11 +344,9 @@ void AutomatedRssDownloader::saveEditedRule()
rule->setSavePath(ui->lineSavePath->text());
else
rule->setSavePath("");
rule->setLabel(ui->comboLabel->currentText());
rule->setCategory(ui->comboCategory->currentText());
rule->setAddPaused(Rss::DownloadRule::AddPausedState(ui->comboAddPaused->currentIndex()));
// Save new label
if (!rule->label().isEmpty())
Preferences::instance()->addTorrentLabelExternal(rule->label());
rule->setIgnoreDays(ui->spinIgnorePeriod->value());
//rule->setRssFeeds(getSelectedFeeds());
// Save it

2
src/gui/rss/automatedrssdownloader.h

@ -90,7 +90,7 @@ private slots: @@ -90,7 +90,7 @@ private slots:
private:
Rss::DownloadRulePtr getCurrentRule() const;
void initLabelCombobox();
void initCategoryCombobox();
void addFeedArticlesToTree(const Rss::FeedPtr& feed, const QStringList& articles);
private:

6
src/gui/rss/automatedrssdownloader.ui

@ -177,14 +177,14 @@ @@ -177,14 +177,14 @@
</sizepolicy>
</property>
<property name="text">
<string>Assign Label:</string>
<string>Assign Category:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboLabel">
<widget class="QComboBox" name="comboCategory">
<property name="editable">
<bool>true</bool>
<bool>false</bool>
</property>
</widget>
</item>

2
src/gui/rss/rss_imp.cpp

@ -352,7 +352,7 @@ void RSSImp::downloadSelectedTorrents() @@ -352,7 +352,7 @@ void RSSImp::downloadSelectedTorrents()
if (article->torrentUrl().isEmpty())
continue;
if (Preferences::instance()->useAdditionDialog())
if (AddNewTorrentDialog::isEnabled())
AddNewTorrentDialog::show(article->torrentUrl());
else
BitTorrent::Session::instance()->addTorrent(article->torrentUrl());

4
src/gui/scanfoldersdelegate.cpp

@ -36,7 +36,7 @@ @@ -36,7 +36,7 @@
#include <QItemSelectionModel>
#include "base/scanfoldersmodel.h"
#include "base/preferences.h"
#include "base/bittorrent/session.h"
#include "scanfoldersdelegate.h"
@ -102,7 +102,7 @@ void ScanFoldersDelegate::setModelData(QWidget *editor, QAbstractItemModel *mode @@ -102,7 +102,7 @@ void ScanFoldersDelegate::setModelData(QWidget *editor, QAbstractItemModel *mode
0, tr("Choose save path"),
index.data(Qt::UserRole).toInt() == ScanFoldersModel::CUSTOM_LOCATION ?
index.data().toString() :
Preferences::instance()->getSavePath()),
BitTorrent::Session::instance()->defaultSavePath()),
Qt::DisplayRole);
break;

2
src/gui/search/searchwidget.cpp

@ -259,7 +259,7 @@ void SearchWidget::saveResultsColumnsWidth() @@ -259,7 +259,7 @@ void SearchWidget::saveResultsColumnsWidth()
void SearchWidget::downloadTorrent(QString url)
{
if (Preferences::instance()->useAdditionDialog())
if (AddNewTorrentDialog::isEnabled())
AddNewTorrentDialog::show(url, this);
else
BitTorrent::Session::instance()->addTorrent(url);

12
src/gui/torrentmodel.cpp

@ -104,7 +104,7 @@ QVariant TorrentModel::headerData(int section, Qt::Orientation orientation, int @@ -104,7 +104,7 @@ QVariant TorrentModel::headerData(int section, Qt::Orientation orientation, int
case TR_UPSPEED: return tr("Up Speed", "i.e: Upload speed");
case TR_RATIO: return tr("Ratio", "Share ratio");
case TR_ETA: return tr("ETA", "i.e: Estimated Time of Arrival / Time left");
case TR_LABEL: return tr("Label");
case TR_CATEGORY: return tr("Category");
case TR_ADD_DATE: return tr("Added On", "Torrent was added to transfer list on 01/01/2010 08:00");
case TR_SEED_DATE: return tr("Completed On", "Torrent was completed on 01/01/2010 08:00");
case TR_TRACKER: return tr("Tracker");
@ -196,8 +196,8 @@ QVariant TorrentModel::data(const QModelIndex &index, int role) const @@ -196,8 +196,8 @@ QVariant TorrentModel::data(const QModelIndex &index, int role) const
return torrent->eta();
case TR_RATIO:
return torrent->realRatio();
case TR_LABEL:
return torrent->label();
case TR_CATEGORY:
return torrent->category();
case TR_ADD_DATE:
return torrent->addedTime();
case TR_SEED_DATE:
@ -250,13 +250,13 @@ bool TorrentModel::setData(const QModelIndex &index, const QVariant &value, int @@ -250,13 +250,13 @@ bool TorrentModel::setData(const QModelIndex &index, const QVariant &value, int
BitTorrent::TorrentHandle *const torrent = m_torrents.value(index.row());
if (!torrent) return false;
// Label, seed date and Name columns can be edited
// Category, seed date and Name columns can be edited
switch(index.column()) {
case TR_NAME:
torrent->setName(value.toString());
break;
case TR_LABEL:
torrent->setLabel(value.toString());
case TR_CATEGORY:
torrent->setCategory(value.toString());
break;
default:
return false;

2
src/gui/torrentmodel.h

@ -61,7 +61,7 @@ public: @@ -61,7 +61,7 @@ public:
TR_UPSPEED,
TR_ETA,
TR_RATIO,
TR_LABEL,
TR_CATEGORY,
TR_ADD_DATE,
TR_SEED_DATE,
TR_TRACKER,

293
src/gui/transferlistfilterswidget.cpp

@ -177,162 +177,156 @@ void StatusFiltersWidget::handleNewTorrent(BitTorrent::TorrentHandle *const) {} @@ -177,162 +177,156 @@ void StatusFiltersWidget::handleNewTorrent(BitTorrent::TorrentHandle *const) {}
void StatusFiltersWidget::torrentAboutToBeDeleted(BitTorrent::TorrentHandle *const) {}
LabelFiltersList::LabelFiltersList(QWidget *parent, TransferListWidget *transferList)
CategoryFiltersList::CategoryFiltersList(QWidget *parent, TransferListWidget *transferList)
: FiltersBase(parent, transferList)
, m_totalTorrents(0)
, m_totalLabeled(0)
{
connect(BitTorrent::Session::instance(), SIGNAL(torrentLabelChanged(BitTorrent::TorrentHandle *const, QString)), SLOT(torrentChangedLabel(BitTorrent::TorrentHandle *const, QString)));
// Add Label filters
QListWidgetItem *allLabels = new QListWidgetItem(this);
allLabels->setData(Qt::DisplayRole, QVariant(tr("All (0)", "this is for the label filter")));
allLabels->setData(Qt::DecorationRole, GuiIconProvider::instance()->getIcon("inode-directory"));
QListWidgetItem *noLabel = new QListWidgetItem(this);
noLabel->setData(Qt::DisplayRole, QVariant(tr("Unlabeled (0)")));
noLabel->setData(Qt::DecorationRole, GuiIconProvider::instance()->getIcon("inode-directory"));
const Preferences* const pref = Preferences::instance();
QStringList labelList = pref->getTorrentLabels();
for (int i=0; i < labelList.size(); ++i)
addItem(labelList[i], false);
connect(BitTorrent::Session::instance(), SIGNAL(torrentCategoryChanged(BitTorrent::TorrentHandle *const, QString)), SLOT(torrentCategoryChanged(BitTorrent::TorrentHandle *const, QString)));
connect(BitTorrent::Session::instance(), SIGNAL(categoryAdded(QString)), SLOT(addItem(QString)));
connect(BitTorrent::Session::instance(), SIGNAL(categoryRemoved(QString)), SLOT(categoryRemoved(QString)));
connect(BitTorrent::Session::instance(), SIGNAL(subcategoriesSupportChanged()), SLOT(subcategoriesSupportChanged()));
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
toggleFilter(pref->getLabelFilterState());
refresh();
toggleFilter(Preferences::instance()->getCategoryFilterState());
}
LabelFiltersList::~LabelFiltersList()
void CategoryFiltersList::refresh()
{
Preferences::instance()->setTorrentLabels(m_labels.keys());
clear();
m_categories.clear();
m_totalTorrents = 0;
m_totalCategorized = 0;
QListWidgetItem *allCategories = new QListWidgetItem(this);
allCategories->setData(Qt::DisplayRole, QVariant(tr("All (0)", "this is for the category filter")));
allCategories->setData(Qt::DecorationRole, GuiIconProvider::instance()->getIcon("inode-directory"));
QListWidgetItem *noCategory = new QListWidgetItem(this);
noCategory->setData(Qt::DisplayRole, QVariant(tr("Uncategorized (0)")));
noCategory->setData(Qt::DecorationRole, GuiIconProvider::instance()->getIcon("inode-directory"));
foreach (const QString &category, BitTorrent::Session::instance()->categories())
addItem(category, false);
foreach (BitTorrent::TorrentHandle *const torrent, BitTorrent::Session::instance()->torrents())
handleNewTorrent(torrent);
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
}
void LabelFiltersList::addItem(QString &label, bool hasTorrent)
void CategoryFiltersList::addItem(const QString &category, bool hasTorrent)
{
int labelCount = 0;
QListWidgetItem *labelItem = 0;
label = Utils::Fs::toValidFileSystemName(label.trimmed());
item(0)->setText(tr("All (%1)", "this is for the label filter").arg(m_totalTorrents));
if (category.isEmpty()) return;
if (label.isEmpty()) {
item(1)->setText(tr("Unlabeled (%1)").arg(m_totalTorrents - m_totalLabeled));
return;
}
int torrentsInCategory = 0;
QListWidgetItem *categoryItem = 0;
bool exists = m_labels.contains(label);
bool exists = m_categories.contains(category);
if (exists) {
labelCount = m_labels.value(label);
labelItem = item(rowFromLabel(label));
torrentsInCategory = m_categories.value(category);
categoryItem = item(rowFromCategory(category));
}
else {
labelItem = new QListWidgetItem();
labelItem->setData(Qt::DecorationRole, GuiIconProvider::instance()->getIcon("inode-directory"));
categoryItem = new QListWidgetItem();
categoryItem->setData(Qt::DecorationRole, GuiIconProvider::instance()->getIcon("inode-directory"));
}
if (hasTorrent) {
++m_totalLabeled;
++labelCount;
}
item(1)->setText(tr("Unlabeled (%1)").arg(m_totalTorrents - m_totalLabeled));
if (hasTorrent)
++torrentsInCategory;
Preferences::instance()->addTorrentLabel(label);
m_labels.insert(label, labelCount);
labelItem->setText(tr("%1 (%2)", "label_name (10)").arg(label).arg(labelCount));
if (exists)
return;
m_categories.insert(category, torrentsInCategory);
categoryItem->setText(tr("%1 (%2)", "category_name (10)").arg(category).arg(torrentsInCategory));
if (exists) return;
Q_ASSERT(count() >= 2);
for (int i = 2; i<count(); ++i) {
for (int i = 2; i < count(); ++i) {
bool less = false;
if (!(Utils::String::naturalSort(label, item(i)->text(), less)))
less = (label.localeAwareCompare(item(i)->text()) < 0);
if (!(Utils::String::naturalSort(category, item(i)->text(), less)))
less = (category.localeAwareCompare(item(i)->text()) < 0);
if (less) {
insertItem(i, labelItem);
insertItem(i, categoryItem);
updateGeometry();
return;
}
}
QListWidget::addItem(labelItem);
QListWidget::addItem(categoryItem);
updateGeometry();
}
void LabelFiltersList::removeItem(const QString &label)
void CategoryFiltersList::removeItem(const QString &category)
{
item(0)->setText(tr("All (%1)", "this is for the label filter").arg(m_totalTorrents));
if (label.isEmpty()) {
// In case we here from torrentAboutToBeDeleted()
item(1)->setText(tr("Unlabeled (%1)").arg(m_totalTorrents - m_totalLabeled));
return;
}
if (category.isEmpty()) return;
--m_totalLabeled;
item(1)->setText(tr("Unlabeled (%1)").arg(m_totalTorrents - m_totalLabeled));
int torrentsInCategory = m_categories.value(category) - 1;
int row = rowFromCategory(category);
if (row < 2) return;
int labelCount = m_labels.value(label) - 1;
int row = rowFromLabel(label);
if (row < 2)
return;
QListWidgetItem *labelItem = item(row);
labelItem->setText(tr("%1 (%2)", "label_name (10)").arg(label).arg(labelCount));
m_labels.insert(label, labelCount);
QListWidgetItem *categoryItem = item(row);
categoryItem->setText(tr("%1 (%2)", "category_name (10)").arg(category).arg(torrentsInCategory));
m_categories.insert(category, torrentsInCategory);
}
void LabelFiltersList::removeSelectedLabel()
void CategoryFiltersList::removeSelectedCategory()
{
QList<QListWidgetItem*> items = selectedItems();
if (items.size() == 0) return;
const int labelRow = row(items.first());
if (labelRow < 2) return;
const int categoryRow = row(items.first());
if (categoryRow < 2) return;
const QString &label = labelFromRow(labelRow);
Q_ASSERT(m_labels.contains(label));
m_labels.remove(label);
// Select first label
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
// Un display filter
delete takeItem(labelRow);
transferList->removeLabelFromRows(label);
// Save custom labels to remember it was deleted
Preferences::instance()->removeTorrentLabel(label);
BitTorrent::Session::instance()->removeCategory(categoryFromRow(categoryRow));
updateGeometry();
}
void LabelFiltersList::removeUnusedLabels()
void CategoryFiltersList::removeUnusedCategories()
{
QStringList unusedLabels;
QHash<QString, int>::const_iterator i;
for (i = m_labels.begin(); i != m_labels.end(); ++i) {
if (i.value() == 0)
unusedLabels << i.key();
foreach (const QString &category, m_categories.keys())
if (m_categories[category] == 0)
BitTorrent::Session::instance()->removeCategory(category);
updateGeometry();
}
void CategoryFiltersList::torrentCategoryChanged(BitTorrent::TorrentHandle *const torrent, const QString &oldCategory)
{
qDebug() << "Torrent category changed from" << oldCategory << "to" << torrent->category();
if (torrent->category().isEmpty() && !oldCategory.isEmpty())
--m_totalCategorized;
else if (!torrent->category().isEmpty() && oldCategory.isEmpty())
++m_totalCategorized;
item(1)->setText(tr("Uncategorized (%1)").arg(m_totalTorrents - m_totalCategorized));
if (BitTorrent::Session::instance()->isSubcategoriesEnabled()) {
foreach (const QString &subcategory, BitTorrent::Session::expandCategory(oldCategory))
removeItem(subcategory);
foreach (const QString &subcategory, BitTorrent::Session::expandCategory(torrent->category()))
addItem(subcategory, true);
}
foreach (const QString &label, unusedLabels) {
m_labels.remove(label);
delete takeItem(rowFromLabel(label));
Preferences::instance()->removeTorrentLabel(label);
else {
removeItem(oldCategory);
addItem(torrent->category(), true);
}
}
if (!unusedLabels.isEmpty())
updateGeometry();
void CategoryFiltersList::categoryRemoved(const QString &category)
{
m_categories.remove(category);
delete takeItem(rowFromCategory(category));
}
void LabelFiltersList::torrentChangedLabel(BitTorrent::TorrentHandle *const torrent, const QString &oldLabel)
void CategoryFiltersList::subcategoriesSupportChanged()
{
qDebug("Torrent label changed from %s to %s", qPrintable(oldLabel), qPrintable(torrent->label()));
removeItem(oldLabel);
QString newLabel = torrent->label();
addItem(newLabel, true);
refresh();
}
void LabelFiltersList::showMenu(QPoint)
void CategoryFiltersList::showMenu(QPoint)
{
QMenu menu(this);
QAction *addAct = menu.addAction(GuiIconProvider::instance()->getIcon("list-add"), tr("Add label..."));
QAction *addAct = menu.addAction(GuiIconProvider::instance()->getIcon("list-add"), tr("Add category..."));
QAction *removeAct = 0;
QAction *removeUnusedAct = 0;
if (!selectedItems().empty() && row(selectedItems().first()) > 1)
removeAct = menu.addAction(GuiIconProvider::instance()->getIcon("list-remove"), tr("Remove label"));
removeUnusedAct = menu.addAction(GuiIconProvider::instance()->getIcon("list-remove"), tr("Remove unused labels"));
removeAct = menu.addAction(GuiIconProvider::instance()->getIcon("list-remove"), tr("Remove category"));
removeUnusedAct = menu.addAction(GuiIconProvider::instance()->getIcon("list-remove"), tr("Remove unused categories"));
menu.addSeparator();
QAction *startAct = menu.addAction(GuiIconProvider::instance()->getIcon("media-playback-start"), tr("Resume torrents"));
QAction *pauseAct = menu.addAction(GuiIconProvider::instance()->getIcon("media-playback-pause"), tr("Pause torrents"));
@ -343,10 +337,10 @@ void LabelFiltersList::showMenu(QPoint) @@ -343,10 +337,10 @@ void LabelFiltersList::showMenu(QPoint)
return;
if (act == removeAct) {
removeSelectedLabel();
removeSelectedCategory();
}
else if (act == removeUnusedAct) {
removeUnusedLabels();
removeUnusedCategories();
}
else if (act == deleteTorrentsAct) {
transferList->deleteVisibleTorrents();
@ -359,64 +353,90 @@ void LabelFiltersList::showMenu(QPoint) @@ -359,64 +353,90 @@ void LabelFiltersList::showMenu(QPoint)
}
else if (act == addAct) {
bool ok;
QString label = "";
QString category = "";
bool invalid;
do {
invalid = false;
label = AutoExpandableDialog::getText(this, tr("New Label"), tr("Label:"), QLineEdit::Normal, label, &ok);
if (ok && !label.isEmpty()) {
if (Utils::Fs::isValidFileSystemName(label)) {
addItem(label, false);
category = AutoExpandableDialog::getText(this, tr("New Category"), tr("Category:"), QLineEdit::Normal, category, &ok);
if (ok && !category.isEmpty()) {
if (!BitTorrent::Session::isValidCategoryName(category)) {
QMessageBox::warning(this, tr("Invalid category name"),
tr("Category name must not contain '\\'.\n"
"Category name must not start/end with '/'.\n"
"Category name must not contain '//' sequence."));
invalid = true;
}
else {
QMessageBox::warning(this, tr("Invalid label name"), tr("Please don't use any special characters in the label name."));
invalid = true;
BitTorrent::Session::instance()->addCategory(category);
}
}
} while (invalid);
}
}
void LabelFiltersList::applyFilter(int row)
void CategoryFiltersList::applyFilter(int row)
{
transferList->applyLabelFilter(labelFromRow(row));
if (row >= 0)
transferList->applyCategoryFilter(categoryFromRow(row));
}
void LabelFiltersList::handleNewTorrent(BitTorrent::TorrentHandle *const torrent)
void CategoryFiltersList::handleNewTorrent(BitTorrent::TorrentHandle *const torrent)
{
Q_ASSERT(torrent);
++m_totalTorrents;
QString label = torrent->label();
addItem(label, true);
// FIXME: Drop this confusion.
// labelFilters->addItem() may have changed the label, update the model accordingly.
torrent->setLabel(label);
if (!torrent->category().isEmpty())
++m_totalCategorized;
item(0)->setText(tr("All (%1)", "this is for the category filter").arg(m_totalTorrents));
item(1)->setText(tr("Uncategorized (%1)").arg(m_totalTorrents - m_totalCategorized));
if (BitTorrent::Session::instance()->isSubcategoriesEnabled()) {
foreach (const QString &subcategory, BitTorrent::Session::expandCategory(torrent->category()))
addItem(subcategory, true);
}
else {
addItem(torrent->category(), true);
}
}
void LabelFiltersList::torrentAboutToBeDeleted(BitTorrent::TorrentHandle *const torrent)
void CategoryFiltersList::torrentAboutToBeDeleted(BitTorrent::TorrentHandle *const torrent)
{
Q_ASSERT(torrent);
--m_totalTorrents;
removeItem(torrent->label());
if (!torrent->category().isEmpty())
--m_totalCategorized;
item(0)->setText(tr("All (%1)", "this is for the category filter").arg(m_totalTorrents));
item(1)->setText(tr("Uncategorized (%1)").arg(m_totalTorrents - m_totalCategorized));
if (BitTorrent::Session::instance()->isSubcategoriesEnabled()) {
foreach (const QString &subcategory, BitTorrent::Session::expandCategory(torrent->category()))
removeItem(subcategory);
}
else {
removeItem(torrent->category());
}
}
QString LabelFiltersList::labelFromRow(int row) const
QString CategoryFiltersList::categoryFromRow(int row) const
{
if (row == 0) return QString(); // All
if (row == 1) return QLatin1String(""); // Unlabeled
if (row == 1) return QLatin1String(""); // Uncategorized
const QString &label = item(row)->text();
QStringList parts = label.split(" ");
const QString &category = item(row)->text();
QStringList parts = category.split(" ");
Q_ASSERT(parts.size() >= 2);
parts.removeLast(); // Remove trailing number
return parts.join(" ");
}
int LabelFiltersList::rowFromLabel(const QString &label) const
int CategoryFiltersList::rowFromCategory(const QString &category) const
{
Q_ASSERT(!label.isEmpty());
Q_ASSERT(!category.isEmpty());
for (int i = 2; i<count(); ++i)
if (label == labelFromRow(i)) return i;
if (category == categoryFromRow(i)) return i;
return -1;
}
@ -425,7 +445,7 @@ TrackerFiltersList::TrackerFiltersList(QWidget *parent, TransferListWidget *tran @@ -425,7 +445,7 @@ TrackerFiltersList::TrackerFiltersList(QWidget *parent, TransferListWidget *tran
, m_totalTorrents(0)
{
QListWidgetItem *allTrackers = new QListWidgetItem(this);
allTrackers->setData(Qt::DisplayRole, QVariant(tr("All (0)", "this is for the label filter")));
allTrackers->setData(Qt::DisplayRole, QVariant(tr("All (0)", "this is for the tracker filter")));
allTrackers->setData(Qt::DecorationRole, GuiIconProvider::instance()->getIcon("network-server"));
QListWidgetItem *noTracker = new QListWidgetItem(this);
noTracker->setData(Qt::DisplayRole, QVariant(tr("Trackerless (0)")));
@ -799,13 +819,13 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi @@ -799,13 +819,13 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi
StatusFiltersWidget *statusFilters = new StatusFiltersWidget(this, transferList);
frameLayout->addWidget(statusFilters);
QCheckBox *labelLabel = new QCheckBox(tr("Labels"), this);
labelLabel->setChecked(pref->getLabelFilterState());
labelLabel->setFont(font);
frameLayout->addWidget(labelLabel);
QCheckBox *categoryLabel = new QCheckBox(tr("Categories"), this);
categoryLabel->setChecked(pref->getCategoryFilterState());
categoryLabel->setFont(font);
frameLayout->addWidget(categoryLabel);
LabelFiltersList *labelFilters = new LabelFiltersList(this, transferList);
frameLayout->addWidget(labelFilters);
CategoryFiltersList *categoryFilters = new CategoryFiltersList(this, transferList);
frameLayout->addWidget(categoryFilters);
QCheckBox *trackerLabel = new QCheckBox(tr("Trackers"), this);
trackerLabel->setChecked(pref->getTrackerFilterState());
@ -817,9 +837,8 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi @@ -817,9 +837,8 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi
connect(statusLabel, SIGNAL(toggled(bool)), statusFilters, SLOT(toggleFilter(bool)));
connect(statusLabel, SIGNAL(toggled(bool)), pref, SLOT(setStatusFilterState(const bool)));
connect(labelLabel, SIGNAL(toggled(bool)), labelFilters, SLOT(toggleFilter(bool)));
connect(labelLabel, SIGNAL(toggled(bool)), pref, SLOT(setLabelFilterState(const bool)));
connect(pref, SIGNAL(externalLabelAdded(QString&)), labelFilters, SLOT(addItem(QString&)));
connect(categoryLabel, SIGNAL(toggled(bool)), categoryFilters, SLOT(toggleFilter(bool)));
connect(categoryLabel, SIGNAL(toggled(bool)), pref, SLOT(setCategoryFilterState(const bool)));
connect(trackerLabel, SIGNAL(toggled(bool)), trackerFilters, SLOT(toggleFilter(bool)));
connect(trackerLabel, SIGNAL(toggled(bool)), pref, SLOT(setTrackerFilterState(const bool)));
connect(this, SIGNAL(trackerSuccess(const QString &, const QString &)), trackerFilters, SLOT(trackerSuccess(const QString &, const QString &)));

27
src/gui/transferlistfilterswidget.h

@ -90,22 +90,22 @@ private: @@ -90,22 +90,22 @@ private:
virtual void torrentAboutToBeDeleted(BitTorrent::TorrentHandle *const);
};
class LabelFiltersList: public FiltersBase
class CategoryFiltersList: public FiltersBase
{
Q_OBJECT
public:
LabelFiltersList(QWidget *parent, TransferListWidget *transferList);
~LabelFiltersList();
CategoryFiltersList(QWidget *parent, TransferListWidget *transferList);
private slots:
// Redefine addItem() to make sure the list stays sorted
void addItem(QString &label, bool hasTorrent = false);
void removeItem(const QString &label);
void removeSelectedLabel();
void removeUnusedLabels();
void torrentChangedLabel(BitTorrent::TorrentHandle *const torrent, const QString &oldLabel);
void addItem(const QString &category, bool hasTorrent = false);
void removeItem(const QString &category);
void removeSelectedCategory();
void removeUnusedCategories();
void torrentCategoryChanged(BitTorrent::TorrentHandle *const torrent, const QString &oldCategory);
void categoryRemoved(const QString &category);
void subcategoriesSupportChanged();
private:
// These 4 methods are virtual slots in the base class.
@ -114,13 +114,14 @@ private: @@ -114,13 +114,14 @@ private:
virtual void applyFilter(int row);
virtual void handleNewTorrent(BitTorrent::TorrentHandle *const torrent);
virtual void torrentAboutToBeDeleted(BitTorrent::TorrentHandle *const torrent);
QString labelFromRow(int row) const;
int rowFromLabel(const QString &label) const;
QString categoryFromRow(int row) const;
int rowFromCategory(const QString &category) const;
void refresh();
private:
QHash<QString, int> m_labels;
QHash<QString, int> m_categories;
int m_totalTorrents;
int m_totalLabeled;
int m_totalCategorized;
};
class TrackerFiltersList: public FiltersBase

8
src/gui/transferlistsortmodel.cpp

@ -47,15 +47,15 @@ void TransferListSortModel::setStatusFilter(TorrentFilter::Type filter) @@ -47,15 +47,15 @@ void TransferListSortModel::setStatusFilter(TorrentFilter::Type filter)
invalidateFilter();
}
void TransferListSortModel::setLabelFilter(const QString &label)
void TransferListSortModel::setCategoryFilter(const QString &category)
{
if (m_filter.setLabel(label))
if (m_filter.setCategory(category))
invalidateFilter();
}
void TransferListSortModel::disableLabelFilter()
void TransferListSortModel::disableCategoryFilter()
{
if (m_filter.setLabel(TorrentFilter::AnyLabel))
if (m_filter.setCategory(TorrentFilter::AnyCategory))
invalidateFilter();
}

4
src/gui/transferlistsortmodel.h

@ -44,8 +44,8 @@ public: @@ -44,8 +44,8 @@ public:
TransferListSortModel(QObject *parent = 0);
void setStatusFilter(TorrentFilter::Type filter);
void setLabelFilter(const QString &label);
void disableLabelFilter();
void setCategoryFilter(const QString &category);
void disableCategoryFilter();
void setTrackerFilter(const QStringList &hashes);
void disableTrackerFilter();

128
src/gui/transferlistwidget.cpp

@ -60,6 +60,7 @@ @@ -60,6 +60,7 @@
#include "propertieswidget.h"
#include "guiiconprovider.h"
#include "base/utils/fs.h"
#include "base/utils/string.h"
#include "autoexpandabledialog.h"
#include "transferlistsortmodel.h"
@ -565,21 +566,30 @@ void TransferListWidget::toggleSelectedFirstLastPiecePrio() const @@ -565,21 +566,30 @@ void TransferListWidget::toggleSelectedFirstLastPiecePrio() const
torrent->toggleFirstLastPiecePriority();
}
void TransferListWidget::askNewLabelForSelection()
void TransferListWidget::setSelectedASMEnabled(bool enabled) const
{
// Ask for label
foreach (BitTorrent::TorrentHandle *const torrent, getSelectedTorrents())
torrent->setASMEnabled(enabled);
}
void TransferListWidget::askNewCategoryForSelection()
{
// Ask for category
bool ok;
bool invalid;
do {
invalid = false;
const QString label = AutoExpandableDialog::getText(this, tr("New Label"), tr("Label:"), QLineEdit::Normal, "", &ok).trimmed();
if (ok && !label.isEmpty()) {
if (Utils::Fs::isValidFileSystemName(label)) {
setSelectionLabel(label);
const QString category = AutoExpandableDialog::getText(this, tr("New Category"), tr("Category:"), QLineEdit::Normal, "", &ok).trimmed();
if (ok && !category.isEmpty()) {
if (!BitTorrent::Session::isValidCategoryName(category)) {
QMessageBox::warning(this, tr("Invalid category name"),
tr("Category name must not contain '\\'.\n"
"Category name must not start/end with '/'.\n"
"Category name must not contain '//' sequence."));
invalid = true;
}
else {
QMessageBox::warning(this, tr("Invalid label name"), tr("Please don't use any special characters in the label name."));
invalid = true;
setSelectionCategory(category);
}
}
} while(invalid);
@ -605,19 +615,10 @@ void TransferListWidget::renameSelectedTorrent() @@ -605,19 +615,10 @@ void TransferListWidget::renameSelectedTorrent()
}
}
void TransferListWidget::setSelectionLabel(QString label)
void TransferListWidget::setSelectionCategory(QString category)
{
foreach (const QModelIndex &index, selectionModel()->selectedRows())
listModel->setData(listModel->index(mapToSource(index).row(), TorrentModel::TR_LABEL), label, Qt::DisplayRole);
}
void TransferListWidget::removeLabelFromRows(QString label)
{
for (int i = 0; i < listModel->rowCount(); ++i) {
if (listModel->data(listModel->index(i, TorrentModel::TR_LABEL)) == label) {
listModel->setData(listModel->index(i, TorrentModel::TR_LABEL), "", Qt::DisplayRole);
}
}
listModel->setData(listModel->index(mapToSource(index).row(), TorrentModel::TR_CATEGORY), category, Qt::DisplayRole);
}
void TransferListWidget::displayListMenu(const QPoint&)
@ -671,6 +672,9 @@ void TransferListWidget::displayListMenu(const QPoint&) @@ -671,6 +672,9 @@ void TransferListWidget::displayListMenu(const QPoint&)
QAction actionFirstLastPiece_prio(tr("Download first and last pieces first"), 0);
actionFirstLastPiece_prio.setCheckable(true);
connect(&actionFirstLastPiece_prio, SIGNAL(triggered()), this, SLOT(toggleSelectedFirstLastPiecePrio()));
QAction actionEnableASM(tr("Enable Advanced Saving Management"), 0);
actionEnableASM.setCheckable(true);
connect(&actionEnableASM, SIGNAL(triggered(bool)), this, SLOT(setSelectedASMEnabled(bool)));
// End of actions
// Enable/disable pause/start action given the DL state
@ -680,8 +684,10 @@ void TransferListWidget::displayListMenu(const QPoint&) @@ -680,8 +684,10 @@ void TransferListWidget::displayListMenu(const QPoint&)
bool all_same_sequential_download_mode = true, all_same_prio_firstlast = true;
bool sequential_download_mode = false, prioritize_first_last = false;
bool one_has_metadata = false, one_not_seed = false;
bool all_same_label = true;
QString first_label;
bool allSameCategory = true;
bool allSameASM = true;
bool firstASM = false;
QString firstCategory;
bool first = true;
BitTorrent::TorrentHandle *torrent;
@ -692,10 +698,15 @@ void TransferListWidget::displayListMenu(const QPoint&) @@ -692,10 +698,15 @@ void TransferListWidget::displayListMenu(const QPoint&)
torrent = listModel->torrentHandle(mapToSource(index));
if (!torrent) continue;
if (first_label.isEmpty() && first)
first_label = torrent->label();
if (firstCategory.isEmpty() && first)
firstCategory = torrent->category();
if (firstCategory != torrent->category())
allSameCategory = false;
all_same_label = (first_label == torrent->label());
if (first)
firstASM = torrent->isASMEnabled();
if (firstASM != torrent->isASMEnabled())
allSameASM = false;
if (torrent->hasMetadata())
one_has_metadata = true;
@ -738,8 +749,8 @@ void TransferListWidget::displayListMenu(const QPoint&) @@ -738,8 +749,8 @@ void TransferListWidget::displayListMenu(const QPoint&)
first = false;
if (one_has_metadata && one_not_seed && !all_same_sequential_download_mode
&& !all_same_prio_firstlast && !all_same_super_seeding && !all_same_label
&& needs_start && needs_force && needs_pause && needs_preview) {
&& !all_same_prio_firstlast && !all_same_super_seeding && !allSameCategory
&& needs_start && needs_force && needs_pause && needs_preview && !allSameASM) {
break;
}
}
@ -756,24 +767,30 @@ void TransferListWidget::displayListMenu(const QPoint&) @@ -756,24 +767,30 @@ void TransferListWidget::displayListMenu(const QPoint&)
listMenu.addAction(&actionSetTorrentPath);
if (selectedIndexes.size() == 1)
listMenu.addAction(&actionRename);
// Label Menu
QStringList customLabels = Preferences::instance()->getTorrentLabels();
customLabels.sort();
QList<QAction*> labelActions;
QMenu *labelMenu = listMenu.addMenu(GuiIconProvider::instance()->getIcon("view-categories"), tr("Label"));
labelActions << labelMenu->addAction(GuiIconProvider::instance()->getIcon("list-add"), tr("New...", "New label..."));
labelActions << labelMenu->addAction(GuiIconProvider::instance()->getIcon("edit-clear"), tr("Reset", "Reset label"));
labelMenu->addSeparator();
foreach (QString label, customLabels) {
label.replace('&', "&&"); // avoid '&' becomes accelerator key
QAction *lb = new QAction(GuiIconProvider::instance()->getIcon("inode-directory"), label, labelMenu);
if (all_same_label && (label == first_label)) {
lb->setCheckable(true);
lb->setChecked(true);
// Category Menu
QStringList categories = BitTorrent::Session::instance()->categories();
std::sort(categories.begin(), categories.end(), Utils::String::NaturalCompare());
QList<QAction*> categoryActions;
QMenu *categoryMenu = listMenu.addMenu(GuiIconProvider::instance()->getIcon("view-categories"), tr("Category"));
categoryActions << categoryMenu->addAction(GuiIconProvider::instance()->getIcon("list-add"), tr("New...", "New category..."));
categoryActions << categoryMenu->addAction(GuiIconProvider::instance()->getIcon("edit-clear"), tr("Reset", "Reset category"));
categoryMenu->addSeparator();
foreach (QString category, categories) {
category.replace('&', "&&"); // avoid '&' becomes accelerator key
QAction *cat = new QAction(GuiIconProvider::instance()->getIcon("inode-directory"), category, categoryMenu);
if (allSameCategory && (category == firstCategory)) {
cat->setCheckable(true);
cat->setChecked(true);
}
labelMenu->addAction(lb);
labelActions << lb;
categoryMenu->addAction(cat);
categoryActions << cat;
}
if (allSameASM) {
actionEnableASM.setChecked(firstASM);
listMenu.addAction(&actionEnableASM);
}
listMenu.addSeparator();
if (one_not_seed)
listMenu.addAction(&actionSet_download_limit);
@ -801,6 +818,7 @@ void TransferListWidget::displayListMenu(const QPoint&) @@ -801,6 +818,7 @@ void TransferListWidget::displayListMenu(const QPoint&)
added_preview_action = true;
}
}
if (added_preview_action)
listMenu.addSeparator();
if (one_has_metadata) {
@ -823,20 +841,20 @@ void TransferListWidget::displayListMenu(const QPoint&) @@ -823,20 +841,20 @@ void TransferListWidget::displayListMenu(const QPoint&)
QAction *act = 0;
act = listMenu.exec(QCursor::pos());
if (act) {
// Parse label actions only (others have slots assigned)
int i = labelActions.indexOf(act);
// Parse category actions only (others have slots assigned)
int i = categoryActions.indexOf(act);
if (i >= 0) {
// Label action
// Category action
if (i == 0) {
// New Label
askNewLabelForSelection();
// New Category
askNewCategoryForSelection();
}
else {
QString label = "";
QString category = "";
if (i > 1)
label = customLabels.at(i - 2);
// Update Label
setSelectionLabel(label);
category = categories.at(i - 2);
// Update Category
setSelectionCategory(category);
}
}
}
@ -854,12 +872,12 @@ void TransferListWidget::currentChanged(const QModelIndex& current, const QModel @@ -854,12 +872,12 @@ void TransferListWidget::currentChanged(const QModelIndex& current, const QModel
emit currentTorrentChanged(torrent);
}
void TransferListWidget::applyLabelFilter(QString label)
void TransferListWidget::applyCategoryFilter(QString category)
{
if (label.isNull())
nameFilterModel->disableLabelFilter();
if (category.isNull())
nameFilterModel->disableCategoryFilter();
else
nameFilterModel->setLabelFilter(label);
nameFilterModel->setCategoryFilter(category);
}
void TransferListWidget::applyTrackerFilterAll()

8
src/gui/transferlistwidget.h

@ -59,7 +59,7 @@ public: @@ -59,7 +59,7 @@ public:
TorrentModel* getSourceModel() const;
public slots:
void setSelectionLabel(QString label);
void setSelectionCategory(QString category);
void setSelectedTorrentsLocation();
void pauseAllTorrents();
void resumeAllTorrents();
@ -86,11 +86,10 @@ public slots: @@ -86,11 +86,10 @@ public slots:
void displayDLHoSMenu(const QPoint&);
void applyNameFilter(const QString& name);
void applyStatusFilter(int f);
void applyLabelFilter(QString label);
void applyCategoryFilter(QString category);
void applyTrackerFilterAll();
void applyTrackerFilter(const QStringList &hashes);
void previewFile(QString filePath);
void removeLabelFromRows(QString label);
void renameSelectedTorrent();
protected:
@ -106,7 +105,8 @@ protected slots: @@ -106,7 +105,8 @@ protected slots:
void toggleSelectedTorrentsSuperSeeding() const;
void toggleSelectedTorrentsSequentialDownload() const;
void toggleSelectedFirstLastPiecePrio() const;
void askNewLabelForSelection();
void setSelectedASMEnabled(bool enabled) const;
void askNewCategoryForSelection();
void saveSettings();
signals:

2
src/webui/abstractwebapplication.cpp

@ -243,7 +243,7 @@ void AbstractWebApplication::translateDocument(QString& data) @@ -243,7 +243,7 @@ void AbstractWebApplication::translateDocument(QString& data)
"options_imp", "Preferences", "TrackersAdditionDlg", "ScanFoldersModel",
"PropTabBar", "TorrentModel", "downloadFromURL", "MainWindow", "misc",
"StatusBar", "AboutDlg", "about", "PeerListWidget", "StatusFiltersWidget",
"LabelFiltersList"
"CategoryFiltersList"
};
const size_t context_count = sizeof(contexts) / sizeof(contexts[0]);
int i = 0;

23
src/webui/btjson.cpp

@ -102,7 +102,7 @@ static const char KEY_TORRENT_ETA[] = "eta"; @@ -102,7 +102,7 @@ static const char KEY_TORRENT_ETA[] = "eta";
static const char KEY_TORRENT_STATE[] = "state";
static const char KEY_TORRENT_SEQUENTIAL_DOWNLOAD[] = "seq_dl";
static const char KEY_TORRENT_FIRST_LAST_PIECE_PRIO[] = "f_l_piece_prio";
static const char KEY_TORRENT_LABEL[] = "label";
static const char KEY_TORRENT_CATEGORY[] = "category";
static const char KEY_TORRENT_SUPER_SEEDING[] = "super_seeding";
static const char KEY_TORRENT_FORCE_START[] = "force_start";
static const char KEY_TORRENT_SAVE_PATH[] = "save_path";
@ -279,13 +279,13 @@ private: @@ -279,13 +279,13 @@ private:
* - "seq_dl": Torrent sequential download state
* - "f_l_piece_prio": Torrent first last piece priority state
* - "force_start": Torrent force start state
* - "label": Torrent label
* - "category": Torrent category
*/
QByteArray btjson::getTorrents(QString filter, QString label,
QByteArray btjson::getTorrents(QString filter, QString category,
QString sortedColumn, bool reverse, int limit, int offset)
{
QVariantList torrentList;
TorrentFilter torrentFilter(filter, TorrentFilter::AnyHash, label);
TorrentFilter torrentFilter(filter, TorrentFilter::AnyHash, category);
foreach (BitTorrent::TorrentHandle *const torrent, BitTorrent::Session::instance()->torrents()) {
if (torrentFilter.match(torrent))
torrentList.append(toMap(torrent));
@ -317,8 +317,8 @@ QByteArray btjson::getTorrents(QString filter, QString label, @@ -317,8 +317,8 @@ QByteArray btjson::getTorrents(QString filter, QString label,
* - "full_update": full data update flag
* - "torrents": dictionary contains information about torrents.
* - "torrents_removed": a list of hashes of removed torrents
* - "labels": list of labels
* - "labels_removed": list of removed labels
* - "categories": list of categories
* - "categories_removed": list of removed categories
* - "server_state": map contains information about the state of the server
* The keys of the 'torrents' dictionary are hashes of torrents.
* Each value of the 'torrents' dictionary contains map. The map can contain following keys:
@ -362,11 +362,11 @@ QByteArray btjson::getSyncMainData(int acceptedResponseId, QVariantMap &lastData @@ -362,11 +362,11 @@ QByteArray btjson::getSyncMainData(int acceptedResponseId, QVariantMap &lastData
data["torrents"] = torrents;
QVariantList labels;
foreach (QString s, Preferences::instance()->getTorrentLabels())
labels << s;
QVariantList categories;
foreach (const QString &category, BitTorrent::Session::instance()->categories())
categories << category;
data["labels"] = labels;
data["categories"] = categories;
QVariantMap serverState = getTranserInfoMap();
serverState[KEY_SYNC_MAINDATA_QUEUEING] = BitTorrent::Session::instance()->isQueueingEnabled();
@ -705,7 +705,7 @@ QVariantMap toMap(BitTorrent::TorrentHandle *const torrent) @@ -705,7 +705,7 @@ QVariantMap toMap(BitTorrent::TorrentHandle *const torrent)
ret[KEY_TORRENT_SEQUENTIAL_DOWNLOAD] = torrent->isSequentialDownload();
if (torrent->hasMetadata())
ret[KEY_TORRENT_FIRST_LAST_PIECE_PRIO] = torrent->hasFirstLastPiecePriority();
ret[KEY_TORRENT_LABEL] = torrent->label();
ret[KEY_TORRENT_CATEGORY] = torrent->category();
ret[KEY_TORRENT_SUPER_SEEDING] = torrent->superSeeding();
ret[KEY_TORRENT_FORCE_START] = torrent->isForced();
ret[KEY_TORRENT_SAVE_PATH] = Utils::Fs::toNativePath(torrent->savePath());
@ -760,6 +760,7 @@ void processMap(QVariantMap prevData, QVariantMap data, QVariantMap &syncData) @@ -760,6 +760,7 @@ void processMap(QVariantMap prevData, QVariantMap data, QVariantMap &syncData)
case QMetaType::Double:
case QMetaType::ULongLong:
case QMetaType::UInt:
case QMetaType::QDateTime:
if (prevData[key] != data[key])
syncData[key] = data[key];
break;

2
src/webui/btjson.h

@ -43,7 +43,7 @@ private: @@ -43,7 +43,7 @@ private:
btjson() {}
public:
static QByteArray getTorrents(QString filter = "all", QString label = QString(),
static QByteArray getTorrents(QString filter = "all", QString category = QString(),
QString sortedColumn = "name", bool reverse = false, int limit = 0, int offset = 0);
static QByteArray getSyncMainData(int acceptedResponseId, QVariantMap &lastData, QVariantMap &lastAcceptedData);
static QByteArray getSyncTorrentPeersData(int acceptedResponseId, QString hash, QVariantMap &lastData, QVariantMap &lastAcceptedData);

2
src/webui/extra_translations.h

@ -84,7 +84,7 @@ static const char *__TRANSLATIONS__[] = { @@ -84,7 +84,7 @@ static const char *__TRANSLATIONS__[] = {
QT_TRANSLATE_NOOP("HttpServer", "Active"),
QT_TRANSLATE_NOOP("HttpServer", "Inactive"),
QT_TRANSLATE_NOOP("HttpServer", "Save files to location:"),
QT_TRANSLATE_NOOP("HttpServer", "Label:"),
QT_TRANSLATE_NOOP("HttpServer", "Category:"),
QT_TRANSLATE_NOOP("HttpServer", "Cookie:"),
QT_TRANSLATE_NOOP("HttpServer", "Type folder here"),
QT_TRANSLATE_NOOP("HttpServer", "Run an external program on torrent completion"),

30
src/webui/prefjson.cpp

@ -28,11 +28,6 @@ @@ -28,11 +28,6 @@
* Contact : chris@qbittorrent.org
*/
#include "prefjson.h"
#include "base/preferences.h"
#include "base/scanfoldersmodel.h"
#include "base/utils/fs.h"
#ifndef QT_NO_OPENSSL
#include <QSslCertificate>
#include <QSslKey>
@ -40,7 +35,13 @@ @@ -40,7 +35,13 @@
#include <QStringList>
#include <QTranslator>
#include <QCoreApplication>
#include "base/preferences.h"
#include "base/scanfoldersmodel.h"
#include "base/utils/fs.h"
#include "base/bittorrent/session.h"
#include "jsonutils.h"
#include "prefjson.h"
prefjson::prefjson()
{
@ -49,13 +50,14 @@ prefjson::prefjson() @@ -49,13 +50,14 @@ prefjson::prefjson()
QByteArray prefjson::getPreferences()
{
const Preferences* const pref = Preferences::instance();
auto session = BitTorrent::Session::instance();
QVariantMap data;
// Downloads
// Hard Disk
data["save_path"] = Utils::Fs::toNativePath(pref->getSavePath());
data["temp_path_enabled"] = pref->isTempPathEnabled();
data["temp_path"] = Utils::Fs::toNativePath(pref->getTempPath());
data["save_path"] = Utils::Fs::toNativePath(session->defaultSavePath());
data["temp_path_enabled"] = session->isTempPathEnabled();
data["temp_path"] = Utils::Fs::toNativePath(session->tempPath());
data["preallocate_all"] = pref->preAllocateAllFiles();
data["incomplete_files_ext"] = pref->useIncompleteFilesExtension();
QVariantHash dirs = pref->getScanDirs();
@ -140,7 +142,7 @@ QByteArray prefjson::getPreferences() @@ -140,7 +142,7 @@ QByteArray prefjson::getPreferences()
// Share Ratio Limiting
data["max_ratio_enabled"] = (pref->getGlobalMaxRatio() >= 0.);
data["max_ratio"] = pref->getGlobalMaxRatio();
data["max_ratio_act"] = static_cast<int>(pref->getMaxRatioAction());
data["max_ratio_act"] = BitTorrent::Session::instance()->maxRatioAction();
// Web UI
// Language
@ -168,16 +170,17 @@ QByteArray prefjson::getPreferences() @@ -168,16 +170,17 @@ QByteArray prefjson::getPreferences()
void prefjson::setPreferences(const QString& json)
{
Preferences* const pref = Preferences::instance();
auto session = BitTorrent::Session::instance();
const QVariantMap m = json::fromJson(json).toMap();
// Downloads
// Hard Disk
if (m.contains("save_path"))
pref->setSavePath(m["save_path"].toString());
session->setDefaultSavePath(m["save_path"].toString());
if (m.contains("temp_path_enabled"))
pref->setTempPathEnabled(m["temp_path_enabled"].toBool());
session->setTempPathEnabled(m["temp_path_enabled"].toBool());
if (m.contains("temp_path"))
pref->setTempPath(m["temp_path"].toString());
session->setTempPath(m["temp_path"].toString());
if (m.contains("preallocate_all"))
pref->preAllocateAllFiles(m["preallocate_all"].toBool());
if (m.contains("incomplete_files_ext"))
@ -351,7 +354,8 @@ void prefjson::setPreferences(const QString& json) @@ -351,7 +354,8 @@ void prefjson::setPreferences(const QString& json)
else
pref->setGlobalMaxRatio(-1);
if (m.contains("max_ratio_act"))
pref->setMaxRatioAction(static_cast<MaxRatioAction>(m["max_ratio_act"].toInt()));
BitTorrent::Session::instance()->setMaxRatioAction(
static_cast<MaxRatioAction>(m["max_ratio_act"].toInt()));
// Web UI
// Language

42
src/webui/webapplication.cpp

@ -49,8 +49,8 @@ @@ -49,8 +49,8 @@
#include "websessiondata.h"
#include "webapplication.h"
static const int API_VERSION = 8;
static const int API_VERSION_MIN = 8;
static const int API_VERSION = 9;
static const int API_VERSION_MIN = 9;
const QString WWW_FOLDER = ":/www/public/";
const QString PRIVATE_FOLDER = ":/www/private/";
@ -114,7 +114,7 @@ QMap<QString, QMap<QString, WebApplication::Action> > WebApplication::initialize @@ -114,7 +114,7 @@ QMap<QString, QMap<QString, WebApplication::Action> > WebApplication::initialize
ADD_ACTION(command, topPrio);
ADD_ACTION(command, bottomPrio);
ADD_ACTION(command, recheck);
ADD_ACTION(command, setLabel);
ADD_ACTION(command, setCategory);
ADD_ACTION(command, getSavePath);
ADD_ACTION(version, api);
ADD_ACTION(version, api_min);
@ -216,7 +216,7 @@ void WebApplication::action_public_images() @@ -216,7 +216,7 @@ void WebApplication::action_public_images()
// GET params:
// - filter (string): all, downloading, seeding, completed, paused, resumed, active, inactive
// - label (string): torrent label for filtering by it (empty string means "unlabeled"; no "label" param presented means "any label")
// - category (string): torrent category for filtering by it (empty string means "uncategorized"; no "category" param presented means "any category")
// - sort (string): name of column for sorting by its value
// - reverse (bool): enable reverse sorting
// - limit (int): set limit number of torrents returned (if greater than 0, otherwise - unlimited)
@ -227,7 +227,7 @@ void WebApplication::action_query_torrents() @@ -227,7 +227,7 @@ void WebApplication::action_query_torrents()
const QStringMap& gets = request().gets;
print(btjson::getTorrents(
gets["filter"], gets["label"], gets["sort"], gets["reverse"] == "true",
gets["filter"], gets["category"], gets["sort"], gets["reverse"] == "true",
gets["limit"].toInt(), gets["offset"].toInt()
), Http::CONTENT_TYPE_JSON);
}
@ -326,7 +326,7 @@ void WebApplication::action_command_download() @@ -326,7 +326,7 @@ void WebApplication::action_command_download()
QString urls = request().posts["urls"];
QStringList list = urls.split('\n');
QString savepath = request().posts["savepath"];
QString label = request().posts["label"];
QString category = request().posts["category"];
QString cookie = request().posts["cookie"];
QList<QNetworkCookie> cookies;
if (!cookie.isEmpty()) {
@ -345,11 +345,11 @@ void WebApplication::action_command_download() @@ -345,11 +345,11 @@ void WebApplication::action_command_download()
}
savepath = savepath.trimmed();
label = label.trimmed();
category = category.trimmed();
BitTorrent::AddTorrentParams params;
params.savePath = savepath;
params.label = label;
params.category = category;
foreach (QString url, list) {
url = url.trimmed();
@ -365,10 +365,10 @@ void WebApplication::action_command_upload() @@ -365,10 +365,10 @@ void WebApplication::action_command_upload()
qDebug() << Q_FUNC_INFO;
CHECK_URI(0);
QString savepath = request().posts["savepath"];
QString label = request().posts["label"];
QString category = request().posts["category"];
savepath = savepath.trimmed();
label = label.trimmed();
category = category.trimmed();
foreach(const Http::UploadedFile& torrent, request().files) {
QString filePath = saveTmpFile(torrent.data);
@ -382,7 +382,7 @@ void WebApplication::action_command_upload() @@ -382,7 +382,7 @@ void WebApplication::action_command_upload()
else {
BitTorrent::AddTorrentParams params;
params.savePath = savepath;
params.label = label;
params.category = category;
if (!BitTorrent::Session::instance()->addTorrent(torrentInfo, params)) {
status(500, "Internal Server Error");
print(QObject::tr("Error: Could not add torrent to session."), Http::CONTENT_TYPE_TXT);
@ -709,29 +709,29 @@ void WebApplication::action_command_recheck() @@ -709,29 +709,29 @@ void WebApplication::action_command_recheck()
torrent->forceRecheck();
}
void WebApplication::action_command_setLabel()
void WebApplication::action_command_setCategory()
{
CHECK_URI(0);
CHECK_PARAMETERS("hashes" << "label");
CHECK_PARAMETERS("hashes" << "category");
QStringList hashes = request().posts["hashes"].split("|");
QString label = request().posts["label"].trimmed();
if (!Utils::Fs::isValidFileSystemName(label) && !label.isEmpty()) {
status(400, "Labels must not contain special characters");
return;
}
QString category = request().posts["category"].trimmed();
foreach (const QString &hash, hashes) {
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (torrent)
torrent->setLabel(label);
if (torrent) {
if (!torrent->setCategory(category)) {
status(400, "Incorrect category name");
return;
}
}
}
}
void WebApplication::action_command_getSavePath()
{
CHECK_URI(0);
print(Preferences::instance()->getSavePath());
print(BitTorrent::Session::instance()->defaultSavePath());
}
bool WebApplication::isPublicScope()

2
src/webui/webapplication.h

@ -87,7 +87,7 @@ private: @@ -87,7 +87,7 @@ private:
void action_command_topPrio();
void action_command_bottomPrio();
void action_command_recheck();
void action_command_setLabel();
void action_command_setCategory();
void action_command_getSavePath();
void action_version_api();
void action_version_api_min();

82
src/webui/webui.qrc

@ -1,43 +1,43 @@ @@ -1,43 +1,43 @@
<RCC>
<qresource prefix="/">
<file>www/private/index.html</file>
<file>www/private/login.html</file>
<file>www/public/css/Core.css</file>
<file>www/public/css/dynamicTable.css</file>
<file>www/public/css/Layout.css</file>
<file>www/public/css/style.css</file>
<file>www/public/css/Tabs.css</file>
<file>www/public/css/Window.css</file>
<file>www/public/scripts/client.js</file>
<file>www/public/scripts/contextmenu.js</file>
<file>www/public/scripts/download.js</file>
<file>www/public/scripts/dynamicTable.js</file>
<file>www/public/scripts/excanvas-compressed.js</file>
<file>www/public/scripts/misc.js</file>
<file>www/public/scripts/mocha.js</file>
<file>www/public/scripts/mocha-init.js</file>
<file>www/public/scripts/mocha-yc.js</file>
<file>www/public/scripts/mootools-1.2-core-yc.js</file>
<file>www/public/scripts/mootools-1.2-more.js</file>
<file>www/public/scripts/parametrics.js</file>
<file>www/public/scripts/progressbar.js</file>
<file>www/public/about.html</file>
<file>www/public/addtrackers.html</file>
<file>www/public/confirmdeletion.html</file>
<file>www/public/download.html</file>
<file>www/public/downloadlimit.html</file>
<file>www/public/filters.html</file>
<file>www/public/newlabel.html</file>
<file>www/public/preferences.html</file>
<file>www/public/preferences_content.html</file>
<file>www/public/properties.html</file>
<file>www/public/properties_content.html</file>
<file>www/public/scripts/prop-general.js</file>
<file>www/public/scripts/prop-trackers.js</file>
<file>www/public/scripts/prop-webseeds.js</file>
<file>www/public/scripts/prop-files.js</file>
<file>www/public/transferlist.html</file>
<file>www/public/upload.html</file>
<file>www/public/uploadlimit.html</file>
</qresource>
<qresource prefix="/">
<file>www/private/index.html</file>
<file>www/private/login.html</file>
<file>www/public/css/Core.css</file>
<file>www/public/css/dynamicTable.css</file>
<file>www/public/css/Layout.css</file>
<file>www/public/css/style.css</file>
<file>www/public/css/Tabs.css</file>
<file>www/public/css/Window.css</file>
<file>www/public/scripts/client.js</file>
<file>www/public/scripts/contextmenu.js</file>
<file>www/public/scripts/download.js</file>
<file>www/public/scripts/dynamicTable.js</file>
<file>www/public/scripts/excanvas-compressed.js</file>
<file>www/public/scripts/misc.js</file>
<file>www/public/scripts/mocha.js</file>
<file>www/public/scripts/mocha-init.js</file>
<file>www/public/scripts/mocha-yc.js</file>
<file>www/public/scripts/mootools-1.2-core-yc.js</file>
<file>www/public/scripts/mootools-1.2-more.js</file>
<file>www/public/scripts/parametrics.js</file>
<file>www/public/scripts/progressbar.js</file>
<file>www/public/about.html</file>
<file>www/public/addtrackers.html</file>
<file>www/public/confirmdeletion.html</file>
<file>www/public/download.html</file>
<file>www/public/downloadlimit.html</file>
<file>www/public/filters.html</file>
<file>www/public/preferences.html</file>
<file>www/public/preferences_content.html</file>
<file>www/public/properties.html</file>
<file>www/public/properties_content.html</file>
<file>www/public/scripts/prop-general.js</file>
<file>www/public/scripts/prop-trackers.js</file>
<file>www/public/scripts/prop-webseeds.js</file>
<file>www/public/scripts/prop-files.js</file>
<file>www/public/transferlist.html</file>
<file>www/public/upload.html</file>
<file>www/public/uploadlimit.html</file>
<file>www/public/newcategory.html</file>
</qresource>
</RCC>

4
src/webui/www/private/index.html

@ -106,8 +106,8 @@ @@ -106,8 +106,8 @@
<li><a href="#ForceStart"><img src="theme/media-seek-forward" alt="QBT_TR(Force Resume)QBT_TR"/> QBT_TR(Force Resume)QBT_TR</a></li>
<li class="separator"><a href="#Delete"><img src="theme/list-remove" alt="QBT_TR(Delete)QBT_TR"/> QBT_TR(Delete)QBT_TR</a></li>
<li class="separator">
<a href="#Label" class="arrow-right"><img src="theme/view-categories" alt="QBT_TR(Label)QBT_TR"/> QBT_TR(Label)QBT_TR</a>
<ul id="contextLabelList"></ul>
<a href="#Category" class="arrow-right"><img src="theme/view-categories" alt="QBT_TR(Category)QBT_TR"/> QBT_TR(Category)QBT_TR</a>
<ul id="contextCategoryList"></ul>
</li>
<li id="queueingMenuItems" class="separator">
<a href="#priority" class="arrow-right"><span style="display: inline-block; width:16px"></span> QBT_TR(Priority)QBT_TR</a>

34
src/webui/www/public/download.html

@ -1,33 +1,33 @@ @@ -1,33 +1,33 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>QBT_TR(Add Torrent Link)QBT_TR</title>
<link rel="stylesheet" href="css/style.css" type="text/css" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>QBT_TR(Add Torrent Link)QBT_TR</title>
<link rel="stylesheet" href="css/style.css" type="text/css" />
<link rel="stylesheet" href="css/Window.css" type="text/css" />
<script type="text/javascript" src="scripts/mootools-1.2-core-yc.js" charset="utf-8"></script>
<script type="text/javascript" src="scripts/download.js" charset="utf-8"></script>
<script type="text/javascript" src="scripts/mootools-1.2-core-yc.js" charset="utf-8"></script>
<script type="text/javascript" src="scripts/download.js" charset="utf-8"></script>
</head>
<body>
<iframe id="download_frame" name="download_frame" class="invisible" src="javascript:false;"></iframe>
<form action="command/download" enctype="multipart/form-data" method="post" id="downloadForm" style="text-align: center;" target="download_frame">
<center>
<br/>
<h2 class="vcenter">QBT_TR(Download Torrents from their URLs or Magnet links)QBT_TR</h2>
<textarea id="urls" rows="10" name="urls"></textarea>
<br/>
<h2 class="vcenter">QBT_TR(Download Torrents from their URLs or Magnet links)QBT_TR</h2>
<textarea id="urls" rows="10" name="urls"></textarea>
<p>QBT_TR(Only one link per line)QBT_TR</p>
<fieldset class="settings" style="border: 0; text-align: left;">
<div class="formRow" style="margin-top: 6px;">
<label for="savepath" class="leftLabelLarge">QBT_TR(Save files to location:)QBT_TR</label>
<input type="text" id="savepath" name="savepath" style="width: 16em;"/>
<label for="savepath" class="leftLabelLarge">QBT_TR(Save files to location:)QBT_TR</label>
<input type="text" id="savepath" name="savepath" style="width: 16em;"/>
</div>
<div class="formRow">
<label for="cookie" class="leftLabelLarge">QBT_TR(Cookie:)QBT_TR</label>
<input type="text" id="cookie" name="cookie" style="width: 16em;"/>
<label for="cookie" class="leftLabelLarge">QBT_TR(Cookie:)QBT_TR</label>
<input type="text" id="cookie" name="cookie" style="width: 16em;"/>
</div>
<div class="formRow">
<label for="label" class="leftLabelLarge">QBT_TR(Label:)QBT_TR</label>
<input type="text" id="label" name="label" style="width: 16em;"/>
<label for="category" class="leftLabelLarge">QBT_TR(Category:)QBT_TR</label>
<input type="text" id="category" name="category" style="width: 16em;"/>
</div>
<div id="submitbutton" style="margin-top: 12px; text-align: center;">
<button type="submit" id="submitButton">QBT_TR(Download)QBT_TR</button>
@ -39,13 +39,13 @@ @@ -39,13 +39,13 @@
var submitted = false;
$('downloadForm').addEventListener("submit", function() {
$('download_spinner').style.display = "block";
$('download_spinner').style.display = "block";
submitted = true;
});
$('download_frame').addEventListener("load", function() {
if (submitted)
window.parent.closeWindows();
if (submitted)
window.parent.closeWindows();
});
</script>
<div id="download_spinner" class="mochaSpinner"></div>

4
src/webui/www/public/filters.html

@ -11,6 +11,6 @@ @@ -11,6 +11,6 @@
<li id="errored_filter"><a href="#" onclick="setFilter('errored');return false;"><img src="images/skin/error.png"/>QBT_TR(Errored (0))QBT_TR</a></li>
</ul>
<br/>
<span class="filterTitle">QBT_TR(Labels)QBT_TR</span>
<ul id="filterLabelList" class="filterList">
<span class="filterTitle">QBT_TR(Categories)QBT_TR</span>
<ul id="filterCategoryList" class="filterList">
</ul>

30
src/webui/www/public/newlabel.html → src/webui/www/public/newcategory.html

@ -2,41 +2,41 @@ @@ -2,41 +2,41 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>QBT_TR(New Label)QBT_TR</title>
<title>QBT_TR(New Category)QBT_TR</title>
<link rel="stylesheet" href="css/style.css" type="text/css" />
<script type="text/javascript" src="scripts/mootools-1.2-core-yc.js" charset="utf-8"></script>
<script type="text/javascript" src="scripts/mootools-1.2-more.js" charset="utf-8"></script>
<script type="text/javascript">
var newLabelKeyboardEvents = new Keyboard({
var newCategoryKeyboardEvents = new Keyboard({
defaultEventType: 'keydown',
events: {
'enter': function (event) {
$('newLabelButton').click();
$('newCategoryButton').click();
event.preventDefault();
}
}
});
newLabelKeyboardEvents.activate();
newCategoryKeyboardEvents.activate();
window.addEvent('domready', function() {
$('newLabel').focus();
$('newLabelButton').addEvent('click', function(e) {
$('newCategory').focus();
$('newCategoryButton').addEvent('click', function(e) {
new Event(e).stop();
// check field
var labelName = $('newLabel').value.trim();
if (labelName == null || labelName == "")
var categoryName = $('newCategory').value.trim();
if (categoryName == null || categoryName == "")
return false;
if (labelName.match("[\\\\/:?\"*<>|]") !== null) {
alert("QBT_TR(Invalid label name:\nPlease do not use any special characters in the label name.)QBT_TR");
if (categoryName.match("^([^\\\\\\/]|[^\\\\\\/]([^\\\\\\/]|\\/(?=[^\\/]))*[^\\\\\\/])$") === null) {
alert("QBT_TR(Invalid category name:\nPlease do not use any special characters in the category name.)QBT_TR");
return false;
}
var hashesList = new URI().getData('hashes');
new Request({
url: 'command/setLabel',
url: 'command/setCategory',
method: 'post',
data: {
hashes: hashesList,
label: labelName
category: categoryName
},
onComplete: function() {
window.parent.closeWindows();
@ -48,10 +48,10 @@ @@ -48,10 +48,10 @@
</head>
<body>
<div style="padding: 10px 10px 0px 10px;">
<p style="font-weight: bold;">QBT_TR(Label)QBT_TR:</p>
<input type="text" id="newLabel" value="" maxlength="100" style="width: 220px;"/>
<p style="font-weight: bold;">QBT_TR(Category)QBT_TR:</p>
<input type="text" id="newCategory" value="" maxlength="100" style="width: 220px;"/>
<div style="text-align: center;">
<input type="button" value="QBT_TR(Add)QBT_TR" id="newLabelButton"/>
<input type="button" value="QBT_TR(Add)QBT_TR" id="newCategoryButton"/>
</div>
</div>
</body>

14
src/webui/www/public/preferences_content.html

@ -73,7 +73,7 @@ @@ -73,7 +73,7 @@
<i>QBT_TR(Supported parameters (case sensitive):)QBT_TR
<ul>
<li>QBT_TR(%N: Torrent name)QBT_TR</li>
<li>QBT_TR(%L: Label)QBT_TR</li>
<li>QBT_TR(%L: Category)QBT_TR</li>
<li>QBT_TR(%F: Content path (same as root path for multifile torrent))QBT_TR</li>
<li>QBT_TR(%R: Root path (first torrent subdirectory path))QBT_TR</li>
<li>QBT_TR(%D: Save path)QBT_TR</li>
@ -1263,19 +1263,19 @@ applyPreferences = function() { @@ -1263,19 +1263,19 @@ applyPreferences = function() {
var json_str = JSON.encode(settings);
new Request({url: 'command/setPreferences',
method: 'post',
data: {'json': json_str,
method: 'post',
data: {'json': json_str,
},
onFailure: function() {
alert("QBT_TR(Unable to save program preferences, qBittorrent is probably unreachable.)QBT_TR");
window.parent.closeWindows();
},
onSuccess: function() {
onSuccess: function() {
// Close window
window.parent.location.reload();
window.parent.closeWindows();
}
}).send();
window.parent.closeWindows();
}
}).send();
};
loadPreferences();

166
src/webui/www/public/scripts/client.js

@ -38,21 +38,21 @@ var alternativeSpeedLimits = false; @@ -38,21 +38,21 @@ var alternativeSpeedLimits = false;
var queueing_enabled = true;
var syncMainDataTimerPeriod = 1500;
var LABELS_ALL = 1;
var LABELS_UNLABELLED = 2;
var CATEGORIES_ALL = 1;
var CATEGORIES_UNCATEGORIZED = 2;
var label_list = {};
var category_list = {};
var selected_label = LABELS_ALL;
var setLabelFilter = function(){};
var selected_category = CATEGORIES_ALL;
var setCategoryFilter = function(){};
var selected_filter = getLocalStorageItem('selected_filter', 'all');
var setFilter = function(){};
var loadSelectedLabel = function () {
selected_label = getLocalStorageItem('selected_label', LABELS_ALL);
var loadSelectedCategory = function () {
selected_category = getLocalStorageItem('selected_category', CATEGORIES_ALL);
};
loadSelectedLabel();
loadSelectedCategory();
function genHash(string) {
var hash = 0;
@ -103,10 +103,10 @@ window.addEvent('load', function () { @@ -103,10 +103,10 @@ window.addEvent('load', function () {
resizeLimit : [100, 300]
});
setLabelFilter = function(hash) {
selected_label = hash;
localStorage.setItem('selected_label', selected_label);
highlightSelectedLabel();
setCategoryFilter = function(hash) {
selected_category = hash;
localStorage.setItem('selected_category', selected_category);
highlightSelectedCategory();
if (typeof torrentsTable.table != 'undefined')
updateMainData();
};
@ -170,59 +170,59 @@ window.addEvent('load', function () { @@ -170,59 +170,59 @@ window.addEvent('load', function () {
var syncMainDataLastResponseId = 0;
var serverState = {};
var removeTorrentFromLabelList = function(hash) {
var removeTorrentFromCategoryList = function(hash) {
if (hash == null || hash == "")
return false;
var removed = false;
Object.each(label_list, function(label) {
if (Object.contains(label.torrents, hash)) {
Object.each(category_list, function(category) {
if (Object.contains(category.torrents, hash)) {
removed = true;
label.torrents.splice(label.torrents.indexOf(hash), 1);
category.torrents.splice(category.torrents.indexOf(hash), 1);
}
});
return removed;
};
var addTorrentToLabelList = function(torrent) {
var label = torrent['label'];
if (label == null)
var addTorrentToCategoryList = function(torrent) {
var category = torrent['category'];
if (category == null)
return false;
if (label.length === 0) { // Empty label
removeTorrentFromLabelList(torrent['hash']);
if (category.length === 0) { // Empty category
removeTorrentFromCategoryList(torrent['hash']);
return true;
}
var labelHash = genHash(label);
if (label_list[labelHash] == null) // This should not happen
label_list[labelHash] = {name: label, torrents: []};
if (!Object.contains(label_list[labelHash].torrents, torrent['hash'])) {
removeTorrentFromLabelList(torrent['hash']);
label_list[labelHash].torrents = label_list[labelHash].torrents.combine([torrent['hash']]);
var categoryHash = genHash(category);
if (category_list[categoryHash] == null) // This should not happen
category_list[categoryHash] = {name: category, torrents: []};
if (!Object.contains(category_list[categoryHash].torrents, torrent['hash'])) {
removeTorrentFromCategoryList(torrent['hash']);
category_list[categoryHash].torrents = category_list[categoryHash].torrents.combine([torrent['hash']]);
return true;
}
return false;
};
var updateContextMenu = function () {
var labelList = $('contextLabelList');
labelList.empty();
labelList.appendChild(new Element('li', {html: '<a href="javascript:newLabelFN();"><img src="theme/list-add" alt="QBT_TR(New...)QBT_TR"/> QBT_TR(New...)QBT_TR</a>'}));
labelList.appendChild(new Element('li', {html: '<a href="javascript:updateLabelFN(0);"><img src="theme/edit-clear" alt="QBT_TR(Reset)QBT_TR"/> QBT_TR(Reset)QBT_TR</a>'}));
var sortedLabels = []
Object.each(label_list, function(label) {
sortedLabels.push(label.name);
var categoryList = $('contextCategoryList');
categoryList.empty();
categoryList.appendChild(new Element('li', {html: '<a href="javascript:newCategoryFN();"><img src="theme/list-add" alt="QBT_TR(New...)QBT_TR"/> QBT_TR(New...)QBT_TR</a>'}));
categoryList.appendChild(new Element('li', {html: '<a href="javascript:updateCategoryFN(0);"><img src="theme/edit-clear" alt="QBT_TR(Reset)QBT_TR"/> QBT_TR(Reset)QBT_TR</a>'}));
var sortedCategories = []
Object.each(category_list, function(category) {
sortedCategories.push(category.name);
});
sortedLabels.sort();
sortedCategories.sort();
var first = true;
Object.each(sortedLabels, function(labelName) {
var labelHash = genHash(labelName);
var el = new Element('li', {html: '<a href="javascript:updateLabelFN(\'' + labelHash + '\');"><img src="theme/inode-directory"/> ' + labelName + '</a>'});
Object.each(sortedCategories, function(categoryName) {
var categoryHash = genHash(categoryName);
var el = new Element('li', {html: '<a href="javascript:updateCategoryFN(\'' + categoryHash + '\');"><img src="theme/inode-directory"/> ' + categoryName + '</a>'});
if (first) {
el.addClass('separator');
first = false;
}
labelList.appendChild(el);
categoryList.appendChild(el);
});
};
@ -242,50 +242,50 @@ window.addEvent('load', function () { @@ -242,50 +242,50 @@ window.addEvent('load', function () {
updateFilter('errored', 'QBT_TR(Errored (%1))QBT_TR');
};
var updateLabelList = function() {
var labelList = $('filterLabelList');
if (!labelList)
var updateCategoryList = function() {
var categoryList = $('filterCategoryList');
if (!categoryList)
return;
labelList.empty();
categoryList.empty();
var create_link = function(hash, text, count) {
var html = '<a href="#" onclick="setLabelFilter(' + hash + ');return false;">' +
var html = '<a href="#" onclick="setCategoryFilter(' + hash + ');return false;">' +
'<img src="theme/inode-directory"/>' +
text + ' (' + count + ')' + '</a>';
return new Element('li', {id: hash, html: html});
};
var all = torrentsTable.getRowIds().length;
var unlabelled = 0;
var uncategorized = 0;
Object.each(torrentsTable.rows, function(row) {
if (row['full_data'].label.length === 0)
unlabelled += 1;
if (row['full_data'].category.length === 0)
uncategorized += 1;
});
labelList.appendChild(create_link(LABELS_ALL, 'QBT_TR(All (0))QBT_TR'.replace(' (0)', ''), all));
labelList.appendChild(create_link(LABELS_UNLABELLED, 'QBT_TR(Unlabeled (0))QBT_TR'.replace(' (0)', ''), unlabelled));
categoryList.appendChild(create_link(CATEGORIES_ALL, 'QBT_TR(All (0))QBT_TR'.replace(' (0)', ''), all));
categoryList.appendChild(create_link(CATEGORIES_UNCATEGORIZED, 'QBT_TR(Uncategorized (0))QBT_TR'.replace(' (0)', ''), uncategorized));
var sortedLabels = []
Object.each(label_list, function(label) {
sortedLabels.push(label.name);
var sortedCategories = []
Object.each(category_list, function(category) {
sortedCategories.push(category.name);
});
sortedLabels.sort();
sortedCategories.sort();
Object.each(sortedLabels, function(labelName) {
var labelHash = genHash(labelName);
var labelCount = label_list[labelHash].torrents.length;
labelList.appendChild(create_link(labelHash, labelName, labelCount));
Object.each(sortedCategories, function(categoryName) {
var categoryHash = genHash(categoryName);
var categoryCount = category_list[categoryHash].torrents.length;
categoryList.appendChild(create_link(categoryHash, categoryName, categoryCount));
});
highlightSelectedLabel();
highlightSelectedCategory();
};
var highlightSelectedLabel = function() {
var labelList = $('filterLabelList');
if (!labelList)
var highlightSelectedCategory = function() {
var categoryList = $('filterCategoryList');
if (!categoryList)
return;
var childrens = labelList.childNodes;
var childrens = categoryList.childNodes;
for (var i in childrens) {
if (childrens[i].id == selected_label)
if (childrens[i].id == selected_category)
childrens[i].className = "selectedFilter";
else
childrens[i].className = "";
@ -308,43 +308,43 @@ window.addEvent('load', function () { @@ -308,43 +308,43 @@ window.addEvent('load', function () {
onSuccess : function (response) {
$('error_div').set('html', '');
if (response) {
var update_labels = false;
var update_categories = false;
var full_update = (response['full_update'] == true);
if (full_update) {
torrentsTable.clear();
label_list = {};
category_list = {};
}
if (response['rid']) {
syncMainDataLastResponseId = response['rid'];
}
if (response['labels']) {
response['labels'].each(function(label) {
var labelHash = genHash(label);
label_list[labelHash] = {name: label, torrents: []};
if (response['categories']) {
response['categories'].each(function(category) {
var categoryHash = genHash(category);
category_list[categoryHash] = {name: category, torrents: []};
});
update_labels = true;
update_categories = true;
}
if (response['labels_removed']) {
response['labels_removed'].each(function(label) {
var labelHash = genHash(label);
delete label_list[labelHash];
if (response['categories_removed']) {
response['categories_removed'].each(function(category) {
var categoryHash = genHash(category);
delete category_list[categoryHash];
});
update_labels = true;
update_categories = true;
}
if (response['torrents']) {
for (var key in response['torrents']) {
response['torrents'][key]['hash'] = key;
response['torrents'][key]['rowId'] = key;
torrentsTable.updateRowData(response['torrents'][key]);
if (addTorrentToLabelList(response['torrents'][key]))
update_labels = true;
if (addTorrentToCategoryList(response['torrents'][key]))
update_categories = true;
}
}
if (response['torrents_removed'])
response['torrents_removed'].each(function (hash) {
torrentsTable.removeRow(hash);
removeTorrentFromLabelList(hash);
update_labels = true; // Allways to update All label
removeTorrentFromCategoryList(hash);
update_categories = true; // Allways to update All category
});
torrentsTable.updateTable(full_update);
torrentsTable.altRow();
@ -355,8 +355,8 @@ window.addEvent('load', function () { @@ -355,8 +355,8 @@ window.addEvent('load', function () {
processServerState();
}
updateFiltersList();
if (update_labels) {
updateLabelList();
if (update_categories) {
updateCategoryList();
updateContextMenu();
}
}
@ -683,4 +683,4 @@ var loadTorrentPeersData = function(){ @@ -683,4 +683,4 @@ var loadTorrentPeersData = function(){
updateTorrentPeersData = function(){
clearTimeout(loadTorrentPeersTimer);
loadTorrentPeersData();
};
};

14
src/webui/www/public/scripts/dynamicTable.js

@ -452,7 +452,7 @@ var TorrentsTable = new Class({ @@ -452,7 +452,7 @@ var TorrentsTable = new Class({
this.newColumn('upspeed', 'width: 100px', 'QBT_TR(Up Speed)QBT_TR');
this.newColumn('eta', 'width: 100px', 'QBT_TR(ETA)QBT_TR');
this.newColumn('ratio', 'width: 100px', 'QBT_TR(Ratio)QBT_TR');
this.newColumn('label', 'width: 100px', 'QBT_TR(Label)QBT_TR');
this.newColumn('category', 'width: 100px', 'QBT_TR(Category)QBT_TR');
this.columns['state_icon'].onclick = '';
this.columns['state_icon'].dataProperties[0] = 'state';
@ -620,7 +620,7 @@ var TorrentsTable = new Class({ @@ -620,7 +620,7 @@ var TorrentsTable = new Class({
};
},
applyFilter : function (row, filterName, labelName) {
applyFilter : function (row, filterName, categoryName) {
var state = row['full_data'].state;
var inactive = false;
var r;
@ -662,13 +662,13 @@ var TorrentsTable = new Class({ @@ -662,13 +662,13 @@ var TorrentsTable = new Class({
break;
}
if (labelName == LABELS_ALL)
if (categoryName == CATEGORIES_ALL)
return true;
if (labelName == LABELS_UNLABELLED && row['full_data'].label.length === 0)
if (categoryName == CATEGORIES_UNCATEGORIZED && row['full_data'].category.length === 0)
return true;
if (labelName != genHash( row['full_data'].label) )
if (categoryName != genHash(row['full_data'].category))
return false;
return true;
@ -679,7 +679,7 @@ var TorrentsTable = new Class({ @@ -679,7 +679,7 @@ var TorrentsTable = new Class({
var rows = this.rows.getValues();
for (i = 0; i < rows.length; i++)
if (this.applyFilter(rows[i], filterName, LABELS_ALL)) cnt++;
if (this.applyFilter(rows[i], filterName, CATEGORIES_ALL)) cnt++;
return cnt;
},
@ -690,7 +690,7 @@ var TorrentsTable = new Class({ @@ -690,7 +690,7 @@ var TorrentsTable = new Class({
var rows = this.rows.getValues();
for (i = 0; i < rows.length; i++)
if (this.applyFilter(rows[i], selected_filter, selected_label)) {
if (this.applyFilter(rows[i], selected_filter, selected_category)) {
filteredRows.push(rows[i]);
filteredRows[rows[i].rowId] = rows[i];
}

20
src/webui/www/public/scripts/mocha-init.js

@ -304,14 +304,14 @@ initializeWindows = function() { @@ -304,14 +304,14 @@ initializeWindows = function() {
}
};
newLabelFN = function () {
newCategoryFN = function () {
var h = torrentsTable.selectedRowsIds();
if (h.length) {
new MochaUI.Window({
id: 'newLabelPage',
title: "QBT_TR(New Label)QBT_TR",
id: 'newCategoryPage',
title: "QBT_TR(New Category)QBT_TR",
loadMethod: 'iframe',
contentURL: 'newlabel.html?hashes=' + h.join('|'),
contentURL: 'newcategory.html?hashes=' + h.join('|'),
scrollbars: false,
resizable: false,
maximizable: false,
@ -323,18 +323,18 @@ initializeWindows = function() { @@ -323,18 +323,18 @@ initializeWindows = function() {
}
};
updateLabelFN = function (labelHash) {
var labelName = '';
if (labelHash != 0)
var labelName = label_list[labelHash].name;
updateCategoryFN = function (categoryHash) {
var categoryName = '';
if (categoryHash != 0)
var categoryName = category_list[categoryHash].name;
var h = torrentsTable.selectedRowsIds();
if (h.length) {
new Request({
url: 'command/setLabel',
url: 'command/setCategory',
method: 'post',
data: {
hashes: h.join("|"),
label: labelName
category: categoryName
}
}).send();
}

2
src/webui/www/public/upload.html

@ -22,7 +22,7 @@ @@ -22,7 +22,7 @@
<input type="text" id="savepath" name="savepath" style="width: 16em;"/>
</div>
<div class="formRow">
<label for="label" class="leftLabelLarge">QBT_TR(Label:)QBT_TR</label>
<label for="label" class="leftLabelLarge">QBT_TR(Category:)QBT_TR</label>
<input type="text" id="label" name="label"/ style="width: 16em;"/>
</div>
<div id="submitbutton" style="margin-top: 30px; text-align: center;">

Loading…
Cancel
Save