From a8d95dd8bdbc1ff25cf20d4309670b8649975bd4 Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Fri, 13 May 2016 20:32:47 +0200 Subject: [PATCH] Save relative paths in fastresume files Conditionally change absolute paths to relative in the fastresume data files. The condition is specified by user via a command line parameter and paths are relative to the profile dir. On Windows the convertion to relative path is performed if the path and the profile are on the same drive only. --- src/app/application.cpp | 6 +++- src/app/options.cpp | 11 +++++-- src/app/options.h | 1 + src/base/bittorrent/session.cpp | 3 +- src/base/bittorrent/torrenthandle.cpp | 7 ++++- src/base/private/profile_p.cpp | 41 +++++++++++++++++++++++++++ src/base/private/profile_p.h | 26 +++++++++++++++++ src/base/profile.cpp | 41 +++++++++++++++++++-------- src/base/profile.h | 12 ++++++-- 9 files changed, 127 insertions(+), 21 deletions(-) diff --git a/src/app/application.cpp b/src/app/application.cpp index ee000292a..061604f71 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -111,7 +111,8 @@ Application::Application(const QString &id, int &argc, char **argv) ? QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(DEFAULT_PORTABLE_MODE_PROFILE_DIR) : m_commandLineArgs.profileDir; - Profile::initialize(profileDir, m_commandLineArgs.configurationName); + Profile::initialize(profileDir, m_commandLineArgs.configurationName, + m_commandLineArgs.relativeFastresumePaths || m_commandLineArgs.portableMode); Logger::initInstance(); SettingsStorage::initInstance(); @@ -660,4 +661,7 @@ 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/options.cpp b/src/app/options.cpp index 5cbf08b22..857ddceea 100644 --- a/src/app/options.cpp +++ b/src/app/options.cpp @@ -246,6 +246,7 @@ namespace 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) @@ -260,6 +261,7 @@ QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &en #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)) { @@ -301,6 +303,9 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args) 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; } @@ -362,9 +367,9 @@ QString makeUsage(const QString &prgName) 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 << PORTABLE_OPTION.usage() << QObject::tr("Shortcut for --profile=/profile") << '\n'; - stream << CONFIGURATION_OPTION.usage(QLatin1String("name")) << QObject::tr("Store configuration files in directories qBittorrent_") - << '\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'; diff --git a/src/app/options.h b/src/app/options.h index 2c1e2d67b..ad15a4e78 100644 --- a/src/app/options.h +++ b/src/app/options.h @@ -53,6 +53,7 @@ struct QBtCommandLineParameters #endif int webUiPort; QString profileDir; + bool relativeFastresumePaths; bool portableMode; QString configurationName; QStringList torrents; diff --git a/src/base/bittorrent/session.cpp b/src/base/bittorrent/session.cpp index c94683956..56474550e 100644 --- a/src/base/bittorrent/session.cpp +++ b/src/base/bittorrent/session.cpp @@ -3633,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/private/profile_p.cpp b/src/base/private/profile_p.cpp index fb2f0f03c..d57bf53a5 100644 --- a/src/base/private/profile_p.cpp +++ b/src/base/private/profile_p.cpp @@ -207,3 +207,44 @@ SettingsPtr Private::CustomProfile::applicationSettings(const QString &name) con 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 index e37b92923..46f381b9c 100644 --- a/src/base/private/profile_p.h +++ b/src/base/private/profile_p.h @@ -91,5 +91,31 @@ namespace Private 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 index dc463dc40..112303300 100644 --- a/src/base/profile.cpp +++ b/src/base/profile.cpp @@ -36,8 +36,9 @@ Profile *Profile::m_instance = nullptr; -Profile::Profile(Private::Profile *impl) - : m_impl(impl) +Profile::Profile(Private::Profile *impl, Private::PathConverter *pathConverter) + : m_profileImpl(impl) + , m_pathConverterImpl(pathConverter) { ensureDirectoryExists(SpecialFolder::Cache); ensureDirectoryExists(SpecialFolder::Config); @@ -48,11 +49,17 @@ Profile::Profile(Private::Profile *impl) // to generate correct call to ProfilePrivate::~ProfileImpl() Profile::~Profile() = default; -void Profile::initialize(const QString &rootProfilePath, const QString &configurationName) +void Profile::initialize(const QString &rootProfilePath, const QString &configurationName, + bool convertPathsToProfileRelative) { - m_instance = new Profile(rootProfilePath.isEmpty() - ? static_cast(new Private::DefaultProfile(configurationName)) - : static_cast(new Private::CustomProfile(rootProfilePath, configurationName))); + 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() @@ -65,16 +72,16 @@ QString Profile::location(SpecialFolder folder) const QString result; switch (folder) { case SpecialFolder::Cache: - result = m_impl->cacheLocation(); + result = m_profileImpl->cacheLocation(); break; case SpecialFolder::Config: - result = m_impl->configLocation(); + result = m_profileImpl->configLocation(); break; case SpecialFolder::Data: - result = m_impl->dataLocation(); + result = m_profileImpl->dataLocation(); break; case SpecialFolder::Downloads: - result = m_impl->downloadLocation(); + result = m_profileImpl->downloadLocation(); break; } @@ -85,12 +92,12 @@ QString Profile::location(SpecialFolder folder) const QString Profile::configurationName() const { - return m_impl->configurationName(); + return m_profileImpl->configurationName(); } SettingsPtr Profile::applicationSettings(const QString &name) const { - return m_impl->applicationSettings(name); + return m_profileImpl->applicationSettings(name); } void Profile::ensureDirectoryExists(SpecialFolder folder) @@ -99,3 +106,13 @@ void Profile::ensureDirectoryExists(SpecialFolder 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 index 72fed6eb2..a278af39f 100644 --- a/src/base/profile.h +++ b/src/base/profile.h @@ -42,6 +42,7 @@ class Application; namespace Private { class Profile; + class PathConverter; } using SettingsPtr = std::unique_ptr; @@ -64,17 +65,22 @@ public: /// 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); + Profile(Private::Profile *impl, Private::PathConverter *pathConverter); ~Profile(); friend class ::Application; - static void initialize(const QString &rootProfilePath, const QString &configurationName); + static void initialize(const QString &rootProfilePath, const QString &configurationName, + bool convertPathsToProfileRelative); void ensureDirectoryExists(SpecialFolder folder); - QScopedPointer m_impl; + QScopedPointer m_profileImpl; + QScopedPointer m_pathConverterImpl; static Profile *m_instance; };