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; };