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