diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index d13ab7dac..a71d2963b 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -4,11 +4,13 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(QBT_APP_HEADERS application.h filelogger.h +options.h ) set(QBT_APP_SOURCES application.cpp filelogger.cpp +options.cpp main.cpp ) diff --git a/src/app/app.pri b/src/app/app.pri index cdf5b4b52..c89127e63 100644 --- a/src/app/app.pri +++ b/src/app/app.pri @@ -16,11 +16,13 @@ usesystemqtsingleapplication { HEADERS += \ $$PWD/application.h \ - $$PWD/filelogger.h + $$PWD/filelogger.h \ + $$PWD/options.h SOURCES += \ $$PWD/application.cpp \ $$PWD/filelogger.cpp \ + $$PWD/options.cpp \ $$PWD/main.cpp unix: HEADERS += $$PWD/stacktrace.h diff --git a/src/app/application.cpp b/src/app/application.cpp index 8eb2c8f26..061604f71 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -63,6 +63,7 @@ #include "base/logger.h" #include "base/preferences.h" #include "base/settingsstorage.h" +#include "base/profile.h" #include "base/utils/fs.h" #include "base/utils/misc.h" #include "base/iconprovider.h" @@ -93,17 +94,34 @@ namespace const QString LOG_FOLDER("logs"); const char PARAMS_SEPARATOR[] = "|"; + + const QString DEFAULT_PORTABLE_MODE_PROFILE_DIR = QLatin1String("profile"); } Application::Application(const QString &id, int &argc, char **argv) : BaseApplication(id, argc, argv) , m_running(false) , m_shutdownAct(ShutdownDialogAction::Exit) + , m_commandLineArgs(parseCommandLine(this->arguments())) { + setApplicationName("qBittorrent"); + validateCommandLineParameters(); + + QString profileDir = m_commandLineArgs.portableMode + ? QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(DEFAULT_PORTABLE_MODE_PROFILE_DIR) + : m_commandLineArgs.profileDir; + + Profile::initialize(profileDir, m_commandLineArgs.configurationName, + m_commandLineArgs.relativeFastresumePaths || m_commandLineArgs.portableMode); + Logger::initInstance(); SettingsStorage::initInstance(); Preferences::initInstance(); + if (m_commandLineArgs.webUiPort > 0) { // it will be -1 when user did not set any value + Preferences::instance()->setWebUiPort(m_commandLineArgs.webUiPort); + } + #if defined(Q_OS_MACX) && !defined(DISABLE_GUI) if (QSysInfo::MacintoshVersion > QSysInfo::MV_10_8) { // fix Mac OS X 10.9 (mavericks) font issue @@ -111,7 +129,6 @@ Application::Application(const QString &id, int &argc, char **argv) QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande"); } #endif - setApplicationName("qBittorrent"); initializeTranslation(); #ifndef DISABLE_GUI setAttribute(Qt::AA_UseHighDpiPixmaps, true); // opt-in to the high DPI pixmap support @@ -137,6 +154,11 @@ QPointer Application::mainWindow() } #endif +const QBtCommandLineParameters &Application::commandLineArgs() const +{ + return m_commandLineArgs; +} + bool Application::isFileLoggerEnabled() const { return settings()->loadValue(KEY_FILELOGGER_ENABLED, true).toBool(); @@ -153,7 +175,8 @@ void Application::setFileLoggerEnabled(bool value) QString Application::fileLoggerPath() const { - return settings()->loadValue(KEY_FILELOGGER_PATH, QVariant(Utils::Fs::QDesktopServicesDataLocation() + LOG_FOLDER)).toString(); + return settings()->loadValue(KEY_FILELOGGER_PATH, + QVariant(specialFolderLocation(SpecialFolder::Data) + LOG_FOLDER)).toString(); } void Application::setFileLoggerPath(const QString &value) @@ -633,3 +656,12 @@ void Application::cleanup() Utils::Misc::shutdownComputer(m_shutdownAct); } } + +void Application::validateCommandLineParameters() +{ + if (m_commandLineArgs.portableMode && !m_commandLineArgs.profileDir.isEmpty()) + throw CommandLineParameterError(tr("Portable mode and explicit profile directory options are mutually exclusive")); + + if (m_commandLineArgs.portableMode && m_commandLineArgs.relativeFastresumePaths) + Logger::instance()->addMessage(tr("Portable mode implies relative fastresume"), Log::WARNING); +} diff --git a/src/app/application.h b/src/app/application.h index 48a3c8b56..7d7fdbaa8 100644 --- a/src/app/application.h +++ b/src/app/application.h @@ -51,6 +51,7 @@ typedef QtSingleCoreApplication BaseApplication; #endif #include "base/utils/misc.h" +#include "options.h" #ifndef DISABLE_WEBUI class WebUI; @@ -80,6 +81,8 @@ public: QPointer mainWindow(); #endif + const QBtCommandLineParameters &commandLineArgs() const; + // FileLogger properties bool isFileLoggerEnabled() const; void setFileLoggerEnabled(bool value); @@ -116,6 +119,7 @@ private slots: private: bool m_running; ShutdownDialogAction m_shutdownAct; + QBtCommandLineParameters m_commandLineArgs; #ifndef DISABLE_GUI QPointer m_window; @@ -136,6 +140,7 @@ private: void processParams(const QStringList ¶ms); void runExternalProgram(BitTorrent::TorrentHandle *const torrent) const; void sendNotificationEmail(BitTorrent::TorrentHandle *const torrent); + void validateCommandLineParameters(); }; #endif // APPLICATION_H diff --git a/src/app/main.cpp b/src/app/main.cpp index cd6b00c3a..09ccd680a 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -68,6 +68,8 @@ Q_IMPORT_PLUGIN(QICOPlugin) #include #include #include "application.h" +#include "options.h" +#include "base/profile.h" #include "base/utils/misc.h" #include "base/preferences.h" @@ -86,44 +88,12 @@ const char *sysSigName[] = { }; #endif -struct QBtCommandLineParameters -{ - bool showHelp; -#ifndef Q_OS_WIN - bool showVersion; -#endif -#ifndef DISABLE_GUI - bool noSplash; -#else - bool shouldDaemonize; -#endif - int webUiPort; - QStringList torrents; - QString unknownParameter; - - QBtCommandLineParameters() - : showHelp(false) -#ifndef Q_OS_WIN - , showVersion(false) -#endif -#ifndef DISABLE_GUI - , noSplash(Preferences::instance()->isSplashScreenDisabled()) -#else - , shouldDaemonize(false) -#endif - , webUiPort(Preferences::instance()->getWebUiPort()) - { - } -}; - #ifndef DISABLE_GUI void showSplashScreen(); #endif void displayVersion(); -void displayUsage(const QString &prg_name); bool userAgreesWithLegalNotice(); void displayBadArgMessage(const QString &message); -QBtCommandLineParameters parseCommandLine(); // Main int main(int argc, char *argv[]) @@ -137,103 +107,86 @@ int main(int argc, char *argv[]) macMigratePlists(); #endif + try { + // Create Application + QString appId = QLatin1String("qBittorrent-") + Utils::Misc::getUserIDString(); + QScopedPointer app(new Application(appId, argc, argv)); #ifndef DISABLE_GUI - migrateRSS(); + // after the application object creation because we need a profile to be set already + // for the migration + migrateRSS(); #endif + const QBtCommandLineParameters ¶ms = app->commandLineArgs(); - // Create Application - QString appId = QLatin1String("qBittorrent-") + Utils::Misc::getUserIDString(); - QScopedPointer app(new Application(appId, argc, argv)); - - const QBtCommandLineParameters params = parseCommandLine(); - - if (!params.unknownParameter.isEmpty()) { - displayBadArgMessage(QObject::tr("%1 is an unknown command line parameter.", "--random-parameter is an unknown command line parameter.") - .arg(params.unknownParameter)); - return EXIT_FAILURE; - } - + if (!params.unknownParameter.isEmpty()) { + throw CommandLineParameterError(QObject::tr("%1 is an unknown command line parameter.", + "--random-parameter is an unknown command line parameter.") + .arg(params.unknownParameter)); + } #ifndef Q_OS_WIN - if (params.showVersion) { - if (isOneArg) { - displayVersion(); - return EXIT_SUCCESS; + if (params.showVersion) { + if (isOneArg) { + displayVersion(); + return EXIT_SUCCESS; + } + throw CommandLineParameterError(QObject::tr("%1 must be the single command line parameter.") + .arg(QLatin1String("-v (or --version)"))); } - else { - displayBadArgMessage(QObject::tr("%1 must be the single command line parameter.") - .arg(QLatin1String("-v (or --version)"))); - return EXIT_FAILURE; - } - } #endif - - if (params.showHelp) { - if (isOneArg) { - displayUsage(argv[0]); - return EXIT_SUCCESS; - } - else { - displayBadArgMessage(QObject::tr("%1 must be the single command line parameter.") + if (params.showHelp) { + if (isOneArg) { + displayUsage(argv[0]); + return EXIT_SUCCESS; + } + throw CommandLineParameterError(QObject::tr("%1 must be the single command line parameter.") .arg(QLatin1String("-h (or --help)"))); - return EXIT_FAILURE; } - } - if ((params.webUiPort > 0) && (params.webUiPort <= 65535)) { - Preferences::instance()->setWebUiPort(params.webUiPort); - } - else { - displayBadArgMessage(QObject::tr("%1 must specify the correct port (1 to 65535).") - .arg(QLatin1String("--webui-port"))); - return EXIT_FAILURE; - } - - // Set environment variable - if (!qputenv("QBITTORRENT", QBT_VERSION)) - std::cerr << "Couldn't set environment variable...\n"; + // Set environment variable + if (!qputenv("QBITTORRENT", QBT_VERSION)) + std::cerr << "Couldn't set environment variable...\n"; #ifndef DISABLE_GUI - if (!userAgreesWithLegalNotice()) - return EXIT_SUCCESS; + if (!userAgreesWithLegalNotice()) + return EXIT_SUCCESS; #else - if (!params.shouldDaemonize - && isatty(fileno(stdin)) - && isatty(fileno(stdout)) - && !userAgreesWithLegalNotice()) - return EXIT_SUCCESS; + if (!params.shouldDaemonize + && isatty(fileno(stdin)) + && isatty(fileno(stdout)) + && !userAgreesWithLegalNotice()) + return EXIT_SUCCESS; #endif - // Check if qBittorrent is already running for this user - if (app->isRunning()) { + // Check if qBittorrent is already running for this user + if (app->isRunning()) { #ifdef DISABLE_GUI - if (params.shouldDaemonize) { - displayBadArgMessage(QObject::tr("You cannot use %1: qBittorrent is already running for this user.") - .arg(QLatin1String("-d (or --daemon)"))); - return EXIT_FAILURE; - } - else + if (params.shouldDaemonize) { + throw CommandLineParameterError(QObject::tr("You cannot use %1: qBittorrent is already running for this user.") + .arg(QLatin1String("-d (or --daemon)"))); + } + else #endif - qDebug("qBittorrent is already running for this user."); + qDebug("qBittorrent is already running for this user."); - Utils::Misc::msleep(300); - app->sendParams(params.torrents); + Utils::Misc::msleep(300); + app->sendParams(params.torrents); - return EXIT_SUCCESS; - } + return EXIT_SUCCESS; + } #if defined(Q_OS_WIN) - // This affects only Windows apparently and Qt5. - // When QNetworkAccessManager is instantiated it regularly starts polling - // the network interfaces to see what's available and their status. - // This polling creates jitter and high ping with wifi interfaces. - // So here we disable it for lack of better measure. - // It will also spew this message in the console: QObject::startTimer: Timers cannot have negative intervals - // For more info see: - // 1. https://github.com/qbittorrent/qBittorrent/issues/4209 - // 2. https://bugreports.qt.io/browse/QTBUG-40332 - // 3. https://bugreports.qt.io/browse/QTBUG-46015 + // This affects only Windows apparently and Qt5. + // When QNetworkAccessManager is instantiated it regularly starts polling + // the network interfaces to see what's available and their status. + // This polling creates jitter and high ping with wifi interfaces. + // So here we disable it for lack of better measure. + // It will also spew this message in the console: QObject::startTimer: Timers cannot have negative intervals + // For more info see: + // 1. https://github.com/qbittorrent/qBittorrent/issues/4209 + // 2. https://bugreports.qt.io/browse/QTBUG-40332 + // 3. https://bugreports.qt.io/browse/QTBUG-46015 - qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1)); + qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1)); #endif #if defined(Q_OS_MAC) @@ -248,94 +201,45 @@ int main(int argc, char *argv[]) #endif #ifndef DISABLE_GUI - if (!upgrade()) return EXIT_FAILURE; + if (!upgrade()) return EXIT_FAILURE; #else - if (!upgrade(!params.shouldDaemonize - && isatty(fileno(stdin)) - && isatty(fileno(stdout)))) return EXIT_FAILURE; + if (!upgrade(!params.shouldDaemonize + && isatty(fileno(stdin)) + && isatty(fileno(stdout)))) return EXIT_FAILURE; #endif - #ifdef DISABLE_GUI - if (params.shouldDaemonize) { - app.reset(); // Destroy current application - if ((daemon(1, 0) == 0)) { - app.reset(new Application(appId, argc, argv)); - if (app->isRunning()) { - // Another instance had time to start. + if (params.shouldDaemonize) { + app.reset(); // Destroy current application + if ((daemon(1, 0) == 0)) { + app.reset(new Application(appId, argc, argv)); + if (app->isRunning()) { + // Another instance had time to start. + return EXIT_FAILURE; + } + } + else { + qCritical("Something went wrong while daemonizing, exiting..."); return EXIT_FAILURE; } } - else { - qCritical("Something went wrong while daemonizing, exiting..."); - return EXIT_FAILURE; - } - } #else - if (!params.noSplash) - showSplashScreen(); + if (!(params.noSplash || Preferences::instance()->isSplashScreenDisabled())) + showSplashScreen(); #endif #if defined(Q_OS_UNIX) || defined(STACKTRACE_WIN) - signal(SIGINT, sigNormalHandler); - signal(SIGTERM, sigNormalHandler); - signal(SIGABRT, sigAbnormalHandler); - signal(SIGSEGV, sigAbnormalHandler); + signal(SIGINT, sigNormalHandler); + signal(SIGTERM, sigNormalHandler); + signal(SIGABRT, sigAbnormalHandler); + signal(SIGSEGV, sigAbnormalHandler); #endif - return app->exec(params.torrents); -} - -QBtCommandLineParameters parseCommandLine() -{ - QBtCommandLineParameters result; - QStringList appArguments = qApp->arguments(); - - for (int i = 1; i < appArguments.size(); ++i) { - const QString& arg = appArguments[i]; - - if ((arg.startsWith("--") && !arg.endsWith(".torrent")) || - (arg.startsWith("-") && arg.size() == 2)) { - //Parse known parameters - if ((arg == QLatin1String("-h")) || (arg == QLatin1String("--help"))) { - result.showHelp = true; - } -#ifndef Q_OS_WIN - else if ((arg == QLatin1String("-v")) || (arg == QLatin1String("--version"))) { - result.showVersion = true; - } -#endif - else if (arg.startsWith(QLatin1String("--webui-port="))) { - QStringList parts = arg.split(QLatin1Char('=')); - if (parts.size() == 2) - result.webUiPort = parts.last().toInt(); - } -#ifndef DISABLE_GUI - else if (arg == QLatin1String("--no-splash")) { - result.noSplash = true; - } -#else - else if ((arg == QLatin1String("-d")) || (arg == QLatin1String("--daemon"))) { - result.shouldDaemonize = true; - } -#endif - else { - //Unknown argument - result.unknownParameter = arg; - break; - } - } - else { - QFileInfo torrentPath; - torrentPath.setFile(arg); - - if (torrentPath.exists()) - result.torrents += torrentPath.absoluteFilePath(); - else - result.torrents += arg; - } + return app->exec(params.torrents); + } + catch (CommandLineParameterError &er) { + displayBadArgMessage(er.messageForUser()); + return EXIT_FAILURE; } - - return result; } #if defined(Q_OS_UNIX) || defined(STACKTRACE_WIN) @@ -396,53 +300,6 @@ void displayVersion() std::cout << qPrintable(qApp->applicationName()) << " " << QBT_VERSION << std::endl; } -QString makeUsage(const QString &prg_name) -{ - QString text; - - text += QObject::tr("Usage:") + QLatin1Char('\n'); -#ifndef Q_OS_WIN - text += QLatin1Char('\t') + prg_name + QLatin1String(" (-v | --version)") + QLatin1Char('\n'); -#endif - text += QLatin1Char('\t') + prg_name + QLatin1String(" (-h | --help)") + QLatin1Char('\n'); - text += QLatin1Char('\t') + prg_name - + QLatin1String(" [--webui-port=]") -#ifndef DISABLE_GUI - + QLatin1String(" [--no-splash]") -#else - + QLatin1String(" [-d | --daemon]") -#endif - + QLatin1String("[( | )...]") + QLatin1Char('\n'); - text += QObject::tr("Options:") + QLatin1Char('\n'); -#ifndef Q_OS_WIN - text += QLatin1String("\t-v | --version\t\t") + QObject::tr("Displays program version") + QLatin1Char('\n'); -#endif - text += QLatin1String("\t-h | --help\t\t") + QObject::tr("Displays this help message") + QLatin1Char('\n'); - text += QLatin1String("\t--webui-port=\t") - + QObject::tr("Changes the Web UI port (current: %1)").arg(QString::number(Preferences::instance()->getWebUiPort())) - + QLatin1Char('\n'); -#ifndef DISABLE_GUI - text += QLatin1String("\t--no-splash\t\t") + QObject::tr("Disable splash screen") + QLatin1Char('\n'); -#else - text += QLatin1String("\t-d | --daemon\t\t") + QObject::tr("Run in daemon-mode (background)") + QLatin1Char('\n'); -#endif - text += QLatin1String("\tfiles or urls\t\t") + QObject::tr("Downloads the torrents passed by the user"); - - return text; -} - -void displayUsage(const QString& prg_name) -{ -#ifndef Q_OS_WIN - std::cout << qPrintable(makeUsage(prg_name)) << std::endl; -#else - QMessageBox msgBox(QMessageBox::Information, QObject::tr("Help"), makeUsage(prg_name), QMessageBox::Ok); - msgBox.show(); // Need to be shown or to moveToCenter does not work - msgBox.move(Utils::Misc::screenCenter(&msgBox)); - msgBox.exec(); -#endif -} - void displayBadArgMessage(const QString& message) { QString help = QObject::tr("Run application with -h option to read about command line parameters."); diff --git a/src/app/options.cpp b/src/app/options.cpp new file mode 100644 index 000000000..857ddceea --- /dev/null +++ b/src/app/options.cpp @@ -0,0 +1,398 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2016 Eugene Shalygin + * Copyright (C) 2014 Vladimir Golovnev + * Copyright (C) 2006 Christophe Dumez + * + * 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. + * + * Contact : chris@qbittorrent.org + */ + +#include "options.h" + +#include + +#include +#include +#include +#include + +#ifdef Q_OS_WIN +#include +#endif + +#include "base/utils/misc.h" + +namespace +{ + // Base option class. Encapsulates name operations. + class Option + { + protected: + constexpr Option(const char *name, char shortcut = 0) + : m_name {name} + , m_shortcut {shortcut} + { + } + + QString fullParameter() const + { + return QLatin1String("--") + QLatin1String(m_name); + } + + QString shortcutParameter() const + { + return QLatin1String("-") + QLatin1Char(m_shortcut); + } + + bool hasShortcut() const + { + return m_shortcut != 0; + } + + QString envVarName() const + { + return QLatin1String("QBT_") + + QString(QLatin1String(m_name)).toUpper().replace(QLatin1Char('-'), QLatin1Char('_')); + } + + static QString padUsageText(const QString &usage) + { + const int TAB_WIDTH = 8; + QString res = QLatin1String("\t") + usage; + if (usage.size() < 2 * TAB_WIDTH) + return res + QLatin1String("\t\t"); + else + return res + QLatin1String("\t"); + } + + private: + const char *m_name; + const char m_shortcut; + }; + + // Boolean option. + class BoolOption: protected Option + { + public: + constexpr BoolOption(const char *name, char shortcut = 0) + : Option {name, shortcut} + { + } + + bool operator==(const QString &arg) const + { + return (hasShortcut() && ((arg.size() == 2) && (arg == shortcutParameter()))) + || (arg == fullParameter()); + } + + bool value(const QProcessEnvironment &env) const + { + QString val = env.value(envVarName()); + // we accept "1" and "true" (upper or lower cased) as boolean 'true' values + return (val == QLatin1String("1") || val.toUpper() == QLatin1String("TRUE")); + } + + QString usage() const + { + QString res; + if (hasShortcut()) + res += shortcutParameter() + QLatin1String(" | "); + res += fullParameter(); + return padUsageText(res); + } + }; + + inline bool operator==(const QString &s, const BoolOption &o) + { + return o == s; + } + + // Option with string value. May not have a shortcut + struct StringOption: protected Option + { + public: + constexpr StringOption(const char *name) + : Option {name, 0} + { + } + + bool operator==(const QString &arg) const + { + return arg.startsWith(parameterAssignment()); + } + + QString value(const QString &arg) const + { + QStringList parts = arg.split(QLatin1Char('=')); + if (parts.size() == 2) + return unquote(parts[1]); + throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'", + "e.g. Parameter '--webui-port' must follow syntax '--webui-port=value'") + .arg(fullParameter()).arg(QLatin1String(""))); + } + + QString value(const QProcessEnvironment &env, const QString &defaultValue = QString()) const + { + QString val = env.value(envVarName()); + return val.isEmpty() ? defaultValue : unquote(val); + } + + QString usage(const QString &valueName) const + { + return padUsageText(parameterAssignment() + QLatin1Char('<') + valueName + QLatin1Char('>')); + } + + private: + QString parameterAssignment() const + { + return fullParameter() + QLatin1Char('='); + } + + static QString unquote(const QString &s) + { + auto isStringQuoted = + [](const QString &s, QChar quoteChar) + { + return (s.startsWith(quoteChar) && s.endsWith(quoteChar)); + }; + + if ((s.size() >= 2) && (isStringQuoted(s, QLatin1Char('\'')) || isStringQuoted(s, QLatin1Char('"')))) + return s.mid(1, s.size() - 2); + return s; + } + }; + + inline bool operator==(const QString &s, const StringOption &o) + { + return o == s; + } + + // Option with integer value. May not have a shortcut + class IntOption: protected StringOption + { + public: + constexpr IntOption(const char *name) + : StringOption {name} + { + } + + using StringOption::operator==; + using StringOption::usage; + + int value(const QString &arg) const + { + QString val = StringOption::value(arg); + bool ok = false; + int res = val.toInt(&ok); + if (!ok) + throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'", + "e.g. Parameter '--webui-port' must follow syntax '--webui-port='") + .arg(fullParameter()).arg(QLatin1String(""))); + return res; + } + + int value(const QProcessEnvironment &env, int defaultValue) const + { + QString val = env.value(envVarName()); + if (val.isEmpty()) return defaultValue; + + bool ok; + int res = val.toInt(&ok); + if (!ok) { + qDebug() << QObject::tr("Expected integer number in environment variable '%1', but got '%2'") + .arg(envVarName()).arg(val); + return defaultValue; + } + return res; + } + }; + + inline bool operator==(const QString &s, const IntOption &o) + { + return o == s; + } + + constexpr const BoolOption SHOW_HELP_OPTION = {"help", 'h'}; + constexpr const BoolOption SHOW_VERSION_OPTION = {"version", 'v'}; +#ifdef DISABLE_GUI + constexpr const BoolOption DAEMON_OPTION = {"daemon", 'd'}; +#else + constexpr const BoolOption NO_SPLASH_OPTION = {"no-splash"}; +#endif + constexpr const IntOption WEBUI_PORT_OPTION = {"webui-port"}; + constexpr const StringOption PROFILE_OPTION = {"profile"}; + constexpr const StringOption CONFIGURATION_OPTION = {"configuration"}; + constexpr const BoolOption PORTABLE_OPTION = {"portable"}; + constexpr const BoolOption RELATIVE_FASTRESUME = {"relative-fastresume"}; +} + +QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &env) + : showHelp(false) +#ifndef Q_OS_WIN + , showVersion(false) +#endif +#ifndef DISABLE_GUI + , noSplash(NO_SPLASH_OPTION.value(env)) +#else + , shouldDaemonize(DAEMON_OPTION.value(env)) +#endif + , webUiPort(WEBUI_PORT_OPTION.value(env, -1)) + , profileDir(PROFILE_OPTION.value(env)) + , relativeFastresumePaths(RELATIVE_FASTRESUME.value(env)) + , portableMode(PORTABLE_OPTION.value(env)) + , configurationName(CONFIGURATION_OPTION.value(env)) +{ +} + +QBtCommandLineParameters parseCommandLine(const QStringList &args) +{ + QBtCommandLineParameters result {QProcessEnvironment::systemEnvironment()}; + + for (int i = 1; i < args.count(); ++i) { + const QString &arg = args[i]; + + if ((arg.startsWith("--") && !arg.endsWith(".torrent")) + || (arg.startsWith("-") && (arg.size() == 2))) { + // Parse known parameters + if ((arg == SHOW_HELP_OPTION)) { + result.showHelp = true; + } +#ifndef Q_OS_WIN + else if (arg == SHOW_VERSION_OPTION) { + result.showVersion = true; + } +#endif + else if (arg == WEBUI_PORT_OPTION) { + result.webUiPort = WEBUI_PORT_OPTION.value(arg); + if ((result.webUiPort < 1) || (result.webUiPort > 65535)) + throw CommandLineParameterError(QObject::tr("%1 must specify the correct port (1 to 65535).") + .arg(QLatin1String("--webui-port"))); + } +#ifndef DISABLE_GUI + else if (arg == NO_SPLASH_OPTION) { + result.noSplash = true; + } +#else + else if (arg == DAEMON_OPTION) { + result.shouldDaemonize = true; + } +#endif + else if (arg == PROFILE_OPTION) { + result.profileDir = PROFILE_OPTION.value(arg); + } + else if (arg == RELATIVE_FASTRESUME) { + result.relativeFastresumePaths = true; + } + else if (arg == PORTABLE_OPTION) { + result.portableMode = true; + } + else if (arg == CONFIGURATION_OPTION) { + result.configurationName = CONFIGURATION_OPTION.value(arg); + } + else { + // Unknown argument + result.unknownParameter = arg; + break; + } + } + else { + QFileInfo torrentPath; + torrentPath.setFile(arg); + + if (torrentPath.exists()) + result.torrents += torrentPath.absoluteFilePath(); + else + result.torrents += arg; + } + } + + return result; +} + +CommandLineParameterError::CommandLineParameterError(const QString &messageForUser) + : std::runtime_error(messageForUser.toLocal8Bit().data()) + , m_messageForUser(messageForUser) +{ +} + +const QString& CommandLineParameterError::messageForUser() const +{ + return m_messageForUser; +} + +QString makeUsage(const QString &prgName) +{ + QString text; + QTextStream stream(&text, QIODevice::WriteOnly); + + stream << QObject::tr("Usage:") << '\n'; +#ifndef Q_OS_WIN + stream << '\t' << prgName << " [options] [( | )...]" << '\n'; +#endif + + stream << QObject::tr("Options:") << '\n'; +#ifndef Q_OS_WIN + stream << SHOW_VERSION_OPTION.usage() << QObject::tr("Displays program version and exit") << '\n'; +#endif + stream << SHOW_HELP_OPTION.usage() << QObject::tr("Displays this help message and exit") << '\n'; + stream << WEBUI_PORT_OPTION.usage(QLatin1String("port")) + << QObject::tr("Changes the Web UI port") + << '\n'; +#ifndef DISABLE_GUI + stream << NO_SPLASH_OPTION.usage() << QObject::tr("Disable splash screen") << '\n'; +#else + stream << DAEMON_OPTION.usage() << QObject::tr("Run in daemon-mode (background)") << '\n'; +#endif + stream << PROFILE_OPTION.usage(QLatin1String("dir")) << QObject::tr("Store configuration files in ") << '\n'; + stream << CONFIGURATION_OPTION.usage(QLatin1String("name")) << QObject::tr("Store configuration files in directories qBittorrent_") << '\n'; + stream << RELATIVE_FASTRESUME.usage() << QObject::tr("Hack into libtorrent fastresume files and make file paths relative to the profile directory") << '\n'; + stream << PORTABLE_OPTION.usage() << QObject::tr("Shortcut for --profile=/profile --relative-fastresume") << '\n'; + stream << "\tfiles or urls\t\t" << QObject::tr("Downloads the torrents passed by the user") << '\n' + << '\n'; + + stream << QObject::tr("Option values may be supplied via environment variables.") << '\n' + << QObject::tr("For option named 'parameter-name', environment variable name is 'QBT_PARAMETER_NAME' (in upper case, '-' replaced with '_')") << '\n' + << QObject::tr("To pass flag values, set the variable to '1' or 'TRUE'.") << '\n' + << QObject::tr("For example, to disable the splash screen: ") + << "QBT_NO_SPLASH=1 " << prgName << '\n' + << '\n' + << QObject::tr("Command line parameters take precedence over environment variables") << '\n'; + + stream << flush; + return text; +} + +void displayUsage(const QString &prgName) +{ +#ifndef Q_OS_WIN + std::cout << qPrintable(makeUsage(prgName)) << std::endl; +#else + QMessageBox msgBox(QMessageBox::Information, QObject::tr("Help"), makeUsage(prgName), QMessageBox::Ok); + msgBox.show(); // Need to be shown or to moveToCenter does not work + msgBox.move(Utils::Misc::screenCenter(&msgBox)); + msgBox.exec(); +#endif +} diff --git a/src/base/qinisettings.h b/src/app/options.h similarity index 55% rename from src/base/qinisettings.h rename to src/app/options.h index be5e9561e..ad15a4e78 100644 --- a/src/base/qinisettings.h +++ b/src/app/options.h @@ -1,5 +1,7 @@ /* - * Bittorrent Client using Qt4 and libtorrent. + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2016 Eugene Shalygin + * Copyright (C) 2014 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -28,46 +30,49 @@ * Contact : chris@qbittorrent.org */ -#ifndef QINISETTINGS_H -#define QINISETTINGS_H +#ifndef APP_OPTIONS_H +#define APP_OPTIONS_H -#include +#include -class QIniSettings : public QSettings { - Q_OBJECT - Q_DISABLE_COPY (QIniSettings) +#include +#include -public: - QIniSettings(const QString &organization = "qBittorrent", const QString &application = "qBittorrent", QObject *parent = 0 ): -#if defined(Q_OS_WIN) || defined(Q_OS_MAC) - QSettings(QSettings::IniFormat, QSettings::UserScope, organization, application, parent) +class QProcessEnvironment; + +struct QBtCommandLineParameters +{ + bool showHelp; +#ifndef Q_OS_WIN + bool showVersion; +#endif +#ifndef DISABLE_GUI + bool noSplash; #else - QSettings(organization, application, parent) + bool shouldDaemonize; #endif - { + int webUiPort; + QString profileDir; + bool relativeFastresumePaths; + bool portableMode; + QString configurationName; + QStringList torrents; + QString unknownParameter; - } - - QIniSettings(const QString &fileName, Format format, QObject *parent = 0 ) : QSettings(fileName, format, parent) { - - } - -#ifdef Q_OS_WIN - QVariant value(const QString & key, const QVariant &defaultValue = QVariant()) const { - QString key_tmp(key); - QVariant ret = QSettings::value(key_tmp); - if (ret.isNull()) - return defaultValue; - return ret; - } - - void setValue(const QString &key, const QVariant &val) { - QString key_tmp(key); - if (format() == QSettings::NativeFormat) // Using registry, don't touch replace here - key_tmp.replace("\\", "/"); - QSettings::setValue(key_tmp, val); - } -#endif + QBtCommandLineParameters(const QProcessEnvironment&); }; -#endif // QINISETTINGS_H +class CommandLineParameterError: public std::runtime_error +{ +public: + CommandLineParameterError(const QString &messageForUser); + const QString& messageForUser() const; + +private: + const QString m_messageForUser; +}; + +QBtCommandLineParameters parseCommandLine(const QStringList &args); +void displayUsage(const QString &prgName); + +#endif // APP_OPTIONS_H diff --git a/src/app/upgrade.h b/src/app/upgrade.h index f9b69140a..40c720f4d 100644 --- a/src/app/upgrade.h +++ b/src/app/upgrade.h @@ -47,13 +47,16 @@ #endif #include #include +#ifdef Q_OS_MAC +#include +#endif #include "base/logger.h" +#include "base/profile.h" #include "base/utils/fs.h" #include "base/utils/misc.h" #include "base/utils/string.h" #include "base/preferences.h" -#include "base/qinisettings.h" bool userAcceptsUpgrade() { @@ -145,7 +148,7 @@ bool upgrade(bool ask = true) // Upgrade preferences Preferences::instance()->upgrade(); - QString backupFolderPath = Utils::Fs::expandPathAbs(Utils::Fs::QDesktopServicesDataLocation() + "BT_backup"); + QString backupFolderPath = Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + "BT_backup"); QDir backupFolderDir(backupFolderPath); // **************************************************************************************** @@ -156,15 +159,10 @@ bool upgrade(bool ask = true) upgradeResumeFile(backupFolderDir.absoluteFilePath(backupFile)); // **************************************************************************************** -#ifdef Q_OS_MAC - // native .plist - QSettings *oldResumeSettings = new QSettings("qBittorrent", "qBittorrent-resume"); -#else - QIniSettings *oldResumeSettings = new QIniSettings("qBittorrent", "qBittorrent-resume"); -#endif + SettingsPtr oldResumeSettings = Profile::instance().applicationSettings(QLatin1String("qBittorrent-resume")); QString oldResumeFilename = oldResumeSettings->fileName(); QVariantHash oldResumeData = oldResumeSettings->value("torrents").toHash(); - delete oldResumeSettings; + oldResumeSettings.reset(); if (oldResumeData.isEmpty()) { Utils::Fs::forceRemove(oldResumeFilename); @@ -231,7 +229,7 @@ bool upgrade(bool ask = true) #ifdef Q_OS_MAC void migratePlistToIni(const QString &application) { - QIniSettings iniFile("qBittorrent", application); + QSettings iniFile(QSettings::IniFormat, QSettings::UserScope, "qBittorrent", application); if (!iniFile.allKeys().isEmpty()) return; // We copy the contents of plist, only if inifile does not exist(is empty). QSettings *plistFile = new QSettings("qBittorrent", application); @@ -260,16 +258,16 @@ void macMigratePlists() void migrateRSS() { // Copy old feed items to new file if needed - QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss-feeds"); - if (!qBTRSS.allKeys().isEmpty()) return; // We move the contents of RSS old_items only if inifile does not exist (is empty). + SettingsPtr qBTRSS = Profile::instance().applicationSettings(QLatin1String("qBittorrent-rss-feeds")); + if (!qBTRSS->allKeys().isEmpty()) return; // We move the contents of RSS old_items only if inifile does not exist (is empty). - QIniSettings qBTRSSLegacy("qBittorrent", "qBittorrent-rss"); - QHash allOldItems = qBTRSSLegacy.value("old_items", QHash()).toHash(); + SettingsPtr qBTRSSLegacy = Profile::instance().applicationSettings(QLatin1String("qBittorrent-rss")); + QHash allOldItems = qBTRSSLegacy->value("old_items", QHash()).toHash(); if (!allOldItems.empty()) { qDebug("Moving %d old items for feeds to qBittorrent-rss-feeds", allOldItems.size()); - qBTRSS.setValue("old_items", allOldItems); - qBTRSSLegacy.remove("old_items"); + qBTRSS->setValue("old_items", allOldItems); + qBTRSSLegacy->remove("old_items"); } } #endif diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 8f3654822..a9eb2f5c8 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -33,6 +33,7 @@ net/private/geoipdatabase.h net/proxyconfigurationmanager.h net/reverseresolution.h net/smtp.h +private/profile_p.h rss/private/rssparser.h rss/rssarticle.h rss/rssdownloadrule.h @@ -52,7 +53,7 @@ iconprovider.h indexrange.h logger.h preferences.h -qinisettings.h +profile.h scanfoldersmodel.h searchengine.h settingsstorage.h @@ -94,6 +95,7 @@ net/private/geoipdatabase.cpp net/proxyconfigurationmanager.cpp net/reverseresolution.cpp net/smtp.cpp +private/profile_p.cpp rss/private/rssparser.cpp rss/rssarticle.cpp rss/rssdownloadrule.cpp @@ -112,6 +114,7 @@ filesystemwatcher.cpp iconprovider.cpp logger.cpp preferences.cpp +profile.cpp scanfoldersmodel.cpp searchengine.cpp settingsstorage.cpp diff --git a/src/base/base.pri b/src/base/base.pri index 14ee8fe1c..92e781d0c 100644 --- a/src/base/base.pri +++ b/src/base/base.pri @@ -2,7 +2,6 @@ HEADERS += \ $$PWD/types.h \ $$PWD/tristatebool.h \ $$PWD/filesystemwatcher.h \ - $$PWD/qinisettings.h \ $$PWD/logger.h \ $$PWD/settingsstorage.h \ $$PWD/settingvalue.h \ @@ -55,6 +54,8 @@ HEADERS += \ $$PWD/utils/net.h \ $$PWD/utils/random.h \ $$PWD/utils/string.h \ + $$PWD/profile.h \ + $$PWD/private/profile_p.h \ $$PWD/unicodestrings.h \ $$PWD/torrentfileguard.h \ $$PWD/torrentfilter.h \ @@ -112,6 +113,8 @@ SOURCES += \ $$PWD/utils/net.cpp \ $$PWD/utils/random.cpp \ $$PWD/utils/string.cpp \ + $$PWD/profile.cpp \ + $$PWD/private/profile_p.cpp \ $$PWD/torrentfileguard.cpp \ $$PWD/torrentfilter.cpp \ $$PWD/scanfoldersmodel.cpp \ diff --git a/src/base/bittorrent/private/statistics.cpp b/src/base/bittorrent/private/statistics.cpp index aa554cff0..d2e562373 100644 --- a/src/base/bittorrent/private/statistics.cpp +++ b/src/base/bittorrent/private/statistics.cpp @@ -1,11 +1,12 @@ +#include "statistics.h" + #include #include -#include "base/qinisettings.h" #include "base/bittorrent/sessionstatus.h" +#include "base/profile.h" #include "base/bittorrent/session.h" -#include "statistics.h" static const qint64 SAVE_INTERVAL = 15 * 60 * 1000; @@ -64,19 +65,19 @@ void Statistics::save() const if (!m_dirty || ((now - m_lastWrite) < SAVE_INTERVAL)) return; - QIniSettings s("qBittorrent", "qBittorrent-data"); + SettingsPtr s = Profile::instance().applicationSettings(QLatin1String("qBittorrent-data")); QVariantHash v; v.insert("AlltimeDL", m_alltimeDL + m_sessionDL); v.insert("AlltimeUL", m_alltimeUL + m_sessionUL); - s.setValue("Stats/AllStats", v); + s->setValue("Stats/AllStats", v); m_dirty = false; m_lastWrite = now; } void Statistics::load() { - QIniSettings s("qBittorrent", "qBittorrent-data"); - QVariantHash v = s.value("Stats/AllStats").toHash(); + SettingsPtr s = Profile::instance().applicationSettings(QLatin1String("qBittorrent-data")); + QVariantHash v = s->value("Stats/AllStats").toHash(); m_alltimeDL = v["AlltimeDL"].toULongLong(); m_alltimeUL = v["AlltimeUL"].toULongLong(); diff --git a/src/base/bittorrent/session.cpp b/src/base/bittorrent/session.cpp index 2497ef2a6..56474550e 100644 --- a/src/base/bittorrent/session.cpp +++ b/src/base/bittorrent/session.cpp @@ -66,6 +66,7 @@ #include #include "base/logger.h" +#include "base/profile.h" #include "base/net/downloadhandler.h" #include "base/net/downloadmanager.h" #include "base/net/portforwarder.h" @@ -132,7 +133,7 @@ namespace return tmp; } - QString normalizeSavePath(QString path, const QString &defaultPath = Utils::Fs::QDesktopServicesDownloadLocation()) + QString normalizeSavePath(QString path, const QString &defaultPath = specialFolderLocation(SpecialFolder::Downloads)) { path = path.trimmed(); if (path.isEmpty()) @@ -266,7 +267,7 @@ Session::Session(QObject *parent) , m_isProxyPeerConnectionsEnabled(BITTORRENT_SESSION_KEY("ProxyPeerConnections"), false) , m_storedCategories(BITTORRENT_SESSION_KEY("Categories")) , m_maxRatioAction(BITTORRENT_SESSION_KEY("MaxRatioAction"), Pause) - , m_defaultSavePath(BITTORRENT_SESSION_KEY("DefaultSavePath"), Utils::Fs::QDesktopServicesDownloadLocation(), normalizePath) + , m_defaultSavePath(BITTORRENT_SESSION_KEY("DefaultSavePath"), specialFolderLocation(SpecialFolder::Downloads), normalizePath) , m_tempPath(BITTORRENT_SESSION_KEY("TempPath"), defaultSavePath() + "temp/", normalizePath) , m_isSubcategoriesEnabled(BITTORRENT_SESSION_KEY("SubcategoriesEnabled"), false) , m_isTempPathEnabled(BITTORRENT_SESSION_KEY("TempPathEnabled"), false) @@ -3003,7 +3004,7 @@ bool Session::hasPerTorrentRatioLimit() const void Session::initResumeFolder() { - m_resumeFolderPath = Utils::Fs::expandPathAbs(Utils::Fs::QDesktopServicesDataLocation() + RESUME_FOLDER); + m_resumeFolderPath = Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + RESUME_FOLDER); QDir resumeFolderDir(m_resumeFolderPath); if (resumeFolderDir.exists() || resumeFolderDir.mkpath(resumeFolderDir.absolutePath())) { m_resumeFolderLock.setFileName(resumeFolderDir.absoluteFilePath("session.lock")); @@ -3632,7 +3633,8 @@ namespace if (ec || (fast.type() != libt::bdecode_node::dict_t)) return false; #endif - torrentData.savePath = Utils::Fs::fromNativePath(QString::fromStdString(fast.dict_find_string_value("qBt-savePath"))); + torrentData.savePath = Profile::instance().fromPortablePath( + Utils::Fs::fromNativePath(QString::fromStdString(fast.dict_find_string_value("qBt-savePath")))); torrentData.ratioLimit = QString::fromStdString(fast.dict_find_string_value("qBt-ratioLimit")).toDouble(); // ************************************************************************************** // Workaround to convert legacy label to category diff --git a/src/base/bittorrent/torrenthandle.cpp b/src/base/bittorrent/torrenthandle.cpp index 11a112b61..607551d7b 100644 --- a/src/base/bittorrent/torrenthandle.cpp +++ b/src/base/bittorrent/torrenthandle.cpp @@ -54,6 +54,7 @@ #include "base/logger.h" #include "base/preferences.h" +#include "base/profile.h" #include "base/utils/string.h" #include "base/utils/fs.h" #include "base/utils/misc.h" @@ -1477,7 +1478,11 @@ void TorrentHandle::handleSaveResumeDataAlert(libtorrent::save_resume_data_alert resumeData["qBt-paused"] = isPaused(); resumeData["qBt-forced"] = isForced(); } - resumeData["qBt-savePath"] = m_useAutoTMM ? "" : m_savePath.toStdString(); + else { + auto savePath = resumeData.find_key("save_path")->string(); + resumeData["save_path"] = Profile::instance().toPortablePath(QString::fromStdString(savePath)).toStdString(); + } + resumeData["qBt-savePath"] = m_useAutoTMM ? "" : Profile::instance().toPortablePath(m_savePath).toStdString(); resumeData["qBt-ratioLimit"] = QString::number(m_ratioLimit).toStdString(); resumeData["qBt-category"] = m_category.toStdString(); resumeData["qBt-name"] = m_name.toStdString(); diff --git a/src/base/net/geoipmanager.cpp b/src/base/net/geoipmanager.cpp index 990c2f48c..19c74455d 100644 --- a/src/base/net/geoipmanager.cpp +++ b/src/base/net/geoipmanager.cpp @@ -35,6 +35,7 @@ #include "base/logger.h" #include "base/preferences.h" +#include "base/profile.h" #include "base/utils/fs.h" #include "base/utils/gzip.h" #include "downloadmanager.h" @@ -94,7 +95,7 @@ void GeoIPManager::loadDatabase() } QString filepath = Utils::Fs::expandPathAbs( - QString("%1%2/%3").arg(Utils::Fs::QDesktopServicesDataLocation()) + QString("%1%2/%3").arg(specialFolderLocation(SpecialFolder::Data)) .arg(GEOIP_FOLDER).arg(GEOIP_FILENAME)); QString error; @@ -431,7 +432,7 @@ void GeoIPManager::downloadFinished(const QString &url, QByteArray data) .arg(m_geoIPDatabase->type()).arg(m_geoIPDatabase->buildEpoch().toString()), Log::INFO); QString targetPath = Utils::Fs::expandPathAbs( - Utils::Fs::QDesktopServicesDataLocation() + GEOIP_FOLDER); + specialFolderLocation(SpecialFolder::Data) + GEOIP_FOLDER); if (!QDir(targetPath).exists()) QDir().mkpath(targetPath); QFile targetFile(QString("%1/%2").arg(targetPath).arg(GEOIP_FILENAME)); diff --git a/src/base/private/profile_p.cpp b/src/base/private/profile_p.cpp new file mode 100644 index 000000000..d57bf53a5 --- /dev/null +++ b/src/base/private/profile_p.cpp @@ -0,0 +1,250 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2016 Eugene Shalygin + * Copyright (C) 2012 Christophe Dumez + * + * 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 "profile_p.h" + +#include + +#include + +#ifdef Q_OS_MAC +#include +#include +#endif + +#ifdef Q_OS_WIN +#include +#endif + +#include "base/utils/fs.h" + +Private::Profile::Profile(const QString &configurationName) + : m_configurationName {configurationName.isEmpty() + ? QCoreApplication::applicationName() + : QCoreApplication::applicationName() + QLatin1Char('_') + configurationName} +{ +} + +QString Private::Profile::configurationName() const +{ + return m_configurationName; +} + +Private::DefaultProfile::DefaultProfile(const QString &configurationName) + : Profile(configurationName) +{ +} + +QString Private::DefaultProfile::baseDirectory() const +{ + return QDir::homePath(); +} + +QString Private::DefaultProfile::cacheLocation() const +{ + QString result; +#if defined(Q_OS_WIN) || defined(Q_OS_OS2) + result = dataLocation() + QLatin1String("cache"); +#else +#ifdef Q_OS_MAC + // http://developer.apple.com/documentation/Carbon/Reference/Folder_Manager/Reference/reference.html + FSRef ref; + OSErr err = FSFindFolder(kUserDomain, kCachedDataFolderType, false, &ref); + if (err) + return QString(); + QByteArray ba(2048, 0); + if (FSRefMakePath(&ref, reinterpret_cast(ba.data()), ba.size()) == noErr) + result = QString::fromUtf8(ba).normalized(QString::NormalizationForm_C); + result += QLatin1Char('/') + configurationName(); +#else + QString xdgCacheHome = QLatin1String(qgetenv("XDG_CACHE_HOME")); + if (xdgCacheHome.isEmpty()) + xdgCacheHome = QDir::homePath() + QLatin1String("/.cache"); + xdgCacheHome += QLatin1Char('/') + configurationName(); + result = xdgCacheHome; +#endif +#endif + if (!result.endsWith("/")) + result += "/"; + return result; +} + +QString Private::DefaultProfile::configLocation() const +{ + QString result; +#if defined(Q_OS_WIN) || defined(Q_OS_OS2) + result = dataLocation() + QLatin1String("config"); +#else +#ifdef Q_OS_MAC + result = QDir::homePath() + QLatin1String("/Library/Preferences/") + configurationName(); +#else + QString xdgConfigHome = QLatin1String(qgetenv("XDG_CONFIG_HOME")); + if (xdgConfigHome.isEmpty()) + xdgConfigHome = QDir::homePath() + QLatin1String("/.config"); + xdgConfigHome += QLatin1Char('/') + configurationName(); + result = xdgConfigHome; +#endif +#endif + return result; +} + +QString Private::DefaultProfile::dataLocation() const +{ + QString result; +#if defined(Q_OS_WIN) + wchar_t path[MAX_PATH + 1] = {L'\0'}; + if (SHGetSpecialFolderPathW(0, path, CSIDL_LOCAL_APPDATA, FALSE)) + result = Utils::Fs::fromNativePath(QString::fromWCharArray(path)); + if (!QCoreApplication::applicationName().isEmpty()) + result += QLatin1String("/") + qApp->applicationName(); +#elif defined(Q_OS_MAC) + FSRef ref; + OSErr err = FSFindFolder(kUserDomain, kApplicationSupportFolderType, false, &ref); + if (err) + return QString(); + QByteArray ba(2048, 0); + if (FSRefMakePath(&ref, reinterpret_cast(ba.data()), ba.size()) == noErr) + result = QString::fromUtf8(ba).normalized(QString::NormalizationForm_C); + result += QLatin1Char('/') + qApp->applicationName(); +#else + QString xdgDataHome = QLatin1String(qgetenv("XDG_DATA_HOME")); + if (xdgDataHome.isEmpty()) + xdgDataHome = QDir::homePath() + QLatin1String("/.local/share"); + xdgDataHome += QLatin1String("/data/") + + qApp->applicationName(); + result = xdgDataHome; +#endif + if (!result.endsWith("/")) + result += "/"; + return result; +} + +QString Private::DefaultProfile::downloadLocation() const +{ +#if defined(Q_OS_WIN) + if (QSysInfo::windowsVersion() <= QSysInfo::WV_XP) // Windows XP + return QDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)).absoluteFilePath( + QCoreApplication::translate("fsutils", "Downloads")); +#endif + return QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); +} + +SettingsPtr Private::DefaultProfile::applicationSettings(const QString &name) const +{ +#if defined(Q_OS_WIN) || defined(Q_OS_MAC) + return SettingsPtr(new QSettings(QSettings::IniFormat, QSettings::UserScope, configurationName(), name)); +#else + return SettingsPtr(new QSettings(configurationName(), name)); +#endif +} + +Private::CustomProfile::CustomProfile(const QString &rootPath, const QString &configurationName) + : Profile {configurationName} + , m_rootDirectory {QDir(rootPath).absoluteFilePath(this->configurationName())} +{ +} + +QString Private::CustomProfile::baseDirectory() const +{ + return m_rootDirectory.canonicalPath(); +} + +QString Private::CustomProfile::cacheLocation() const +{ + return m_rootDirectory.absoluteFilePath(QLatin1String(cacheDirName)); +} + +QString Private::CustomProfile::configLocation() const +{ + return m_rootDirectory.absoluteFilePath(QLatin1String(configDirName)); +} + +QString Private::CustomProfile::dataLocation() const +{ + return m_rootDirectory.absoluteFilePath(QLatin1String(dataDirName)); +} + +QString Private::CustomProfile::downloadLocation() const +{ + return m_rootDirectory.absoluteFilePath(QLatin1String(downloadsDirName)); +} + +SettingsPtr Private::CustomProfile::applicationSettings(const QString &name) const +{ + // here we force QSettings::IniFormat format always because we need it to be portable across platforms +#if defined(Q_OS_WIN) || defined(Q_OS_MAC) + constexpr const char *CONF_FILE_EXTENSION = ".ini"; +#else + constexpr const char *CONF_FILE_EXTENSION = ".conf"; +#endif + const QString settingsFileName {QDir(configLocation()).absoluteFilePath(name + QLatin1String(CONF_FILE_EXTENSION))}; + return SettingsPtr(new QSettings(settingsFileName, QSettings::IniFormat)); +} + +QString Private::NoConvertConverter::fromPortablePath(const QString &portablePath) const +{ + return portablePath; +} + +QString Private::NoConvertConverter::toPortablePath(const QString &path) const +{ + return path; +} + +Private::Converter::Converter(const QString &basePath) + : m_baseDir {basePath} +{ + m_baseDir.makeAbsolute(); +} + +QString Private::Converter::toPortablePath(const QString &path) const +{ + if (path.isEmpty() || m_baseDir.path().isEmpty()) + return path; + +#ifdef Q_OS_WIN + if (QDir::isAbsolutePath(path)) { + QChar driveLeter = path[0].toUpper(); + QChar baseDriveLetter = m_baseDir.path()[0].toUpper(); + bool onSameDrive = (driveLeter.category() == QChar::Letter_Uppercase) && (driveLeter == baseDriveLetter); + if (!onSameDrive) + return path; + } +#endif + return m_baseDir.relativeFilePath(path); +} + +QString Private::Converter::fromPortablePath(const QString &portablePath) const +{ + if (QDir::isAbsolutePath(portablePath)) + return portablePath; + + return QDir::cleanPath(m_baseDir.absoluteFilePath(portablePath)); +} diff --git a/src/base/private/profile_p.h b/src/base/private/profile_p.h new file mode 100644 index 000000000..46f381b9c --- /dev/null +++ b/src/base/private/profile_p.h @@ -0,0 +1,121 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2016 Eugene Shalygin + * Copyright (C) 2012 Christophe Dumez + * + * 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 QBT_PROFILE_P_H +#define QBT_PROFILE_P_H + +#include +#include "base/profile.h" + +namespace Private +{ + class Profile + { + public: + virtual QString baseDirectory() const = 0; + virtual QString cacheLocation() const = 0; + virtual QString configLocation() const = 0; + virtual QString dataLocation() const = 0; + virtual QString downloadLocation() const = 0; + virtual SettingsPtr applicationSettings(const QString &name) const = 0; + + virtual ~Profile() = default; + + QString configurationName() const; + + protected: + Profile(const QString &configurationName); + + private: + QString m_configurationName; + }; + + /// Default implementation. Takes paths from system + class DefaultProfile: public Profile + { + public: + DefaultProfile(const QString &configurationName); + + QString baseDirectory() const override; + QString cacheLocation() const override; + QString configLocation() const override; + QString dataLocation() const override; + QString downloadLocation() const override; + SettingsPtr applicationSettings(const QString &name) const override; + }; + + /// Custom tree: creates directories under the specified root directory + class CustomProfile: public Profile + { + public: + CustomProfile(const QString &rootPath, const QString &configurationName); + + QString baseDirectory() const override; + QString cacheLocation() const override; + QString configLocation() const override; + QString dataLocation() const override; + QString downloadLocation() const override; + SettingsPtr applicationSettings(const QString &name) const override; + + private: + QDir m_rootDirectory; + static constexpr const char *cacheDirName = "cache"; + static constexpr const char *configDirName = "config"; + static constexpr const char *dataDirName = "data"; + static constexpr const char *downloadsDirName = "downloads"; + }; + + class PathConverter + { + public: + virtual QString toPortablePath(const QString &path) const = 0; + virtual QString fromPortablePath(const QString &portablePath) const = 0; + virtual ~PathConverter() = default; + }; + + class NoConvertConverter: public PathConverter + { + public: + QString toPortablePath(const QString &path) const override; + QString fromPortablePath(const QString &portablePath) const override; + }; + + class Converter: public PathConverter + { + public: + Converter(const QString &basePath); + QString toPortablePath(const QString &path) const override; + QString fromPortablePath(const QString &portablePath) const override; + + private: + QDir m_baseDir; + }; +} +#endif // QBT_PROFILE_P_H diff --git a/src/base/profile.cpp b/src/base/profile.cpp new file mode 100644 index 000000000..112303300 --- /dev/null +++ b/src/base/profile.cpp @@ -0,0 +1,118 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2016 Eugene Shalygin + * Copyright (C) 2012 Christophe Dumez + * + * 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 "profile.h" + +#include + +#include "private/profile_p.h" + +Profile *Profile::m_instance = nullptr; + +Profile::Profile(Private::Profile *impl, Private::PathConverter *pathConverter) + : m_profileImpl(impl) + , m_pathConverterImpl(pathConverter) +{ + ensureDirectoryExists(SpecialFolder::Cache); + ensureDirectoryExists(SpecialFolder::Config); + ensureDirectoryExists(SpecialFolder::Data); + ensureDirectoryExists(SpecialFolder::Downloads); +} + +// to generate correct call to ProfilePrivate::~ProfileImpl() +Profile::~Profile() = default; + +void Profile::initialize(const QString &rootProfilePath, const QString &configurationName, + bool convertPathsToProfileRelative) +{ + QScopedPointer profile(rootProfilePath.isEmpty() + ? static_cast(new Private::DefaultProfile(configurationName)) + : static_cast(new Private::CustomProfile(rootProfilePath, configurationName))); + + QScopedPointer converter(convertPathsToProfileRelative + ? static_cast(new Private::Converter(profile->baseDirectory())) + : static_cast(new Private::NoConvertConverter())); + m_instance = new Profile(profile.take(), converter.take()); +} + +const Profile &Profile::instance() +{ + return *m_instance; +} + +QString Profile::location(SpecialFolder folder) const +{ + QString result; + switch (folder) { + case SpecialFolder::Cache: + result = m_profileImpl->cacheLocation(); + break; + case SpecialFolder::Config: + result = m_profileImpl->configLocation(); + break; + case SpecialFolder::Data: + result = m_profileImpl->dataLocation(); + break; + case SpecialFolder::Downloads: + result = m_profileImpl->downloadLocation(); + break; + } + + if (!result.endsWith(QLatin1Char('/'))) + result += QLatin1Char('/'); + return result; +} + +QString Profile::configurationName() const +{ + return m_profileImpl->configurationName(); +} + +SettingsPtr Profile::applicationSettings(const QString &name) const +{ + return m_profileImpl->applicationSettings(name); +} + +void Profile::ensureDirectoryExists(SpecialFolder folder) +{ + QString locationPath = location(folder); + if (!locationPath.isEmpty() && !QDir().mkpath(locationPath)) + qFatal("Could not create required directory '%s'", qPrintable(locationPath)); +} + +QString Profile::toPortablePath(const QString &absolutePath) const +{ + return m_pathConverterImpl->toPortablePath(absolutePath); +} + +QString Profile::fromPortablePath(const QString &portablePath) const +{ + return m_pathConverterImpl->fromPortablePath(portablePath); +} diff --git a/src/base/profile.h b/src/base/profile.h new file mode 100644 index 000000000..a278af39f --- /dev/null +++ b/src/base/profile.h @@ -0,0 +1,92 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2016 Eugene Shalygin + * Copyright (C) 2012 Christophe Dumez + * + * 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 QBT_PROFILE_H +#define QBT_PROFILE_H + +#include + +#include +#include +#include + +class Application; + +namespace Private +{ + class Profile; + class PathConverter; +} + +using SettingsPtr = std::unique_ptr; + +enum class SpecialFolder +{ + Cache, + Config, + Data, + Downloads +}; + +class Profile +{ +public: + QString location(SpecialFolder folder) const; + SettingsPtr applicationSettings(const QString &name) const; + + /// Returns either default name for configuration file (QCoreApplication::applicationName()) + /// or the value, supplied via parameters + QString configurationName() const; + + QString toPortablePath(const QString &absolutePath) const; + QString fromPortablePath(const QString &portablePath) const; + + static const Profile &instance(); + +private: + Profile(Private::Profile *impl, Private::PathConverter *pathConverter); + ~Profile(); + + friend class ::Application; + static void initialize(const QString &rootProfilePath, const QString &configurationName, + bool convertPathsToProfileRelative); + void ensureDirectoryExists(SpecialFolder folder); + + QScopedPointer m_profileImpl; + QScopedPointer m_pathConverterImpl; + static Profile *m_instance; +}; + +inline QString specialFolderLocation(SpecialFolder folder) +{ + return Profile::instance().location(folder); +} + +#endif // QBT_PROFILE_H diff --git a/src/base/rss/rssdownloadrulelist.cpp b/src/base/rss/rssdownloadrulelist.cpp index 49f16ea92..865ce4f02 100644 --- a/src/base/rss/rssdownloadrulelist.cpp +++ b/src/base/rss/rssdownloadrulelist.cpp @@ -28,13 +28,14 @@ * Contact : chris@qbittorrent.org */ +#include "rssdownloadrulelist.h" + #include #include #include #include "base/preferences.h" -#include "base/qinisettings.h" -#include "rssdownloadrulelist.h" +#include "base/profile.h" using namespace Rss; @@ -66,14 +67,14 @@ void DownloadRuleList::replace(DownloadRuleList *other) void DownloadRuleList::saveRulesToStorage() { - QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); - qBTRSS.setValue("download_rules", toVariantHash()); + SettingsPtr qBTRSS = Profile::instance().applicationSettings(QLatin1String("qBittorrent-rss")); + qBTRSS->setValue("download_rules", toVariantHash()); } void DownloadRuleList::loadRulesFromStorage() { - QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); - loadRulesFromVariantHash(qBTRSS.value("download_rules").toHash()); + SettingsPtr qBTRSS = Profile::instance().applicationSettings(QLatin1String("qBittorrent-rss")); + loadRulesFromVariantHash(qBTRSS->value("download_rules").toHash()); } QVariantHash DownloadRuleList::toVariantHash() const diff --git a/src/base/rss/rssfeed.cpp b/src/base/rss/rssfeed.cpp index 60d0489e3..30038be38 100644 --- a/src/base/rss/rssfeed.cpp +++ b/src/base/rss/rssfeed.cpp @@ -30,11 +30,13 @@ * Contact: chris@qbittorrent.org, arnaud@qbittorrent.org */ +#include "rssfeed.h" + #include #include "base/preferences.h" -#include "base/qinisettings.h" #include "base/logger.h" +#include "base/profile.h" #include "base/bittorrent/session.h" #include "base/bittorrent/magneturi.h" #include "base/utils/misc.h" @@ -46,7 +48,6 @@ #include "rssarticle.h" #include "rssfolder.h" #include "rssmanager.h" -#include "rssfeed.h" namespace Rss { @@ -99,7 +100,7 @@ void Feed::saveItemsToDisk() m_dirty = false; - QIniSettings qBTRSSFeeds("qBittorrent", "qBittorrent-rss-feeds"); + SettingsPtr qBTRSSFeeds = Profile::instance().applicationSettings(QLatin1String("qBittorrent-rss-feeds")); QVariantList oldItems; ArticleHash::ConstIterator it = m_articles.begin(); @@ -107,16 +108,15 @@ void Feed::saveItemsToDisk() for (; it != itend; ++it) oldItems << it.value()->toHash(); qDebug("Saving %d old items for feed %s", oldItems.size(), qPrintable(displayName())); - QHash allOldItems = qBTRSSFeeds.value("old_items", QHash()).toHash(); + QHash allOldItems = qBTRSSFeeds->value("old_items", QHash()).toHash(); allOldItems[m_url] = oldItems; - qBTRSSFeeds.setValue("old_items", allOldItems); + qBTRSSFeeds->setValue("old_items", allOldItems); } void Feed::loadItemsFromDisk() { - QIniSettings qBTRSSFeeds("qBittorrent", "qBittorrent-rss-feeds"); - QHash allOldItems = qBTRSSFeeds.value("old_items", QHash()).toHash(); - + SettingsPtr qBTRSSFeeds = Profile::instance().applicationSettings(QLatin1String("qBittorrent-rss-feeds")); + QHash allOldItems = qBTRSSFeeds->value("old_items", QHash()).toHash(); const QVariantList oldItems = allOldItems.value(m_url, QVariantList()).toList(); qDebug("Loading %d old items for feed %s", oldItems.size(), qPrintable(displayName())); @@ -193,22 +193,22 @@ QString Feed::id() const void Feed::removeAllSettings() { qDebug() << "Removing all settings / history for feed: " << m_url; - QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss"); - QVariantHash feedsWDownloader = qBTRSS.value("downloader_on", QVariantHash()).toHash(); + SettingsPtr qBTRSS = Profile::instance().applicationSettings(QLatin1String("qBittorrent-rss")); + QVariantHash feedsWDownloader = qBTRSS->value("downloader_on", QVariantHash()).toHash(); if (feedsWDownloader.contains(m_url)) { feedsWDownloader.remove(m_url); - qBTRSS.setValue("downloader_on", feedsWDownloader); + qBTRSS->setValue("downloader_on", feedsWDownloader); } - QVariantHash allFeedsFilters = qBTRSS.value("feed_filters", QVariantHash()).toHash(); + QVariantHash allFeedsFilters = qBTRSS->value("feed_filters", QVariantHash()).toHash(); if (allFeedsFilters.contains(m_url)) { allFeedsFilters.remove(m_url); - qBTRSS.setValue("feed_filters", allFeedsFilters); + qBTRSS->setValue("feed_filters", allFeedsFilters); } - QIniSettings qBTRSSFeeds("qBittorrent", "qBittorrent-rss-feeds"); - QVariantHash allOldItems = qBTRSSFeeds.value("old_items", QVariantHash()).toHash(); + SettingsPtr qBTRSSFeeds = Profile::instance().applicationSettings(QLatin1String("qBittorrent-rss-feeds")); + QVariantHash allOldItems = qBTRSSFeeds->value("old_items", QVariantHash()).toHash(); if (allOldItems.contains(m_url)) { allOldItems.remove(m_url); - qBTRSSFeeds.setValue("old_items", allOldItems); + qBTRSSFeeds->setValue("old_items", allOldItems); } } diff --git a/src/base/searchengine.cpp b/src/base/searchengine.cpp index 88e922c7e..df86e2312 100644 --- a/src/base/searchengine.cpp +++ b/src/base/searchengine.cpp @@ -37,6 +37,7 @@ #include "base/utils/fs.h" #include "base/utils/misc.h" #include "base/preferences.h" +#include "base/profile.h" #include "base/net/downloadmanager.h" #include "base/net/downloadhandler.h" #include "searchengine.h" @@ -325,7 +326,7 @@ QString SearchEngine::engineLocation() QString folder = "nova"; if (Utils::Misc::pythonVersion() >= 3) folder = "nova3"; - const QString location = Utils::Fs::expandPathAbs(Utils::Fs::QDesktopServicesDataLocation() + folder); + const QString location = Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + folder); QDir locationDir(location); if (!locationDir.exists()) locationDir.mkpath(locationDir.absolutePath()); diff --git a/src/base/settingsstorage.cpp b/src/base/settingsstorage.cpp index 99b5341ec..e7323c2ef 100644 --- a/src/base/settingsstorage.cpp +++ b/src/base/settingsstorage.cpp @@ -33,9 +33,9 @@ #include #include #include -#include #include "logger.h" +#include "profile.h" #include "utils/fs.h" namespace @@ -62,16 +62,6 @@ namespace QString deserialize(const QString &name, QVariantHash &data); QString serialize(const QString &name, const QVariantHash &data); - using SettingsPtr = std::unique_ptr; - SettingsPtr createSettings(const QString &name) - { -#if defined(Q_OS_WIN) || defined(Q_OS_MAC) - return SettingsPtr(new QSettings(QSettings::IniFormat, QSettings::UserScope, "qBittorrent", name)); -#else - return SettingsPtr(new QSettings("qBittorrent", name)); -#endif - } - QString m_name; }; @@ -290,7 +280,7 @@ bool TransactionalSettings::write(const QVariantHash &data) QString TransactionalSettings::deserialize(const QString &name, QVariantHash &data) { - SettingsPtr settings = createSettings(name); + SettingsPtr settings = Profile::instance().applicationSettings(name); if (settings->allKeys().isEmpty()) return QString(); @@ -306,7 +296,7 @@ QString TransactionalSettings::deserialize(const QString &name, QVariantHash &da QString TransactionalSettings::serialize(const QString &name, const QVariantHash &data) { - SettingsPtr settings = createSettings(name); + SettingsPtr settings = Profile::instance().applicationSettings(name); for (auto i = data.begin(); i != data.end(); ++i) settings->setValue(i.key(), i.value()); diff --git a/src/base/utils/fs.cpp b/src/base/utils/fs.cpp index 372048e4d..eca53876d 100644 --- a/src/base/utils/fs.cpp +++ b/src/base/utils/fs.cpp @@ -28,19 +28,15 @@ * Contact : chris@qbittorrent.org */ +#include "fs.h" + #include #include #include #include -#include #include #include -#ifdef Q_OS_MAC -#include -#include -#endif - #ifndef Q_OS_WIN #if defined(Q_OS_MAC) || defined(Q_OS_FREEBSD) #include @@ -51,16 +47,9 @@ #include #endif #else -#include -#include +#include #endif -#include - - -#include "misc.h" -#include "fs.h" - /** * Converts a path to a string suitable for display. * This function makes sure the directory separator used is consistent @@ -326,85 +315,6 @@ QString Utils::Fs::expandPathAbs(const QString& path) return ret; } -QString Utils::Fs::QDesktopServicesDataLocation() -{ - QString result; -#if defined(Q_OS_WIN) - wchar_t path[MAX_PATH + 1] = {L'\0'}; - if (SHGetSpecialFolderPathW(0, path, CSIDL_LOCAL_APPDATA, FALSE)) - result = fromNativePath(QString::fromWCharArray(path)); - if (!QCoreApplication::applicationName().isEmpty()) - result += QLatin1String("/") + qApp->applicationName(); -#elif defined(Q_OS_MAC) - FSRef ref; - OSErr err = FSFindFolder(kUserDomain, kApplicationSupportFolderType, false, &ref); - if (err) - return QString(); - QByteArray ba(2048, 0); - if (FSRefMakePath(&ref, reinterpret_cast(ba.data()), ba.size()) == noErr) - result = QString::fromUtf8(ba).normalized(QString::NormalizationForm_C); - result += QLatin1Char('/') + qApp->applicationName(); -#else - QString xdgDataHome = QLatin1String(qgetenv("XDG_DATA_HOME")); - if (xdgDataHome.isEmpty()) - xdgDataHome = QDir::homePath() + QLatin1String("/.local/share"); - xdgDataHome += QLatin1String("/data/") - + qApp->applicationName(); - result = xdgDataHome; -#endif - if (!result.endsWith("/")) - result += "/"; - return result; -} - -QString Utils::Fs::QDesktopServicesCacheLocation() -{ - QString result; -#if defined(Q_OS_WIN) || defined(Q_OS_OS2) - result = QDesktopServicesDataLocation() + QLatin1String("cache"); -#else -#ifdef Q_OS_MAC - // http://developer.apple.com/documentation/Carbon/Reference/Folder_Manager/Reference/reference.html - FSRef ref; - OSErr err = FSFindFolder(kUserDomain, kCachedDataFolderType, false, &ref); - if (err) - return QString(); - QByteArray ba(2048, 0); - if (FSRefMakePath(&ref, reinterpret_cast(ba.data()), ba.size()) == noErr) - result = QString::fromUtf8(ba).normalized(QString::NormalizationForm_C); - result += QLatin1Char('/') + qApp->applicationName(); -#else - QString xdgCacheHome = QLatin1String(qgetenv("XDG_CACHE_HOME")); - if (xdgCacheHome.isEmpty()) - xdgCacheHome = QDir::homePath() + QLatin1String("/.cache"); - xdgCacheHome += QLatin1Char('/') + QCoreApplication::applicationName(); - result = xdgCacheHome; -#endif -#endif - if (!result.endsWith("/")) - result += "/"; - return result; -} - -QString Utils::Fs::QDesktopServicesDownloadLocation() -{ -#if defined(Q_OS_WIN) - if (QSysInfo::windowsVersion() <= QSysInfo::WV_XP) // Windows XP - return QDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)).absoluteFilePath( - QCoreApplication::translate("fsutils", "Downloads")); -#endif - return QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); -} - -QString Utils::Fs::cacheLocation() -{ - QString location = expandPathAbs(QDesktopServicesCacheLocation()); - QDir locationDir(location); - if (!locationDir.exists()) - locationDir.mkpath(locationDir.absolutePath()); - return location; -} - QString Utils::Fs::tempPath() { static const QString path = QDir::tempPath() + "/.qBittorrent/"; diff --git a/src/base/utils/fs.h b/src/base/utils/fs.h index 3ee9fec5a..56320c1dc 100644 --- a/src/base/utils/fs.h +++ b/src/base/utils/fs.h @@ -60,13 +60,6 @@ namespace Utils bool forceRemove(const QString& file_path); void removeDirRecursive(const QString& dirName); - /* Ported from Qt4 to drop dependency on QtGui */ - QString QDesktopServicesDataLocation(); - QString QDesktopServicesCacheLocation(); - QString QDesktopServicesDownloadLocation(); - /* End of Qt4 code */ - - QString cacheLocation(); QString tempPath(); } } diff --git a/src/base/utils/misc.cpp b/src/base/utils/misc.cpp index 6b422bace..981892f0c 100644 --- a/src/base/utils/misc.cpp +++ b/src/base/utils/misc.cpp @@ -35,7 +35,6 @@ #include #include #include -#include #include #include #include diff --git a/src/gui/rss/htmlbrowser.cpp b/src/gui/rss/htmlbrowser.cpp index 82ea75821..e378b0405 100644 --- a/src/gui/rss/htmlbrowser.cpp +++ b/src/gui/rss/htmlbrowser.cpp @@ -10,14 +10,14 @@ #include #include -#include "base/utils/fs.h" +#include "base/profile.h" HtmlBrowser::HtmlBrowser(QWidget* parent) : QTextBrowser(parent) { m_netManager = new QNetworkAccessManager(this); m_diskCache = new QNetworkDiskCache(this); - m_diskCache->setCacheDirectory(QDir::cleanPath(Utils::Fs::cacheLocation() + "/rss")); + m_diskCache->setCacheDirectory(QDir::cleanPath(specialFolderLocation(SpecialFolder::Cache) + "/rss")); m_diskCache->setMaximumCacheSize(50 * 1024 * 1024); qDebug() << "HtmlBrowser cache path:" << m_diskCache->cacheDirectory() << " max size:" << m_diskCache->maximumCacheSize() / 1024 / 1024 << "MB"; m_netManager->setCache(m_diskCache);