diff --git a/src/app/application.cpp b/src/app/application.cpp index 1a20c1f45..22b6b8e77 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -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) #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) 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())); @@ -466,6 +468,7 @@ void Application::cleanup() #endif Net::DownloadManager::freeInstance(); Preferences::freeInstance(); + SettingsStorage::freeInstance(); Logger::freeInstance(); IconProvider::freeInstance(); #ifndef DISABLE_GUI diff --git a/src/app/upgrade.h b/src/app/upgrade.h index 7dc907b70..42928c2c5 100644 --- a/src/app/upgrade.h +++ b/src/app/upgrade.h @@ -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); diff --git a/src/base/base.pri b/src/base/base.pri index e5edf06e4..d52c23445 100644 --- a/src/base/base.pri +++ b/src/base/base.pri @@ -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 += \ $$PWD/tristatebool.cpp \ $$PWD/filesystemwatcher.cpp \ $$PWD/logger.cpp \ + $$PWD/settingsstorage.cpp \ $$PWD/preferences.cpp \ $$PWD/iconprovider.cpp \ $$PWD/http/connection.cpp \ diff --git a/src/base/preferences.cpp b/src/base/preferences.cpp index e38b78375..6ce3281f8 100644 --- a/src/base/preferences.cpp +++ b/src/base/preferences.cpp @@ -30,15 +30,9 @@ * Contact : hammered999@gmail.com */ -#include "preferences.h" -#include "qinisettings.h" -#include "logger.h" - #include #include #include -#include -#include #ifndef DISABLE_GUI #include @@ -56,74 +50,19 @@ #endif #include -#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"); - - 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() } } -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::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 @@ -643,7 +525,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(); @@ -2487,22 +2368,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 +2439,6 @@ void Preferences::setToolbarTextPosition(const int position) setValue("Toolbar/textPosition", position); } -void Preferences::moveRSSCookies() -{ - QList cookies = getNetworkCookies(); - QVariantMap hostsTable = value("Rss/hosts_cookies").toMap(); - foreach (const QString &key, hostsTable.keys()) { - QVariant value = hostsTable[key]; - QList 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 Preferences::getNetworkCookies() const { QList cookies; @@ -2639,8 +2479,31 @@ 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 cookies = getNetworkCookies(); + QVariantMap hostsTable = value("Rss/hosts_cookies").toMap(); + foreach (const QString &key, hostsTable.keys()) { + QVariant value = hostsTable[key]; + QList 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); + + SettingsStorage::instance()->removeValue("Rss/hosts_cookies"); +} + void Preferences::apply() { - if (save()) + if (SettingsStorage::instance()->save()) emit changed(); } diff --git a/src/base/preferences.h b/src/base/preferences.h index ca29ab46b..bcf25cb9b 100644 --- a/src/base/preferences.h +++ b/src/base/preferences.h @@ -41,7 +41,7 @@ #include #include -#include "base/types.h" +#include "types.h" enum scheduler_days { @@ -89,26 +89,20 @@ namespace DNS }; } +class SettingsStorage; + class Preferences: public QObject { Q_OBJECT Q_DISABLE_COPY(Preferences) -private: Preferences(); - ~Preferences(); - static Preferences* m_instance; - QHash 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(); @@ -526,11 +520,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 +537,6 @@ public: // Network QList getNetworkCookies() const; void setNetworkCookies(const QList &cookies); - // Temporary method for upgrade purposes - void moveRSSCookies(); // SpeedWidget int getSpeedWidgetPeriod() const; @@ -557,6 +544,8 @@ 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); diff --git a/src/base/settingsstorage.cpp b/src/base/settingsstorage.cpp new file mode 100644 index 000000000..cbc93e1f0 --- /dev/null +++ b/src/base/settingsstorage.cpp @@ -0,0 +1,209 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2016 Vladimir Golovnev + * Copyright (C) 2014 sledgehammer999 + * + * 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 +#include +#include +#include + +#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 + } + + QString mapKey(const QString &key) + { + static const QHash keyMapping = {}; + 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); + } +} diff --git a/src/base/settingsstorage.h b/src/base/settingsstorage.h new file mode 100644 index 000000000..1f035fad4 --- /dev/null +++ b/src/base/settingsstorage.h @@ -0,0 +1,65 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2016 Vladimir Golovnev + * Copyright (C) 2014 sledgehammer999 + * + * 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 +#include +#include +#include + +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