mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-08-20 21:33:27 -07:00
feat: Add tilde expansion for home directory paths
Enables using ~ and ~/path in configurations instead of hard-coded absolute paths, making settings portable across users. - Add Utils::Fs::expandTilde() function - Integrate tilde expansion in session path handling - Support category path tilde expansion - Add comprehensive test coverage
This commit is contained in:
parent
7a1a214f73
commit
242d33ab53
5 changed files with 108 additions and 8 deletions
|
@ -552,8 +552,8 @@ SessionImpl::SessionImpl(QObject *parent)
|
|||
, m_storedTags(BITTORRENT_SESSION_KEY(u"Tags"_s))
|
||||
, m_shareLimitAction(BITTORRENT_SESSION_KEY(u"ShareLimitAction"_s), ShareLimitAction::Stop
|
||||
, [](const ShareLimitAction action) { return (action == ShareLimitAction::Default) ? ShareLimitAction::Stop : action; })
|
||||
, m_savePath(BITTORRENT_SESSION_KEY(u"DefaultSavePath"_s), specialFolderLocation(SpecialFolder::Downloads))
|
||||
, m_downloadPath(BITTORRENT_SESSION_KEY(u"TempPath"_s), (savePath() / Path(u"temp"_s)))
|
||||
, m_savePath(BITTORRENT_SESSION_KEY(u"DefaultSavePath"_s), specialFolderLocation(SpecialFolder::Downloads), Utils::Fs::expandTilde)
|
||||
, m_downloadPath(BITTORRENT_SESSION_KEY(u"TempPath"_s), (savePath() / Path(u"temp"_s)), Utils::Fs::expandTilde)
|
||||
, m_isDownloadPathEnabled(BITTORRENT_SESSION_KEY(u"TempPathEnabled"_s), false)
|
||||
, m_isSubcategoriesEnabled(BITTORRENT_SESSION_KEY(u"SubcategoriesEnabled"_s), false)
|
||||
, m_useCategoryPathsInManualMode(BITTORRENT_SESSION_KEY(u"UseCategoryPathsInManualMode"_s), false)
|
||||
|
@ -1006,6 +1006,14 @@ bool SessionImpl::addCategory(const QString &name, const CategoryOptions &option
|
|||
if (!isValidCategoryName(name) || m_categories.contains(name))
|
||||
return false;
|
||||
|
||||
// Expand tildes in category save path
|
||||
CategoryOptions expandedOptions = options;
|
||||
expandedOptions.savePath = Utils::Fs::expandTilde(options.savePath);
|
||||
if (expandedOptions.downloadPath.has_value())
|
||||
{
|
||||
expandedOptions.downloadPath->path = Utils::Fs::expandTilde(expandedOptions.downloadPath->path);
|
||||
}
|
||||
|
||||
if (isSubcategoriesEnabled())
|
||||
{
|
||||
for (const QString &parent : asConst(expandCategory(name)))
|
||||
|
@ -1018,7 +1026,7 @@ bool SessionImpl::addCategory(const QString &name, const CategoryOptions &option
|
|||
}
|
||||
}
|
||||
|
||||
m_categories[name] = options;
|
||||
m_categories[name] = expandedOptions;
|
||||
storeCategories();
|
||||
emit categoryAdded(name);
|
||||
|
||||
|
@ -1031,8 +1039,16 @@ bool SessionImpl::editCategory(const QString &name, const CategoryOptions &optio
|
|||
if (it == m_categories.end())
|
||||
return false;
|
||||
|
||||
// Expand tildes in category save path
|
||||
CategoryOptions expandedOptions = options;
|
||||
expandedOptions.savePath = Utils::Fs::expandTilde(options.savePath);
|
||||
if (expandedOptions.downloadPath.has_value())
|
||||
{
|
||||
expandedOptions.downloadPath->path = Utils::Fs::expandTilde(expandedOptions.downloadPath->path);
|
||||
}
|
||||
|
||||
CategoryOptions ¤tOptions = it.value();
|
||||
if (options == currentOptions)
|
||||
if (expandedOptions == currentOptions)
|
||||
return false;
|
||||
|
||||
if (isDisableAutoTMMWhenCategorySavePathChanged())
|
||||
|
@ -1047,7 +1063,7 @@ bool SessionImpl::editCategory(const QString &name, const CategoryOptions &optio
|
|||
}
|
||||
}
|
||||
|
||||
currentOptions = options;
|
||||
currentOptions = expandedOptions;
|
||||
storeCategories();
|
||||
|
||||
for (TorrentImpl *const torrent : asConst(m_torrents))
|
||||
|
@ -3281,7 +3297,8 @@ void SessionImpl::removeTorrentsQueue()
|
|||
|
||||
void SessionImpl::setSavePath(const Path &path)
|
||||
{
|
||||
const auto newPath = (path.isAbsolute() ? path : (specialFolderLocation(SpecialFolder::Downloads) / path));
|
||||
const Path expandedPath = Utils::Fs::expandTilde(path);
|
||||
const auto newPath = (expandedPath.isAbsolute() ? expandedPath : (specialFolderLocation(SpecialFolder::Downloads) / expandedPath));
|
||||
if (newPath == m_savePath)
|
||||
return;
|
||||
|
||||
|
@ -3321,7 +3338,8 @@ void SessionImpl::setSavePath(const Path &path)
|
|||
|
||||
void SessionImpl::setDownloadPath(const Path &path)
|
||||
{
|
||||
const Path newPath = (path.isAbsolute() ? path : (savePath() / Path(u"temp"_s) / path));
|
||||
const Path expandedPath = Utils::Fs::expandTilde(path);
|
||||
const Path newPath = (expandedPath.isAbsolute() ? expandedPath : (savePath() / Path(u"temp"_s) / expandedPath));
|
||||
if (newPath == m_downloadPath)
|
||||
return;
|
||||
|
||||
|
@ -5581,7 +5599,15 @@ void SessionImpl::loadCategories()
|
|||
for (auto it = jsonObj.constBegin(); it != jsonObj.constEnd(); ++it)
|
||||
{
|
||||
const QString &categoryName = it.key();
|
||||
const auto categoryOptions = CategoryOptions::fromJSON(it.value().toObject());
|
||||
auto categoryOptions = CategoryOptions::fromJSON(it.value().toObject());
|
||||
|
||||
// Expand tildes in loaded category paths
|
||||
categoryOptions.savePath = Utils::Fs::expandTilde(categoryOptions.savePath);
|
||||
if (categoryOptions.downloadPath.has_value())
|
||||
{
|
||||
categoryOptions.downloadPath->path = Utils::Fs::expandTilde(categoryOptions.downloadPath->path);
|
||||
}
|
||||
|
||||
m_categories[categoryName] = categoryOptions;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -219,6 +219,33 @@ Path Utils::Fs::tempPath()
|
|||
return path;
|
||||
}
|
||||
|
||||
Path Utils::Fs::expandTilde(const Path &path)
|
||||
{
|
||||
if (path.isEmpty())
|
||||
return path;
|
||||
|
||||
const QString pathStr = path.data();
|
||||
|
||||
// Check if path starts with ~ (tilde)
|
||||
if (pathStr.startsWith(u'~'))
|
||||
{
|
||||
if (pathStr.size() == 1)
|
||||
{
|
||||
// Just ~ means home directory
|
||||
return homePath();
|
||||
}
|
||||
else if (pathStr.at(1) == u'/')
|
||||
{
|
||||
// ~/something means home directory + something
|
||||
return homePath() / Path(pathStr.sliced(2));
|
||||
}
|
||||
// ~username is not supported for simplicity and security
|
||||
}
|
||||
|
||||
// Return original path if no tilde expansion needed
|
||||
return path;
|
||||
}
|
||||
|
||||
bool Utils::Fs::isRegularFile(const Path &path)
|
||||
{
|
||||
std::error_code ec;
|
||||
|
|
|
@ -71,4 +71,5 @@ namespace Utils::Fs
|
|||
|
||||
Path homePath();
|
||||
Path tempPath();
|
||||
Path expandTilde(const Path &path);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ set(testFiles
|
|||
testglobal.cpp
|
||||
testorderedset.cpp
|
||||
testpath.cpp
|
||||
testtildeexpansion.cpp
|
||||
testutilsbytearray.cpp
|
||||
testutilscompare.cpp
|
||||
testutilsdatetime.cpp
|
||||
|
|
45
test/testtildeexpansion.cpp
Normal file
45
test/testtildeexpansion.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#include <QDir>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QTest>
|
||||
|
||||
#include "base/path.h"
|
||||
#include "base/utils/fs.h"
|
||||
|
||||
class TestTildeExpansion : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void testExpandTilde_data();
|
||||
void testExpandTilde();
|
||||
};
|
||||
|
||||
void TestTildeExpansion::testExpandTilde_data()
|
||||
{
|
||||
QTest::addColumn<QString>("input");
|
||||
QTest::addColumn<QString>("expected");
|
||||
|
||||
const QString homeDir = QDir::homePath();
|
||||
|
||||
QTest::newRow("tilde only") << u"~"_s << homeDir;
|
||||
QTest::newRow("tilde with path") << u"~/Downloads"_s << (homeDir + u"/Downloads"_s);
|
||||
QTest::newRow("tilde with nested path") << u"~/Documents/qBittorrent"_s << (homeDir + u"/Documents/qBittorrent"_s);
|
||||
QTest::newRow("absolute path") << u"/absolute/path"_s << u"/absolute/path"_s;
|
||||
QTest::newRow("relative path") << u"relative/path"_s << u"relative/path"_s;
|
||||
QTest::newRow("empty path") << u""_s << u""_s;
|
||||
}
|
||||
|
||||
void TestTildeExpansion::testExpandTilde()
|
||||
{
|
||||
QFETCH(QString, input);
|
||||
QFETCH(QString, expected);
|
||||
|
||||
const Path inputPath(input);
|
||||
const Path expandedPath = Utils::Fs::expandTilde(inputPath);
|
||||
|
||||
QCOMPARE(expandedPath.data(), expected);
|
||||
}
|
||||
|
||||
QTEST_APPLESS_MAIN(TestTildeExpansion)
|
||||
#include "testtildeexpansion.moc"
|
Loading…
Add table
Add a link
Reference in a new issue