Implement class for handling filesystem paths

PR #15915.
This commit is contained in:
Vladimir Golovnev 2022-02-08 06:03:48 +03:00 committed by GitHub
parent facfa26eed
commit dd1bd8ad10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
131 changed files with 2252 additions and 1868 deletions

View File

@ -45,7 +45,6 @@
#include <QAtomicInt>
#include <QDebug>
#include <QDir>
#include <QLibraryInfo>
#include <QProcess>
@ -80,6 +79,7 @@
#include "base/torrentfileswatcher.h"
#include "base/utils/compare.h"
#include "base/utils/fs.h"
#include "base/path.h"
#include "base/utils/misc.h"
#include "base/version.h"
#include "applicationinstancemanager.h"
@ -105,7 +105,7 @@ namespace
const QString LOG_FOLDER = QStringLiteral("logs");
const QChar PARAMS_SEPARATOR = QLatin1Char('|');
const QString DEFAULT_PORTABLE_MODE_PROFILE_DIR = QStringLiteral("profile");
const Path DEFAULT_PORTABLE_MODE_PROFILE_DIR {QStringLiteral("profile")};
const int MIN_FILELOG_SIZE = 1024; // 1KiB
const int MAX_FILELOG_SIZE = 1000 * 1024 * 1024; // 1000MiB
@ -143,16 +143,16 @@ Application::Application(int &argc, char **argv)
QPixmapCache::setCacheLimit(PIXMAP_CACHE_SIZE);
#endif
const bool portableModeEnabled = m_commandLineArgs.profileDir.isEmpty()
&& QDir(QCoreApplication::applicationDirPath()).exists(DEFAULT_PORTABLE_MODE_PROFILE_DIR);
const auto portableProfilePath = Path(QCoreApplication::applicationDirPath()) / DEFAULT_PORTABLE_MODE_PROFILE_DIR;
const bool portableModeEnabled = m_commandLineArgs.profileDir.isEmpty() && portableProfilePath.exists();
const QString profileDir = portableModeEnabled
? QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(DEFAULT_PORTABLE_MODE_PROFILE_DIR)
const Path profileDir = portableModeEnabled
? portableProfilePath
: m_commandLineArgs.profileDir;
Profile::initInstance(profileDir, m_commandLineArgs.configurationName,
(m_commandLineArgs.relativeFastresumePaths || portableModeEnabled));
m_instanceManager = new ApplicationInstanceManager {Profile::instance()->location(SpecialFolder::Config), this};
m_instanceManager = new ApplicationInstanceManager(Profile::instance()->location(SpecialFolder::Config), this);
Logger::initInstance();
SettingsStorage::initInstance();
@ -175,13 +175,13 @@ Application::Application(int &argc, char **argv)
Logger::instance()->addMessage(tr("qBittorrent %1 started", "qBittorrent v3.2.0alpha started").arg(QBT_VERSION));
if (portableModeEnabled)
{
Logger::instance()->addMessage(tr("Running in portable mode. Auto detected profile folder at: %1").arg(profileDir));
Logger::instance()->addMessage(tr("Running in portable mode. Auto detected profile folder at: %1").arg(profileDir.toString()));
if (m_commandLineArgs.relativeFastresumePaths)
Logger::instance()->addMessage(tr("Redundant command line flag detected: \"%1\". Portable mode implies relative fastresume.").arg("--relative-fastresume"), Log::WARNING); // to avoid translating the `--relative-fastresume` string
}
else
{
Logger::instance()->addMessage(tr("Using config directory: %1").arg(Profile::instance()->location(SpecialFolder::Config)));
Logger::instance()->addMessage(tr("Using config directory: %1").arg(Profile::instance()->location(SpecialFolder::Config).toString()));
}
}
@ -218,12 +218,12 @@ void Application::setFileLoggerEnabled(const bool value)
m_storeFileLoggerEnabled = value;
}
QString Application::fileLoggerPath() const
Path Application::fileLoggerPath() const
{
return m_storeFileLoggerPath.get(QDir(specialFolderLocation(SpecialFolder::Data)).absoluteFilePath(LOG_FOLDER));
return m_storeFileLoggerPath.get(specialFolderLocation(SpecialFolder::Data) / Path(LOG_FOLDER));
}
void Application::setFileLoggerPath(const QString &path)
void Application::setFileLoggerPath(const Path &path)
{
if (m_fileLogger)
m_fileLogger->changePath(path);
@ -327,16 +327,16 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
break;
case u'D':
#if defined(Q_OS_WIN)
program.replace(i, 2, chopPathSep(Utils::Fs::toNativePath(torrent->savePath())));
program.replace(i, 2, chopPathSep(torrent->savePath().toString()));
#else
program.replace(i, 2, Utils::Fs::toNativePath(torrent->savePath()));
program.replace(i, 2, torrent->savePath().toString());
#endif
break;
case u'F':
#if defined(Q_OS_WIN)
program.replace(i, 2, chopPathSep(Utils::Fs::toNativePath(torrent->contentPath())));
program.replace(i, 2, chopPathSep(torrent->contentPath().toString()));
#else
program.replace(i, 2, Utils::Fs::toNativePath(torrent->contentPath()));
program.replace(i, 2, torrent->contentPath().toString());
#endif
break;
case u'G':
@ -359,9 +359,9 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
break;
case u'R':
#if defined(Q_OS_WIN)
program.replace(i, 2, chopPathSep(Utils::Fs::toNativePath(torrent->rootPath())));
program.replace(i, 2, chopPathSep(torrent->rootPath().toString()));
#else
program.replace(i, 2, Utils::Fs::toNativePath(torrent->rootPath()));
program.replace(i, 2, torrent->rootPath().toString());
#endif
break;
case u'T':
@ -439,7 +439,7 @@ void Application::sendNotificationEmail(const BitTorrent::Torrent *torrent)
// Prepare mail content
const QString content = tr("Torrent name: %1").arg(torrent->name()) + '\n'
+ tr("Torrent size: %1").arg(Utils::Misc::friendlyUnit(torrent->wantedSize())) + '\n'
+ tr("Save path: %1").arg(torrent->savePath()) + "\n\n"
+ tr("Save path: %1").arg(torrent->savePath().toString()) + "\n\n"
+ tr("The torrent was downloaded in %1.", "The torrent was downloaded in 1 hour and 20 seconds")
.arg(Utils::Misc::userFriendlyDuration(torrent->activeTime())) + "\n\n\n"
+ tr("Thank you for using qBittorrent.") + '\n';
@ -545,7 +545,7 @@ void Application::processParams(const QStringList &params)
if (param.startsWith(QLatin1String("@savePath=")))
{
torrentParams.savePath = param.mid(10);
torrentParams.savePath = Path(param.mid(10));
continue;
}
@ -821,7 +821,7 @@ void Application::cleanup()
Logger::freeInstance();
IconProvider::freeInstance();
SearchPluginManager::freeInstance();
Utils::Fs::removeDirRecursive(Utils::Fs::tempPath());
Utils::Fs::removeDirRecursively(Utils::Fs::tempPath());
#ifndef DISABLE_GUI
if (m_window)

View File

@ -47,6 +47,7 @@ class QSessionManager;
using BaseApplication = QCoreApplication;
#endif // DISABLE_GUI
#include "base/path.h"
#include "base/settingvalue.h"
#include "base/types.h"
#include "cmdoptions.h"
@ -91,8 +92,8 @@ public:
// FileLogger properties
bool isFileLoggerEnabled() const;
void setFileLoggerEnabled(bool value);
QString fileLoggerPath() const;
void setFileLoggerPath(const QString &path);
Path fileLoggerPath() const;
void setFileLoggerPath(const Path &path);
bool isFileLoggerBackup() const;
void setFileLoggerBackup(bool value);
bool isFileLoggerDeleteOld() const;
@ -152,5 +153,5 @@ private:
SettingValue<int> m_storeFileLoggerMaxSize;
SettingValue<int> m_storeFileLoggerAge;
SettingValue<int> m_storeFileLoggerAgeType;
SettingValue<QString> m_storeFileLoggerPath;
SettingValue<Path> m_storeFileLoggerPath;
};

View File

@ -37,18 +37,19 @@
#include <QSharedMemory>
#endif
#include "base/path.h"
#include "qtlocalpeer/qtlocalpeer.h"
ApplicationInstanceManager::ApplicationInstanceManager(const QString &instancePath, QObject *parent)
ApplicationInstanceManager::ApplicationInstanceManager(const Path &instancePath, QObject *parent)
: QObject {parent}
, m_peer {new QtLocalPeer {instancePath, this}}
, m_peer {new QtLocalPeer(instancePath.data(), this)}
, m_isFirstInstance {!m_peer->isClient()}
{
connect(m_peer, &QtLocalPeer::messageReceived, this, &ApplicationInstanceManager::messageReceived);
#ifdef Q_OS_WIN
const QString sharedMemoryKey = instancePath + QLatin1String {"/shared-memory"};
auto sharedMem = new QSharedMemory {sharedMemoryKey, this};
const QString sharedMemoryKey = instancePath.data() + QLatin1String("/shared-memory");
auto sharedMem = new QSharedMemory(sharedMemoryKey, this);
if (m_isFirstInstance)
{
// First instance creates shared memory and store PID

View File

@ -30,6 +30,8 @@
#include <QObject>
#include "base/pathfwd.h"
class QtLocalPeer;
class ApplicationInstanceManager final : public QObject
@ -38,7 +40,7 @@ class ApplicationInstanceManager final : public QObject
Q_DISABLE_COPY_MOVE(ApplicationInstanceManager)
public:
explicit ApplicationInstanceManager(const QString &instancePath, QObject *parent = nullptr);
explicit ApplicationInstanceManager(const Path &instancePath, QObject *parent = nullptr);
bool isFirstInstance() const;

View File

@ -372,7 +372,7 @@ QStringList QBtCommandLineParameters::paramList() const
// torrent paths or URLs.
if (!savePath.isEmpty())
result.append(QLatin1String("@savePath=") + savePath);
result.append(QLatin1String("@savePath=") + savePath.data());
if (addPaused.has_value())
result.append(*addPaused ? QLatin1String {"@addPaused=1"} : QLatin1String {"@addPaused=0"});
@ -438,7 +438,7 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
#endif
else if (arg == PROFILE_OPTION)
{
result.profileDir = PROFILE_OPTION.value(arg);
result.profileDir = Path(PROFILE_OPTION.value(arg));
}
else if (arg == RELATIVE_FASTRESUME)
{
@ -450,7 +450,7 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
}
else if (arg == SAVE_PATH_OPTION)
{
result.savePath = SAVE_PATH_OPTION.value(arg);
result.savePath = Path(SAVE_PATH_OPTION.value(arg));
}
else if (arg == PAUSED_OPTION)
{

View File

@ -36,6 +36,7 @@
#include <QStringList>
#include "base/exceptions.h"
#include "base/path.h"
class QProcessEnvironment;
@ -58,9 +59,9 @@ struct QBtCommandLineParameters
std::optional<bool> addPaused;
std::optional<bool> skipDialog;
QStringList torrents;
QString profileDir;
Path profileDir;
QString configurationName;
QString savePath;
Path savePath;
QString category;
QString unknownParameter;

View File

@ -44,7 +44,9 @@ namespace
const std::chrono::seconds FLUSH_INTERVAL {2};
}
FileLogger::FileLogger(const QString &path, const bool backup, const int maxSize, const bool deleteOld, const int age, const FileLogAgeType ageType)
FileLogger::FileLogger(const Path &path, const bool backup
, const int maxSize, const bool deleteOld, const int age
, const FileLogAgeType ageType)
: m_backup(backup)
, m_maxSize(maxSize)
{
@ -68,26 +70,25 @@ FileLogger::~FileLogger()
closeLogFile();
}
void FileLogger::changePath(const QString &newPath)
void FileLogger::changePath(const Path &newPath)
{
const QDir dir(newPath);
dir.mkpath(newPath);
const QString tmpPath = dir.absoluteFilePath("qbittorrent.log");
// compare paths as strings to perform case sensitive comparison on all the platforms
if (newPath.data() == m_path.parentPath().data())
return;
if (tmpPath != m_path)
{
m_path = tmpPath;
closeLogFile();
closeLogFile();
m_logFile.setFileName(m_path);
openLogFile();
}
m_path = newPath / Path("qbittorrent.log");
m_logFile.setFileName(m_path.data());
Utils::Fs::mkpath(newPath);
openLogFile();
}
void FileLogger::deleteOld(const int age, const FileLogAgeType ageType)
{
const QDateTime date = QDateTime::currentDateTime();
const QDir dir(Utils::Fs::branchPath(m_path));
const QDir dir {m_path.parentPath().data()};
const QFileInfoList fileList = dir.entryInfoList(QStringList("qbittorrent.log.bak*")
, (QDir::Files | QDir::Writable), (QDir::Time | QDir::Reversed));
@ -107,7 +108,7 @@ void FileLogger::deleteOld(const int age, const FileLogAgeType ageType)
}
if (modificationDate > date)
break;
Utils::Fs::forceRemove(file.absoluteFilePath());
Utils::Fs::removeFile(Path(file.absoluteFilePath()));
}
}
@ -151,15 +152,15 @@ void FileLogger::addLogMessage(const Log::Msg &msg)
{
closeLogFile();
int counter = 0;
QString backupLogFilename = m_path + ".bak";
Path backupLogFilename = m_path + ".bak";
while (QFile::exists(backupLogFilename))
while (backupLogFilename.exists())
{
++counter;
backupLogFilename = m_path + ".bak" + QString::number(counter);
}
QFile::rename(m_path, backupLogFilename);
Utils::Fs::renameFile(m_path, backupLogFilename);
openLogFile();
}
else

View File

@ -32,6 +32,8 @@
#include <QObject>
#include <QTimer>
#include "base/path.h"
namespace Log
{
struct Msg;
@ -50,10 +52,10 @@ public:
YEARS
};
FileLogger(const QString &path, bool backup, int maxSize, bool deleteOld, int age, FileLogAgeType ageType);
FileLogger(const Path &path, bool backup, int maxSize, bool deleteOld, int age, FileLogAgeType ageType);
~FileLogger();
void changePath(const QString &newPath);
void changePath(const Path &newPath);
void deleteOld(int age, FileLogAgeType ageType);
void setBackup(bool value);
void setMaxSize(int value);
@ -66,7 +68,7 @@ private:
void openLogFile();
void closeLogFile();
QString m_path;
Path m_path;
bool m_backup;
int m_maxSize;
QFile m_logFile;

View File

@ -48,7 +48,7 @@ namespace
void exportWebUIHttpsFiles()
{
const auto migrate = [](const QString &oldKey, const QString &newKey, const QString &savePath)
const auto migrate = [](const QString &oldKey, const QString &newKey, const Path &savePath)
{
SettingsStorage *settingsStorage {SettingsStorage::instance()};
const auto oldData {settingsStorage->loadValue<QByteArray>(oldKey)};
@ -61,24 +61,24 @@ namespace
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(savePath, oldData);
if (!result)
{
LogMsg(errorMsgFormat.arg(savePath, result.error()) , Log::WARNING);
LogMsg(errorMsgFormat.arg(savePath.toString(), result.error()) , Log::WARNING);
return;
}
settingsStorage->storeValue(newKey, savePath);
settingsStorage->removeValue(oldKey);
LogMsg(QObject::tr("Migrated preferences: WebUI https, exported data to file: \"%1\"").arg(savePath)
LogMsg(QObject::tr("Migrated preferences: WebUI https, exported data to file: \"%1\"").arg(savePath.toString())
, Log::INFO);
};
const QString configPath {specialFolderLocation(SpecialFolder::Config)};
const Path configPath = specialFolderLocation(SpecialFolder::Config);
migrate(QLatin1String("Preferences/WebUI/HTTPS/Certificate")
, QLatin1String("Preferences/WebUI/HTTPS/CertificatePath")
, Utils::Fs::toNativePath(configPath + QLatin1String("/WebUICertificate.crt")));
, (configPath / Path("WebUICertificate.crt")));
migrate(QLatin1String("Preferences/WebUI/HTTPS/Key")
, QLatin1String("Preferences/WebUI/HTTPS/KeyPath")
, Utils::Fs::toNativePath(configPath + QLatin1String("/WebUIPrivateKey.pem")));
, (configPath / Path("WebUIPrivateKey.pem")));
}
void upgradeTorrentContentLayout()

View File

@ -62,6 +62,8 @@ add_library(qbt_base STATIC
net/reverseresolution.h
net/smtp.h
orderedset.h
path.h
pathfwd.h
preferences.h
profile.h
profile_p.h
@ -144,6 +146,7 @@ add_library(qbt_base STATIC
net/proxyconfigurationmanager.cpp
net/reverseresolution.cpp
net/smtp.cpp
path.cpp
preferences.cpp
profile.cpp
profile_p.cpp

View File

@ -31,21 +31,22 @@
#include <QDebug>
#include <QMetaObject>
#include "base/utils/fs.h"
#include "base/utils/io.h"
AsyncFileStorage::AsyncFileStorage(const QString &storageFolderPath, QObject *parent)
AsyncFileStorage::AsyncFileStorage(const Path &storageFolderPath, QObject *parent)
: QObject(parent)
, m_storageDir(storageFolderPath)
, m_lockFile(m_storageDir.absoluteFilePath(QStringLiteral("storage.lock")))
, m_lockFile((m_storageDir / Path(QStringLiteral("storage.lock"))).data())
{
if (!m_storageDir.mkpath(m_storageDir.absolutePath()))
throw AsyncFileStorageError
{tr("Could not create directory '%1'.")
.arg(m_storageDir.absolutePath())};
Q_ASSERT(m_storageDir.isAbsolute());
if (!Utils::Fs::mkpath(m_storageDir))
throw AsyncFileStorageError(tr("Could not create directory '%1'.").arg(m_storageDir.toString()));
// TODO: This folder locking approach does not work for UNIX systems. Implement it.
if (!m_lockFile.open(QFile::WriteOnly))
throw AsyncFileStorageError {m_lockFile.errorString()};
throw AsyncFileStorageError(m_lockFile.errorString());
}
AsyncFileStorage::~AsyncFileStorage()
@ -54,21 +55,21 @@ AsyncFileStorage::~AsyncFileStorage()
m_lockFile.remove();
}
void AsyncFileStorage::store(const QString &fileName, const QByteArray &data)
void AsyncFileStorage::store(const Path &filePath, const QByteArray &data)
{
QMetaObject::invokeMethod(this, [this, data, fileName]() { store_impl(fileName, data); }
QMetaObject::invokeMethod(this, [this, data, filePath]() { store_impl(filePath, data); }
, Qt::QueuedConnection);
}
QDir AsyncFileStorage::storageDir() const
Path AsyncFileStorage::storageDir() const
{
return m_storageDir;
}
void AsyncFileStorage::store_impl(const QString &fileName, const QByteArray &data)
void AsyncFileStorage::store_impl(const Path &fileName, const QByteArray &data)
{
const QString filePath = m_storageDir.absoluteFilePath(fileName);
qDebug() << "AsyncFileStorage: Saving data to" << filePath;
const Path filePath = m_storageDir / fileName;
qDebug() << "AsyncFileStorage: Saving data to" << filePath.toString();
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(filePath, data);
if (!result)

View File

@ -28,11 +28,11 @@
#pragma once
#include <QDir>
#include <QFile>
#include <QObject>
#include "base/exceptions.h"
#include "base/path.h"
class AsyncFileStorageError : public RuntimeError
{
@ -46,19 +46,19 @@ class AsyncFileStorage : public QObject
Q_DISABLE_COPY_MOVE(AsyncFileStorage)
public:
explicit AsyncFileStorage(const QString &storageFolderPath, QObject *parent = nullptr);
explicit AsyncFileStorage(const Path &storageFolderPath, QObject *parent = nullptr);
~AsyncFileStorage() override;
void store(const QString &fileName, const QByteArray &data);
void store(const Path &filePath, const QByteArray &data);
QDir storageDir() const;
Path storageDir() const;
signals:
void failed(const QString &fileName, const QString &errorString);
void failed(const Path &filePath, const QString &errorString);
private:
Q_INVOKABLE void store_impl(const QString &fileName, const QByteArray &data);
Q_INVOKABLE void store_impl(const Path &fileName, const QByteArray &data);
QDir m_storageDir;
Path m_storageDir;
QFile m_lockFile;
};

View File

@ -61,6 +61,8 @@ HEADERS += \
$$PWD/net/reverseresolution.h \
$$PWD/net/smtp.h \
$$PWD/orderedset.h \
$$PWD/path.h \
$$PWD/pathfwd.h \
$$PWD/preferences.h \
$$PWD/profile.h \
$$PWD/profile_p.h \
@ -144,6 +146,7 @@ SOURCES += \
$$PWD/net/proxyconfigurationmanager.cpp \
$$PWD/net/reverseresolution.cpp \
$$PWD/net/smtp.cpp \
$$PWD/path.cpp \
$$PWD/preferences.cpp \
$$PWD/profile.cpp \
$$PWD/profile_p.cpp \

View File

@ -33,93 +33,63 @@
#include <QVector>
#include "base/exceptions.h"
#include "base/path.h"
#include "base/utils/fs.h"
#if defined(Q_OS_WIN)
const Qt::CaseSensitivity CASE_SENSITIVITY {Qt::CaseInsensitive};
#else
const Qt::CaseSensitivity CASE_SENSITIVITY {Qt::CaseSensitive};
#endif
namespace
void BitTorrent::AbstractFileStorage::renameFile(const Path &oldPath, const Path &newPath)
{
bool areSameFileNames(QString first, QString second)
{
return QString::compare(first, second, CASE_SENSITIVITY) == 0;
}
}
void BitTorrent::AbstractFileStorage::renameFile(const QString &oldPath, const QString &newPath)
{
if (!Utils::Fs::isValidFileSystemName(oldPath, true))
throw RuntimeError {tr("The old path is invalid: '%1'.").arg(oldPath)};
if (!Utils::Fs::isValidFileSystemName(newPath, true))
throw RuntimeError {tr("The new path is invalid: '%1'.").arg(newPath)};
const QString oldFilePath = Utils::Fs::toUniformPath(oldPath);
if (oldFilePath.endsWith(QLatin1Char {'/'}))
throw RuntimeError {tr("Invalid file path: '%1'.").arg(oldFilePath)};
const QString newFilePath = Utils::Fs::toUniformPath(newPath);
if (newFilePath.endsWith(QLatin1Char {'/'}))
throw RuntimeError {tr("Invalid file path: '%1'.").arg(newFilePath)};
if (QDir().isAbsolutePath(newFilePath))
throw RuntimeError {tr("Absolute path isn't allowed: '%1'.").arg(newFilePath)};
if (!oldPath.isValid())
throw RuntimeError(tr("The old path is invalid: '%1'.").arg(oldPath.toString()));
if (!newPath.isValid())
throw RuntimeError(tr("The new path is invalid: '%1'.").arg(newPath.toString()));
if (newPath.isAbsolute())
throw RuntimeError(tr("Absolute path isn't allowed: '%1'.").arg(newPath.toString()));
int renamingFileIndex = -1;
for (int i = 0; i < filesCount(); ++i)
{
const QString path = filePath(i);
const Path path = filePath(i);
if ((renamingFileIndex < 0) && areSameFileNames(path, oldFilePath))
if ((renamingFileIndex < 0) && (path == oldPath))
renamingFileIndex = i;
else if (areSameFileNames(path, newFilePath))
throw RuntimeError {tr("The file already exists: '%1'.").arg(newFilePath)};
else if (path == newPath)
throw RuntimeError(tr("The file already exists: '%1'.").arg(newPath.toString()));
}
if (renamingFileIndex < 0)
throw RuntimeError {tr("No such file: '%1'.").arg(oldFilePath)};
throw RuntimeError(tr("No such file: '%1'.").arg(oldPath.toString()));
renameFile(renamingFileIndex, newFilePath);
renameFile(renamingFileIndex, newPath);
}
void BitTorrent::AbstractFileStorage::renameFolder(const QString &oldPath, const QString &newPath)
void BitTorrent::AbstractFileStorage::renameFolder(const Path &oldFolderPath, const Path &newFolderPath)
{
if (!Utils::Fs::isValidFileSystemName(oldPath, true))
throw RuntimeError {tr("The old path is invalid: '%1'.").arg(oldPath)};
if (!Utils::Fs::isValidFileSystemName(newPath, true))
throw RuntimeError {tr("The new path is invalid: '%1'.").arg(newPath)};
const auto cleanFolderPath = [](const QString &path) -> QString
{
const QString uniformPath = Utils::Fs::toUniformPath(path);
return (uniformPath.endsWith(QLatin1Char {'/'}) ? uniformPath : uniformPath + QLatin1Char {'/'});
};
const QString oldFolderPath = cleanFolderPath(oldPath);
const QString newFolderPath = cleanFolderPath(newPath);
if (QDir().isAbsolutePath(newFolderPath))
throw RuntimeError {tr("Absolute path isn't allowed: '%1'.").arg(newFolderPath)};
if (!oldFolderPath.isValid())
throw RuntimeError(tr("The old path is invalid: '%1'.").arg(oldFolderPath.toString()));
if (!newFolderPath.isValid())
throw RuntimeError(tr("The new path is invalid: '%1'.").arg(newFolderPath.toString()));
if (newFolderPath.isAbsolute())
throw RuntimeError(tr("Absolute path isn't allowed: '%1'.").arg(newFolderPath.toString()));
QVector<int> renamingFileIndexes;
renamingFileIndexes.reserve(filesCount());
for (int i = 0; i < filesCount(); ++i)
{
const QString path = filePath(i);
const Path path = filePath(i);
if (path.startsWith(oldFolderPath, CASE_SENSITIVITY))
if (path.hasAncestor(oldFolderPath))
renamingFileIndexes.append(i);
else if (path.startsWith(newFolderPath, CASE_SENSITIVITY))
throw RuntimeError {tr("The folder already exists: '%1'.").arg(newFolderPath)};
else if (path.hasAncestor(newFolderPath))
throw RuntimeError(tr("The folder already exists: '%1'.").arg(newFolderPath.toString()));
}
if (renamingFileIndexes.isEmpty())
throw RuntimeError {tr("No such folder: '%1'.").arg(oldFolderPath)};
throw RuntimeError(tr("No such folder: '%1'.").arg(oldFolderPath.toString()));
for (const int index : renamingFileIndexes)
{
const QString newFilePath = newFolderPath + filePath(index).mid(oldFolderPath.size());
const Path newFilePath = newFolderPath / oldFolderPath.relativePathOf(filePath(index));
renameFile(index, newFilePath);
}
}

View File

@ -31,6 +31,8 @@
#include <QtGlobal>
#include <QCoreApplication>
#include "base/pathfwd.h"
class QString;
namespace BitTorrent
@ -43,12 +45,12 @@ namespace BitTorrent
virtual ~AbstractFileStorage() = default;
virtual int filesCount() const = 0;
virtual QString filePath(int index) const = 0;
virtual Path filePath(int index) const = 0;
virtual qlonglong fileSize(int index) const = 0;
virtual void renameFile(int index, const QString &name) = 0;
virtual void renameFile(int index, const Path &newPath) = 0;
void renameFile(const QString &oldPath, const QString &newPath);
void renameFolder(const QString &oldPath, const QString &newPath);
void renameFile(const Path &oldPath, const Path &newPath);
void renameFolder(const Path &oldFolderPath, const Path &newFolderPath);
};
}

View File

@ -34,6 +34,7 @@
#include <QString>
#include <QVector>
#include "base/path.h"
#include "base/tagset.h"
#include "torrent.h"
#include "torrentcontentlayout.h"
@ -47,14 +48,14 @@ namespace BitTorrent
QString name;
QString category;
TagSet tags;
QString savePath;
Path savePath;
std::optional<bool> useDownloadPath;
QString downloadPath;
Path downloadPath;
bool sequential = false;
bool firstLastPiecePriority = false;
bool addForced = false;
std::optional<bool> addPaused;
QStringList filePaths; // used if TorrentInfo is set
PathList filePaths; // used if TorrentInfo is set
QVector<DownloadPriority> filePriorities; // used if TorrentInfo is set
bool skipChecking = false;
std::optional<BitTorrent::TorrentContentLayout> contentLayout;

View File

@ -58,14 +58,14 @@ namespace BitTorrent
Q_DISABLE_COPY_MOVE(Worker)
public:
explicit Worker(const QDir &resumeDataDir);
explicit Worker(const Path &resumeDataDir);
void store(const TorrentID &id, const LoadTorrentParams &resumeData) const;
void remove(const TorrentID &id) const;
void storeQueue(const QVector<TorrentID> &queue) const;
private:
const QDir m_resumeDataDir;
const Path m_resumeDataDir;
};
}
@ -89,20 +89,22 @@ namespace
}
}
BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const QString &path, QObject *parent)
BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const Path &path, QObject *parent)
: ResumeDataStorage {parent}
, m_resumeDataDir {path}
, m_resumeDataPath {path}
, m_ioThread {new QThread {this}}
, m_asyncWorker {new Worker {m_resumeDataDir}}
, m_asyncWorker {new Worker(m_resumeDataPath)}
{
if (!m_resumeDataDir.exists() && !m_resumeDataDir.mkpath(m_resumeDataDir.absolutePath()))
Q_ASSERT(path.isAbsolute());
if (!m_resumeDataPath.exists() && !Utils::Fs::mkpath(m_resumeDataPath))
{
throw RuntimeError {tr("Cannot create torrent resume folder: \"%1\"")
.arg(Utils::Fs::toNativePath(m_resumeDataDir.absolutePath()))};
throw RuntimeError(tr("Cannot create torrent resume folder: \"%1\"")
.arg(m_resumeDataPath.toString()));
}
const QRegularExpression filenamePattern {QLatin1String("^([A-Fa-f0-9]{40})\\.fastresume$")};
const QStringList filenames = m_resumeDataDir.entryList(QStringList(QLatin1String("*.fastresume")), QDir::Files, QDir::Unsorted);
const QStringList filenames = QDir(m_resumeDataPath.data()).entryList(QStringList(QLatin1String("*.fastresume")), QDir::Files, QDir::Unsorted);
m_registeredTorrents.reserve(filenames.size());
for (const QString &filename : filenames)
@ -112,7 +114,7 @@ BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const QString &pa
m_registeredTorrents.append(TorrentID::fromString(rxMatch.captured(1)));
}
loadQueue(m_resumeDataDir.absoluteFilePath(QLatin1String("queue")));
loadQueue(m_resumeDataPath / Path("queue"));
qDebug() << "Registered torrents count: " << m_registeredTorrents.size();
@ -135,20 +137,20 @@ QVector<BitTorrent::TorrentID> BitTorrent::BencodeResumeDataStorage::registeredT
std::optional<BitTorrent::LoadTorrentParams> BitTorrent::BencodeResumeDataStorage::load(const TorrentID &id) const
{
const QString idString = id.toString();
const QString fastresumePath = m_resumeDataDir.absoluteFilePath(QString::fromLatin1("%1.fastresume").arg(idString));
const QString torrentFilePath = m_resumeDataDir.absoluteFilePath(QString::fromLatin1("%1.torrent").arg(idString));
const Path fastresumePath = m_resumeDataPath / Path(idString + QLatin1String(".fastresume"));
const Path torrentFilePath = m_resumeDataPath / Path(idString + QLatin1String(".torrent"));
QFile resumeDataFile {fastresumePath};
QFile resumeDataFile {fastresumePath.data()};
if (!resumeDataFile.open(QIODevice::ReadOnly))
{
LogMsg(tr("Cannot read file %1: %2").arg(fastresumePath, resumeDataFile.errorString()), Log::WARNING);
LogMsg(tr("Cannot read file %1: %2").arg(fastresumePath.toString(), resumeDataFile.errorString()), Log::WARNING);
return std::nullopt;
}
QFile metadataFile {torrentFilePath};
QFile metadataFile {torrentFilePath.data()};
if (metadataFile.exists() && !metadataFile.open(QIODevice::ReadOnly))
{
LogMsg(tr("Cannot read file %1: %2").arg(torrentFilePath, metadataFile.errorString()), Log::WARNING);
LogMsg(tr("Cannot read file %1: %2").arg(torrentFilePath.toString(), metadataFile.errorString()), Log::WARNING);
return std::nullopt;
}
@ -178,12 +180,12 @@ std::optional<BitTorrent::LoadTorrentParams> BitTorrent::BencodeResumeDataStorag
torrentParams.seedingTimeLimit = root.dict_find_int_value("qBt-seedingTimeLimit", Torrent::USE_GLOBAL_SEEDING_TIME);
torrentParams.savePath = Profile::instance()->fromPortablePath(
Utils::Fs::toUniformPath(fromLTString(root.dict_find_string_value("qBt-savePath"))));
Path(fromLTString(root.dict_find_string_value("qBt-savePath"))));
torrentParams.useAutoTMM = torrentParams.savePath.isEmpty();
if (!torrentParams.useAutoTMM)
{
torrentParams.downloadPath = Profile::instance()->fromPortablePath(
Utils::Fs::toUniformPath(fromLTString(root.dict_find_string_value("qBt-downloadPath"))));
Path(fromLTString(root.dict_find_string_value("qBt-downloadPath"))));
}
// TODO: The following code is deprecated. Replace with the commented one after several releases in 4.4.x.
@ -224,7 +226,8 @@ std::optional<BitTorrent::LoadTorrentParams> BitTorrent::BencodeResumeDataStorag
lt::add_torrent_params &p = torrentParams.ltAddTorrentParams;
p = lt::read_resume_data(root, ec);
p.save_path = Profile::instance()->fromPortablePath(fromLTString(p.save_path)).toStdString();
p.save_path = Profile::instance()->fromPortablePath(
Path(fromLTString(p.save_path))).toString().toStdString();
if (p.flags & lt::torrent_flags::stop_when_ready)
{
@ -273,9 +276,9 @@ void BitTorrent::BencodeResumeDataStorage::storeQueue(const QVector<TorrentID> &
});
}
void BitTorrent::BencodeResumeDataStorage::loadQueue(const QString &queueFilename)
void BitTorrent::BencodeResumeDataStorage::loadQueue(const Path &queueFilename)
{
QFile queueFile {queueFilename};
QFile queueFile {queueFilename.data()};
if (!queueFile.exists())
return;
@ -306,7 +309,7 @@ void BitTorrent::BencodeResumeDataStorage::loadQueue(const QString &queueFilenam
}
}
BitTorrent::BencodeResumeDataStorage::Worker::Worker(const QDir &resumeDataDir)
BitTorrent::BencodeResumeDataStorage::Worker::Worker(const Path &resumeDataDir)
: m_resumeDataDir {resumeDataDir}
{
}
@ -315,7 +318,8 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co
{
// We need to adjust native libtorrent resume data
lt::add_torrent_params p = resumeData.ltAddTorrentParams;
p.save_path = Profile::instance()->toPortablePath(QString::fromStdString(p.save_path)).toStdString();
p.save_path = Profile::instance()->toPortablePath(Path(p.save_path))
.toString().toStdString();
if (resumeData.stopped)
{
p.flags |= lt::torrent_flags::paused;
@ -349,12 +353,12 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co
metadataDict.insert(dataDict.extract("created by"));
metadataDict.insert(dataDict.extract("comment"));
const QString torrentFilepath = m_resumeDataDir.absoluteFilePath(QString::fromLatin1("%1.torrent").arg(id.toString()));
const Path torrentFilepath = m_resumeDataDir / Path(QString::fromLatin1("%1.torrent").arg(id.toString()));
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(torrentFilepath, metadata);
if (!result)
{
LogMsg(tr("Couldn't save torrent metadata to '%1'. Error: %2.")
.arg(torrentFilepath, result.error()), Log::CRITICAL);
.arg(torrentFilepath.toString(), result.error()), Log::CRITICAL);
return;
}
}
@ -370,26 +374,26 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co
if (!resumeData.useAutoTMM)
{
data["qBt-savePath"] = Profile::instance()->toPortablePath(resumeData.savePath).toStdString();
data["qBt-downloadPath"] = Profile::instance()->toPortablePath(resumeData.downloadPath).toStdString();
data["qBt-savePath"] = Profile::instance()->toPortablePath(resumeData.savePath).data().toStdString();
data["qBt-downloadPath"] = Profile::instance()->toPortablePath(resumeData.downloadPath).data().toStdString();
}
const QString resumeFilepath = m_resumeDataDir.absoluteFilePath(QString::fromLatin1("%1.fastresume").arg(id.toString()));
const Path resumeFilepath = m_resumeDataDir / Path(QString::fromLatin1("%1.fastresume").arg(id.toString()));
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(resumeFilepath, data);
if (!result)
{
LogMsg(tr("Couldn't save torrent resume data to '%1'. Error: %2.")
.arg(resumeFilepath, result.error()), Log::CRITICAL);
.arg(resumeFilepath.toString(), result.error()), Log::CRITICAL);
}
}
void BitTorrent::BencodeResumeDataStorage::Worker::remove(const TorrentID &id) const
{
const QString resumeFilename = QString::fromLatin1("%1.fastresume").arg(id.toString());
Utils::Fs::forceRemove(m_resumeDataDir.absoluteFilePath(resumeFilename));
const Path resumeFilename {QString::fromLatin1("%1.fastresume").arg(id.toString())};
Utils::Fs::removeFile(m_resumeDataDir / resumeFilename);
const QString torrentFilename = QString::fromLatin1("%1.torrent").arg(id.toString());
Utils::Fs::forceRemove(m_resumeDataDir.absoluteFilePath(torrentFilename));
const Path torrentFilename {QString::fromLatin1("%1.torrent").arg(id.toString())};
Utils::Fs::removeFile(m_resumeDataDir / torrentFilename);
}
void BitTorrent::BencodeResumeDataStorage::Worker::storeQueue(const QVector<TorrentID> &queue) const
@ -399,11 +403,11 @@ void BitTorrent::BencodeResumeDataStorage::Worker::storeQueue(const QVector<Torr
for (const BitTorrent::TorrentID &torrentID : queue)
data += (torrentID.toString().toLatin1() + '\n');
const QString filepath = m_resumeDataDir.absoluteFilePath(QLatin1String("queue"));
const Path filepath = m_resumeDataDir / Path("queue");
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(filepath, data);
if (!result)
{
LogMsg(tr("Couldn't save data to '%1'. Error: %2")
.arg(filepath, result.error()), Log::CRITICAL);
.arg(filepath.toString(), result.error()), Log::CRITICAL);
}
}

View File

@ -31,6 +31,7 @@
#include <QDir>
#include <QVector>
#include "base/path.h"
#include "resumedatastorage.h"
class QByteArray;
@ -44,7 +45,7 @@ namespace BitTorrent
Q_DISABLE_COPY_MOVE(BencodeResumeDataStorage)
public:
explicit BencodeResumeDataStorage(const QString &path, QObject *parent = nullptr);
explicit BencodeResumeDataStorage(const Path &path, QObject *parent = nullptr);
~BencodeResumeDataStorage() override;
QVector<TorrentID> registeredTorrents() const override;
@ -54,10 +55,10 @@ namespace BitTorrent
void storeQueue(const QVector<TorrentID> &queue) const override;
private:
void loadQueue(const QString &queueFilename);
void loadQueue(const Path &queueFilename);
std::optional<LoadTorrentParams> loadTorrentResumeData(const QByteArray &data, const QByteArray &metadata) const;
const QDir m_resumeDataDir;
const Path m_resumeDataPath;
QVector<TorrentID> m_registeredTorrents;
QThread *m_ioThread = nullptr;

View File

@ -37,13 +37,13 @@ const QString OPTION_DOWNLOADPATH {QStringLiteral("download_path")};
BitTorrent::CategoryOptions BitTorrent::CategoryOptions::fromJSON(const QJsonObject &jsonObj)
{
CategoryOptions options;
options.savePath = jsonObj.value(OPTION_SAVEPATH).toString();
options.savePath = Path(jsonObj.value(OPTION_SAVEPATH).toString());
const QJsonValue downloadPathValue = jsonObj.value(OPTION_DOWNLOADPATH);
if (downloadPathValue.isBool())
options.downloadPath = {downloadPathValue.toBool(), {}};
else if (downloadPathValue.isString())
options.downloadPath = {true, downloadPathValue.toString()};
options.downloadPath = {true, Path(downloadPathValue.toString())};
return options;
}
@ -54,13 +54,13 @@ QJsonObject BitTorrent::CategoryOptions::toJSON() const
if (downloadPath)
{
if (downloadPath->enabled)
downloadPathValue = downloadPath->path;
downloadPathValue = downloadPath->path.data();
else
downloadPathValue = false;
}
return {
{OPTION_SAVEPATH, savePath},
{OPTION_SAVEPATH, savePath.data()},
{OPTION_DOWNLOADPATH, downloadPathValue}
};
}

View File

@ -32,6 +32,8 @@
#include <QString>
#include "base/path.h"
class QJsonObject;
namespace BitTorrent
@ -41,10 +43,10 @@ namespace BitTorrent
struct DownloadPathOption
{
bool enabled;
QString path;
Path path;
};
QString savePath;
Path savePath;
std::optional<DownloadPathOption> downloadPath;
static CategoryOptions fromJSON(const QJsonObject &jsonObj);

View File

@ -30,8 +30,6 @@
#include <libtorrent/download_priority.hpp>
#include <QDir>
#include "base/utils/fs.h"
#include "common.h"
@ -53,12 +51,13 @@ lt::storage_holder CustomDiskIOThread::new_torrent(const lt::storage_params &sto
{
lt::storage_holder storageHolder = m_nativeDiskIO->new_torrent(storageParams, torrent);
const QString savePath = Utils::Fs::expandPathAbs(QString::fromStdString(storageParams.path));
const Path savePath {storageParams.path};
m_storageData[storageHolder] =
{
savePath
, storageParams.mapped_files ? *storageParams.mapped_files : storageParams.files
, storageParams.priorities};
savePath,
storageParams.mapped_files ? *storageParams.mapped_files : storageParams.files,
storageParams.priorities
};
return storageHolder;
}
@ -99,7 +98,7 @@ void CustomDiskIOThread::async_hash2(lt::storage_index_t storage, lt::piece_inde
void CustomDiskIOThread::async_move_storage(lt::storage_index_t storage, std::string path, lt::move_flags_t flags
, std::function<void (lt::status_t, const std::string &, const lt::storage_error &)> handler)
{
const QString newSavePath {Utils::Fs::expandPathAbs(QString::fromStdString(path))};
const Path newSavePath {path};
if (flags == lt::move_flags_t::dont_replace)
handleCompleteFiles(storage, newSavePath);
@ -192,9 +191,8 @@ void CustomDiskIOThread::settings_updated()
m_nativeDiskIO->settings_updated();
}
void CustomDiskIOThread::handleCompleteFiles(lt::storage_index_t storage, const QString &savePath)
void CustomDiskIOThread::handleCompleteFiles(lt::storage_index_t storage, const Path &savePath)
{
const QDir saveDir {savePath};
const StorageData storageData = m_storageData[storage];
const lt::file_storage &fileStorage = storageData.files;
for (const lt::file_index_t fileIndex : fileStorage.file_range())
@ -206,16 +204,16 @@ void CustomDiskIOThread::handleCompleteFiles(lt::storage_index_t storage, const
// ignore pad files
if (fileStorage.pad_file_at(fileIndex)) continue;
const QString filePath = QString::fromStdString(fileStorage.file_path(fileIndex));
if (filePath.endsWith(QB_EXT))
const Path filePath {fileStorage.file_path(fileIndex)};
if (filePath.hasExtension(QB_EXT))
{
const QString completeFilePath = filePath.left(filePath.size() - QB_EXT.size());
QFile completeFile {saveDir.absoluteFilePath(completeFilePath)};
if (completeFile.exists())
const Path incompleteFilePath = savePath / filePath;
Path completeFilePath = incompleteFilePath;
completeFilePath.removeExtension();
if (completeFilePath.exists())
{
QFile incompleteFile {saveDir.absoluteFilePath(filePath)};
incompleteFile.remove();
completeFile.rename(incompleteFile.fileName());
Utils::Fs::removeFile(incompleteFilePath);
Utils::Fs::renameFile(completeFilePath, incompleteFilePath);
}
}
}
@ -230,7 +228,7 @@ lt::storage_interface *customStorageConstructor(const lt::storage_params &params
CustomStorage::CustomStorage(const lt::storage_params &params, lt::file_pool &filePool)
: lt::default_storage {params, filePool}
, m_savePath {Utils::Fs::expandPathAbs(QString::fromStdString(params.path))}
, m_savePath {params.path}
{
}
@ -248,7 +246,7 @@ void CustomStorage::set_file_priority(lt::aux::vector<lt::download_priority_t, l
lt::status_t CustomStorage::move_storage(const std::string &savePath, lt::move_flags_t flags, lt::storage_error &ec)
{
const QString newSavePath {Utils::Fs::expandPathAbs(QString::fromStdString(savePath))};
const Path newSavePath {savePath};
if (flags == lt::move_flags_t::dont_replace)
handleCompleteFiles(newSavePath);
@ -260,10 +258,8 @@ lt::status_t CustomStorage::move_storage(const std::string &savePath, lt::move_f
return ret;
}
void CustomStorage::handleCompleteFiles(const QString &savePath)
void CustomStorage::handleCompleteFiles(const Path &savePath)
{
const QDir saveDir {savePath};
const lt::file_storage &fileStorage = files();
for (const lt::file_index_t fileIndex : fileStorage.file_range())
{
@ -274,16 +270,16 @@ void CustomStorage::handleCompleteFiles(const QString &savePath)
// ignore pad files
if (fileStorage.pad_file_at(fileIndex)) continue;
const QString filePath = QString::fromStdString(fileStorage.file_path(fileIndex));
if (filePath.endsWith(QB_EXT))
const Path filePath {fileStorage.file_path(fileIndex)};
if (filePath.hasExtension(QB_EXT))
{
const QString completeFilePath = filePath.left(filePath.size() - QB_EXT.size());
QFile completeFile {saveDir.absoluteFilePath(completeFilePath)};
if (completeFile.exists())
const Path incompleteFilePath = savePath / filePath;
Path completeFilePath = incompleteFilePath;
completeFilePath.removeExtension();
if (completeFilePath.exists())
{
QFile incompleteFile {saveDir.absoluteFilePath(filePath)};
incompleteFile.remove();
completeFile.rename(incompleteFile.fileName());
Utils::Fs::removeFile(incompleteFilePath);
Utils::Fs::renameFile(completeFilePath, incompleteFilePath);
}
}
}

View File

@ -33,6 +33,8 @@
#include <QString>
#include "base/path.h"
#ifdef QBT_USES_LIBTORRENT2
#include <libtorrent/disk_interface.hpp>
#include <libtorrent/file_storage.hpp>
@ -86,13 +88,13 @@ public:
void settings_updated() override;
private:
void handleCompleteFiles(libtorrent::storage_index_t storage, const QString &savePath);
void handleCompleteFiles(libtorrent::storage_index_t storage, const Path &savePath);
std::unique_ptr<lt::disk_interface> m_nativeDiskIO;
struct StorageData
{
QString savePath;
Path savePath;
lt::file_storage files;
lt::aux::vector<lt::download_priority_t, lt::file_index_t> filePriorities;
};
@ -113,9 +115,9 @@ public:
lt::status_t move_storage(const std::string &savePath, lt::move_flags_t flags, lt::storage_error &ec) override;
private:
void handleCompleteFiles(const QString &savePath);
void handleCompleteFiles(const Path &savePath);
lt::aux::vector<lt::download_priority_t, lt::file_index_t> m_filePriorities;
QString m_savePath;
Path m_savePath;
};
#endif

View File

@ -49,6 +49,7 @@
#include "base/exceptions.h"
#include "base/global.h"
#include "base/logger.h"
#include "base/path.h"
#include "base/profile.h"
#include "base/utils/fs.h"
#include "base/utils/string.h"
@ -173,7 +174,7 @@ namespace BitTorrent
Q_DISABLE_COPY_MOVE(Worker)
public:
Worker(const QString &dbPath, const QString &dbConnectionName);
Worker(const Path &dbPath, const QString &dbConnectionName);
void openDatabase() const;
void closeDatabase() const;
@ -183,19 +184,19 @@ namespace BitTorrent
void storeQueue(const QVector<TorrentID> &queue) const;
private:
const QString m_path;
const Path m_path;
const QString m_connectionName;
};
}
BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const QString &dbPath, QObject *parent)
BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject *parent)
: ResumeDataStorage {parent}
, m_ioThread {new QThread(this)}
{
const bool needCreateDB = !QFile::exists(dbPath);
const bool needCreateDB = !dbPath.exists();
auto db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), DB_CONNECTION_NAME);
db.setDatabaseName(dbPath);
db.setDatabaseName(dbPath.data());
if (!db.open())
throw RuntimeError(db.lastError().text());
@ -308,12 +309,12 @@ std::optional<BitTorrent::LoadTorrentParams> BitTorrent::DBResumeDataStorage::lo
resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
resumeData.savePath = Profile::instance()->fromPortablePath(
Utils::Fs::toUniformPath(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
resumeData.useAutoTMM = resumeData.savePath.isEmpty();
if (!resumeData.useAutoTMM)
{
resumeData.downloadPath = Profile::instance()->fromPortablePath(
Utils::Fs::toUniformPath(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
Path(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
}
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
@ -325,13 +326,11 @@ std::optional<BitTorrent::LoadTorrentParams> BitTorrent::DBResumeDataStorage::lo
lt::error_code ec;
const lt::bdecode_node root = lt::bdecode(allData, ec);
resumeData.downloadPath = Profile::instance()->fromPortablePath(
Utils::Fs::toUniformPath(fromLTString(root.dict_find_string_value("qBt-downloadPath"))));
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
p = lt::read_resume_data(root, ec);
p.save_path = Profile::instance()->fromPortablePath(fromLTString(p.save_path)).toStdString();
p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path)))
.toString().toStdString();
return resumeData;
}
@ -485,7 +484,7 @@ void BitTorrent::DBResumeDataStorage::updateDBFromVersion1() const
}
}
BitTorrent::DBResumeDataStorage::Worker::Worker(const QString &dbPath, const QString &dbConnectionName)
BitTorrent::DBResumeDataStorage::Worker::Worker(const Path &dbPath, const QString &dbConnectionName)
: m_path {dbPath}
, m_connectionName {dbConnectionName}
{
@ -494,7 +493,7 @@ BitTorrent::DBResumeDataStorage::Worker::Worker(const QString &dbPath, const QSt
void BitTorrent::DBResumeDataStorage::Worker::openDatabase() const
{
auto db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), m_connectionName);
db.setDatabaseName(m_path);
db.setDatabaseName(m_path.data());
if (!db.open())
throw RuntimeError(db.lastError().text());
}
@ -508,7 +507,8 @@ void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const L
{
// We need to adjust native libtorrent resume data
lt::add_torrent_params p = resumeData.ltAddTorrentParams;
p.save_path = Profile::instance()->toPortablePath(QString::fromStdString(p.save_path)).toStdString();
p.save_path = Profile::instance()->toPortablePath(Path(p.save_path))
.toString().toStdString();
if (resumeData.stopped)
{
p.flags |= lt::torrent_flags::paused;
@ -603,8 +603,8 @@ void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const L
if (!resumeData.useAutoTMM)
{
query.bindValue(DB_COLUMN_TARGET_SAVE_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.savePath));
query.bindValue(DB_COLUMN_DOWNLOAD_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.downloadPath));
query.bindValue(DB_COLUMN_TARGET_SAVE_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.savePath).data());
query.bindValue(DB_COLUMN_DOWNLOAD_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.downloadPath).data());
}
query.bindValue(DB_COLUMN_RESUMEDATA.placeholder, bencodedResumeData);

View File

@ -28,6 +28,7 @@
#pragma once
#include "base/pathfwd.h"
#include "resumedatastorage.h"
class QThread;
@ -40,7 +41,7 @@ namespace BitTorrent
Q_DISABLE_COPY_MOVE(DBResumeDataStorage)
public:
explicit DBResumeDataStorage(const QString &dbPath, QObject *parent = nullptr);
explicit DBResumeDataStorage(const Path &dbPath, QObject *parent = nullptr);
~DBResumeDataStorage() override;
QVector<TorrentID> registeredTorrents() const override;

View File

@ -27,37 +27,33 @@
*/
#include "filesearcher.h"
#include <QDir>
#include "base/bittorrent/common.h"
#include "base/bittorrent/infohash.h"
void FileSearcher::search(const BitTorrent::TorrentID &id, const QStringList &originalFileNames
, const QString &savePath, const QString &downloadPath)
void FileSearcher::search(const BitTorrent::TorrentID &id, const PathList &originalFileNames
, const Path &savePath, const Path &downloadPath)
{
const auto findInDir = [](const QString &dirPath, QStringList &fileNames) -> bool
const auto findInDir = [](const Path &dirPath, PathList &fileNames) -> bool
{
const QDir dir {dirPath};
bool found = false;
for (QString &fileName : fileNames)
for (Path &fileName : fileNames)
{
if (dir.exists(fileName))
if ((dirPath / fileName).exists())
{
found = true;
}
else if (dir.exists(fileName + QB_EXT))
else if ((dirPath / fileName + QB_EXT).exists())
{
found = true;
fileName += QB_EXT;
fileName = fileName + QB_EXT;
}
}
return found;
};
QString usedPath = savePath;
QStringList adjustedFileNames = originalFileNames;
Path usedPath = savePath;
PathList adjustedFileNames = originalFileNames;
const bool found = findInDir(usedPath, adjustedFileNames);
if (!found && !downloadPath.isEmpty())
{

View File

@ -30,6 +30,8 @@
#include <QObject>
#include "base/path.h"
namespace BitTorrent
{
class TorrentID;
@ -44,9 +46,9 @@ public:
FileSearcher() = default;
public slots:
void search(const BitTorrent::TorrentID &id, const QStringList &originalFileNames
, const QString &savePath, const QString &downloadPath);
void search(const BitTorrent::TorrentID &id, const PathList &originalFileNames
, const Path &savePath, const Path &downloadPath);
signals:
void searchFinished(const BitTorrent::TorrentID &id, const QString &savePath, const QStringList &fileNames);
void searchFinished(const BitTorrent::TorrentID &id, const Path &savePath, const PathList &fileNames);
};

View File

@ -124,7 +124,7 @@ FilterParserThread::~FilterParserThread()
int FilterParserThread::parseDATFilterFile()
{
int ruleCount = 0;
QFile file(m_filePath);
QFile file {m_filePath.data()};
if (!file.exists()) return ruleCount;
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
@ -288,7 +288,7 @@ int FilterParserThread::parseDATFilterFile()
int FilterParserThread::parseP2PFilterFile()
{
int ruleCount = 0;
QFile file(m_filePath);
QFile file {m_filePath.data()};
if (!file.exists()) return ruleCount;
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
@ -469,7 +469,7 @@ int FilterParserThread::getlineInStream(QDataStream &stream, std::string &name,
int FilterParserThread::parseP2BFilterFile()
{
int ruleCount = 0;
QFile file(m_filePath);
QFile file {m_filePath.data()};
if (!file.exists()) return ruleCount;
if (!file.open(QIODevice::ReadOnly))
@ -592,7 +592,7 @@ int FilterParserThread::parseP2BFilterFile()
// * eMule IP list (DAT): http://wiki.phoenixlabs.org/wiki/DAT_Format
// * PeerGuardian Text (P2P): http://wiki.phoenixlabs.org/wiki/P2P_Format
// * PeerGuardian Binary (P2B): http://wiki.phoenixlabs.org/wiki/P2B_Format
void FilterParserThread::processFilterFile(const QString &filePath)
void FilterParserThread::processFilterFile(const Path &filePath)
{
if (isRunning())
{
@ -617,17 +617,17 @@ void FilterParserThread::run()
{
qDebug("Processing filter file");
int ruleCount = 0;
if (m_filePath.endsWith(".p2p", Qt::CaseInsensitive))
if (m_filePath.hasExtension(QLatin1String(".p2p")))
{
// PeerGuardian p2p file
ruleCount = parseP2PFilterFile();
}
else if (m_filePath.endsWith(".p2b", Qt::CaseInsensitive))
else if (m_filePath.hasExtension(QLatin1String(".p2b")))
{
// PeerGuardian p2b file
ruleCount = parseP2BFilterFile();
}
else if (m_filePath.endsWith(".dat", Qt::CaseInsensitive))
else if (m_filePath.hasExtension(QLatin1String(".dat")))
{
// eMule DAT format
ruleCount = parseDATFilterFile();

View File

@ -32,16 +32,19 @@
#include <QThread>
#include "base/path.h"
class QDataStream;
class FilterParserThread final : public QThread
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(FilterParserThread)
public:
FilterParserThread(QObject *parent = nullptr);
~FilterParserThread();
void processFilterFile(const QString &filePath);
void processFilterFile(const Path &filePath);
lt::ip_filter IPfilter();
signals:
@ -60,6 +63,6 @@ private:
int parseP2BFilterFile();
bool m_abort;
QString m_filePath;
Path m_filePath;
lt::ip_filter m_filter;
};

View File

@ -32,6 +32,7 @@
#include <QString>
#include "base/path.h"
#include "base/tagset.h"
#include "torrent.h"
#include "torrentcontentlayout.h"
@ -45,8 +46,8 @@ namespace BitTorrent
QString name;
QString category;
TagSet tags;
QString savePath;
QString downloadPath;
Path savePath;
Path downloadPath;
TorrentContentLayout contentLayout = TorrentContentLayout::Original;
TorrentOperatingMode operatingMode = TorrentOperatingMode::AutoManaged;
bool useAutoTMM = false;

View File

@ -108,7 +108,7 @@
using namespace BitTorrent;
const QString CATEGORIES_FILE_NAME {QStringLiteral("categories.json")};
const Path CATEGORIES_FILE_NAME {"categories.json"};
namespace
{
@ -394,8 +394,8 @@ Session::Session(QObject *parent)
, clampValue(SeedChokingAlgorithm::RoundRobin, SeedChokingAlgorithm::AntiLeech))
, m_storedTags(BITTORRENT_SESSION_KEY("Tags"))
, m_maxRatioAction(BITTORRENT_SESSION_KEY("MaxRatioAction"), Pause)
, m_savePath(BITTORRENT_SESSION_KEY("DefaultSavePath"), specialFolderLocation(SpecialFolder::Downloads), Utils::Fs::toUniformPath)
, m_downloadPath(BITTORRENT_SESSION_KEY("TempPath"), specialFolderLocation(SpecialFolder::Downloads) + QLatin1String("/temp"), Utils::Fs::toUniformPath)
, m_savePath(BITTORRENT_SESSION_KEY("DefaultSavePath"), specialFolderLocation(SpecialFolder::Downloads))
, m_downloadPath(BITTORRENT_SESSION_KEY("TempPath"), (savePath() / Path("temp")))
, m_isDownloadPathEnabled(BITTORRENT_SESSION_KEY("TempPathEnabled"), false)
, m_isSubcategoriesEnabled(BITTORRENT_SESSION_KEY("SubcategoriesEnabled"), false)
, m_useCategoryPathsInManualMode(BITTORRENT_SESSION_KEY("UseCategoryPathsInManualMode"), false)
@ -594,36 +594,34 @@ void Session::setPreallocationEnabled(const bool enabled)
m_isPreallocationEnabled = enabled;
}
QString Session::torrentExportDirectory() const
Path Session::torrentExportDirectory() const
{
return Utils::Fs::toUniformPath(m_torrentExportDirectory);
return m_torrentExportDirectory;
}
void Session::setTorrentExportDirectory(QString path)
void Session::setTorrentExportDirectory(const Path &path)
{
path = Utils::Fs::toUniformPath(path);
if (path != torrentExportDirectory())
m_torrentExportDirectory = path;
}
QString Session::finishedTorrentExportDirectory() const
Path Session::finishedTorrentExportDirectory() const
{
return Utils::Fs::toUniformPath(m_finishedTorrentExportDirectory);
return m_finishedTorrentExportDirectory;
}
void Session::setFinishedTorrentExportDirectory(QString path)
void Session::setFinishedTorrentExportDirectory(const Path &path)
{
path = Utils::Fs::toUniformPath(path);
if (path != finishedTorrentExportDirectory())
m_finishedTorrentExportDirectory = path;
}
QString Session::savePath() const
Path Session::savePath() const
{
return m_savePath;
}
QString Session::downloadPath() const
Path Session::downloadPath() const
{
return m_downloadPath;
}
@ -667,20 +665,20 @@ CategoryOptions Session::categoryOptions(const QString &categoryName) const
return m_categories.value(categoryName);
}
QString Session::categorySavePath(const QString &categoryName) const
Path Session::categorySavePath(const QString &categoryName) const
{
const QString basePath = savePath();
const Path basePath = savePath();
if (categoryName.isEmpty())
return basePath;
QString path = m_categories.value(categoryName).savePath;
Path path = m_categories.value(categoryName).savePath;
if (path.isEmpty()) // use implicit save path
path = Utils::Fs::toValidFileSystemName(categoryName, true);
path = Utils::Fs::toValidPath(categoryName);
return (QDir::isAbsolutePath(path) ? path : Utils::Fs::resolvePath(path, basePath));
return (path.isAbsolute() ? path : (basePath / path));
}
QString Session::categoryDownloadPath(const QString &categoryName) const
Path Session::categoryDownloadPath(const QString &categoryName) const
{
const CategoryOptions categoryOptions = m_categories.value(categoryName);
const CategoryOptions::DownloadPathOption downloadPathOption =
@ -688,15 +686,15 @@ QString Session::categoryDownloadPath(const QString &categoryName) const
if (!downloadPathOption.enabled)
return {};
const QString basePath = downloadPath();
const Path basePath = downloadPath();
if (categoryName.isEmpty())
return basePath;
const QString path = (!downloadPathOption.path.isEmpty()
const Path path = (!downloadPathOption.path.isEmpty()
? downloadPathOption.path
: Utils::Fs::toValidFileSystemName(categoryName, true)); // use implicit download path
: Utils::Fs::toValidPath(categoryName)); // use implicit download path
return (QDir::isAbsolutePath(path) ? path : Utils::Fs::resolvePath(path, basePath));
return (path.isAbsolute() ? path : (basePath / path));
}
bool Session::addCategory(const QString &name, const CategoryOptions &options)
@ -1722,7 +1720,7 @@ void Session::handleDownloadFinished(const Net::DownloadResult &result)
}
}
void Session::fileSearchFinished(const TorrentID &id, const QString &savePath, const QStringList &fileNames)
void Session::fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames)
{
TorrentImpl *torrent = m_torrents.value(id);
if (torrent)
@ -1739,11 +1737,11 @@ void Session::fileSearchFinished(const TorrentID &id, const QString &savePath, c
lt::add_torrent_params &p = params.ltAddTorrentParams;
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
p.save_path = savePath.toString().toStdString();
const TorrentInfo torrentInfo {*p.ti};
const auto nativeIndexes = torrentInfo.nativeIndexes();
for (int i = 0; i < fileNames.size(); ++i)
p.renamed_files[nativeIndexes[i]] = fileNames[i].toStdString();
p.renamed_files[nativeIndexes[i]] = fileNames[i].toString().toStdString();
loadTorrent(params);
}
@ -1811,7 +1809,7 @@ bool Session::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption
// Remove it from session
if (deleteOption == DeleteTorrent)
{
m_removingTorrents[torrent->id()] = {torrent->name(), "", deleteOption};
m_removingTorrents[torrent->id()] = {torrent->name(), {}, deleteOption};
const lt::torrent_handle nativeHandle {torrent->nativeHandle()};
const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
@ -2033,8 +2031,9 @@ bool Session::addTorrent(const QString &source, const AddTorrentParams &params)
if (magnetUri.isValid())
return addTorrent(magnetUri, params);
TorrentFileGuard guard {source};
const nonstd::expected<TorrentInfo, QString> loadResult = TorrentInfo::loadFromFile(source);
const Path path {source};
TorrentFileGuard guard {path};
const nonstd::expected<TorrentInfo, QString> loadResult = TorrentInfo::loadFromFile(path);
if (!loadResult)
{
LogMsg(tr("Couldn't load torrent: %1").arg(loadResult.error()), Log::WARNING);
@ -2079,27 +2078,27 @@ LoadTorrentParams Session::initLoadTorrentParams(const AddTorrentParams &addTorr
if (!loadTorrentParams.useAutoTMM)
{
if (QDir::isAbsolutePath(addTorrentParams.savePath))
if (addTorrentParams.savePath.isAbsolute())
{
loadTorrentParams.savePath = addTorrentParams.savePath;
}
else
{
const QString basePath = useCategoryPathsInManualMode() ? categorySavePath(loadTorrentParams.category) : savePath();
loadTorrentParams.savePath = Utils::Fs::resolvePath(addTorrentParams.savePath, basePath);
const Path basePath = useCategoryPathsInManualMode() ? categorySavePath(loadTorrentParams.category) : savePath();
loadTorrentParams.savePath = basePath / addTorrentParams.savePath;
}
const bool useDownloadPath = addTorrentParams.useDownloadPath.value_or(isDownloadPathEnabled());
if (useDownloadPath)
{
if (QDir::isAbsolutePath(addTorrentParams.downloadPath))
if (addTorrentParams.downloadPath.isAbsolute())
{
loadTorrentParams.downloadPath = addTorrentParams.downloadPath;
}
else
{
const QString basePath = useCategoryPathsInManualMode() ? categoryDownloadPath(loadTorrentParams.category) : downloadPath();
loadTorrentParams.downloadPath = Utils::Fs::resolvePath(addTorrentParams.downloadPath, basePath);
const Path basePath = useCategoryPathsInManualMode() ? categoryDownloadPath(loadTorrentParams.category) : downloadPath();
loadTorrentParams.downloadPath = basePath / addTorrentParams.downloadPath;
}
}
}
@ -2165,7 +2164,7 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
bool isFindingIncompleteFiles = false;
const bool useAutoTMM = loadTorrentParams.useAutoTMM;
const QString actualSavePath = useAutoTMM ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath;
const Path actualSavePath = useAutoTMM ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath;
if (hasMetadata)
{
@ -2175,16 +2174,16 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
const TorrentContentLayout contentLayout = ((loadTorrentParams.contentLayout == TorrentContentLayout::Original)
? detectContentLayout(torrentInfo.filePaths()) : loadTorrentParams.contentLayout);
QStringList filePaths = (!addTorrentParams.filePaths.isEmpty() ? addTorrentParams.filePaths : torrentInfo.filePaths());
applyContentLayout(filePaths, contentLayout, Utils::Fs::findRootFolder(torrentInfo.filePaths()));
PathList filePaths = (!addTorrentParams.filePaths.isEmpty() ? addTorrentParams.filePaths : torrentInfo.filePaths());
applyContentLayout(filePaths, contentLayout, Path::findRootFolder(torrentInfo.filePaths()));
// if torrent name wasn't explicitly set we handle the case of
// initial renaming of torrent content and rename torrent accordingly
if (loadTorrentParams.name.isEmpty())
{
QString contentName = Utils::Fs::findRootFolder(filePaths);
QString contentName = Path::findRootFolder(filePaths).toString();
if (contentName.isEmpty() && (filePaths.size() == 1))
contentName = Utils::Fs::fileName(filePaths.at(0));
contentName = filePaths.at(0).filename();
if (!contentName.isEmpty() && (contentName != torrentInfo.name()))
loadTorrentParams.name = contentName;
@ -2192,7 +2191,7 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
if (!loadTorrentParams.hasSeedStatus)
{
const QString actualDownloadPath = useAutoTMM
const Path actualDownloadPath = useAutoTMM
? categoryDownloadPath(loadTorrentParams.category) : loadTorrentParams.downloadPath;
findIncompleteFiles(torrentInfo, actualSavePath, actualDownloadPath, filePaths);
isFindingIncompleteFiles = true;
@ -2202,7 +2201,7 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
if (!filePaths.isEmpty())
{
for (int index = 0; index < addTorrentParams.filePaths.size(); ++index)
p.renamed_files[nativeIndexes[index]] = Utils::Fs::toNativePath(addTorrentParams.filePaths.at(index)).toStdString();
p.renamed_files[nativeIndexes[index]] = addTorrentParams.filePaths.at(index).toString().toStdString();
}
Q_ASSERT(p.file_priorities.empty());
@ -2224,7 +2223,7 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
loadTorrentParams.name = QString::fromStdString(p.name);
}
p.save_path = Utils::Fs::toNativePath(actualSavePath).toStdString();
p.save_path = actualSavePath.toString().toStdString();
p.upload_limit = addTorrentParams.uploadLimit;
p.download_limit = addTorrentParams.downloadLimit;
@ -2288,13 +2287,13 @@ bool Session::loadTorrent(LoadTorrentParams params)
return true;
}
void Session::findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath
, const QString &downloadPath, const QStringList &filePaths) const
void Session::findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath
, const Path &downloadPath, const PathList &filePaths) const
{
Q_ASSERT(filePaths.isEmpty() || (filePaths.size() == torrentInfo.filesCount()));
const auto searchId = TorrentID::fromInfoHash(torrentInfo.infoHash());
const QStringList originalFileNames = (filePaths.isEmpty() ? torrentInfo.filePaths() : filePaths);
const PathList originalFileNames = (filePaths.isEmpty() ? torrentInfo.filePaths() : filePaths);
QMetaObject::invokeMethod(m_fileSearcher, [=]()
{
m_fileSearcher->search(searchId, originalFileNames, savePath, downloadPath);
@ -2333,8 +2332,8 @@ bool Session::downloadMetadata(const MagnetUri &magnetUri)
p.max_connections = maxConnectionsPerTorrent();
p.max_uploads = maxUploadsPerTorrent();
const QString savePath = Utils::Fs::tempPath() + id.toString();
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
const Path savePath = Utils::Fs::tempPath() / Path(id.toString());
p.save_path = savePath.toString().toStdString();
// Forced start
p.flags &= ~lt::torrent_flags::paused;
@ -2360,28 +2359,27 @@ bool Session::downloadMetadata(const MagnetUri &magnetUri)
return true;
}
void Session::exportTorrentFile(const TorrentInfo &torrentInfo, const QString &folderPath, const QString &baseName)
void Session::exportTorrentFile(const TorrentInfo &torrentInfo, const Path &folderPath, const QString &baseName)
{
const QString validName = Utils::Fs::toValidFileSystemName(baseName);
QString torrentExportFilename = QString::fromLatin1("%1.torrent").arg(validName);
const QDir exportDir {folderPath};
if (exportDir.exists() || exportDir.mkpath(exportDir.absolutePath()))
{
QString newTorrentPath = exportDir.absoluteFilePath(torrentExportFilename);
int counter = 0;
while (QFile::exists(newTorrentPath))
{
// Append number to torrent name to make it unique
torrentExportFilename = QString::fromLatin1("%1 %2.torrent").arg(validName).arg(++counter);
newTorrentPath = exportDir.absoluteFilePath(torrentExportFilename);
}
if (!folderPath.exists() && !Utils::Fs::mkpath(folderPath))
return;
const nonstd::expected<void, QString> result = torrentInfo.saveToFile(newTorrentPath);
if (!result)
{
LogMsg(tr("Couldn't export torrent metadata file '%1'. Reason: %2.")
.arg(newTorrentPath, result.error()), Log::WARNING);
}
const QString validName = Utils::Fs::toValidFileName(baseName);
QString torrentExportFilename = QString::fromLatin1("%1.torrent").arg(validName);
Path newTorrentPath = folderPath / Path(torrentExportFilename);
int counter = 0;
while (newTorrentPath.exists())
{
// Append number to torrent name to make it unique
torrentExportFilename = QString::fromLatin1("%1 %2.torrent").arg(validName).arg(++counter);
newTorrentPath = folderPath / Path(torrentExportFilename);
}
const nonstd::expected<void, QString> result = torrentInfo.saveToFile(newTorrentPath);
if (!result)
{
LogMsg(tr("Couldn't export torrent metadata file '%1'. Reason: %2.")
.arg(newTorrentPath.toString(), result.error()), Log::WARNING);
}
}
@ -2455,13 +2453,13 @@ void Session::removeTorrentsQueue() const
m_resumeDataStorage->storeQueue({});
}
void Session::setSavePath(const QString &path)
void Session::setSavePath(const Path &path)
{
const QString baseSavePath = specialFolderLocation(SpecialFolder::Downloads);
const QString resolvedPath = (QDir::isAbsolutePath(path) ? path : Utils::Fs::resolvePath(path, baseSavePath));
if (resolvedPath == m_savePath) return;
const auto newPath = (path.isAbsolute() ? path : (specialFolderLocation(SpecialFolder::Downloads) / path));
if (newPath == m_savePath)
return;
m_savePath = resolvedPath;
m_savePath = newPath;
if (isDisableAutoTMMWhenDefaultSavePathChanged())
{
@ -2475,13 +2473,12 @@ void Session::setSavePath(const QString &path)
}
}
void Session::setDownloadPath(const QString &path)
void Session::setDownloadPath(const Path &path)
{
const QString baseDownloadPath = specialFolderLocation(SpecialFolder::Downloads) + QLatin1String("/temp");
const QString resolvedPath = (QDir::isAbsolutePath(path) ? path : Utils::Fs::resolvePath(path, baseDownloadPath));
if (resolvedPath != m_downloadPath)
const Path newPath = (path.isAbsolute() ? path : (savePath() / Path("temp") / path));
if (newPath != m_downloadPath)
{
m_downloadPath = resolvedPath;
m_downloadPath = newPath;
for (TorrentImpl *const torrent : asConst(m_torrents))
torrent->handleDownloadPathChanged();
@ -2953,14 +2950,13 @@ void Session::setIPFilteringEnabled(const bool enabled)
}
}
QString Session::IPFilterFile() const
Path Session::IPFilterFile() const
{
return Utils::Fs::toUniformPath(m_IPFilterFile);
return m_IPFilterFile;
}
void Session::setIPFilterFile(QString path)
void Session::setIPFilterFile(const Path &path)
{
path = Utils::Fs::toUniformPath(path);
if (path != IPFilterFile())
{
m_IPFilterFile = path;
@ -3994,13 +3990,13 @@ void Session::handleTorrentFinished(TorrentImpl *const torrent)
qDebug("Checking if the torrent contains torrent files to download");
// Check if there are torrent files inside
for (const QString &torrentRelpath : asConst(torrent->filePaths()))
for (const Path &torrentRelpath : asConst(torrent->filePaths()))
{
if (torrentRelpath.endsWith(".torrent", Qt::CaseInsensitive))
if (torrentRelpath.hasExtension(QLatin1String(".torrent")))
{
qDebug("Found possible recursive torrent download.");
const QString torrentFullpath = torrent->actualStorageLocation() + '/' + torrentRelpath;
qDebug("Full subtorrent path is %s", qUtf8Printable(torrentFullpath));
const Path torrentFullpath = torrent->actualStorageLocation() / torrentRelpath;
qDebug("Full subtorrent path is %s", qUtf8Printable(torrentFullpath.toString()));
if (TorrentInfo::loadFromFile(torrentFullpath))
{
qDebug("emitting recursiveTorrentDownloadPossible()");
@ -4010,7 +4006,7 @@ void Session::handleTorrentFinished(TorrentImpl *const torrent)
else
{
qDebug("Caught error loading torrent");
LogMsg(tr("Unable to decode '%1' torrent file.").arg(Utils::Fs::toNativePath(torrentFullpath)), Log::CRITICAL);
LogMsg(tr("Unable to decode '%1' torrent file.").arg(torrentFullpath.toString()), Log::CRITICAL);
}
}
}
@ -4047,12 +4043,12 @@ void Session::handleTorrentTrackerError(TorrentImpl *const torrent, const QStrin
emit trackerError(torrent, trackerUrl);
}
bool Session::addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newPath, const MoveStorageMode mode)
bool Session::addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, const MoveStorageMode mode)
{
Q_ASSERT(torrent);
const lt::torrent_handle torrentHandle = torrent->nativeHandle();
const QString currentLocation = Utils::Fs::toNativePath(torrent->actualStorageLocation());
const Path currentLocation = torrent->actualStorageLocation();
if (m_moveStorageQueue.size() > 1)
{
@ -4065,7 +4061,7 @@ bool Session::addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newP
if (iter != m_moveStorageQueue.end())
{
// remove existing inactive job
LogMsg(tr("Cancelled moving \"%1\" from \"%2\" to \"%3\".").arg(torrent->name(), currentLocation, iter->path));
LogMsg(tr("Cancelled moving \"%1\" from \"%2\" to \"%3\".").arg(torrent->name(), currentLocation.toString(), iter->path.toString()));
iter = m_moveStorageQueue.erase(iter);
iter = std::find_if(iter, m_moveStorageQueue.end(), [&torrentHandle](const MoveStorageJob &job)
@ -4082,26 +4078,26 @@ bool Session::addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newP
{
// if there is active job for this torrent prevent creating meaningless
// job that will move torrent to the same location as current one
if (QDir {m_moveStorageQueue.first().path} == QDir {newPath})
if (m_moveStorageQueue.first().path == newPath)
{
LogMsg(tr("Couldn't enqueue move of \"%1\" to \"%2\". Torrent is currently moving to the same destination location.")
.arg(torrent->name(), newPath));
.arg(torrent->name(), newPath.toString()));
return false;
}
}
else
{
if (QDir {currentLocation} == QDir {newPath})
if (currentLocation == newPath)
{
LogMsg(tr("Couldn't enqueue move of \"%1\" from \"%2\" to \"%3\". Both paths point to the same location.")
.arg(torrent->name(), currentLocation, newPath));
.arg(torrent->name(), currentLocation.toString(), newPath.toString()));
return false;
}
}
const MoveStorageJob moveStorageJob {torrentHandle, newPath, mode};
m_moveStorageQueue << moveStorageJob;
LogMsg(tr("Enqueued to move \"%1\" from \"%2\" to \"%3\".").arg(torrent->name(), currentLocation, newPath));
LogMsg(tr("Enqueued to move \"%1\" from \"%2\" to \"%3\".").arg(torrent->name(), currentLocation.toString(), newPath.toString()));
if (m_moveStorageQueue.size() == 1)
moveTorrentStorage(moveStorageJob);
@ -4118,9 +4114,9 @@ void Session::moveTorrentStorage(const MoveStorageJob &job) const
#endif
const TorrentImpl *torrent = m_torrents.value(id);
const QString torrentName = (torrent ? torrent->name() : id.toString());
LogMsg(tr("Moving \"%1\" to \"%2\"...").arg(torrentName, job.path));
LogMsg(tr("Moving \"%1\" to \"%2\"...").arg(torrentName, job.path.toString()));
job.torrentHandle.move_storage(job.path.toUtf8().constData()
job.torrentHandle.move_storage(job.path.toString().toStdString()
, ((job.mode == MoveStorageMode::Overwrite)
? lt::move_flags_t::always_replace_files : lt::move_flags_t::dont_replace));
}
@ -4164,13 +4160,13 @@ void Session::storeCategories() const
jsonObj[categoryName] = categoryOptions.toJSON();
}
const QString path = QDir(specialFolderLocation(SpecialFolder::Config)).absoluteFilePath(CATEGORIES_FILE_NAME);
const Path path = specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME;
const QByteArray data = QJsonDocument(jsonObj).toJson();
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, data);
if (!result)
{
LogMsg(tr("Couldn't store Categories configuration to %1. Error: %2")
.arg(path, result.error()), Log::WARNING);
.arg(path.toString(), result.error()), Log::WARNING);
}
}
@ -4181,7 +4177,7 @@ void Session::upgradeCategories()
{
const QString categoryName = it.key();
CategoryOptions categoryOptions;
categoryOptions.savePath = it.value().toString();
categoryOptions.savePath = Path(it.value().toString());
m_categories[categoryName] = categoryOptions;
}
@ -4192,7 +4188,7 @@ void Session::loadCategories()
{
m_categories.clear();
QFile confFile {QDir(specialFolderLocation(SpecialFolder::Config)).absoluteFilePath(CATEGORIES_FILE_NAME)};
QFile confFile {(specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME).data()};
if (!confFile.exists())
{
// TODO: Remove the following upgrade code in v4.5
@ -4308,14 +4304,14 @@ void Session::recursiveTorrentDownload(const TorrentID &id)
TorrentImpl *const torrent = m_torrents.value(id);
if (!torrent) return;
for (const QString &torrentRelpath : asConst(torrent->filePaths()))
for (const Path &torrentRelpath : asConst(torrent->filePaths()))
{
if (torrentRelpath.endsWith(QLatin1String(".torrent")))
if (torrentRelpath.hasExtension(QLatin1String(".torrent")))
{
LogMsg(tr("Recursive download of file '%1' embedded in torrent '%2'"
, "Recursive download of 'test.torrent' embedded in torrent 'test2'")
.arg(Utils::Fs::toNativePath(torrentRelpath), torrent->name()));
const QString torrentFullpath = torrent->savePath() + '/' + torrentRelpath;
.arg(torrentRelpath.toString(), torrent->name()));
const Path torrentFullpath = torrent->savePath() / torrentRelpath;
AddTorrentParams params;
// Passing the save path along to the sub torrent file
@ -4343,9 +4339,8 @@ void Session::startUpTorrents()
{
qDebug("Initializing torrents resume data storage...");
const QString dbPath = Utils::Fs::expandPathAbs(
specialFolderLocation(SpecialFolder::Data) + QLatin1String("/torrents.db"));
const bool dbStorageExists = QFile::exists(dbPath);
const Path dbPath = specialFolderLocation(SpecialFolder::Data) / Path("torrents.db");
const bool dbStorageExists = dbPath.exists();
ResumeDataStorage *startupStorage = nullptr;
if (resumeDataStorageType() == ResumeDataStorageType::SQLite)
@ -4354,15 +4349,13 @@ void Session::startUpTorrents()
if (!dbStorageExists)
{
const QString dataPath = Utils::Fs::expandPathAbs(
specialFolderLocation(SpecialFolder::Data) + QLatin1String("/BT_backup"));
const Path dataPath = specialFolderLocation(SpecialFolder::Data) / Path("BT_backup");
startupStorage = new BencodeResumeDataStorage(dataPath, this);
}
}
else
{
const QString dataPath = Utils::Fs::expandPathAbs(
specialFolderLocation(SpecialFolder::Data) + QLatin1String("/BT_backup"));
const Path dataPath = specialFolderLocation(SpecialFolder::Data) / Path("BT_backup");
m_resumeDataStorage = new BencodeResumeDataStorage(dataPath, this);
if (dbStorageExists)
@ -4491,7 +4484,7 @@ void Session::startUpTorrents()
{
delete startupStorage;
if (resumeDataStorageType() == ResumeDataStorageType::Legacy)
Utils::Fs::forceRemove(dbPath);
Utils::Fs::removeFile(dbPath);
if (isQueueingSystemEnabled())
m_resumeDataStorage->storeQueue(queue);
@ -5064,7 +5057,7 @@ void Session::handleStorageMovedAlert(const lt::storage_moved_alert *p)
const MoveStorageJob &currentJob = m_moveStorageQueue.first();
Q_ASSERT(currentJob.torrentHandle == p->handle);
const QString newPath {p->storage_path()};
const Path newPath {QString::fromUtf8(p->storage_path())};
Q_ASSERT(newPath == currentJob.path);
#ifdef QBT_USES_LIBTORRENT2
@ -5075,7 +5068,7 @@ void Session::handleStorageMovedAlert(const lt::storage_moved_alert *p)
TorrentImpl *torrent = m_torrents.value(id);
const QString torrentName = (torrent ? torrent->name() : id.toString());
LogMsg(tr("\"%1\" is successfully moved to \"%2\".").arg(torrentName, newPath));
LogMsg(tr("\"%1\" is successfully moved to \"%2\".").arg(torrentName, newPath.toString()));
handleMoveTorrentStorageJobFinished();
}
@ -5098,7 +5091,7 @@ void Session::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert
const QString currentLocation = QString::fromStdString(p->handle.status(lt::torrent_handle::query_save_path).save_path);
const QString errorMessage = QString::fromStdString(p->message());
LogMsg(tr("Failed to move \"%1\" from \"%2\" to \"%3\". Reason: %4.")
.arg(torrentName, currentLocation, currentJob.path, errorMessage), Log::CRITICAL);
.arg(torrentName, currentLocation, currentJob.path.toString(), errorMessage), Log::CRITICAL);
handleMoveTorrentStorageJobFinished();
}

View File

@ -43,6 +43,7 @@
#include <QtContainerFwd>
#include <QVector>
#include "base/path.h"
#include "base/settingvalue.h"
#include "base/types.h"
#include "addtorrentparams.h"
@ -202,7 +203,7 @@ namespace BitTorrent
} disk;
};
class Session : public QObject
class Session final : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(Session)
@ -212,10 +213,10 @@ namespace BitTorrent
static void freeInstance();
static Session *instance();
QString savePath() const;
void setSavePath(const QString &path);
QString downloadPath() const;
void setDownloadPath(const QString &path);
Path savePath() const;
void setSavePath(const Path &path);
Path downloadPath() const;
void setDownloadPath(const Path &path);
bool isDownloadPathEnabled() const;
void setDownloadPathEnabled(bool enabled);
@ -225,8 +226,8 @@ namespace BitTorrent
QStringList categories() const;
CategoryOptions categoryOptions(const QString &categoryName) const;
QString categorySavePath(const QString &categoryName) const;
QString categoryDownloadPath(const QString &categoryName) const;
Path categorySavePath(const QString &categoryName) const;
Path categoryDownloadPath(const QString &categoryName) const;
bool addCategory(const QString &name, const CategoryOptions &options = {});
bool editCategory(const QString &name, const CategoryOptions &options);
bool removeCategory(const QString &name);
@ -283,10 +284,10 @@ namespace BitTorrent
void setRefreshInterval(int value);
bool isPreallocationEnabled() const;
void setPreallocationEnabled(bool enabled);
QString torrentExportDirectory() const;
void setTorrentExportDirectory(QString path);
QString finishedTorrentExportDirectory() const;
void setFinishedTorrentExportDirectory(QString path);
Path torrentExportDirectory() const;
void setTorrentExportDirectory(const Path &path);
Path finishedTorrentExportDirectory() const;
void setFinishedTorrentExportDirectory(const Path &path);
int globalDownloadSpeedLimit() const;
void setGlobalDownloadSpeedLimit(int limit);
@ -329,8 +330,8 @@ namespace BitTorrent
void setAdditionalTrackers(const QString &trackers);
bool isIPFilteringEnabled() const;
void setIPFilteringEnabled(bool enabled);
QString IPFilterFile() const;
void setIPFilterFile(QString path);
Path IPFilterFile() const;
void setIPFilterFile(const Path &path);
bool announceToAllTrackers() const;
void setAnnounceToAllTrackers(bool val);
bool announceToAllTiers() const;
@ -501,10 +502,10 @@ namespace BitTorrent
void handleTorrentTrackerWarning(TorrentImpl *const torrent, const QString &trackerUrl);
void handleTorrentTrackerError(TorrentImpl *const torrent, const QString &trackerUrl);
bool addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newPath, MoveStorageMode mode);
bool addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, MoveStorageMode mode);
void findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath
, const QString &downloadPath, const QStringList &filePaths = {}) const;
void findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath
, const Path &downloadPath, const PathList &filePaths = {}) const;
signals:
void allTorrentsFinished();
@ -553,7 +554,7 @@ namespace BitTorrent
void handleIPFilterParsed(int ruleCount);
void handleIPFilterError();
void handleDownloadFinished(const Net::DownloadResult &result);
void fileSearchFinished(const TorrentID &id, const QString &savePath, const QStringList &fileNames);
void fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames);
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
// Session reconfiguration triggers
@ -565,14 +566,14 @@ namespace BitTorrent
struct MoveStorageJob
{
lt::torrent_handle torrentHandle;
QString path;
Path path;
MoveStorageMode mode;
};
struct RemovingTorrentData
{
QString name;
QString pathToRemove;
Path pathToRemove;
DeleteOption deleteOption;
};
@ -611,7 +612,7 @@ namespace BitTorrent
bool addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source, const AddTorrentParams &addTorrentParams);
void updateSeedingLimitTimer();
void exportTorrentFile(const TorrentInfo &torrentInfo, const QString &folderPath, const QString &baseName);
void exportTorrentFile(const TorrentInfo &torrentInfo, const Path &folderPath, const QString &baseName);
void handleAlert(const lt::alert *a);
void dispatchTorrentAlert(const lt::alert *a);
@ -663,7 +664,7 @@ namespace BitTorrent
CachedSettingValue<bool> m_isPeXEnabled;
CachedSettingValue<bool> m_isIPFilteringEnabled;
CachedSettingValue<bool> m_isTrackerFilteringEnabled;
CachedSettingValue<QString> m_IPFilterFile;
CachedSettingValue<Path> m_IPFilterFile;
CachedSettingValue<bool> m_announceToAllTrackers;
CachedSettingValue<bool> m_announceToAllTiers;
CachedSettingValue<int> m_asyncIOThreads;
@ -721,8 +722,8 @@ namespace BitTorrent
CachedSettingValue<bool> m_isAppendExtensionEnabled;
CachedSettingValue<int> m_refreshInterval;
CachedSettingValue<bool> m_isPreallocationEnabled;
CachedSettingValue<QString> m_torrentExportDirectory;
CachedSettingValue<QString> m_finishedTorrentExportDirectory;
CachedSettingValue<Path> m_torrentExportDirectory;
CachedSettingValue<Path> m_finishedTorrentExportDirectory;
CachedSettingValue<int> m_globalDownloadSpeedLimit;
CachedSettingValue<int> m_globalUploadSpeedLimit;
CachedSettingValue<int> m_altGlobalDownloadSpeedLimit;
@ -740,8 +741,8 @@ namespace BitTorrent
CachedSettingValue<SeedChokingAlgorithm> m_seedChokingAlgorithm;
CachedSettingValue<QStringList> m_storedTags;
CachedSettingValue<int> m_maxRatioAction;
CachedSettingValue<QString> m_savePath;
CachedSettingValue<QString> m_downloadPath;
CachedSettingValue<Path> m_savePath;
CachedSettingValue<Path> m_downloadPath;
CachedSettingValue<bool> m_isDownloadPathEnabled;
CachedSettingValue<bool> m_isSubcategoriesEnabled;
CachedSettingValue<bool> m_useCategoryPathsInManualMode;

View File

@ -29,10 +29,11 @@
#pragma once
#include <QtContainerFwd>
#include <QMetaType>
#include <QString>
#include <QtContainerFwd>
#include "base/pathfwd.h"
#include "base/tagset.h"
#include "abstractfilestorage.h"
@ -169,13 +170,13 @@ namespace BitTorrent
virtual bool isAutoTMMEnabled() const = 0;
virtual void setAutoTMMEnabled(bool enabled) = 0;
virtual QString savePath() const = 0;
virtual void setSavePath(const QString &savePath) = 0;
virtual QString downloadPath() const = 0;
virtual void setDownloadPath(const QString &downloadPath) = 0;
virtual QString actualStorageLocation() const = 0;
virtual QString rootPath() const = 0;
virtual QString contentPath() const = 0;
virtual Path savePath() const = 0;
virtual void setSavePath(const Path &savePath) = 0;
virtual Path downloadPath() const = 0;
virtual void setDownloadPath(const Path &downloadPath) = 0;
virtual Path actualStorageLocation() const = 0;
virtual Path rootPath() const = 0;
virtual Path contentPath() const = 0;
virtual QString category() const = 0;
virtual bool belongsToCategory(const QString &category) const = 0;
virtual bool setCategory(const QString &category) = 0;
@ -193,8 +194,8 @@ namespace BitTorrent
virtual qreal ratioLimit() const = 0;
virtual int seedingTimeLimit() const = 0;
virtual QString actualFilePath(int index) const = 0;
virtual QStringList filePaths() const = 0;
virtual Path actualFilePath(int index) const = 0;
virtual PathList filePaths() const = 0;
virtual QVector<DownloadPriority> filePriorities() const = 0;
virtual TorrentInfo info() const = 0;

View File

@ -32,36 +32,35 @@
namespace
{
QString removeExtension(const QString &fileName)
Path removeExtension(const Path &fileName)
{
const QString extension = Utils::Fs::fileExtension(fileName);
return extension.isEmpty()
? fileName
: fileName.chopped(extension.size() + 1);
Path result = fileName;
result.removeExtension();
return result;
}
}
BitTorrent::TorrentContentLayout BitTorrent::detectContentLayout(const QStringList &filePaths)
BitTorrent::TorrentContentLayout BitTorrent::detectContentLayout(const PathList &filePaths)
{
const QString rootFolder = Utils::Fs::findRootFolder(filePaths);
const Path rootFolder = Path::findRootFolder(filePaths);
return (rootFolder.isEmpty()
? TorrentContentLayout::NoSubfolder
: TorrentContentLayout::Subfolder);
}
void BitTorrent::applyContentLayout(QStringList &filePaths, const BitTorrent::TorrentContentLayout contentLayout, const QString &rootFolder)
void BitTorrent::applyContentLayout(PathList &filePaths, const BitTorrent::TorrentContentLayout contentLayout, const Path &rootFolder)
{
Q_ASSERT(!filePaths.isEmpty());
switch (contentLayout)
{
case TorrentContentLayout::Subfolder:
if (Utils::Fs::findRootFolder(filePaths).isEmpty())
Utils::Fs::addRootFolder(filePaths, !rootFolder.isEmpty() ? rootFolder : removeExtension(filePaths.at(0)));
if (Path::findRootFolder(filePaths).isEmpty())
Path::addRootFolder(filePaths, !rootFolder.isEmpty() ? rootFolder : removeExtension(filePaths.at(0)));
break;
case TorrentContentLayout::NoSubfolder:
Utils::Fs::stripRootFolder(filePaths);
Path::stripRootFolder(filePaths);
break;
default:

View File

@ -28,8 +28,11 @@
#pragma once
#include <QtContainerFwd>
#include <QMetaEnum>
#include "base/path.h"
namespace BitTorrent
{
// Using `Q_ENUM_NS()` without a wrapper namespace in our case is not advised
@ -49,6 +52,6 @@ namespace BitTorrent
Q_ENUM_NS(TorrentContentLayout)
}
TorrentContentLayout detectContentLayout(const QStringList &filePaths);
void applyContentLayout(QStringList &filePaths, TorrentContentLayout contentLayout, const QString &rootFolder = {});
TorrentContentLayout detectContentLayout(const PathList &filePaths);
void applyContentLayout(PathList &filePaths, TorrentContentLayout contentLayout, const Path &rootFolder = {});
}

View File

@ -52,7 +52,7 @@ namespace
// name starts with a .
bool fileFilter(const std::string &f)
{
return !Utils::Fs::fileName(QString::fromStdString(f)).startsWith('.');
return !Path(f).filename().startsWith('.');
}
#ifdef QBT_USES_LIBTORRENT2
@ -108,50 +108,50 @@ void TorrentCreatorThread::run()
try
{
const QString parentPath = Utils::Fs::branchPath(m_params.inputPath) + '/';
const Path parentPath = m_params.inputPath.parentPath();
const Utils::Compare::NaturalLessThan<Qt::CaseInsensitive> naturalLessThan {};
// Adding files to the torrent
lt::file_storage fs;
if (QFileInfo(m_params.inputPath).isFile())
if (QFileInfo(m_params.inputPath.data()).isFile())
{
lt::add_files(fs, Utils::Fs::toNativePath(m_params.inputPath).toStdString(), fileFilter);
lt::add_files(fs, m_params.inputPath.toString().toStdString(), fileFilter);
}
else
{
// need to sort the file names by natural sort order
QStringList dirs = {m_params.inputPath};
QStringList dirs = {m_params.inputPath.data()};
QDirIterator dirIter {m_params.inputPath, (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories};
QDirIterator dirIter {m_params.inputPath.data(), (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories};
while (dirIter.hasNext())
{
dirIter.next();
dirs += dirIter.filePath();
dirs.append(dirIter.filePath());
}
std::sort(dirs.begin(), dirs.end(), naturalLessThan);
QStringList fileNames;
QHash<QString, qint64> fileSizeMap;
for (const auto &dir : asConst(dirs))
for (const QString &dir : asConst(dirs))
{
QStringList tmpNames; // natural sort files within each dir
QDirIterator fileIter(dir, QDir::Files);
QDirIterator fileIter {dir, QDir::Files};
while (fileIter.hasNext())
{
fileIter.next();
const QString relFilePath = fileIter.filePath().mid(parentPath.length());
tmpNames += relFilePath;
fileSizeMap[relFilePath] = fileIter.fileInfo().size();
const auto relFilePath = parentPath.relativePathOf(Path(fileIter.filePath()));
tmpNames.append(relFilePath.toString());
fileSizeMap[tmpNames.last()] = fileIter.fileInfo().size();
}
std::sort(tmpNames.begin(), tmpNames.end(), naturalLessThan);
fileNames += tmpNames;
}
for (const auto &fileName : asConst(fileNames))
for (const QString &fileName : asConst(fileNames))
fs.add_file(fileName.toStdString(), fileSizeMap[fileName]);
}
@ -182,7 +182,7 @@ void TorrentCreatorThread::run()
}
// calculate the hash for all pieces
lt::set_piece_hashes(newTorrent, Utils::Fs::toNativePath(parentPath).toStdString()
lt::set_piece_hashes(newTorrent, parentPath.toString().toStdString()
, [this, &newTorrent](const lt::piece_index_t n)
{
checkInterruptionRequested();
@ -225,16 +225,16 @@ void TorrentCreatorThread::run()
}
#ifdef QBT_USES_LIBTORRENT2
int TorrentCreatorThread::calculateTotalPieces(const QString &inputPath, const int pieceSize, const TorrentFormat torrentFormat)
int TorrentCreatorThread::calculateTotalPieces(const Path &inputPath, const int pieceSize, const TorrentFormat torrentFormat)
#else
int TorrentCreatorThread::calculateTotalPieces(const QString &inputPath, const int pieceSize, const bool isAlignmentOptimized, const int paddedFileSizeLimit)
int TorrentCreatorThread::calculateTotalPieces(const Path &inputPath, const int pieceSize, const bool isAlignmentOptimized, const int paddedFileSizeLimit)
#endif
{
if (inputPath.isEmpty())
return 0;
lt::file_storage fs;
lt::add_files(fs, Utils::Fs::toNativePath(inputPath).toStdString(), fileFilter);
lt::add_files(fs, inputPath.toString().toStdString(), fileFilter);
#ifdef QBT_USES_LIBTORRENT2
return lt::create_torrent {fs, pieceSize, toNativeTorrentFormatFlag(torrentFormat)}.num_pieces();

View File

@ -31,6 +31,8 @@
#include <QStringList>
#include <QThread>
#include "base/path.h"
namespace BitTorrent
{
#ifdef QBT_USES_LIBTORRENT2
@ -52,8 +54,8 @@ namespace BitTorrent
int paddedFileSizeLimit;
#endif
int pieceSize;
QString inputPath;
QString savePath;
Path inputPath;
Path savePath;
QString comment;
QString source;
QStringList trackers;
@ -72,15 +74,15 @@ namespace BitTorrent
void create(const TorrentCreatorParams &params);
#ifdef QBT_USES_LIBTORRENT2
static int calculateTotalPieces(const QString &inputPath, const int pieceSize, const TorrentFormat torrentFormat);
static int calculateTotalPieces(const Path &inputPath, const int pieceSize, const TorrentFormat torrentFormat);
#else
static int calculateTotalPieces(const QString &inputPath
static int calculateTotalPieces(const Path &inputPath
, const int pieceSize, const bool isAlignmentOptimized, int paddedFileSizeLimit);
#endif
signals:
void creationFailure(const QString &msg);
void creationSuccess(const QString &path, const QString &branchPath);
void creationSuccess(const Path &path, const Path &branchPath);
void updateProgress(int progress);
private:

View File

@ -45,7 +45,6 @@
#endif
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QStringList>
#include <QUrl>
@ -282,8 +281,10 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession
{
const lt::file_index_t nativeIndex = m_torrentInfo.nativeIndexes().at(i);
m_indexMap[nativeIndex] = i;
const QString filePath = Utils::Fs::toUniformPath(QString::fromStdString(fileStorage.file_path(nativeIndex)));
m_filePaths.append(filePath.endsWith(QB_EXT, Qt::CaseInsensitive) ? filePath.chopped(QB_EXT.size()) : filePath);
Path filePath {fileStorage.file_path(nativeIndex)};
if (filePath.hasExtension(QB_EXT))
filePath.removeExtension();
m_filePaths.append(filePath);
}
}
@ -295,24 +296,22 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession
// TODO: Remove the following upgrade code in v4.4
// == BEGIN UPGRADE CODE ==
const QString spath = actualStorageLocation();
const Path spath = actualStorageLocation();
for (int i = 0; i < filesCount(); ++i)
{
const QString filepath = filePath(i);
const Path filepath = filePath(i);
// Move "unwanted" files back to their original folder
const QString parentRelPath = Utils::Fs::branchPath(filepath);
if (QDir(parentRelPath).dirName() == ".unwanted")
const Path parentRelPath = filepath.parentPath();
if (parentRelPath.filename() == QLatin1String(".unwanted"))
{
const QString oldName = Utils::Fs::fileName(filepath);
const QString newRelPath = Utils::Fs::branchPath(parentRelPath);
if (newRelPath.isEmpty())
renameFile(i, oldName);
else
renameFile(i, QDir(newRelPath).filePath(oldName));
const QString oldName = filepath.filename();
const Path newRelPath = parentRelPath.parentPath();
renameFile(i, (newRelPath / Path(oldName)));
// Remove .unwanted directory if empty
qDebug() << "Attempting to remove \".unwanted\" folder at " << QDir(spath + '/' + newRelPath).absoluteFilePath(".unwanted");
QDir(spath + '/' + newRelPath).rmdir(".unwanted");
const Path newPath = spath / newRelPath;
qDebug() << "Attempting to remove \".unwanted\" folder at " << (newPath / Path(".unwanted")).toString();
Utils::Fs::rmdir(newPath / Path(".unwanted"));
}
}
// == END UPGRADE CODE ==
@ -396,18 +395,18 @@ QString TorrentImpl::currentTracker() const
return QString::fromStdString(m_nativeStatus.current_tracker);
}
QString TorrentImpl::savePath() const
Path TorrentImpl::savePath() const
{
return isAutoTMMEnabled() ? m_session->categorySavePath(category()) : m_savePath;
}
void TorrentImpl::setSavePath(const QString &path)
void TorrentImpl::setSavePath(const Path &path)
{
Q_ASSERT(!isAutoTMMEnabled());
const QString basePath = m_session->useCategoryPathsInManualMode()
const Path basePath = m_session->useCategoryPathsInManualMode()
? m_session->categorySavePath(category()) : m_session->savePath();
const QString resolvedPath = (QDir::isAbsolutePath(path) ? path : Utils::Fs::resolvePath(path, basePath));
const Path resolvedPath = (path.isAbsolute() ? path : (basePath / path));
if (resolvedPath == savePath())
return;
@ -420,18 +419,18 @@ void TorrentImpl::setSavePath(const QString &path)
moveStorage(savePath(), MoveStorageMode::KeepExistingFiles);
}
QString TorrentImpl::downloadPath() const
Path TorrentImpl::downloadPath() const
{
return isAutoTMMEnabled() ? m_session->categoryDownloadPath(category()) : m_downloadPath;
}
void TorrentImpl::setDownloadPath(const QString &path)
void TorrentImpl::setDownloadPath(const Path &path)
{
Q_ASSERT(!isAutoTMMEnabled());
const QString basePath = m_session->useCategoryPathsInManualMode()
const Path basePath = m_session->useCategoryPathsInManualMode()
? m_session->categoryDownloadPath(category()) : m_session->downloadPath();
const QString resolvedPath = ((path.isEmpty() || QDir::isAbsolutePath(path)) ? path : Utils::Fs::resolvePath(path, basePath));
const Path resolvedPath = (path.isEmpty() || path.isAbsolute()) ? path : (basePath / path);
if (resolvedPath == m_downloadPath)
return;
@ -444,27 +443,27 @@ void TorrentImpl::setDownloadPath(const QString &path)
moveStorage((m_downloadPath.isEmpty() ? savePath() : m_downloadPath), MoveStorageMode::KeepExistingFiles);
}
QString TorrentImpl::rootPath() const
Path TorrentImpl::rootPath() const
{
if (!hasMetadata())
return {};
const QString relativeRootPath = Utils::Fs::findRootFolder(filePaths());
const Path relativeRootPath = Path::findRootFolder(filePaths());
if (relativeRootPath.isEmpty())
return {};
return QDir(actualStorageLocation()).absoluteFilePath(relativeRootPath);
return (actualStorageLocation() / relativeRootPath);
}
QString TorrentImpl::contentPath() const
Path TorrentImpl::contentPath() const
{
if (!hasMetadata())
return {};
if (filesCount() == 1)
return QDir(actualStorageLocation()).absoluteFilePath(filePath(0));
return (actualStorageLocation() / filePath(0));
const QString rootPath = this->rootPath();
const Path rootPath = this->rootPath();
return (rootPath.isEmpty() ? actualStorageLocation() : rootPath);
}
@ -491,9 +490,9 @@ void TorrentImpl::setAutoTMMEnabled(bool enabled)
adjustStorageLocation();
}
QString TorrentImpl::actualStorageLocation() const
Path TorrentImpl::actualStorageLocation() const
{
return Utils::Fs::toUniformPath(QString::fromStdString(m_nativeStatus.save_path));
return Path(m_nativeStatus.save_path);
}
void TorrentImpl::setAutoManaged(const bool enable)
@ -799,16 +798,15 @@ int TorrentImpl::seedingTimeLimit() const
return m_seedingTimeLimit;
}
QString TorrentImpl::filePath(const int index) const
Path TorrentImpl::filePath(const int index) const
{
return m_filePaths.at(index);
}
QString TorrentImpl::actualFilePath(const int index) const
Path TorrentImpl::actualFilePath(const int index) const
{
const auto nativeIndex = m_torrentInfo.nativeIndexes().at(index);
const std::string filePath = m_nativeHandle.torrent_file()->files().file_path(nativeIndex);
return Utils::Fs::toUniformPath(QString::fromStdString(filePath));
return Path(m_nativeHandle.torrent_file()->files().file_path(nativeIndex));
}
qlonglong TorrentImpl::fileSize(const int index) const
@ -816,7 +814,7 @@ qlonglong TorrentImpl::fileSize(const int index) const
return m_torrentInfo.fileSize(index);
}
QStringList TorrentImpl::filePaths() const
PathList TorrentImpl::filePaths() const
{
return m_filePaths;
}
@ -1481,12 +1479,12 @@ void TorrentImpl::applyFirstLastPiecePriority(const bool enabled, const QVector<
m_nativeHandle.prioritize_pieces(piecePriorities);
}
void TorrentImpl::fileSearchFinished(const QString &savePath, const QStringList &fileNames)
void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileNames)
{
endReceivedMetadataHandling(savePath, fileNames);
}
void TorrentImpl::endReceivedMetadataHandling(const QString &savePath, const QStringList &fileNames)
void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathList &fileNames)
{
Q_ASSERT(m_filePaths.isEmpty());
Q_ASSERT(m_indexMap.isEmpty());
@ -1502,11 +1500,14 @@ void TorrentImpl::endReceivedMetadataHandling(const QString &savePath, const QSt
const auto nativeIndex = nativeIndexes.at(i);
m_indexMap[nativeIndex] = i;
const QString filePath = fileNames.at(i);
m_filePaths.append(filePath.endsWith(QB_EXT, Qt::CaseInsensitive) ? filePath.chopped(QB_EXT.size()) : filePath);
p.renamed_files[nativeIndex] = filePath.toStdString();
Path filePath = fileNames.at(i);
p.renamed_files[nativeIndex] = filePath.toString().toStdString();
if (filePath.hasExtension(QB_EXT))
filePath.removeExtension();
m_filePaths.append(filePath);
}
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
p.save_path = savePath.toString().toStdString();
p.ti = metadata;
const int internalFilesCount = p.ti->files().num_files(); // including .pad files
@ -1613,19 +1614,20 @@ void TorrentImpl::resume(const TorrentOperatingMode mode)
}
}
void TorrentImpl::moveStorage(const QString &newPath, const MoveStorageMode mode)
void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageMode mode)
{
if (m_session->addMoveTorrentStorageJob(this, Utils::Fs::toNativePath(newPath), mode))
if (m_session->addMoveTorrentStorageJob(this, newPath, mode))
{
m_storageIsMoving = true;
updateStatus();
}
}
void TorrentImpl::renameFile(const int index, const QString &path)
void TorrentImpl::renameFile(const int index, const Path &path)
{
++m_renameCount;
m_nativeHandle.rename_file(m_torrentInfo.nativeIndexes().at(index), Utils::Fs::toNativePath(path).toStdString());
m_nativeHandle.rename_file(m_torrentInfo.nativeIndexes().at(index)
, path.toString().toStdString());
}
void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
@ -1790,7 +1792,7 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
TorrentInfo metadata = TorrentInfo(*m_nativeHandle.torrent_file());
QStringList filePaths = metadata.filePaths();
PathList filePaths = metadata.filePaths();
applyContentLayout(filePaths, m_contentLayout);
m_session->findIncompleteFiles(metadata, savePath(), downloadPath(), filePaths);
}
@ -1876,37 +1878,23 @@ void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
// Remove empty leftover folders
// For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
// be removed if they are empty
const QString oldFilePath = m_filePaths.at(fileIndex);
const QString newFilePath = Utils::Fs::toUniformPath(p->new_name());
const Path oldFilePath = m_filePaths.at(fileIndex);
const Path newFilePath {QString(p->new_name())};
// Check if ".!qB" extension was just added or removed
if ((oldFilePath != newFilePath) && (oldFilePath != newFilePath.chopped(QB_EXT.size())))
// We should compare path in a case sensitive manner even on case insensitive
// platforms since it can be renamed by only changing case of some character(s)
if ((oldFilePath.data() != newFilePath.data())
&& ((oldFilePath + QB_EXT) != newFilePath))
{
m_filePaths[fileIndex] = newFilePath;
QList<QStringView> oldPathParts = QStringView(oldFilePath).split('/', Qt::SkipEmptyParts);
oldPathParts.removeLast(); // drop file name part
QList<QStringView> newPathParts = QStringView(newFilePath).split('/', Qt::SkipEmptyParts);
newPathParts.removeLast(); // drop file name part
#if defined(Q_OS_WIN)
const Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive;
#else
const Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive;
#endif
int pathIdx = 0;
while ((pathIdx < oldPathParts.size()) && (pathIdx < newPathParts.size()))
Path oldParentPath = oldFilePath.parentPath();
const Path commonBasePath = Path::commonPath(oldParentPath, newFilePath.parentPath());
while (oldParentPath != commonBasePath)
{
if (oldPathParts[pathIdx].compare(newPathParts[pathIdx], caseSensitivity) != 0)
break;
++pathIdx;
}
for (int i = (oldPathParts.size() - 1); i >= pathIdx; --i)
{
QDir().rmdir(savePath() + Utils::String::join(oldPathParts, QString::fromLatin1("/")));
oldPathParts.removeLast();
Utils::Fs::rmdir(actualStorageLocation() / oldParentPath);
oldParentPath = oldParentPath.parentPath();
}
}
@ -1923,7 +1911,7 @@ void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert
Q_ASSERT(fileIndex >= 0);
LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
.arg(name(), filePath(fileIndex), QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING);
.arg(name(), filePath(fileIndex).toString(), QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING);
--m_renameCount;
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
@ -1939,11 +1927,11 @@ void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
const int fileIndex = m_indexMap.value(p->index, -1);
Q_ASSERT(fileIndex >= 0);
const QString path = filePath(fileIndex);
const QString actualPath = actualFilePath(fileIndex);
const Path path = filePath(fileIndex);
const Path actualPath = actualFilePath(fileIndex);
if (actualPath != path)
{
qDebug("Renaming %s to %s", qUtf8Printable(actualPath), qUtf8Printable(path));
qDebug("Renaming %s to %s", qUtf8Printable(actualPath.toString()), qUtf8Printable(path.toString()));
renameFile(fileIndex, path);
}
}
@ -2064,18 +2052,17 @@ void TorrentImpl::manageIncompleteFiles()
for (int i = 0; i < filesCount(); ++i)
{
const QString path = filePath(i);
const Path path = filePath(i);
const auto nativeIndex = m_torrentInfo.nativeIndexes().at(i);
const QString actualPath = Utils::Fs::toUniformPath(
QString::fromStdString(nativeFiles.file_path(nativeIndex)));
const Path actualPath {nativeFiles.file_path(nativeIndex)};
if (isAppendExtensionEnabled && (fileSize(i) > 0) && (fp[i] < 1))
{
const QString wantedPath = path + QB_EXT;
const Path wantedPath = path + QB_EXT;
if (actualPath != wantedPath)
{
qDebug() << "Renaming" << actualPath << "to" << wantedPath;
qDebug() << "Renaming" << actualPath.toString() << "to" << wantedPath.toString();
renameFile(i, wantedPath);
}
}
@ -2083,7 +2070,7 @@ void TorrentImpl::manageIncompleteFiles()
{
if (actualPath != path)
{
qDebug() << "Renaming" << actualPath << "to" << path;
qDebug() << "Renaming" << actualPath.toString() << "to" << path.toString();
renameFile(i, path);
}
}
@ -2092,12 +2079,12 @@ void TorrentImpl::manageIncompleteFiles()
void TorrentImpl::adjustStorageLocation()
{
const QString downloadPath = this->downloadPath();
const Path downloadPath = this->downloadPath();
const bool isFinished = isSeed() || m_hasSeedStatus;
const QDir targetDir {((isFinished || downloadPath.isEmpty()) ? savePath() : downloadPath)};
const Path targetPath = ((isFinished || downloadPath.isEmpty()) ? savePath() : downloadPath);
if ((targetDir != QDir(actualStorageLocation())) || isMoveInProgress())
moveStorage(targetDir.absolutePath(), MoveStorageMode::Overwrite);
if ((targetPath != actualStorageLocation()) || isMoveInProgress())
moveStorage(targetPath, MoveStorageMode::Overwrite);
}
lt::torrent_handle TorrentImpl::nativeHandle() const

View File

@ -46,6 +46,7 @@
#include <QString>
#include <QVector>
#include "base/path.h"
#include "base/tagset.h"
#include "infohash.h"
#include "speedmonitor.h"
@ -102,13 +103,13 @@ namespace BitTorrent
bool isAutoTMMEnabled() const override;
void setAutoTMMEnabled(bool enabled) override;
QString savePath() const override;
void setSavePath(const QString &path) override;
QString downloadPath() const override;
void setDownloadPath(const QString &path) override;
QString actualStorageLocation() const override;
QString rootPath() const override;
QString contentPath() const override;
Path savePath() const override;
void setSavePath(const Path &path) override;
Path downloadPath() const override;
void setDownloadPath(const Path &path) override;
Path actualStorageLocation() const override;
Path rootPath() const override;
Path contentPath() const override;
QString category() const override;
bool belongsToCategory(const QString &category) const override;
bool setCategory(const QString &category) override;
@ -127,10 +128,10 @@ namespace BitTorrent
qreal ratioLimit() const override;
int seedingTimeLimit() const override;
QString filePath(int index) const override;
QString actualFilePath(int index) const override;
Path filePath(int index) const override;
Path actualFilePath(int index) const override;
qlonglong fileSize(int index) const override;
QStringList filePaths() const override;
PathList filePaths() const override;
QVector<DownloadPriority> filePriorities() const override;
TorrentInfo info() const override;
@ -205,7 +206,7 @@ namespace BitTorrent
void forceReannounce(int index = -1) override;
void forceDHTAnnounce() override;
void forceRecheck() override;
void renameFile(int index, const QString &path) override;
void renameFile(int index, const Path &path) override;
void prioritizeFiles(const QVector<DownloadPriority> &priorities) override;
void setRatioLimit(qreal limit) override;
void setSeedingTimeLimit(int limit) override;
@ -237,7 +238,7 @@ namespace BitTorrent
void handleAppendExtensionToggled();
void saveResumeData();
void handleMoveStorageJobFinished(bool hasOutstandingJob);
void fileSearchFinished(const QString &savePath, const QStringList &fileNames);
void fileSearchFinished(const Path &savePath, const PathList &fileNames);
private:
using EventTrigger = std::function<void ()>;
@ -271,12 +272,12 @@ namespace BitTorrent
void setAutoManaged(bool enable);
void adjustStorageLocation();
void moveStorage(const QString &newPath, MoveStorageMode mode);
void moveStorage(const Path &newPath, MoveStorageMode mode);
void manageIncompleteFiles();
void applyFirstLastPiecePriority(bool enabled, const QVector<DownloadPriority> &updatedFilePrio = {});
void prepareResumeData(const lt::add_torrent_params &params);
void endReceivedMetadataHandling(const QString &savePath, const QStringList &fileNames);
void endReceivedMetadataHandling(const Path &savePath, const PathList &fileNames);
void reload();
Session *const m_session;
@ -285,7 +286,7 @@ namespace BitTorrent
lt::torrent_status m_nativeStatus;
TorrentState m_state = TorrentState::Unknown;
TorrentInfo m_torrentInfo;
QStringList m_filePaths;
PathList m_filePaths;
QHash<lt::file_index_t, int> m_indexMap;
SpeedMonitor m_speedMonitor;
@ -304,8 +305,8 @@ namespace BitTorrent
// Persistent data
QString m_name;
QString m_savePath;
QString m_downloadPath;
Path m_savePath;
Path m_downloadPath;
QString m_category;
TagSet m_tags;
qreal m_ratioLimit;

View File

@ -99,16 +99,16 @@ nonstd::expected<TorrentInfo, QString> TorrentInfo::load(const QByteArray &data)
if (ec)
return nonstd::make_unexpected(QString::fromStdString(ec.message()));
lt::torrent_info nativeInfo {node, ec};
const lt::torrent_info nativeInfo {node, ec};
if (ec)
return nonstd::make_unexpected(QString::fromStdString(ec.message()));
return TorrentInfo(nativeInfo);
}
nonstd::expected<TorrentInfo, QString> TorrentInfo::loadFromFile(const QString &path) noexcept
nonstd::expected<TorrentInfo, QString> TorrentInfo::loadFromFile(const Path &path) noexcept
{
QFile file {path};
QFile file {path.data()};
if (!file.open(QIODevice::ReadOnly))
return nonstd::make_unexpected(file.errorString());
@ -133,7 +133,7 @@ nonstd::expected<TorrentInfo, QString> TorrentInfo::loadFromFile(const QString &
return load(data);
}
nonstd::expected<void, QString> TorrentInfo::saveToFile(const QString &path) const
nonstd::expected<void, QString> TorrentInfo::saveToFile(const Path &path) const
{
if (!isValid())
return nonstd::make_unexpected(tr("Invalid metadata"));
@ -236,17 +236,16 @@ int TorrentInfo::piecesCount() const
return m_nativeInfo->num_pieces();
}
QString TorrentInfo::filePath(const int index) const
Path TorrentInfo::filePath(const int index) const
{
if (!isValid()) return {};
return Utils::Fs::toUniformPath(
QString::fromStdString(m_nativeInfo->orig_files().file_path(m_nativeIndexes[index])));
return Path(m_nativeInfo->orig_files().file_path(m_nativeIndexes[index]));
}
QStringList TorrentInfo::filePaths() const
PathList TorrentInfo::filePaths() const
{
QStringList list;
PathList list;
list.reserve(filesCount());
for (int i = 0; i < filesCount(); ++i)
list << filePath(i);
@ -312,15 +311,15 @@ QByteArray TorrentInfo::metadata() const
#endif
}
QStringList TorrentInfo::filesForPiece(const int pieceIndex) const
PathList TorrentInfo::filesForPiece(const int pieceIndex) const
{
// no checks here because fileIndicesForPiece() will return an empty list
const QVector<int> fileIndices = fileIndicesForPiece(pieceIndex);
QStringList res;
PathList res;
res.reserve(fileIndices.size());
std::transform(fileIndices.begin(), fileIndices.end(), std::back_inserter(res),
[this](int i) { return filePath(i); });
std::transform(fileIndices.begin(), fileIndices.end(), std::back_inserter(res)
, [this](int i) { return filePath(i); });
return res;
}
@ -359,15 +358,15 @@ QVector<QByteArray> TorrentInfo::pieceHashes() const
return hashes;
}
TorrentInfo::PieceRange TorrentInfo::filePieces(const QString &file) const
TorrentInfo::PieceRange TorrentInfo::filePieces(const Path &filePath) const
{
if (!isValid()) // if we do not check here the debug message will be printed, which would be not correct
return {};
const int index = fileIndex(file);
const int index = fileIndex(filePath);
if (index == -1)
{
qDebug() << "Filename" << file << "was not found in torrent" << name();
qDebug() << "Filename" << filePath.toString() << "was not found in torrent" << name();
return {};
}
return filePieces(index);
@ -396,13 +395,13 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(const int fileIndex) const
return makeInterval(beginIdx, endIdx);
}
int TorrentInfo::fileIndex(const QString &fileName) const
int TorrentInfo::fileIndex(const Path &filePath) const
{
// the check whether the object is valid is not needed here
// because if filesCount() returns -1 the loop exits immediately
for (int i = 0; i < filesCount(); ++i)
{
if (fileName == filePath(i))
if (filePath == this->filePath(i))
return i;
}

View File

@ -35,6 +35,7 @@
#include "base/3rdparty/expected.hpp"
#include "base/indexrange.h"
#include "base/pathfwd.h"
#include "torrentcontentlayout.h"
class QByteArray;
@ -58,8 +59,8 @@ namespace BitTorrent
explicit TorrentInfo(const lt::torrent_info &nativeInfo);
static nonstd::expected<TorrentInfo, QString> load(const QByteArray &data) noexcept;
static nonstd::expected<TorrentInfo, QString> loadFromFile(const QString &path) noexcept;
nonstd::expected<void, QString> saveToFile(const QString &path) const;
static nonstd::expected<TorrentInfo, QString> loadFromFile(const Path &path) noexcept;
nonstd::expected<void, QString> saveToFile(const Path &path) const;
TorrentInfo &operator=(const TorrentInfo &other);
@ -75,21 +76,21 @@ namespace BitTorrent
int pieceLength() const;
int pieceLength(int index) const;
int piecesCount() const;
QString filePath(int index) const;
QStringList filePaths() const;
Path filePath(int index) const;
PathList filePaths() const;
qlonglong fileSize(int index) const;
qlonglong fileOffset(int index) const;
QVector<TrackerEntry> trackers() const;
QVector<QUrl> urlSeeds() const;
QByteArray metadata() const;
QStringList filesForPiece(int pieceIndex) const;
PathList filesForPiece(int pieceIndex) const;
QVector<int> fileIndicesForPiece(int pieceIndex) const;
QVector<QByteArray> pieceHashes() const;
using PieceRange = IndexRange<int>;
// returns pair of the first and the last pieces into which
// the given file extends (maybe partially).
PieceRange filePieces(const QString &file) const;
PieceRange filePieces(const Path &filePath) const;
PieceRange filePieces(int fileIndex) const;
std::shared_ptr<lt::torrent_info> nativeInfo() const;
@ -97,7 +98,7 @@ namespace BitTorrent
private:
// returns file index or -1 if fileName is not found
int fileIndex(const QString &fileName) const;
int fileIndex(const Path &filePath) const;
TorrentContentLayout contentLayout() const;
std::shared_ptr<const lt::torrent_info> m_nativeInfo;

View File

@ -29,7 +29,7 @@
#include "iconprovider.h"
#include <QFileInfo>
#include "base/path.h"
IconProvider::IconProvider(QObject *parent)
: QObject(parent)
@ -55,14 +55,14 @@ IconProvider *IconProvider::instance()
return m_instance;
}
QString IconProvider::getIconPath(const QString &iconId) const
Path IconProvider::getIconPath(const QString &iconId) const
{
// there are a few icons not available in svg
const QString pathSvg = ":/icons/" + iconId + ".svg";
if (QFileInfo::exists(pathSvg))
const Path pathSvg {":/icons/" + iconId + ".svg"};
if (pathSvg.exists())
return pathSvg;
const QString pathPng = ":/icons/" + iconId + ".png";
const Path pathPng {":/icons/" + iconId + ".png"};
return pathPng;
}

View File

@ -31,6 +31,8 @@
#include <QObject>
#include "base/pathfwd.h"
class QString;
class IconProvider : public QObject
@ -42,7 +44,7 @@ public:
static void freeInstance();
static IconProvider *instance();
virtual QString getIconPath(const QString &iconId) const;
virtual Path getIconPath(const QString &iconId) const;
protected:
explicit IconProvider(QObject *parent = nullptr);

View File

@ -42,14 +42,14 @@ const int MAX_REDIRECTIONS = 20; // the common value for web browsers
namespace
{
nonstd::expected<QString, QString> saveToTempFile(const QByteArray &data)
nonstd::expected<Path, QString> saveToTempFile(const QByteArray &data)
{
QTemporaryFile file {Utils::Fs::tempPath()};
QTemporaryFile file {Utils::Fs::tempPath().data()};
if (!file.open() || (file.write(data) != data.length()) || !file.flush())
return nonstd::make_unexpected(file.errorString());
file.setAutoRemove(false);
return file.fileName();
return Path(file.fileName());
}
}
@ -127,10 +127,10 @@ void DownloadHandlerImpl::processFinishedDownload()
if (m_downloadRequest.saveToFile())
{
const QString destinationPath = m_downloadRequest.destFileName();
const Path destinationPath = m_downloadRequest.destFileName();
if (destinationPath.isEmpty())
{
const nonstd::expected<QString, QString> result = saveToTempFile(m_result.data);
const nonstd::expected<Path, QString> result = saveToTempFile(m_result.data);
if (result)
m_result.filePath = result.value();
else

View File

@ -348,12 +348,12 @@ Net::DownloadRequest &Net::DownloadRequest::saveToFile(const bool value)
return *this;
}
QString Net::DownloadRequest::destFileName() const
Path Net::DownloadRequest::destFileName() const
{
return m_destFileName;
}
Net::DownloadRequest &Net::DownloadRequest::destFileName(const QString &value)
Net::DownloadRequest &Net::DownloadRequest::destFileName(const Path &value)
{
m_destFileName = value;
return *this;

View File

@ -35,6 +35,8 @@
#include <QQueue>
#include <QSet>
#include "base/path.h"
class QNetworkCookie;
class QNetworkReply;
class QSslError;
@ -81,15 +83,15 @@ namespace Net
// if saveToFile is set, the file is saved in destFileName
// (deprecated) if destFileName is not provided, the file will be saved
// in a temporary file, the name of file is set in DownloadResult::filePath
QString destFileName() const;
DownloadRequest &destFileName(const QString &value);
Path destFileName() const;
DownloadRequest &destFileName(const Path &value);
private:
QString m_url;
QString m_userAgent;
qint64 m_limit = 0;
bool m_saveToFile = false;
QString m_destFileName;
Path m_destFileName;
};
struct DownloadResult
@ -98,7 +100,7 @@ namespace Net
DownloadStatus status;
QString errorString;
QByteArray data;
QString filePath;
Path filePath;
QString magnet;
};

View File

@ -26,13 +26,15 @@
* exception statement from your version.
*/
#include "geoipdatabase.h"
#include <QDateTime>
#include <QDebug>
#include <QFile>
#include <QHostAddress>
#include <QVariant>
#include "geoipdatabase.h"
#include "base/path.h"
namespace
{
@ -84,10 +86,10 @@ GeoIPDatabase::GeoIPDatabase(const quint32 size)
{
}
GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
GeoIPDatabase *GeoIPDatabase::load(const Path &filename, QString &error)
{
GeoIPDatabase *db = nullptr;
QFile file(filename);
QFile file {filename.data()};
if (file.size() > MAX_FILE_SIZE)
{
error = tr("Unsupported database file size.");

View File

@ -28,11 +28,15 @@
#pragma once
#include <QCoreApplication>
#include <QtGlobal>
#include <QCoreApplication>
#include <QDateTime>
#include <QHash>
#include <QVariant>
#include "base/pathfwd.h"
class QByteArray;
class QDateTime;
class QHostAddress;
class QString;
@ -43,7 +47,7 @@ class GeoIPDatabase
Q_DECLARE_TR_FUNCTIONS(GeoIPDatabase)
public:
static GeoIPDatabase *load(const QString &filename, QString &error);
static GeoIPDatabase *load(const Path &filename, QString &error);
static GeoIPDatabase *load(const QByteArray &data, QString &error);
~GeoIPDatabase();

View File

@ -30,7 +30,6 @@
#include "geoipmanager.h"
#include <QDateTime>
#include <QDir>
#include <QHostAddress>
#include <QLocale>
@ -43,9 +42,9 @@
#include "downloadmanager.h"
#include "geoipdatabase.h"
static const QString DATABASE_URL = QStringLiteral("https://download.db-ip.com/free/dbip-country-lite-%1.mmdb.gz");
static const char GEODB_FOLDER[] = "GeoDB";
static const char GEODB_FILENAME[] = "dbip-country-lite.mmdb";
const QString DATABASE_URL = QStringLiteral("https://download.db-ip.com/free/dbip-country-lite-%1.mmdb.gz");
const char GEODB_FOLDER[] = "GeoDB";
const char GEODB_FILENAME[] = "dbip-country-lite.mmdb";
using namespace Net;
@ -88,17 +87,21 @@ void GeoIPManager::loadDatabase()
delete m_geoIPDatabase;
m_geoIPDatabase = nullptr;
const QString filepath = Utils::Fs::expandPathAbs(
QString::fromLatin1("%1/%2/%3").arg(specialFolderLocation(SpecialFolder::Data), GEODB_FOLDER, GEODB_FILENAME));
const Path filepath = specialFolderLocation(SpecialFolder::Data)
/ Path(GEODB_FOLDER) / Path(GEODB_FILENAME);
QString error;
m_geoIPDatabase = GeoIPDatabase::load(filepath, error);
if (m_geoIPDatabase)
{
Logger::instance()->addMessage(tr("IP geolocation database loaded. Type: %1. Build time: %2.")
.arg(m_geoIPDatabase->type(), m_geoIPDatabase->buildEpoch().toString()),
Log::INFO);
.arg(m_geoIPDatabase->type(), m_geoIPDatabase->buildEpoch().toString()),
Log::INFO);
}
else
{
Logger::instance()->addMessage(tr("Couldn't load IP geolocation database. Reason: %1").arg(error), Log::WARNING);
}
manageDatabaseUpdate();
}
@ -445,14 +448,13 @@ void GeoIPManager::downloadFinished(const DownloadResult &result)
delete m_geoIPDatabase;
m_geoIPDatabase = geoIPDatabase;
LogMsg(tr("IP geolocation database loaded. Type: %1. Build time: %2.")
.arg(m_geoIPDatabase->type(), m_geoIPDatabase->buildEpoch().toString()),
Log::INFO);
const QString targetPath = Utils::Fs::expandPathAbs(
QDir(specialFolderLocation(SpecialFolder::Data)).absoluteFilePath(GEODB_FOLDER));
if (!QDir(targetPath).exists())
QDir().mkpath(targetPath);
.arg(m_geoIPDatabase->type(), m_geoIPDatabase->buildEpoch().toString())
, Log::INFO);
const Path targetPath = specialFolderLocation(SpecialFolder::Data) / Path(GEODB_FOLDER);
if (!targetPath.exists())
Utils::Fs::mkpath(targetPath);
const auto path = QString::fromLatin1("%1/%2").arg(targetPath, GEODB_FILENAME);
const auto path = targetPath / Path(GEODB_FILENAME);
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, data);
if (result)
{

326
src/base/path.cpp Normal file
View File

@ -0,0 +1,326 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
*
* 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 "path.h"
#include <QDataStream>
#include <QDir>
#include <QFileInfo>
#include <QList>
#include <QMimeDatabase>
#include <QRegularExpression>
#if defined(Q_OS_WIN)
const Qt::CaseSensitivity CASE_SENSITIVITY = Qt::CaseInsensitive;
#else
const Qt::CaseSensitivity CASE_SENSITIVITY = Qt::CaseSensitive;
#endif
const int PATHLIST_TYPEID = qRegisterMetaType<PathList>();
Path::Path(const QString &pathStr)
: m_pathStr {QDir::cleanPath(pathStr)}
{
}
Path::Path(const std::string &pathStr)
: Path(QString::fromStdString(pathStr))
{
}
Path::Path(const char pathStr[])
: Path(QString::fromLatin1(pathStr))
{
}
bool Path::isValid() const
{
if (isEmpty())
return false;
#if defined(Q_OS_WIN)
const QRegularExpression regex {QLatin1String("[:?\"*<>|]")};
#elif defined(Q_OS_MACOS)
const QRegularExpression regex {QLatin1String("[\\0:]")};
#else
const QRegularExpression regex {QLatin1String("[\\0]")};
#endif
return !m_pathStr.contains(regex);
}
bool Path::isEmpty() const
{
return m_pathStr.isEmpty();
}
bool Path::isAbsolute() const
{
return QDir::isAbsolutePath(m_pathStr);
}
bool Path::isRelative() const
{
return QDir::isRelativePath(m_pathStr);
}
bool Path::exists() const
{
return !isEmpty() && QFileInfo::exists(m_pathStr);
}
Path Path::rootItem() const
{
const int slashIndex = m_pathStr.indexOf(QLatin1Char('/'));
if (slashIndex < 0)
return *this;
if (slashIndex == 0) // *nix absolute path
return createUnchecked(QLatin1String("/"));
return createUnchecked(m_pathStr.left(slashIndex));
}
Path Path::parentPath() const
{
const int slashIndex = m_pathStr.lastIndexOf(QLatin1Char('/'));
if (slashIndex == -1)
return {};
if (slashIndex == 0) // *nix absolute path
return (m_pathStr.size() == 1) ? Path() : createUnchecked(QLatin1String("/"));
return createUnchecked(m_pathStr.left(slashIndex));
}
QString Path::filename() const
{
const int slashIndex = m_pathStr.lastIndexOf('/');
if (slashIndex == -1)
return m_pathStr;
return m_pathStr.mid(slashIndex + 1);
}
QString Path::extension() const
{
const QString suffix = QMimeDatabase().suffixForFileName(m_pathStr);
if (!suffix.isEmpty())
return (QLatin1String(".") + suffix);
const int slashIndex = m_pathStr.lastIndexOf(QLatin1Char('/'));
const auto filename = QStringView(m_pathStr).mid(slashIndex + 1);
const int dotIndex = filename.lastIndexOf(QLatin1Char('.'));
return ((dotIndex == -1) ? QString() : filename.mid(dotIndex).toString());
}
bool Path::hasExtension(const QString &ext) const
{
return (extension().compare(ext, Qt::CaseInsensitive) == 0);
}
bool Path::hasAncestor(const Path &other) const
{
if (other.isEmpty() || (m_pathStr.size() <= other.m_pathStr.size()))
return false;
return (m_pathStr[other.m_pathStr.size()] == QLatin1Char('/'))
&& m_pathStr.startsWith(other.m_pathStr, CASE_SENSITIVITY);
}
Path Path::relativePathOf(const Path &childPath) const
{
// If both paths are relative, we assume that they have the same base path
if (isRelative() && childPath.isRelative())
return Path(QDir(QDir::home().absoluteFilePath(m_pathStr)).relativeFilePath(QDir::home().absoluteFilePath(childPath.data())));
return Path(QDir(m_pathStr).relativeFilePath(childPath.data()));
}
void Path::removeExtension()
{
m_pathStr.chop(extension().size());
}
QString Path::data() const
{
return m_pathStr;
}
QString Path::toString() const
{
return QDir::toNativeSeparators(m_pathStr);
}
Path &Path::operator/=(const Path &other)
{
*this = *this / other;
return *this;
}
Path &Path::operator+=(const QString &str)
{
*this = *this + str;
return *this;
}
Path &Path::operator+=(const char str[])
{
return (*this += QString::fromLatin1(str));
}
Path &Path::operator+=(const std::string &str)
{
return (*this += QString::fromStdString(str));
}
Path Path::commonPath(const Path &left, const Path &right)
{
if (left.isEmpty() || right.isEmpty())
return {};
const QList<QStringView> leftPathItems = QStringView(left.m_pathStr).split(u'/');
const QList<QStringView> rightPathItems = QStringView(right.m_pathStr).split(u'/');
int commonItemsCount = 0;
qsizetype commonPathSize = 0;
while ((commonItemsCount < leftPathItems.size()) && (commonItemsCount < rightPathItems.size()))
{
const QStringView leftPathItem = leftPathItems[commonItemsCount];
const QStringView rightPathItem = rightPathItems[commonItemsCount];
if (leftPathItem.compare(rightPathItem, CASE_SENSITIVITY) != 0)
break;
++commonItemsCount;
commonPathSize += leftPathItem.size();
}
if (commonItemsCount > 0)
commonPathSize += (commonItemsCount - 1); // size of intermediate separators
return Path::createUnchecked(left.m_pathStr.left(commonPathSize));
}
Path Path::findRootFolder(const PathList &filePaths)
{
Path rootFolder;
for (const Path &filePath : filePaths)
{
const auto filePathElements = QStringView(filePath.m_pathStr).split(u'/');
// if at least one file has no root folder, no common root folder exists
if (filePathElements.count() <= 1)
return {};
if (rootFolder.isEmpty())
rootFolder.m_pathStr = filePathElements.at(0).toString();
else if (rootFolder.m_pathStr != filePathElements.at(0))
return {};
}
return rootFolder;
}
void Path::stripRootFolder(PathList &filePaths)
{
const Path commonRootFolder = findRootFolder(filePaths);
if (commonRootFolder.isEmpty())
return;
for (Path &filePath : filePaths)
filePath.m_pathStr = filePath.m_pathStr.mid(commonRootFolder.m_pathStr.size() + 1);
}
void Path::addRootFolder(PathList &filePaths, const Path &rootFolder)
{
Q_ASSERT(!rootFolder.isEmpty());
for (Path &filePath : filePaths)
filePath = rootFolder / filePath;
}
Path Path::createUnchecked(const QString &pathStr)
{
Path path;
path.m_pathStr = pathStr;
return path;
}
bool operator==(const Path &lhs, const Path &rhs)
{
return (lhs.m_pathStr.compare(rhs.m_pathStr, CASE_SENSITIVITY) == 0);
}
bool operator!=(const Path &lhs, const Path &rhs)
{
return !(lhs == rhs);
}
Path operator/(const Path &lhs, const Path &rhs)
{
if (rhs.isEmpty())
return lhs;
if (lhs.isEmpty())
return rhs;
return Path(lhs.m_pathStr + QLatin1Char('/') + rhs.m_pathStr);
}
Path operator+(const Path &lhs, const QString &rhs)
{
return Path(lhs.m_pathStr + rhs);
}
Path operator+(const Path &lhs, const char rhs[])
{
return lhs + QString::fromLatin1(rhs);
}
Path operator+(const Path &lhs, const std::string &rhs)
{
return lhs + QString::fromStdString(rhs);
}
QDataStream &operator<<(QDataStream &out, const Path &path)
{
out << path.data();
return out;
}
QDataStream &operator>>(QDataStream &in, Path &path)
{
QString pathStr;
in >> pathStr;
path = Path(pathStr);
return in;
}
uint qHash(const Path &key, const uint seed)
{
return ::qHash(key.data(), seed);
}

100
src/base/path.h Normal file
View File

@ -0,0 +1,100 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
*
* 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.
*/
#pragma once
#include <QMetaType>
#include <QString>
#include "pathfwd.h"
class Path final
{
public:
Path() = default;
explicit Path(const QString &pathStr);
explicit Path(const char pathStr[]);
explicit Path(const std::string &pathStr);
bool isValid() const;
bool isEmpty() const;
bool isAbsolute() const;
bool isRelative() const;
bool exists() const;
Path rootItem() const;
Path parentPath() const;
QString filename() const;
QString extension() const;
bool hasExtension(const QString &ext) const;
void removeExtension();
bool hasAncestor(const Path &other) const;
Path relativePathOf(const Path &childPath) const;
QString data() const;
QString toString() const;
Path &operator/=(const Path &other);
Path &operator+=(const QString &str);
Path &operator+=(const char str[]);
Path &operator+=(const std::string &str);
static Path commonPath(const Path &left, const Path &right);
static Path findRootFolder(const PathList &filePaths);
static void stripRootFolder(PathList &filePaths);
static void addRootFolder(PathList &filePaths, const Path &rootFolder);
friend bool operator==(const Path &lhs, const Path &rhs);
friend Path operator/(const Path &lhs, const Path &rhs);
friend Path operator+(const Path &lhs, const QString &rhs);
private:
// this constructor doesn't perform any checks
// so it's intended for internal use only
static Path createUnchecked(const QString &pathStr);
QString m_pathStr;
};
Q_DECLARE_METATYPE(Path)
bool operator!=(const Path &lhs, const Path &rhs);
Path operator+(const Path &lhs, const char rhs[]);
Path operator+(const Path &lhs, const std::string &rhs);
QDataStream &operator<<(QDataStream &out, const Path &path);
QDataStream &operator>>(QDataStream &in, Path &path);
uint qHash(const Path &key, uint seed);

35
src/base/pathfwd.h Normal file
View File

@ -0,0 +1,35 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
*
* 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.
*/
#pragma once
#include <QtContainerFwd>
class Path;
using PathList = QList<Path>;

View File

@ -54,6 +54,7 @@
#include "algorithm.h"
#include "global.h"
#include "path.h"
#include "profile.h"
#include "settingsstorage.h"
#include "utils/fs.h"
@ -85,11 +86,11 @@ namespace
}
#ifdef Q_OS_WIN
QString makeProfileID(const QString &profilePath, const QString &profileName)
QString makeProfileID(const Path &profilePath, const QString &profileName)
{
return profilePath.isEmpty()
? profileName
: profileName + QLatin1Char('@') + Utils::Fs::toValidFileSystemName(profilePath, false, {});
: profileName + QLatin1Char('@') + Utils::Fs::toValidFileName(profilePath.data(), {});
}
#endif
}
@ -137,12 +138,12 @@ void Preferences::setUseCustomUITheme(const bool use)
setValue("Preferences/General/UseCustomUITheme", use);
}
QString Preferences::customUIThemePath() const
Path Preferences::customUIThemePath() const
{
return value<QString>("Preferences/General/CustomUIThemePath");
return value<Path>("Preferences/General/CustomUIThemePath");
}
void Preferences::setCustomUIThemePath(const QString &path)
void Preferences::setCustomUIThemePath(const Path &path)
{
setValue("Preferences/General/CustomUIThemePath", path);
}
@ -336,7 +337,7 @@ void Preferences::setPreventFromSuspendWhenSeeding(const bool b)
bool Preferences::WinStartup() const
{
const QString profileName = Profile::instance()->profileName();
const QString profilePath = Profile::instance()->rootPath();
const Path profilePath = Profile::instance()->rootPath();
const QString profileID = makeProfileID(profilePath, profileName);
const QSettings settings {"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat};
@ -346,7 +347,7 @@ bool Preferences::WinStartup() const
void Preferences::setWinStartup(const bool b)
{
const QString profileName = Profile::instance()->profileName();
const QString profilePath = Profile::instance()->rootPath();
const Path profilePath = Profile::instance()->rootPath();
const QString profileID = makeProfileID(profilePath, profileName);
QSettings settings {"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat};
if (b)
@ -354,7 +355,7 @@ void Preferences::setWinStartup(const bool b)
const QString configuration = Profile::instance()->configurationName();
const auto cmd = QString::fromLatin1(R"("%1" "--profile=%2" "--configuration=%3")")
.arg(Utils::Fs::toNativePath(qApp->applicationFilePath()), profilePath, configuration);
.arg(Path(qApp->applicationFilePath()).toString(), profilePath.toString(), configuration);
settings.setValue(profileID, cmd);
}
else
@ -365,24 +366,14 @@ void Preferences::setWinStartup(const bool b)
#endif // Q_OS_WIN
// Downloads
QString Preferences::lastLocationPath() const
Path Preferences::getScanDirsLastPath() const
{
return Utils::Fs::toUniformPath(value<QString>("Preferences/Downloads/LastLocationPath"));
return value<Path>("Preferences/Downloads/ScanDirsLastPath");
}
void Preferences::setLastLocationPath(const QString &path)
void Preferences::setScanDirsLastPath(const Path &path)
{
setValue("Preferences/Downloads/LastLocationPath", Utils::Fs::toUniformPath(path));
}
QString Preferences::getScanDirsLastPath() const
{
return Utils::Fs::toUniformPath(value<QString>("Preferences/Downloads/ScanDirsLastPath"));
}
void Preferences::setScanDirsLastPath(const QString &path)
{
setValue("Preferences/Downloads/ScanDirsLastPath", Utils::Fs::toUniformPath(path));
setValue("Preferences/Downloads/ScanDirsLastPath", path);
}
bool Preferences::isMailNotificationEnabled() const
@ -737,22 +728,22 @@ void Preferences::setWebUiHttpsEnabled(const bool enabled)
setValue("Preferences/WebUI/HTTPS/Enabled", enabled);
}
QString Preferences::getWebUIHttpsCertificatePath() const
Path Preferences::getWebUIHttpsCertificatePath() const
{
return value<QString>("Preferences/WebUI/HTTPS/CertificatePath");
return value<Path>("Preferences/WebUI/HTTPS/CertificatePath");
}
void Preferences::setWebUIHttpsCertificatePath(const QString &path)
void Preferences::setWebUIHttpsCertificatePath(const Path &path)
{
setValue("Preferences/WebUI/HTTPS/CertificatePath", path);
}
QString Preferences::getWebUIHttpsKeyPath() const
Path Preferences::getWebUIHttpsKeyPath() const
{
return value<QString>("Preferences/WebUI/HTTPS/KeyPath");
return value<Path>("Preferences/WebUI/HTTPS/KeyPath");
}
void Preferences::setWebUIHttpsKeyPath(const QString &path)
void Preferences::setWebUIHttpsKeyPath(const Path &path)
{
setValue("Preferences/WebUI/HTTPS/KeyPath", path);
}
@ -767,12 +758,12 @@ void Preferences::setAltWebUiEnabled(const bool enabled)
setValue("Preferences/WebUI/AlternativeUIEnabled", enabled);
}
QString Preferences::getWebUiRootFolder() const
Path Preferences::getWebUiRootFolder() const
{
return value<QString>("Preferences/WebUI/RootFolder");
return value<Path>("Preferences/WebUI/RootFolder");
}
void Preferences::setWebUiRootFolder(const QString &path)
void Preferences::setWebUiRootFolder(const Path &path)
{
setValue("Preferences/WebUI/RootFolder", path);
}
@ -1050,14 +1041,14 @@ bool Preferences::isMagnetLinkAssocSet()
const QSettings settings("HKEY_CURRENT_USER\\Software\\Classes", QSettings::NativeFormat);
// Check magnet link assoc
const QString shellCommand = Utils::Fs::toNativePath(settings.value("magnet/shell/open/command/Default", "").toString());
const QString shellCommand = settings.value("magnet/shell/open/command/Default", "").toString();
const QRegularExpressionMatch exeRegMatch = QRegularExpression("\"([^\"]+)\".*").match(shellCommand);
if (!exeRegMatch.hasMatch())
return false;
const QString assocExe = exeRegMatch.captured(1);
if (assocExe.compare(Utils::Fs::toNativePath(qApp->applicationFilePath()), Qt::CaseInsensitive) != 0)
const Path assocExe {exeRegMatch.captured(1)};
if (assocExe != Path(qApp->applicationFilePath()))
return false;
return true;
@ -1090,15 +1081,16 @@ void Preferences::setMagnetLinkAssoc(const bool set)
// Magnet association
if (set)
{
const QString commandStr = '"' + qApp->applicationFilePath() + "\" \"%1\"";
const QString iconStr = '"' + qApp->applicationFilePath() + "\",1";
const QString applicationFilePath = Path(qApp->applicationFilePath()).toString();
const QString commandStr = '"' + applicationFilePath + "\" \"%1\"";
const QString iconStr = '"' + applicationFilePath + "\",1";
settings.setValue("magnet/Default", "URL:Magnet link");
settings.setValue("magnet/Content Type", "application/x-magnet");
settings.setValue("magnet/URL Protocol", "");
settings.setValue("magnet/DefaultIcon/Default", Utils::Fs::toNativePath(iconStr));
settings.setValue("magnet/DefaultIcon/Default", iconStr);
settings.setValue("magnet/shell/Default", "open");
settings.setValue("magnet/shell/open/command/Default", Utils::Fs::toNativePath(commandStr));
settings.setValue("magnet/shell/open/command/Default", commandStr);
}
else if (isMagnetLinkAssocSet())
{
@ -1294,12 +1286,12 @@ void Preferences::setMainVSplitterState(const QByteArray &state)
#endif
}
QString Preferences::getMainLastDir() const
Path Preferences::getMainLastDir() const
{
return value("MainWindow/LastDir", QDir::homePath());
return value("MainWindow/LastDir", Utils::Fs::homePath());
}
void Preferences::setMainLastDir(const QString &path)
void Preferences::setMainLastDir(const Path &path)
{
setValue("MainWindow/LastDir", path);
}

View File

@ -29,10 +29,11 @@
#pragma once
#include <QObject>
#include <QtContainerFwd>
#include <QtGlobal>
#include <QObject>
#include "base/pathfwd.h"
#include "base/utils/net.h"
class QDateTime;
@ -108,8 +109,8 @@ public:
void setLocale(const QString &locale);
bool useCustomUITheme() const;
void setUseCustomUITheme(bool use);
QString customUIThemePath() const;
void setCustomUIThemePath(const QString &path);
Path customUIThemePath() const;
void setCustomUIThemePath(const Path &path);
bool deleteTorrentFilesAsDefault() const;
void setDeleteTorrentFilesAsDefault(bool del);
bool confirmOnExit() const;
@ -140,10 +141,8 @@ public:
#endif
// Downloads
QString lastLocationPath() const;
void setLastLocationPath(const QString &path);
QString getScanDirsLastPath() const;
void setScanDirsLastPath(const QString &path);
Path getScanDirsLastPath() const;
void setScanDirsLastPath(const Path &path);
bool isMailNotificationEnabled() const;
void setMailNotificationEnabled(bool enabled);
QString getMailNotificationSender() const;
@ -220,14 +219,14 @@ public:
// HTTPS
bool isWebUiHttpsEnabled() const;
void setWebUiHttpsEnabled(bool enabled);
QString getWebUIHttpsCertificatePath() const;
void setWebUIHttpsCertificatePath(const QString &path);
QString getWebUIHttpsKeyPath() const;
void setWebUIHttpsKeyPath(const QString &path);
Path getWebUIHttpsCertificatePath() const;
void setWebUIHttpsCertificatePath(const Path &path);
Path getWebUIHttpsKeyPath() const;
void setWebUIHttpsKeyPath(const Path &path);
bool isAltWebUiEnabled() const;
void setAltWebUiEnabled(bool enabled);
QString getWebUiRootFolder() const;
void setWebUiRootFolder(const QString &path);
Path getWebUiRootFolder() const;
void setWebUiRootFolder(const Path &path);
// WebUI custom HTTP headers
bool isWebUICustomHTTPHeadersEnabled() const;
@ -343,8 +342,8 @@ public:
void setMainGeometry(const QByteArray &geometry);
QByteArray getMainVSplitterState() const;
void setMainVSplitterState(const QByteArray &state);
QString getMainLastDir() const;
void setMainLastDir(const QString &path);
Path getMainLastDir() const;
void setMainLastDir(const Path &path);
QByteArray getPeerListState() const;
void setPeerListState(const QByteArray &state);
QString getPropSplitterSizes() const;

View File

@ -29,11 +29,13 @@
#include "profile.h"
#include "base/path.h"
#include "base/utils/fs.h"
#include "profile_p.h"
Profile *Profile::m_instance = nullptr;
Profile::Profile(const QString &rootProfilePath, const QString &configurationName, const bool convertPathsToProfileRelative)
Profile::Profile(const Path &rootProfilePath, const QString &configurationName, const bool convertPathsToProfileRelative)
{
if (rootProfilePath.isEmpty())
m_profileImpl = std::make_unique<Private::DefaultProfile>(configurationName);
@ -50,7 +52,7 @@ Profile::Profile(const QString &rootProfilePath, const QString &configurationNam
m_pathConverterImpl = std::make_unique<Private::NoConvertConverter>();
}
void Profile::initInstance(const QString &rootProfilePath, const QString &configurationName,
void Profile::initInstance(const Path &rootProfilePath, const QString &configurationName,
const bool convertPathsToProfileRelative)
{
if (m_instance)
@ -69,29 +71,29 @@ const Profile *Profile::instance()
return m_instance;
}
QString Profile::location(const SpecialFolder folder) const
Path Profile::location(const SpecialFolder folder) const
{
QString result;
switch (folder)
{
case SpecialFolder::Cache:
result = m_profileImpl->cacheLocation();
break;
return m_profileImpl->cacheLocation();
case SpecialFolder::Config:
result = m_profileImpl->configLocation();
break;
return m_profileImpl->configLocation();
case SpecialFolder::Data:
result = m_profileImpl->dataLocation();
break;
return m_profileImpl->dataLocation();
case SpecialFolder::Downloads:
result = m_profileImpl->downloadLocation();
break;
return m_profileImpl->downloadLocation();
}
return result;
Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown SpecialFolder value.");
return {};
}
QString Profile::rootPath() const
Path Profile::rootPath() const
{
return m_profileImpl->rootPath();
}
@ -113,22 +115,22 @@ SettingsPtr Profile::applicationSettings(const QString &name) const
void Profile::ensureDirectoryExists(const SpecialFolder folder) const
{
const QString locationPath = location(folder);
if (!locationPath.isEmpty() && !QDir().mkpath(locationPath))
qFatal("Could not create required directory '%s'", qUtf8Printable(locationPath));
const Path locationPath = location(folder);
if (!locationPath.isEmpty() && !Utils::Fs::mkpath(locationPath))
qFatal("Could not create required directory '%s'", qUtf8Printable(locationPath.toString()));
}
QString Profile::toPortablePath(const QString &absolutePath) const
Path Profile::toPortablePath(const Path &absolutePath) const
{
return m_pathConverterImpl->toPortablePath(absolutePath);
}
QString Profile::fromPortablePath(const QString &portablePath) const
Path Profile::fromPortablePath(const Path &portablePath) const
{
return m_pathConverterImpl->fromPortablePath(portablePath);
}
QString specialFolderLocation(const SpecialFolder folder)
Path specialFolderLocation(const SpecialFolder folder)
{
return Profile::instance()->location(folder);
}

View File

@ -33,6 +33,8 @@
#include <QSettings>
#include "base/pathfwd.h"
class QString;
namespace Private
@ -54,26 +56,26 @@ enum class SpecialFolder
class Profile
{
public:
static void initInstance(const QString &rootProfilePath, const QString &configurationName,
static void initInstance(const Path &rootProfilePath, const QString &configurationName,
bool convertPathsToProfileRelative);
static void freeInstance();
static const Profile *instance();
QString location(SpecialFolder folder) const;
Path location(SpecialFolder folder) const;
SettingsPtr applicationSettings(const QString &name) const;
QString rootPath() const;
Path rootPath() const;
QString configurationName() const;
/// Returns either default name for configuration file (QCoreApplication::applicationName())
/// or the value, supplied via parameters
QString profileName() const;
QString toPortablePath(const QString &absolutePath) const;
QString fromPortablePath(const QString &portablePath) const;
Path toPortablePath(const Path &absolutePath) const;
Path fromPortablePath(const Path &portablePath) const;
private:
Profile(const QString &rootProfilePath, const QString &configurationName, bool convertPathsToProfileRelative);
Profile(const Path &rootProfilePath, const QString &configurationName, bool convertPathsToProfileRelative);
~Profile() = default; // to generate correct call to ProfilePrivate::~ProfileImpl()
void ensureDirectoryExists(SpecialFolder folder) const;
@ -83,4 +85,4 @@ private:
static Profile *m_instance;
};
QString specialFolderLocation(SpecialFolder folder);
Path specialFolderLocation(SpecialFolder folder);

View File

@ -31,6 +31,8 @@
#include <QCoreApplication>
#include "base/utils/fs.h"
Private::Profile::Profile(const QString &configurationName)
: m_configurationName {configurationName}
{
@ -56,22 +58,22 @@ Private::DefaultProfile::DefaultProfile(const QString &configurationName)
{
}
QString Private::DefaultProfile::rootPath() const
Path Private::DefaultProfile::rootPath() const
{
return {};
}
QString Private::DefaultProfile::basePath() const
Path Private::DefaultProfile::basePath() const
{
return QDir::homePath();
return Utils::Fs::homePath();
}
QString Private::DefaultProfile::cacheLocation() const
Path Private::DefaultProfile::cacheLocation() const
{
return locationWithConfigurationName(QStandardPaths::CacheLocation);
}
QString Private::DefaultProfile::configLocation() const
Path Private::DefaultProfile::configLocation() const
{
#if defined(Q_OS_WIN)
// On Windows QSettings stores files in FOLDERID_RoamingAppData\AppName
@ -81,22 +83,22 @@ QString Private::DefaultProfile::configLocation() const
#endif
}
QString Private::DefaultProfile::dataLocation() const
Path Private::DefaultProfile::dataLocation() const
{
#if defined(Q_OS_WIN) || defined (Q_OS_MACOS)
return locationWithConfigurationName(QStandardPaths::AppLocalDataLocation);
#else
// On Linux keep using the legacy directory ~/.local/share/data/ if it exists
const QString legacyDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
+ QLatin1String("/data/") + profileName();
const Path genericDataPath {QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)};
const Path profilePath {profileName()};
const Path legacyDir = genericDataPath / Path("data") / profilePath;
const QString dataDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
+ QLatin1Char('/') + profileName();
const Path dataDir = genericDataPath / profilePath;
if (!QDir(dataDir).exists() && QDir(legacyDir).exists())
if (!dataDir.exists() && legacyDir.exists())
{
qWarning("The legacy data directory '%s' is used. It is recommended to move its content to '%s'",
qUtf8Printable(legacyDir), qUtf8Printable(dataDir));
qUtf8Printable(legacyDir.toString()), qUtf8Printable(dataDir.toString()));
return legacyDir;
}
@ -105,9 +107,9 @@ QString Private::DefaultProfile::dataLocation() const
#endif
}
QString Private::DefaultProfile::downloadLocation() const
Path Private::DefaultProfile::downloadLocation() const
{
return QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
return Path(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
}
SettingsPtr Private::DefaultProfile::applicationSettings(const QString &name) const
@ -119,48 +121,48 @@ SettingsPtr Private::DefaultProfile::applicationSettings(const QString &name) co
#endif
}
QString Private::DefaultProfile::locationWithConfigurationName(const QStandardPaths::StandardLocation location) const
Path Private::DefaultProfile::locationWithConfigurationName(const QStandardPaths::StandardLocation location) const
{
return QStandardPaths::writableLocation(location) + configurationSuffix();
return Path(QStandardPaths::writableLocation(location) + configurationSuffix());
}
Private::CustomProfile::CustomProfile(const QString &rootPath, const QString &configurationName)
Private::CustomProfile::CustomProfile(const Path &rootPath, const QString &configurationName)
: Profile {configurationName}
, m_rootDir {rootPath}
, m_baseDir {m_rootDir.absoluteFilePath(profileName())}
, m_cacheLocation {m_baseDir.absoluteFilePath(QLatin1String("cache"))}
, m_configLocation {m_baseDir.absoluteFilePath(QLatin1String("config"))}
, m_dataLocation {m_baseDir.absoluteFilePath(QLatin1String("data"))}
, m_downloadLocation {m_baseDir.absoluteFilePath(QLatin1String("downloads"))}
, m_rootPath {rootPath}
, m_basePath {m_rootPath / Path(profileName())}
, m_cacheLocation {m_basePath / Path("cache")}
, m_configLocation {m_basePath / Path("config")}
, m_dataLocation {m_basePath / Path("data")}
, m_downloadLocation {m_basePath / Path("downloads")}
{
}
QString Private::CustomProfile::rootPath() const
Path Private::CustomProfile::rootPath() const
{
return m_rootDir.absolutePath();
return m_rootPath;
}
QString Private::CustomProfile::basePath() const
Path Private::CustomProfile::basePath() const
{
return m_baseDir.absolutePath();
return m_basePath;
}
QString Private::CustomProfile::cacheLocation() const
Path Private::CustomProfile::cacheLocation() const
{
return m_cacheLocation;
}
QString Private::CustomProfile::configLocation() const
Path Private::CustomProfile::configLocation() const
{
return m_configLocation;
}
QString Private::CustomProfile::dataLocation() const
Path Private::CustomProfile::dataLocation() const
{
return m_dataLocation;
}
QString Private::CustomProfile::downloadLocation() const
Path Private::CustomProfile::downloadLocation() const
{
return m_downloadLocation;
}
@ -173,48 +175,48 @@ SettingsPtr Private::CustomProfile::applicationSettings(const QString &name) con
#else
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));
const Path settingsFilePath = configLocation() / Path(name + QLatin1String(CONF_FILE_EXTENSION));
return SettingsPtr(new QSettings(settingsFilePath.data(), QSettings::IniFormat));
}
QString Private::NoConvertConverter::fromPortablePath(const QString &portablePath) const
Path Private::NoConvertConverter::fromPortablePath(const Path &portablePath) const
{
return portablePath;
}
QString Private::NoConvertConverter::toPortablePath(const QString &path) const
Path Private::NoConvertConverter::toPortablePath(const Path &path) const
{
return path;
}
Private::Converter::Converter(const QString &basePath)
: m_baseDir {basePath}
Private::Converter::Converter(const Path &basePath)
: m_basePath {basePath}
{
m_baseDir.makeAbsolute();
Q_ASSERT(basePath.isAbsolute());
}
QString Private::Converter::toPortablePath(const QString &path) const
Path Private::Converter::toPortablePath(const Path &path) const
{
if (path.isEmpty() || m_baseDir.path().isEmpty())
if (path.isEmpty())
return path;
#ifdef Q_OS_WIN
if (QDir::isAbsolutePath(path))
if (path.isAbsolute())
{
const QChar driveLeter = path[0].toUpper();
const QChar baseDriveLetter = m_baseDir.path()[0].toUpper();
const bool onSameDrive = (driveLeter.category() == QChar::Letter_Uppercase) && (driveLeter == baseDriveLetter);
const QChar driveLetter = path.data()[0].toUpper();
const QChar baseDriveLetter = m_basePath.data()[0].toUpper();
const bool onSameDrive = (driveLetter.category() == QChar::Letter_Uppercase) && (driveLetter == baseDriveLetter);
if (!onSameDrive)
return path;
}
#endif
return m_baseDir.relativeFilePath(path);
return m_basePath.relativePathOf(path);
}
QString Private::Converter::fromPortablePath(const QString &portablePath) const
Path Private::Converter::fromPortablePath(const Path &portablePath) const
{
if (portablePath.isEmpty() || QDir::isAbsolutePath(portablePath))
if (portablePath.isEmpty() || portablePath.isAbsolute())
return portablePath;
return QDir::cleanPath(m_baseDir.absoluteFilePath(portablePath));
return m_basePath / portablePath;
}

View File

@ -29,10 +29,10 @@
#pragma once
#include <QDir>
#include <QStandardPaths>
#include "base/profile.h"
#include "base/path.h"
#include "profile.h"
namespace Private
{
@ -41,17 +41,17 @@ namespace Private
public:
virtual ~Profile() = default;
virtual QString rootPath() const = 0;
virtual Path rootPath() const = 0;
/**
* @brief The base path against to which portable (relative) paths are resolved
*/
virtual QString basePath() const = 0;
virtual Path basePath() const = 0;
virtual QString cacheLocation() const = 0;
virtual QString configLocation() const = 0;
virtual QString dataLocation() const = 0;
virtual QString downloadLocation() const = 0;
virtual Path cacheLocation() const = 0;
virtual Path configLocation() const = 0;
virtual Path dataLocation() const = 0;
virtual Path downloadLocation() const = 0;
virtual SettingsPtr applicationSettings(const QString &name) const = 0;
@ -77,12 +77,12 @@ namespace Private
public:
explicit DefaultProfile(const QString &configurationName);
QString rootPath() const override;
QString basePath() const override;
QString cacheLocation() const override;
QString configLocation() const override;
QString dataLocation() const override;
QString downloadLocation() const override;
Path rootPath() const override;
Path basePath() const override;
Path cacheLocation() const override;
Path configLocation() const override;
Path dataLocation() const override;
Path downloadLocation() const override;
SettingsPtr applicationSettings(const QString &name) const override;
private:
@ -92,55 +92,55 @@ namespace Private
* @param location location kind
* @return QStandardPaths::writableLocation(location) / configurationName()
*/
QString locationWithConfigurationName(QStandardPaths::StandardLocation location) const;
Path locationWithConfigurationName(QStandardPaths::StandardLocation location) const;
};
/// Custom tree: creates directories under the specified root directory
class CustomProfile final : public Profile
{
public:
CustomProfile(const QString &rootPath, const QString &configurationName);
CustomProfile(const Path &rootPath, const QString &configurationName);
QString rootPath() const override;
QString basePath() const override;
QString cacheLocation() const override;
QString configLocation() const override;
QString dataLocation() const override;
QString downloadLocation() const override;
Path rootPath() const override;
Path basePath() const override;
Path cacheLocation() const override;
Path configLocation() const override;
Path dataLocation() const override;
Path downloadLocation() const override;
SettingsPtr applicationSettings(const QString &name) const override;
private:
const QDir m_rootDir;
const QDir m_baseDir;
const QString m_cacheLocation;
const QString m_configLocation;
const QString m_dataLocation;
const QString m_downloadLocation;
const Path m_rootPath;
const Path m_basePath;
const Path m_cacheLocation;
const Path m_configLocation;
const Path m_dataLocation;
const Path m_downloadLocation;
};
class PathConverter
{
public:
virtual QString toPortablePath(const QString &path) const = 0;
virtual QString fromPortablePath(const QString &portablePath) const = 0;
virtual Path toPortablePath(const Path &path) const = 0;
virtual Path fromPortablePath(const Path &portablePath) const = 0;
virtual ~PathConverter() = default;
};
class NoConvertConverter final : public PathConverter
{
public:
QString toPortablePath(const QString &path) const override;
QString fromPortablePath(const QString &portablePath) const override;
Path toPortablePath(const Path &path) const override;
Path fromPortablePath(const Path &portablePath) const override;
};
class Converter final : public PathConverter
{
public:
explicit Converter(const QString &basePath);
QString toPortablePath(const QString &path) const override;
QString fromPortablePath(const QString &portablePath) const override;
explicit Converter(const Path &basePath);
Path toPortablePath(const Path &path) const override;
Path fromPortablePath(const Path &portablePath) const override;
private:
QDir m_baseDir;
Path m_basePath;
};
}

View File

@ -58,8 +58,8 @@ struct ProcessingJob
QVariantHash articleData;
};
const QString ConfFolderName(QStringLiteral("rss"));
const QString RulesFileName(QStringLiteral("download_rules.json"));
const QString CONF_FOLDER_NAME {QStringLiteral("rss")};
const QString RULES_FILE_NAME {QStringLiteral("download_rules.json")};
namespace
{
@ -107,17 +107,16 @@ AutoDownloader::AutoDownloader()
Q_ASSERT(!m_instance); // only one instance is allowed
m_instance = this;
m_fileStorage = new AsyncFileStorage(
Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Config) + QLatin1Char('/') + ConfFolderName));
m_fileStorage = new AsyncFileStorage(specialFolderLocation(SpecialFolder::Config) / Path(CONF_FOLDER_NAME));
if (!m_fileStorage)
throw RuntimeError(tr("Directory for RSS AutoDownloader data is unavailable."));
m_fileStorage->moveToThread(m_ioThread);
connect(m_ioThread, &QThread::finished, m_fileStorage, &AsyncFileStorage::deleteLater);
connect(m_fileStorage, &AsyncFileStorage::failed, [](const QString &fileName, const QString &errorString)
connect(m_fileStorage, &AsyncFileStorage::failed, [](const Path &fileName, const QString &errorString)
{
LogMsg(tr("Couldn't save RSS AutoDownloader data in %1. Error: %2")
.arg(fileName, errorString), Log::CRITICAL);
.arg(fileName.toString(), errorString), Log::CRITICAL);
});
m_ioThread->start();
@ -414,7 +413,7 @@ void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job)
void AutoDownloader::load()
{
QFile rulesFile(m_fileStorage->storageDir().absoluteFilePath(RulesFileName));
QFile rulesFile {(m_fileStorage->storageDir() / Path(RULES_FILE_NAME)).data()};
if (!rulesFile.exists())
loadRulesLegacy();
@ -463,7 +462,7 @@ void AutoDownloader::store()
for (const auto &rule : asConst(m_rules))
jsonObj.insert(rule.name(), rule.toJsonObject());
m_fileStorage->store(RulesFileName, QJsonDocument(jsonObj).toJson());
m_fileStorage->store(Path(RULES_FILE_NAME), QJsonDocument(jsonObj).toJson());
}
void AutoDownloader::storeDeferred()

View File

@ -41,6 +41,7 @@
#include <QStringList>
#include "base/global.h"
#include "base/path.h"
#include "base/preferences.h"
#include "base/utils/fs.h"
#include "base/utils/string.h"
@ -132,7 +133,7 @@ namespace RSS
int ignoreDays = 0;
QDateTime lastMatch;
QString savePath;
Path savePath;
QString category;
std::optional<bool> addPaused;
std::optional<BitTorrent::TorrentContentLayout> contentLayout;
@ -466,7 +467,7 @@ QJsonObject AutoDownloadRule::toJsonObject() const
, {Str_MustNotContain, mustNotContain()}
, {Str_EpisodeFilter, episodeFilter()}
, {Str_AffectedFeeds, QJsonArray::fromStringList(feedURLs())}
, {Str_SavePath, savePath()}
, {Str_SavePath, savePath().toString()}
, {Str_AssignedCategory, assignedCategory()}
, {Str_LastMatch, lastMatch().toString(Qt::RFC2822Date)}
, {Str_IgnoreDays, ignoreDays()}
@ -485,7 +486,7 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
rule.setMustNotContain(jsonObj.value(Str_MustNotContain).toString());
rule.setEpisodeFilter(jsonObj.value(Str_EpisodeFilter).toString());
rule.setEnabled(jsonObj.value(Str_Enabled).toBool(true));
rule.setSavePath(jsonObj.value(Str_SavePath).toString());
rule.setSavePath(Path(jsonObj.value(Str_SavePath).toString()));
rule.setCategory(jsonObj.value(Str_AssignedCategory).toString());
rule.setAddPaused(toOptionalBool(jsonObj.value(Str_AddPaused)));
@ -546,7 +547,7 @@ QVariantHash AutoDownloadRule::toLegacyDict() const
return {{"name", name()},
{"must_contain", mustContain()},
{"must_not_contain", mustNotContain()},
{"save_path", savePath()},
{"save_path", savePath().toString()},
{"affected_feeds", feedURLs()},
{"enabled", isEnabled()},
{"category_assigned", assignedCategory()},
@ -567,7 +568,7 @@ AutoDownloadRule AutoDownloadRule::fromLegacyDict(const QVariantHash &dict)
rule.setEpisodeFilter(dict.value("episode_filter").toString());
rule.setFeedURLs(dict.value("affected_feeds").toStringList());
rule.setEnabled(dict.value("enabled", false).toBool());
rule.setSavePath(dict.value("save_path").toString());
rule.setSavePath(Path(dict.value("save_path").toString()));
rule.setCategory(dict.value("category_assigned").toString());
rule.setAddPaused(addPausedLegacyToOptionalBool(dict.value("add_paused").toInt()));
rule.setLastMatch(dict.value("last_match").toDateTime());
@ -624,14 +625,14 @@ void AutoDownloadRule::setName(const QString &name)
m_dataPtr->name = name;
}
QString AutoDownloadRule::savePath() const
Path AutoDownloadRule::savePath() const
{
return m_dataPtr->savePath;
}
void AutoDownloadRule::setSavePath(const QString &savePath)
void AutoDownloadRule::setSavePath(const Path &savePath)
{
m_dataPtr->savePath = Utils::Fs::toUniformPath(savePath);
m_dataPtr->savePath = savePath;
}
std::optional<bool> AutoDownloadRule::addPaused() const

View File

@ -35,6 +35,7 @@
#include <QVariant>
#include "base/bittorrent/torrentcontentlayout.h"
#include "base/pathfwd.h"
class QDateTime;
class QJsonObject;
@ -77,8 +78,8 @@ namespace RSS
QStringList previouslyMatchedEpisodes() const;
void setPreviouslyMatchedEpisodes(const QStringList &previouslyMatchedEpisodes);
QString savePath() const;
void setSavePath(const QString &savePath);
Path savePath() const;
void setSavePath(const Path &savePath);
std::optional<bool> addPaused() const;
void setAddPaused(std::optional<bool> addPaused);
std::optional<BitTorrent::TorrentContentLayout> torrentContentLayout() const;

View File

@ -68,17 +68,16 @@ Feed::Feed(const QUuid &uid, const QString &url, const QString &path, Session *s
, m_url(url)
{
const auto uidHex = QString::fromLatin1(m_uid.toRfc4122().toHex());
m_dataFileName = uidHex + QLatin1String(".json");
m_dataFileName = Path(uidHex + QLatin1String(".json"));
// Move to new file naming scheme (since v4.1.2)
const QString legacyFilename
{Utils::Fs::toValidFileSystemName(m_url, false, QLatin1String("_"))
+ QLatin1String(".json")};
const QDir storageDir {m_session->dataFileStorage()->storageDir()};
if (!QFile::exists(storageDir.absoluteFilePath(m_dataFileName)))
QFile::rename(storageDir.absoluteFilePath(legacyFilename), storageDir.absoluteFilePath(m_dataFileName));
const QString legacyFilename = Utils::Fs::toValidFileName(m_url, QLatin1String("_")) + QLatin1String(".json");
const Path storageDir = m_session->dataFileStorage()->storageDir();
const Path dataFilePath = storageDir / m_dataFileName;
if (!dataFilePath.exists())
Utils::Fs::renameFile((storageDir / Path(legacyFilename)), dataFilePath);
m_iconPath = Utils::Fs::toUniformPath(storageDir.absoluteFilePath(uidHex + QLatin1String(".ico")));
m_iconPath = storageDir / Path(uidHex + QLatin1String(".ico"));
m_parser = new Private::Parser(m_lastBuildDate);
m_parser->moveToThread(m_session->workingThread());
@ -139,7 +138,7 @@ void Feed::refresh()
m_downloadHandler = Net::DownloadManager::instance()->download(m_url);
connect(m_downloadHandler, &Net::DownloadHandler::finished, this, &Feed::handleDownloadFinished);
if (!QFile::exists(m_iconPath))
if (!m_iconPath.exists())
downloadIcon();
m_isLoading = true;
@ -262,7 +261,7 @@ void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result)
void Feed::load()
{
QFile file(m_session->dataFileStorage()->storageDir().absoluteFilePath(m_dataFileName));
QFile file {(m_session->dataFileStorage()->storageDir() / m_dataFileName).data()};
if (!file.exists())
{
@ -278,7 +277,7 @@ void Feed::load()
else
{
LogMsg(tr("Couldn't read RSS Session data from %1. Error: %2")
.arg(m_dataFileName, file.errorString())
.arg(m_dataFileName.toString(), file.errorString())
, Log::WARNING);
}
}
@ -500,7 +499,7 @@ int Feed::updateArticles(const QList<QVariantHash> &loadedArticles)
return newArticlesCount;
}
QString Feed::iconPath() const
Path Feed::iconPath() const
{
return m_iconPath;
}
@ -549,8 +548,8 @@ void Feed::handleArticleRead(Article *article)
void Feed::cleanup()
{
Utils::Fs::forceRemove(m_session->dataFileStorage()->storageDir().absoluteFilePath(m_dataFileName));
Utils::Fs::forceRemove(m_iconPath);
Utils::Fs::removeFile(m_session->dataFileStorage()->storageDir() / m_dataFileName);
Utils::Fs::removeFile(m_iconPath);
}
void Feed::timerEvent(QTimerEvent *event)

View File

@ -35,6 +35,7 @@
#include <QList>
#include <QUuid>
#include "base/path.h"
#include "rss_item.h"
class AsyncFileStorage;
@ -79,7 +80,7 @@ namespace RSS
bool hasError() const;
bool isLoading() const;
Article *articleByGUID(const QString &guid) const;
QString iconPath() const;
Path iconPath() const;
QJsonValue toJsonValue(bool withData = false) const override;
@ -122,8 +123,8 @@ namespace RSS
QHash<QString, Article *> m_articles;
QList<Article *> m_articlesByDate;
int m_unreadCount = 0;
QString m_iconPath;
QString m_dataFileName;
Path m_iconPath;
Path m_dataFileName;
QBasicTimer m_savingTimer;
bool m_dirty = false;
Net::DownloadHandler *m_downloadHandler = nullptr;

View File

@ -49,9 +49,9 @@
#include "rss_item.h"
const int MsecsPerMin = 60000;
const QString ConfFolderName(QStringLiteral("rss"));
const QString DataFolderName(QStringLiteral("rss/articles"));
const QString FeedsFileName(QStringLiteral("feeds.json"));
const QString CONF_FOLDER_NAME(QStringLiteral("rss"));
const QString DATA_FOLDER_NAME(QStringLiteral("rss/articles"));
const QString FEEDS_FILE_NAME(QStringLiteral("feeds.json"));
using namespace RSS;
@ -66,24 +66,22 @@ Session::Session()
Q_ASSERT(!m_instance); // only one instance is allowed
m_instance = this;
m_confFileStorage = new AsyncFileStorage(
Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Config) + QLatin1Char('/') + ConfFolderName));
m_confFileStorage = new AsyncFileStorage(specialFolderLocation(SpecialFolder::Config) / Path(CONF_FOLDER_NAME));
m_confFileStorage->moveToThread(m_workingThread);
connect(m_workingThread, &QThread::finished, m_confFileStorage, &AsyncFileStorage::deleteLater);
connect(m_confFileStorage, &AsyncFileStorage::failed, [](const QString &fileName, const QString &errorString)
connect(m_confFileStorage, &AsyncFileStorage::failed, [](const Path &fileName, const QString &errorString)
{
Logger::instance()->addMessage(QString("Couldn't save RSS Session configuration in %1. Error: %2")
.arg(fileName, errorString), Log::WARNING);
LogMsg(tr("Couldn't save RSS Session configuration in %1. Error: %2")
.arg(fileName.toString(), errorString), Log::WARNING);
});
m_dataFileStorage = new AsyncFileStorage(
Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + QLatin1Char('/') + DataFolderName));
m_dataFileStorage = new AsyncFileStorage(specialFolderLocation(SpecialFolder::Data) / Path(DATA_FOLDER_NAME));
m_dataFileStorage->moveToThread(m_workingThread);
connect(m_workingThread, &QThread::finished, m_dataFileStorage, &AsyncFileStorage::deleteLater);
connect(m_dataFileStorage, &AsyncFileStorage::failed, [](const QString &fileName, const QString &errorString)
connect(m_dataFileStorage, &AsyncFileStorage::failed, [](const Path &fileName, const QString &errorString)
{
Logger::instance()->addMessage(QString("Couldn't save RSS Session data in %1. Error: %2")
.arg(fileName, errorString), Log::WARNING);
LogMsg(tr("Couldn't save RSS Session data in %1. Error: %2")
.arg(fileName.toString(), errorString), Log::WARNING);
});
m_itemsByPath.insert("", new Folder); // root folder
@ -233,7 +231,7 @@ Item *Session::itemByPath(const QString &path) const
void Session::load()
{
QFile itemsFile(m_confFileStorage->storageDir().absoluteFilePath(FeedsFileName));
QFile itemsFile {(m_confFileStorage->storageDir() / Path(FEEDS_FILE_NAME)).data()};
if (!itemsFile.exists())
{
loadLegacy();
@ -372,7 +370,8 @@ void Session::loadLegacy()
void Session::store()
{
m_confFileStorage->store(FeedsFileName, QJsonDocument(rootFolder()->toJsonValue().toObject()).toJson());
m_confFileStorage->store(Path(FEEDS_FILE_NAME)
, QJsonDocument(rootFolder()->toJsonValue().toObject()).toJson());
}
nonstd::expected<Folder *, QString> Session::prepareItemDest(const QString &path)

View File

@ -30,8 +30,9 @@
#include <QProcess>
#include "../utils/foreignapps.h"
#include "../utils/fs.h"
#include "base/path.h"
#include "base/utils/foreignapps.h"
#include "base/utils/fs.h"
#include "searchpluginmanager.h"
SearchDownloadHandler::SearchDownloadHandler(const QString &siteUrl, const QString &url, SearchPluginManager *manager)
@ -44,7 +45,7 @@ SearchDownloadHandler::SearchDownloadHandler(const QString &siteUrl, const QStri
, this, &SearchDownloadHandler::downloadProcessFinished);
const QStringList params
{
Utils::Fs::toNativePath(m_manager->engineLocation() + "/nova2dl.py"),
(m_manager->engineLocation() / Path("nova2dl.py")).toString(),
siteUrl,
url
};

View File

@ -34,6 +34,7 @@
#include <QVector>
#include "base/global.h"
#include "base/path.h"
#include "base/utils/foreignapps.h"
#include "base/utils/fs.h"
#include "searchpluginmanager.h"
@ -67,7 +68,7 @@ SearchHandler::SearchHandler(const QString &pattern, const QString &category, co
const QStringList params
{
Utils::Fs::toNativePath(m_manager->engineLocation() + "/nova2.py"),
(m_manager->engineLocation() / Path("nova2.py")).toString(),
m_usedPlugins.join(','),
m_category
};

View File

@ -52,30 +52,31 @@
namespace
{
void clearPythonCache(const QString &path)
void clearPythonCache(const Path &path)
{
// remove python cache artifacts in `path` and subdirs
QStringList dirs = {path};
QDirIterator iter {path, (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories};
PathList dirs = {path};
QDirIterator iter {path.data(), (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories};
while (iter.hasNext())
dirs += iter.next();
dirs += Path(iter.next());
for (const QString &dir : asConst(dirs))
for (const Path &dir : asConst(dirs))
{
// python 3: remove "__pycache__" folders
if (dir.endsWith("/__pycache__"))
if (dir.filename() == QLatin1String("__pycache__"))
{
Utils::Fs::removeDirRecursive(dir);
Utils::Fs::removeDirRecursively(dir);
continue;
}
// python 2: remove "*.pyc" files
const QStringList files = QDir(dir).entryList(QDir::Files);
const QStringList files = QDir(dir.data()).entryList(QDir::Files);
for (const QString &file : files)
{
if (file.endsWith(".pyc"))
Utils::Fs::forceRemove(file);
const Path path {file};
if (path.hasExtension(QLatin1String(".pyc")))
Utils::Fs::removeFile(path);
}
}
}
@ -210,21 +211,22 @@ void SearchPluginManager::installPlugin(const QString &source)
}
else
{
QString path = source;
if (path.startsWith("file:", Qt::CaseInsensitive))
path = QUrl(path).toLocalFile();
const Path path {source.startsWith("file:", Qt::CaseInsensitive) ? QUrl(source).toLocalFile() : source};
QString pluginName = Utils::Fs::fileName(path);
pluginName.chop(pluginName.size() - pluginName.lastIndexOf('.'));
if (!path.endsWith(".py", Qt::CaseInsensitive))
emit pluginInstallationFailed(pluginName, tr("Unknown search engine plugin file format."));
else
QString pluginName = path.filename();
if (pluginName.endsWith(".py", Qt::CaseInsensitive))
{
pluginName.chop(pluginName.size() - pluginName.lastIndexOf('.'));
installPlugin_impl(pluginName, path);
}
else
{
emit pluginInstallationFailed(pluginName, tr("Unknown search engine plugin file format."));
}
}
}
void SearchPluginManager::installPlugin_impl(const QString &name, const QString &path)
void SearchPluginManager::installPlugin_impl(const QString &name, const Path &path)
{
const PluginVersion newVersion = getPluginVersion(path);
const PluginInfo *plugin = pluginInfo(name);
@ -236,30 +238,31 @@ void SearchPluginManager::installPlugin_impl(const QString &name, const QString
}
// Process with install
const QString destPath = pluginPath(name);
const Path destPath = pluginPath(name);
const Path backupPath = destPath + ".bak";
bool updated = false;
if (QFile::exists(destPath))
if (destPath.exists())
{
// Backup in case install fails
QFile::copy(destPath, destPath + ".bak");
Utils::Fs::forceRemove(destPath);
Utils::Fs::copyFile(destPath, backupPath);
Utils::Fs::removeFile(destPath);
updated = true;
}
// Copy the plugin
QFile::copy(path, destPath);
Utils::Fs::copyFile(path, destPath);
// Update supported plugins
update();
// Check if this was correctly installed
if (!m_plugins.contains(name))
{
// Remove broken file
Utils::Fs::forceRemove(destPath);
Utils::Fs::removeFile(destPath);
LogMsg(tr("Plugin %1 is not supported.").arg(name), Log::INFO);
if (updated)
{
// restore backup
QFile::copy(destPath + ".bak", destPath);
Utils::Fs::forceRemove(destPath + ".bak");
Utils::Fs::copyFile(backupPath, destPath);
Utils::Fs::removeFile(backupPath);
// Update supported plugins
update();
emit pluginUpdateFailed(name, tr("Plugin is not supported."));
@ -275,7 +278,7 @@ void SearchPluginManager::installPlugin_impl(const QString &name, const QString
if (updated)
{
LogMsg(tr("Plugin %1 has been successfully updated.").arg(name), Log::INFO);
Utils::Fs::forceRemove(destPath + ".bak");
Utils::Fs::removeFile(backupPath);
}
}
}
@ -285,12 +288,11 @@ bool SearchPluginManager::uninstallPlugin(const QString &name)
clearPythonCache(engineLocation());
// remove it from hard drive
const QDir pluginsFolder(pluginsLocation());
QStringList filters;
filters << name + ".*";
const QStringList files = pluginsFolder.entryList(filters, QDir::Files, QDir::Unsorted);
const Path pluginsPath = pluginsLocation();
const QStringList filters {name + QLatin1String(".*")};
const QStringList files = QDir(pluginsPath.data()).entryList(filters, QDir::Files, QDir::Unsorted);
for (const QString &file : files)
Utils::Fs::forceRemove(pluginsFolder.absoluteFilePath(file));
Utils::Fs::removeFile(pluginsPath / Path(file));
// Remove it from supported engines
delete m_plugins.take(name);
@ -301,15 +303,17 @@ bool SearchPluginManager::uninstallPlugin(const QString &name)
void SearchPluginManager::updateIconPath(PluginInfo *const plugin)
{
if (!plugin) return;
QString iconPath = QString::fromLatin1("%1/%2.png").arg(pluginsLocation(), plugin->name);
if (QFile::exists(iconPath))
const Path pluginsPath = pluginsLocation();
Path iconPath = pluginsPath / Path(plugin->name + QLatin1String(".png"));
if (iconPath.exists())
{
plugin->iconPath = iconPath;
}
else
{
iconPath = QString::fromLatin1("%1/%2.ico").arg(pluginsLocation(), plugin->name);
if (QFile::exists(iconPath))
iconPath = pluginsPath / Path(plugin->name + QLatin1String(".ico"));
if (iconPath.exists())
plugin->iconPath = iconPath;
}
}
@ -357,20 +361,18 @@ QString SearchPluginManager::pluginFullName(const QString &pluginName)
return pluginInfo(pluginName) ? pluginInfo(pluginName)->fullName : QString();
}
QString SearchPluginManager::pluginsLocation()
Path SearchPluginManager::pluginsLocation()
{
return QString::fromLatin1("%1/engines").arg(engineLocation());
return (engineLocation() / Path("engines"));
}
QString SearchPluginManager::engineLocation()
Path SearchPluginManager::engineLocation()
{
static QString location;
static Path location;
if (location.isEmpty())
{
location = Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + "/nova3");
const QDir locationDir(location);
locationDir.mkpath(locationDir.absolutePath());
location = specialFolderLocation(SpecialFolder::Data) / Path("nova3");
Utils::Fs::mkpath(location);
}
return location;
@ -388,12 +390,12 @@ void SearchPluginManager::pluginDownloadFinished(const Net::DownloadResult &resu
{
if (result.status == Net::DownloadStatus::Success)
{
const QString filePath = Utils::Fs::toUniformPath(result.filePath);
const Path filePath = result.filePath;
QString pluginName = Utils::Fs::fileName(result.url);
pluginName.chop(pluginName.size() - pluginName.lastIndexOf('.')); // Remove extension
installPlugin_impl(pluginName, filePath);
Utils::Fs::forceRemove(filePath);
Path pluginPath {QUrl(result.url).path()};
pluginPath.removeExtension(); // Remove extension
installPlugin_impl(pluginPath.filename(), filePath);
Utils::Fs::removeFile(filePath);
}
else
{
@ -412,37 +414,37 @@ void SearchPluginManager::pluginDownloadFinished(const Net::DownloadResult &resu
void SearchPluginManager::updateNova()
{
// create nova directory if necessary
const QDir searchDir(engineLocation());
const Path enginePath = engineLocation();
QFile packageFile(searchDir.absoluteFilePath("__init__.py"));
QFile packageFile {(enginePath / Path("__init__.py")).data()};
packageFile.open(QIODevice::WriteOnly);
packageFile.close();
searchDir.mkdir("engines");
Utils::Fs::mkdir(enginePath / Path("engines"));
QFile packageFile2(searchDir.absolutePath() + "/engines/__init__.py");
QFile packageFile2 {(enginePath / Path("engines/__init__.py")).data()};
packageFile2.open(QIODevice::WriteOnly);
packageFile2.close();
// Copy search plugin files (if necessary)
const auto updateFile = [](const QString &filename, const bool compareVersion)
const auto updateFile = [&enginePath](const Path &filename, const bool compareVersion)
{
const QString filePathBundled = ":/searchengine/nova3/" + filename;
const QString filePathDisk = QDir(engineLocation()).absoluteFilePath(filename);
const Path filePathBundled = Path(":/searchengine/nova3") / filename;
const Path filePathDisk = enginePath / filename;
if (compareVersion && (getPluginVersion(filePathBundled) <= getPluginVersion(filePathDisk)))
return;
Utils::Fs::forceRemove(filePathDisk);
QFile::copy(filePathBundled, filePathDisk);
Utils::Fs::removeFile(filePathDisk);
Utils::Fs::copyFile(filePathBundled, filePathDisk);
};
updateFile("helpers.py", true);
updateFile("nova2.py", true);
updateFile("nova2dl.py", true);
updateFile("novaprinter.py", true);
updateFile("sgmllib3.py", false);
updateFile("socks.py", false);
updateFile(Path("helpers.py"), true);
updateFile(Path("nova2.py"), true);
updateFile(Path("nova2dl.py"), true);
updateFile(Path("novaprinter.py"), true);
updateFile(Path("sgmllib3.py"), false);
updateFile(Path("socks.py"), false);
}
void SearchPluginManager::update()
@ -450,7 +452,7 @@ void SearchPluginManager::update()
QProcess nova;
nova.setProcessEnvironment(QProcessEnvironment::systemEnvironment());
const QStringList params {Utils::Fs::toNativePath(engineLocation() + "/nova2.py"), "--capabilities"};
const QStringList params {(engineLocation() / Path("/nova2.py")).toString(), QLatin1String("--capabilities")};
nova.start(Utils::ForeignApps::pythonInfo().executableName, params, QIODevice::ReadOnly);
nova.waitForFinished();
@ -559,14 +561,14 @@ bool SearchPluginManager::isUpdateNeeded(const QString &pluginName, const Plugin
return (newVersion > oldVersion);
}
QString SearchPluginManager::pluginPath(const QString &name)
Path SearchPluginManager::pluginPath(const QString &name)
{
return QString::fromLatin1("%1/%2.py").arg(pluginsLocation(), name);
return (pluginsLocation() / Path(name + QLatin1String(".py")));
}
PluginVersion SearchPluginManager::getPluginVersion(const QString &filePath)
PluginVersion SearchPluginManager::getPluginVersion(const Path &filePath)
{
QFile pluginFile(filePath);
QFile pluginFile {filePath.data()};
if (!pluginFile.open(QIODevice::ReadOnly | QIODevice::Text))
return {};
@ -581,7 +583,7 @@ PluginVersion SearchPluginManager::getPluginVersion(const QString &filePath)
return version;
LogMsg(tr("Search plugin '%1' contains invalid version string ('%2')")
.arg(Utils::Fs::fileName(filePath), versionStr), Log::MsgType::WARNING);
.arg(filePath.filename(), versionStr), Log::MsgType::WARNING);
break;
}

View File

@ -33,6 +33,7 @@
#include <QMetaType>
#include <QObject>
#include "base/path.h"
#include "base/utils/version.h"
using PluginVersion = Utils::Version<unsigned short, 2>;
@ -50,7 +51,7 @@ struct PluginInfo
QString fullName;
QString url;
QStringList supportedCategories;
QString iconPath;
Path iconPath;
bool enabled;
};
@ -85,11 +86,11 @@ public:
SearchHandler *startSearch(const QString &pattern, const QString &category, const QStringList &usedPlugins);
SearchDownloadHandler *downloadTorrent(const QString &siteUrl, const QString &url);
static PluginVersion getPluginVersion(const QString &filePath);
static PluginVersion getPluginVersion(const Path &filePath);
static QString categoryFullName(const QString &categoryName);
QString pluginFullName(const QString &pluginName);
static QString pluginsLocation();
static QString engineLocation();
static Path pluginsLocation();
static Path engineLocation();
signals:
void pluginEnabled(const QString &name, bool enabled);
@ -106,13 +107,13 @@ private:
void update();
void updateNova();
void parseVersionInfo(const QByteArray &info);
void installPlugin_impl(const QString &name, const QString &path);
void installPlugin_impl(const QString &name, const Path &path);
bool isUpdateNeeded(const QString &pluginName, PluginVersion newVersion) const;
void versionInfoDownloadFinished(const Net::DownloadResult &result);
void pluginDownloadFinished(const Net::DownloadResult &result);
static QString pluginPath(const QString &name);
static Path pluginPath(const QString &name);
static QPointer<SearchPluginManager> m_instance;

View File

@ -35,6 +35,7 @@
#include "global.h"
#include "logger.h"
#include "path.h"
#include "profile.h"
#include "utils/fs.h"
@ -59,8 +60,8 @@ namespace
// there is no other way to get that name except
// actually create a QSettings object.
// if serialization operation was not successful we return empty string
QString deserialize(const QString &name, QVariantHash &data) const;
QString serialize(const QString &name, const QVariantHash &data) const;
Path deserialize(const QString &name, QVariantHash &data) const;
Path serialize(const QString &name, const QVariantHash &data) const;
const QString m_name;
};
@ -156,7 +157,7 @@ QVariantHash TransactionalSettings::read() const
{
QVariantHash res;
const QString newPath = deserialize(m_name + QLatin1String("_new"), res);
const Path newPath = deserialize(m_name + QLatin1String("_new"), res);
if (!newPath.isEmpty())
{ // "_new" file is NOT empty
// This means that the PC closed either due to power outage
@ -164,15 +165,16 @@ QVariantHash TransactionalSettings::read() const
// in their final position. So assume that qbittorrent_new.ini/qbittorrent_new.conf
// contains the most recent settings.
Logger::instance()->addMessage(QObject::tr("Detected unclean program exit. Using fallback file to restore settings: %1")
.arg(Utils::Fs::toNativePath(newPath))
.arg(newPath.toString())
, Log::WARNING);
QString finalPath = newPath;
int index = finalPath.lastIndexOf("_new", -1, Qt::CaseInsensitive);
finalPath.remove(index, 4);
QString finalPathStr = newPath.data();
const int index = finalPathStr.lastIndexOf("_new", -1, Qt::CaseInsensitive);
finalPathStr.remove(index, 4);
Utils::Fs::forceRemove(finalPath);
QFile::rename(newPath, finalPath);
const Path finalPath {finalPathStr};
Utils::Fs::removeFile(finalPath);
Utils::Fs::renameFile(newPath, finalPath);
}
else
{
@ -189,22 +191,23 @@ bool TransactionalSettings::write(const QVariantHash &data) const
// between deleting the file and recreating it. This is a safety measure.
// Write everything to qBittorrent_new.ini/qBittorrent_new.conf and if it succeeds
// replace qBittorrent.ini/qBittorrent.conf with it.
const QString newPath = serialize(m_name + QLatin1String("_new"), data);
const Path newPath = serialize(m_name + QLatin1String("_new"), data);
if (newPath.isEmpty())
{
Utils::Fs::forceRemove(newPath);
Utils::Fs::removeFile(newPath);
return false;
}
QString finalPath = newPath;
int index = finalPath.lastIndexOf("_new", -1, Qt::CaseInsensitive);
finalPath.remove(index, 4);
QString finalPathStr = newPath.data();
const int index = finalPathStr.lastIndexOf("_new", -1, Qt::CaseInsensitive);
finalPathStr.remove(index, 4);
Utils::Fs::forceRemove(finalPath);
return QFile::rename(newPath, finalPath);
const Path finalPath {finalPathStr};
Utils::Fs::removeFile(finalPath);
return Utils::Fs::renameFile(newPath, finalPath);
}
QString TransactionalSettings::deserialize(const QString &name, QVariantHash &data) const
Path TransactionalSettings::deserialize(const QString &name, QVariantHash &data) const
{
SettingsPtr settings = Profile::instance()->applicationSettings(name);
@ -221,10 +224,10 @@ QString TransactionalSettings::deserialize(const QString &name, QVariantHash &da
data[key] = value;
}
return settings->fileName();
return Path(settings->fileName());
}
QString TransactionalSettings::serialize(const QString &name, const QVariantHash &data) const
Path TransactionalSettings::serialize(const QString &name, const QVariantHash &data) const
{
SettingsPtr settings = Profile::instance()->applicationSettings(name);
for (auto i = data.begin(); i != data.end(); ++i)
@ -235,7 +238,7 @@ QString TransactionalSettings::serialize(const QString &name, const QVariantHash
switch (settings->status())
{
case QSettings::NoError:
return settings->fileName();
return Path(settings->fileName());
case QSettings::AccessError:
Logger::instance()->addMessage(QObject::tr("An access error occurred while trying to write the configuration file."), Log::CRITICAL);
break;

View File

@ -36,6 +36,7 @@
#include <QTimer>
#include <QVariantHash>
#include "path.h"
#include "utils/string.h"
template <typename T>
@ -68,6 +69,11 @@ public:
const typename T::Int value = loadValue(key, static_cast<typename T::Int>(defaultValue));
return T {value};
}
else if constexpr (std::is_same_v<T, Path>)
{
const auto value = loadValue<QString>(key, defaultValue.toString());
return Path(value);
}
else if constexpr (std::is_same_v<T, QVariant>)
{
// fast path for loading QVariant
@ -88,8 +94,10 @@ public:
storeValueImpl(key, Utils::String::fromEnum(value));
else if constexpr (IsQFlags<T>::value)
storeValueImpl(key, static_cast<typename T::Int>(value));
else if constexpr (std::is_same_v<T, Path>)
storeValueImpl(key, value.toString());
else
storeValueImpl(key, value);
storeValueImpl(key, QVariant::fromValue(value));
}
void removeValue(const QString &key);

View File

@ -31,7 +31,7 @@
#include "settingvalue.h"
#include "utils/fs.h"
FileGuard::FileGuard(const QString &path)
FileGuard::FileGuard(const Path &path)
: m_path {path}
, m_remove {true}
{
@ -45,17 +45,17 @@ void FileGuard::setAutoRemove(const bool remove) noexcept
FileGuard::~FileGuard()
{
if (m_remove && !m_path.isEmpty())
Utils::Fs::forceRemove(m_path); // forceRemove() checks for file existence
Utils::Fs::removeFile(m_path); // removeFile() checks for file existence
}
TorrentFileGuard::TorrentFileGuard(const QString &path, const TorrentFileGuard::AutoDeleteMode mode)
: FileGuard {mode != Never ? path : QString()}
TorrentFileGuard::TorrentFileGuard(const Path &path, const TorrentFileGuard::AutoDeleteMode mode)
: FileGuard {mode != Never ? path : Path()}
, m_mode {mode}
, m_wasAdded {false}
{
}
TorrentFileGuard::TorrentFileGuard(const QString &path)
TorrentFileGuard::TorrentFileGuard(const Path &path)
: TorrentFileGuard {path, autoDeleteMode()}
{
}

View File

@ -31,20 +31,22 @@
#include <QObject>
#include <QString>
#include "base/path.h"
template <typename T> class SettingValue;
/// Utility class to defer file deletion
class FileGuard
{
public:
explicit FileGuard(const QString &path = {});
explicit FileGuard(const Path &path = {});
~FileGuard();
/// Cancels or re-enables deferred file deletion
void setAutoRemove(bool remove) noexcept;
private:
QString m_path;
Path m_path;
bool m_remove;
};
@ -55,7 +57,7 @@ class TorrentFileGuard : private FileGuard
Q_GADGET
public:
explicit TorrentFileGuard(const QString &path = {});
explicit TorrentFileGuard(const Path &path = {});
~TorrentFileGuard();
/// marks the torrent file as loaded (added) into the BitTorrent::Session
@ -74,7 +76,7 @@ public:
static void setAutoDeleteMode(AutoDeleteMode mode);
private:
TorrentFileGuard(const QString &path, AutoDeleteMode mode);
TorrentFileGuard(const Path &path, AutoDeleteMode mode);
static SettingValue<AutoDeleteMode> &autoDeleteModeSetting();
Q_ENUM(AutoDeleteMode)

View File

@ -137,9 +137,9 @@ namespace
BitTorrent::AddTorrentParams params;
params.category = jsonObj.value(PARAM_CATEGORY).toString();
params.tags = parseTagSet(jsonObj.value(PARAM_TAGS).toArray());
params.savePath = jsonObj.value(PARAM_SAVEPATH).toString();
params.savePath = Path(jsonObj.value(PARAM_SAVEPATH).toString());
params.useDownloadPath = getOptionalBool(jsonObj, PARAM_USEDOWNLOADPATH);
params.downloadPath = jsonObj.value(PARAM_DOWNLOADPATH).toString();
params.downloadPath = Path(jsonObj.value(PARAM_DOWNLOADPATH).toString());
params.addForced = (getEnum<BitTorrent::TorrentOperatingMode>(jsonObj, PARAM_OPERATINGMODE) == BitTorrent::TorrentOperatingMode::Forced);
params.addPaused = getOptionalBool(jsonObj, PARAM_STOPPED);
params.skipChecking = jsonObj.value(PARAM_SKIPCHECKING).toBool();
@ -158,8 +158,8 @@ namespace
QJsonObject jsonObj {
{PARAM_CATEGORY, params.category},
{PARAM_TAGS, serializeTagSet(params.tags)},
{PARAM_SAVEPATH, params.savePath},
{PARAM_DOWNLOADPATH, params.downloadPath},
{PARAM_SAVEPATH, params.savePath.data()},
{PARAM_DOWNLOADPATH, params.downloadPath.data()},
{PARAM_OPERATINGMODE, Utils::String::fromEnum(params.addForced
? BitTorrent::TorrentOperatingMode::Forced : BitTorrent::TorrentOperatingMode::AutoManaged)},
{PARAM_SKIPCHECKING, params.skipChecking},
@ -208,8 +208,8 @@ public:
Worker();
public slots:
void setWatchedFolder(const QString &path, const TorrentFilesWatcher::WatchedFolderOptions &options);
void removeWatchedFolder(const QString &path);
void setWatchedFolder(const Path &path, const TorrentFilesWatcher::WatchedFolderOptions &options);
void removeWatchedFolder(const Path &path);
signals:
void magnetFound(const BitTorrent::MagnetUri &magnetURI, const BitTorrent::AddTorrentParams &addTorrentParams);
@ -217,21 +217,21 @@ signals:
private:
void onTimeout();
void scheduleWatchedFolderProcessing(const QString &path);
void processWatchedFolder(const QString &path);
void processFolder(const QString &path, const QString &watchedFolderPath, const TorrentFilesWatcher::WatchedFolderOptions &options);
void scheduleWatchedFolderProcessing(const Path &path);
void processWatchedFolder(const Path &path);
void processFolder(const Path &path, const Path &watchedFolderPath, const TorrentFilesWatcher::WatchedFolderOptions &options);
void processFailedTorrents();
void addWatchedFolder(const QString &watchedFolderID, const TorrentFilesWatcher::WatchedFolderOptions &options);
void updateWatchedFolder(const QString &watchedFolderID, const TorrentFilesWatcher::WatchedFolderOptions &options);
void addWatchedFolder(const Path &path, const TorrentFilesWatcher::WatchedFolderOptions &options);
void updateWatchedFolder(const Path &path, const TorrentFilesWatcher::WatchedFolderOptions &options);
QFileSystemWatcher *m_watcher = nullptr;
QTimer *m_watchTimer = nullptr;
QHash<QString, TorrentFilesWatcher::WatchedFolderOptions> m_watchedFolders;
QSet<QString> m_watchedByTimeoutFolders;
QHash<Path, TorrentFilesWatcher::WatchedFolderOptions> m_watchedFolders;
QSet<Path> m_watchedByTimeoutFolders;
// Failed torrents
QTimer *m_retryTorrentTimer = nullptr;
QHash<QString, QHash<QString, int>> m_failedTorrents;
QHash<Path, QHash<Path, int>> m_failedTorrents;
};
TorrentFilesWatcher *TorrentFilesWatcher::m_instance = nullptr;
@ -274,20 +274,9 @@ TorrentFilesWatcher::~TorrentFilesWatcher()
delete m_asyncWorker;
}
QString TorrentFilesWatcher::makeCleanPath(const QString &path)
{
if (path.isEmpty())
throw InvalidArgument(tr("Watched folder path cannot be empty."));
if (QDir::isRelativePath(path))
throw InvalidArgument(tr("Watched folder path cannot be relative."));
return QDir::cleanPath(path);
}
void TorrentFilesWatcher::load()
{
QFile confFile {QDir(specialFolderLocation(SpecialFolder::Config)).absoluteFilePath(CONF_FILE_NAME)};
QFile confFile {(specialFolderLocation(SpecialFolder::Config) / Path(CONF_FILE_NAME)).data()};
if (!confFile.exists())
{
loadLegacy();
@ -320,7 +309,7 @@ void TorrentFilesWatcher::load()
const QJsonObject jsonObj = jsonDoc.object();
for (auto it = jsonObj.constBegin(); it != jsonObj.constEnd(); ++it)
{
const QString &watchedFolder = it.key();
const Path watchedFolder {it.key()};
const WatchedFolderOptions options = parseWatchedFolderOptions(it.value().toObject());
try
{
@ -337,13 +326,13 @@ void TorrentFilesWatcher::loadLegacy()
{
const auto dirs = SettingsStorage::instance()->loadValue<QVariantHash>("Preferences/Downloads/ScanDirsV2");
for (auto i = dirs.cbegin(); i != dirs.cend(); ++i)
for (auto it = dirs.cbegin(); it != dirs.cend(); ++it)
{
const QString watchedFolder = i.key();
const Path watchedFolder {it.key()};
BitTorrent::AddTorrentParams params;
if (i.value().type() == QVariant::Int)
if (it.value().type() == QVariant::Int)
{
if (i.value().toInt() == 0)
if (it.value().toInt() == 0)
{
params.savePath = watchedFolder;
params.useAutoTMM = false;
@ -351,7 +340,7 @@ void TorrentFilesWatcher::loadLegacy()
}
else
{
const QString customSavePath = i.value().toString();
const Path customSavePath {it.value().toString()};
params.savePath = customSavePath;
params.useAutoTMM = false;
}
@ -375,56 +364,60 @@ void TorrentFilesWatcher::store() const
QJsonObject jsonObj;
for (auto it = m_watchedFolders.cbegin(); it != m_watchedFolders.cend(); ++it)
{
const QString &watchedFolder = it.key();
const Path &watchedFolder = it.key();
const WatchedFolderOptions &options = it.value();
jsonObj[watchedFolder] = serializeWatchedFolderOptions(options);
jsonObj[watchedFolder.data()] = serializeWatchedFolderOptions(options);
}
const QString path = QDir(specialFolderLocation(SpecialFolder::Config)).absoluteFilePath(CONF_FILE_NAME);
const Path path = specialFolderLocation(SpecialFolder::Config) / Path(CONF_FILE_NAME);
const QByteArray data = QJsonDocument(jsonObj).toJson();
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, data);
if (!result)
{
LogMsg(tr("Couldn't store Watched Folders configuration to %1. Error: %2")
.arg(path, result.error()), Log::WARNING);
.arg(path.toString(), result.error()), Log::WARNING);
}
}
QHash<QString, TorrentFilesWatcher::WatchedFolderOptions> TorrentFilesWatcher::folders() const
QHash<Path, TorrentFilesWatcher::WatchedFolderOptions> TorrentFilesWatcher::folders() const
{
return m_watchedFolders;
}
void TorrentFilesWatcher::setWatchedFolder(const QString &path, const WatchedFolderOptions &options)
void TorrentFilesWatcher::setWatchedFolder(const Path &path, const WatchedFolderOptions &options)
{
doSetWatchedFolder(path, options);
store();
}
void TorrentFilesWatcher::doSetWatchedFolder(const QString &path, const WatchedFolderOptions &options)
void TorrentFilesWatcher::doSetWatchedFolder(const Path &path, const WatchedFolderOptions &options)
{
const QString cleanPath = makeCleanPath(path);
m_watchedFolders[cleanPath] = options;
if (path.isEmpty())
throw InvalidArgument(tr("Watched folder Path cannot be empty."));
if (path.isRelative())
throw InvalidArgument(tr("Watched folder Path cannot be relative."));
m_watchedFolders[path] = options;
QMetaObject::invokeMethod(m_asyncWorker, [this, path, options]()
{
m_asyncWorker->setWatchedFolder(path, options);
});
emit watchedFolderSet(cleanPath, options);
emit watchedFolderSet(path, options);
}
void TorrentFilesWatcher::removeWatchedFolder(const QString &path)
void TorrentFilesWatcher::removeWatchedFolder(const Path &path)
{
const QString cleanPath = makeCleanPath(path);
if (m_watchedFolders.remove(cleanPath))
if (m_watchedFolders.remove(path))
{
QMetaObject::invokeMethod(m_asyncWorker, [this, cleanPath]()
QMetaObject::invokeMethod(m_asyncWorker, [this, path]()
{
m_asyncWorker->removeWatchedFolder(cleanPath);
m_asyncWorker->removeWatchedFolder(path);
});
emit watchedFolderRemoved(cleanPath);
emit watchedFolderRemoved(path);
store();
}
@ -447,7 +440,10 @@ TorrentFilesWatcher::Worker::Worker()
, m_watchTimer {new QTimer(this)}
, m_retryTorrentTimer {new QTimer(this)}
{
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &Worker::scheduleWatchedFolderProcessing);
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, [this](const QString &path)
{
scheduleWatchedFolderProcessing(Path(path));
});
connect(m_watchTimer, &QTimer::timeout, this, &Worker::onTimeout);
connect(m_retryTorrentTimer, &QTimer::timeout, this, &Worker::processFailedTorrents);
@ -455,11 +451,11 @@ TorrentFilesWatcher::Worker::Worker()
void TorrentFilesWatcher::Worker::onTimeout()
{
for (const QString &path : asConst(m_watchedByTimeoutFolders))
for (const Path &path : asConst(m_watchedByTimeoutFolders))
processWatchedFolder(path);
}
void TorrentFilesWatcher::Worker::setWatchedFolder(const QString &path, const TorrentFilesWatcher::WatchedFolderOptions &options)
void TorrentFilesWatcher::Worker::setWatchedFolder(const Path &path, const TorrentFilesWatcher::WatchedFolderOptions &options)
{
if (m_watchedFolders.contains(path))
updateWatchedFolder(path, options);
@ -467,11 +463,11 @@ void TorrentFilesWatcher::Worker::setWatchedFolder(const QString &path, const To
addWatchedFolder(path, options);
}
void TorrentFilesWatcher::Worker::removeWatchedFolder(const QString &path)
void TorrentFilesWatcher::Worker::removeWatchedFolder(const Path &path)
{
m_watchedFolders.remove(path);
m_watcher->removePath(path);
m_watcher->removePath(path.data());
m_watchedByTimeoutFolders.remove(path);
if (m_watchedByTimeoutFolders.isEmpty())
m_watchTimer->stop();
@ -481,7 +477,7 @@ void TorrentFilesWatcher::Worker::removeWatchedFolder(const QString &path)
m_retryTorrentTimer->stop();
}
void TorrentFilesWatcher::Worker::scheduleWatchedFolderProcessing(const QString &path)
void TorrentFilesWatcher::Worker::scheduleWatchedFolderProcessing(const Path &path)
{
QTimer::singleShot(2000, this, [this, path]()
{
@ -489,7 +485,7 @@ void TorrentFilesWatcher::Worker::scheduleWatchedFolderProcessing(const QString
});
}
void TorrentFilesWatcher::Worker::processWatchedFolder(const QString &path)
void TorrentFilesWatcher::Worker::processWatchedFolder(const Path &path)
{
const TorrentFilesWatcher::WatchedFolderOptions options = m_watchedFolders.value(path);
processFolder(path, path, options);
@ -498,34 +494,32 @@ void TorrentFilesWatcher::Worker::processWatchedFolder(const QString &path)
m_retryTorrentTimer->start(WATCH_INTERVAL);
}
void TorrentFilesWatcher::Worker::processFolder(const QString &path, const QString &watchedFolderPath
void TorrentFilesWatcher::Worker::processFolder(const Path &path, const Path &watchedFolderPath
, const TorrentFilesWatcher::WatchedFolderOptions &options)
{
const QDir watchedDir {watchedFolderPath};
QDirIterator dirIter {path, {"*.torrent", "*.magnet"}, QDir::Files};
QDirIterator dirIter {path.data(), {"*.torrent", "*.magnet"}, QDir::Files};
while (dirIter.hasNext())
{
const QString filePath = dirIter.next();
const Path filePath {dirIter.next()};
BitTorrent::AddTorrentParams addTorrentParams = options.addTorrentParams;
if (path != watchedFolderPath)
{
const QString subdirPath = watchedDir.relativeFilePath(path);
const Path subdirPath = watchedFolderPath.relativePathOf(path);
const bool useAutoTMM = addTorrentParams.useAutoTMM.value_or(!BitTorrent::Session::instance()->isAutoTMMDisabledByDefault());
if (useAutoTMM)
{
addTorrentParams.category = addTorrentParams.category.isEmpty()
? subdirPath : (addTorrentParams.category + QLatin1Char('/') + subdirPath);
? subdirPath.data() : (addTorrentParams.category + QLatin1Char('/') + subdirPath.data());
}
else
{
addTorrentParams.savePath = QDir::cleanPath(QDir(addTorrentParams.savePath).filePath(subdirPath));
addTorrentParams.savePath = addTorrentParams.savePath / subdirPath;
}
}
if (filePath.endsWith(QLatin1String(".magnet"), Qt::CaseInsensitive))
if (filePath.hasExtension(QLatin1String(".magnet")))
{
QFile file {filePath};
QFile file {filePath.data()};
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QTextStream str {&file};
@ -533,7 +527,7 @@ void TorrentFilesWatcher::Worker::processFolder(const QString &path, const QStri
emit magnetFound(BitTorrent::MagnetUri(str.readLine()), addTorrentParams);
file.close();
Utils::Fs::forceRemove(filePath);
Utils::Fs::removeFile(filePath);
}
else
{
@ -546,7 +540,7 @@ void TorrentFilesWatcher::Worker::processFolder(const QString &path, const QStri
if (result)
{
emit torrentFound(result.value(), addTorrentParams);
Utils::Fs::forceRemove(filePath);
Utils::Fs::removeFile(filePath);
}
else
{
@ -560,10 +554,10 @@ void TorrentFilesWatcher::Worker::processFolder(const QString &path, const QStri
if (options.recursive)
{
QDirIterator dirIter {path, (QDir::Dirs | QDir::NoDot | QDir::NoDotDot)};
QDirIterator dirIter {path.data(), (QDir::Dirs | QDir::NoDot | QDir::NoDotDot)};
while (dirIter.hasNext())
{
const QString folderPath = dirIter.next();
const Path folderPath {dirIter.next()};
// Skip processing of subdirectory that is explicitly set as watched folder
if (!m_watchedFolders.contains(folderPath))
processFolder(folderPath, watchedFolderPath, options);
@ -574,45 +568,43 @@ void TorrentFilesWatcher::Worker::processFolder(const QString &path, const QStri
void TorrentFilesWatcher::Worker::processFailedTorrents()
{
// Check which torrents are still partial
Algorithm::removeIf(m_failedTorrents, [this](const QString &watchedFolderPath, QHash<QString, int> &partialTorrents)
Algorithm::removeIf(m_failedTorrents, [this](const Path &watchedFolderPath, QHash<Path, int> &partialTorrents)
{
const QDir dir {watchedFolderPath};
const TorrentFilesWatcher::WatchedFolderOptions options = m_watchedFolders.value(watchedFolderPath);
Algorithm::removeIf(partialTorrents, [this, &dir, &options](const QString &torrentPath, int &value)
Algorithm::removeIf(partialTorrents, [this, &watchedFolderPath, &options](const Path &torrentPath, int &value)
{
if (!QFile::exists(torrentPath))
if (!torrentPath.exists())
return true;
const nonstd::expected<BitTorrent::TorrentInfo, QString> result = BitTorrent::TorrentInfo::loadFromFile(torrentPath);
if (result)
{
BitTorrent::AddTorrentParams addTorrentParams = options.addTorrentParams;
const QString exactDirPath = QFileInfo(torrentPath).canonicalPath();
if (exactDirPath != dir.path())
if (torrentPath != watchedFolderPath)
{
const QString subdirPath = dir.relativeFilePath(exactDirPath);
const Path subdirPath = watchedFolderPath.relativePathOf(torrentPath);
const bool useAutoTMM = addTorrentParams.useAutoTMM.value_or(!BitTorrent::Session::instance()->isAutoTMMDisabledByDefault());
if (useAutoTMM)
{
addTorrentParams.category = addTorrentParams.category.isEmpty()
? subdirPath : (addTorrentParams.category + QLatin1Char('/') + subdirPath);
? subdirPath.data() : (addTorrentParams.category + QLatin1Char('/') + subdirPath.data());
}
else
{
addTorrentParams.savePath = QDir(addTorrentParams.savePath).filePath(subdirPath);
addTorrentParams.savePath = addTorrentParams.savePath / subdirPath;
}
}
emit torrentFound(result.value(), addTorrentParams);
Utils::Fs::forceRemove(torrentPath);
Utils::Fs::removeFile(torrentPath);
return true;
}
if (value >= MAX_FAILED_RETRIES)
{
LogMsg(tr("Rejecting failed torrent file: %1").arg(torrentPath));
QFile::rename(torrentPath, torrentPath + ".qbt_rejected");
LogMsg(tr("Rejecting failed torrent file: %1").arg(torrentPath.toString()));
Utils::Fs::renameFile(torrentPath, (torrentPath + ".qbt_rejected"));
return true;
}
@ -633,14 +625,10 @@ void TorrentFilesWatcher::Worker::processFailedTorrents()
m_retryTorrentTimer->start(WATCH_INTERVAL);
}
void TorrentFilesWatcher::Worker::addWatchedFolder(const QString &path, const TorrentFilesWatcher::WatchedFolderOptions &options)
void TorrentFilesWatcher::Worker::addWatchedFolder(const Path &path, const TorrentFilesWatcher::WatchedFolderOptions &options)
{
#if !defined Q_OS_HAIKU
// Check if the path points to a network file system or not
// Check if the `path` points to a network file system or not
if (Utils::Fs::isNetworkFileSystem(path) || options.recursive)
#else
if (options.recursive)
#endif
{
m_watchedByTimeoutFolders.insert(path);
if (!m_watchTimer->isActive())
@ -648,27 +636,23 @@ void TorrentFilesWatcher::Worker::addWatchedFolder(const QString &path, const To
}
else
{
m_watcher->addPath(path);
m_watcher->addPath(path.data());
scheduleWatchedFolderProcessing(path);
}
m_watchedFolders[path] = options;
LogMsg(tr("Watching folder: \"%1\"").arg(Utils::Fs::toNativePath(path)));
LogMsg(tr("Watching folder: \"%1\"").arg(path.toString()));
}
void TorrentFilesWatcher::Worker::updateWatchedFolder(const QString &path, const TorrentFilesWatcher::WatchedFolderOptions &options)
void TorrentFilesWatcher::Worker::updateWatchedFolder(const Path &path, const TorrentFilesWatcher::WatchedFolderOptions &options)
{
const bool recursiveModeChanged = (m_watchedFolders[path].recursive != options.recursive);
#if !defined Q_OS_HAIKU
if (recursiveModeChanged && !Utils::Fs::isNetworkFileSystem(path))
#else
if (recursiveModeChanged)
#endif
{
if (options.recursive)
{
m_watcher->removePath(path);
m_watcher->removePath(path.data());
m_watchedByTimeoutFolders.insert(path);
if (!m_watchTimer->isActive())
@ -680,7 +664,7 @@ void TorrentFilesWatcher::Worker::updateWatchedFolder(const QString &path, const
if (m_watchedByTimeoutFolders.isEmpty())
m_watchTimer->stop();
m_watcher->addPath(path);
m_watcher->addPath(path.data());
scheduleWatchedFolderProcessing(path);
}
}

View File

@ -32,6 +32,7 @@
#include <QHash>
#include "base/bittorrent/addtorrentparams.h"
#include "base/path.h"
class QThread;
@ -61,15 +62,13 @@ public:
static void freeInstance();
static TorrentFilesWatcher *instance();
static QString makeCleanPath(const QString &path);
QHash<QString, WatchedFolderOptions> folders() const;
void setWatchedFolder(const QString &path, const WatchedFolderOptions &options);
void removeWatchedFolder(const QString &path);
QHash<Path, WatchedFolderOptions> folders() const;
void setWatchedFolder(const Path &path, const WatchedFolderOptions &options);
void removeWatchedFolder(const Path &path);
signals:
void watchedFolderSet(const QString &path, const WatchedFolderOptions &options);
void watchedFolderRemoved(const QString &path);
void watchedFolderSet(const Path &path, const WatchedFolderOptions &options);
void watchedFolderRemoved(const Path &path);
private slots:
void onMagnetFound(const BitTorrent::MagnetUri &magnetURI, const BitTorrent::AddTorrentParams &addTorrentParams);
@ -83,11 +82,11 @@ private:
void loadLegacy();
void store() const;
void doSetWatchedFolder(const QString &path, const WatchedFolderOptions &options);
void doSetWatchedFolder(const Path &path, const WatchedFolderOptions &options);
static TorrentFilesWatcher *m_instance;
QHash<QString, WatchedFolderOptions> m_watchedFolders;
QHash<Path, WatchedFolderOptions> m_watchedFolders;
QThread *m_ioThread = nullptr;

View File

@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -50,57 +51,17 @@
#include <unistd.h>
#endif
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
#include <QMimeDatabase>
#include <QStorageInfo>
#include <QRegularExpression>
#include "base/global.h"
QString Utils::Fs::toNativePath(const QString &path)
{
return QDir::toNativeSeparators(path);
}
QString Utils::Fs::toUniformPath(const QString &path)
{
return QDir::fromNativeSeparators(path);
}
QString Utils::Fs::resolvePath(const QString &relativePath, const QString &basePath)
{
Q_ASSERT(QDir::isRelativePath(relativePath));
Q_ASSERT(QDir::isAbsolutePath(basePath));
return (relativePath.isEmpty() ? basePath : QDir(basePath).absoluteFilePath(relativePath));
}
QString Utils::Fs::fileExtension(const QString &filename)
{
return QMimeDatabase().suffixForFileName(filename);
}
QString Utils::Fs::fileName(const QString &filePath)
{
const QString path = toUniformPath(filePath);
const int slashIndex = path.lastIndexOf('/');
if (slashIndex == -1)
return path;
return path.mid(slashIndex + 1);
}
QString Utils::Fs::folderName(const QString &filePath)
{
const QString path = toUniformPath(filePath);
const int slashIndex = path.lastIndexOf('/');
if (slashIndex == -1)
return {};
return path.left(slashIndex);
}
#include "base/path.h"
/**
* This function will first check if there are only system cache files, e.g. `Thumbs.db`,
@ -111,9 +72,9 @@ QString Utils::Fs::folderName(const QString &filePath)
* that only the above mentioned "useless" files exist but before the whole folder is removed.
* In this case, the folder will not be removed but the "useless" files will be deleted.
*/
bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path)
bool Utils::Fs::smartRemoveEmptyFolderTree(const Path &path)
{
if (path.isEmpty() || !QDir(path).exists())
if (!path.exists())
return true;
const QStringList deleteFilesList =
@ -128,8 +89,8 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path)
};
// travel from the deepest folder and remove anything unwanted on the way out.
QStringList dirList(path + '/'); // get all sub directories paths
QDirIterator iter(path, (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories);
QStringList dirList(path.data() + '/'); // get all sub directories paths
QDirIterator iter {path.data(), (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories};
while (iter.hasNext())
dirList << iter.next() + '/';
// sort descending by directory depth
@ -138,7 +99,7 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path)
for (const QString &p : asConst(dirList))
{
const QDir dir(p);
const QDir dir {p};
// A deeper folder may have not been removed in the previous iteration
// so don't remove anything from this folder either.
if (!dir.isEmpty(QDir::Dirs | QDir::NoDotAndDotDot))
@ -156,38 +117,22 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path)
continue;
for (const QString &f : tmpFileList)
forceRemove(p + f);
removeFile(Path(p + f));
// remove directory if empty
dir.rmdir(p);
}
return QDir(path).exists();
}
/**
* Removes the file with the given filePath.
*
* This function will try to fix the file permissions before removing it.
*/
bool Utils::Fs::forceRemove(const QString &filePath)
{
QFile f(filePath);
if (!f.exists())
return true;
// Make sure we have read/write permissions
f.setPermissions(f.permissions() | QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser);
// Remove the file
return f.remove();
return path.exists();
}
/**
* Removes directory and its content recursively.
*/
void Utils::Fs::removeDirRecursive(const QString &path)
void Utils::Fs::removeDirRecursively(const Path &path)
{
if (!path.isEmpty())
QDir(path).removeRecursively();
QDir(path.data()).removeRecursively();
}
/**
@ -196,16 +141,16 @@ void Utils::Fs::removeDirRecursive(const QString &path)
*
* Returns -1 in case of error.
*/
qint64 Utils::Fs::computePathSize(const QString &path)
qint64 Utils::Fs::computePathSize(const Path &path)
{
// Check if it is a file
const QFileInfo fi(path);
const QFileInfo fi {path.data()};
if (!fi.exists()) return -1;
if (fi.isFile()) return fi.size();
// Compute folder size based on its content
qint64 size = 0;
QDirIterator iter(path, QDir::Files | QDir::Hidden | QDir::NoSymLinks, QDirIterator::Subdirectories);
QDirIterator iter {path.data(), QDir::Files | QDir::Hidden | QDir::NoSymLinks, QDirIterator::Subdirectories};
while (iter.hasNext())
{
iter.next();
@ -217,9 +162,10 @@ qint64 Utils::Fs::computePathSize(const QString &path)
/**
* Makes deep comparison of two files to make sure they are identical.
*/
bool Utils::Fs::sameFiles(const QString &path1, const QString &path2)
bool Utils::Fs::sameFiles(const Path &path1, const Path &path2)
{
QFile f1(path1), f2(path2);
QFile f1 {path1.data()};
QFile f2 {path2.data()};
if (!f1.exists() || !f2.exists()) return false;
if (f1.size() != f2.size()) return false;
if (!f1.open(QIODevice::ReadOnly)) return false;
@ -234,120 +180,65 @@ bool Utils::Fs::sameFiles(const QString &path1, const QString &path2)
return true;
}
QString Utils::Fs::toValidFileSystemName(const QString &name, const bool allowSeparators, const QString &pad)
QString Utils::Fs::toValidFileName(const QString &name, const QString &pad)
{
const QRegularExpression regex(allowSeparators ? "[:?\"*<>|]+" : "[\\\\/:?\"*<>|]+");
const QRegularExpression regex {QLatin1String("[\\\\/:?\"*<>|]+")};
QString validName = name.trimmed();
validName.replace(regex, pad);
qDebug() << "toValidFileSystemName:" << name << "=>" << validName;
return validName;
}
bool Utils::Fs::isValidFileSystemName(const QString &name, const bool allowSeparators)
Path Utils::Fs::toValidPath(const QString &name, const QString &pad)
{
if (name.isEmpty()) return false;
const QRegularExpression regex {QLatin1String("[:?\"*<>|]+")};
#if defined(Q_OS_WIN)
const QRegularExpression regex
{allowSeparators
? QLatin1String("[:?\"*<>|]")
: QLatin1String("[\\\\/:?\"*<>|]")};
#elif defined(Q_OS_MACOS)
const QRegularExpression regex
{allowSeparators
? QLatin1String("[\\0:]")
: QLatin1String("[\\0/:]")};
#else
const QRegularExpression regex
{allowSeparators
? QLatin1String("[\\0]")
: QLatin1String("[\\0/]")};
#endif
return !name.contains(regex);
QString validPathStr = name;
validPathStr.replace(regex, pad);
return Path(validPathStr);
}
qint64 Utils::Fs::freeDiskSpaceOnPath(const QString &path)
qint64 Utils::Fs::freeDiskSpaceOnPath(const Path &path)
{
return QStorageInfo(path).bytesAvailable();
return QStorageInfo(path.data()).bytesAvailable();
}
QString Utils::Fs::branchPath(const QString &filePath, QString *removed)
Path Utils::Fs::tempPath()
{
QString ret = toUniformPath(filePath);
if (ret.endsWith('/'))
ret.chop(1);
const int slashIndex = ret.lastIndexOf('/');
if (slashIndex >= 0)
{
if (removed)
*removed = ret.mid(slashIndex + 1);
ret = ret.left(slashIndex);
}
return ret;
}
bool Utils::Fs::sameFileNames(const QString &first, const QString &second)
{
#if defined(Q_OS_UNIX) || defined(Q_WS_QWS)
return QString::compare(first, second, Qt::CaseSensitive) == 0;
#else
return QString::compare(first, second, Qt::CaseInsensitive) == 0;
#endif
}
QString Utils::Fs::expandPath(const QString &path)
{
const QString ret = path.trimmed();
if (ret.isEmpty())
return ret;
return QDir::cleanPath(ret);
}
QString Utils::Fs::expandPathAbs(const QString &path)
{
return QDir(expandPath(path)).absolutePath();
}
QString Utils::Fs::tempPath()
{
static const QString path = QDir::tempPath() + "/.qBittorrent/";
QDir().mkdir(path);
static const Path path = Path(QDir::tempPath()) / Path(".qBittorrent");
mkdir(path);
return path;
}
bool Utils::Fs::isRegularFile(const QString &path)
bool Utils::Fs::isRegularFile(const Path &path)
{
struct ::stat st;
if (::stat(path.toUtf8().constData(), &st) != 0)
if (::stat(path.toString().toUtf8().constData(), &st) != 0)
{
// analyse erno and log the error
const auto err = errno;
qDebug("Could not get file stats for path '%s'. Error: %s"
, qUtf8Printable(path), qUtf8Printable(strerror(err)));
, qUtf8Printable(path.toString()), qUtf8Printable(strerror(err)));
return false;
}
return (st.st_mode & S_IFMT) == S_IFREG;
}
#if !defined Q_OS_HAIKU
bool Utils::Fs::isNetworkFileSystem(const QString &path)
bool Utils::Fs::isNetworkFileSystem(const Path &path)
{
#if defined(Q_OS_WIN)
const std::wstring pathW {path.toStdWString()};
auto volumePath = std::make_unique<wchar_t[]>(path.length() + 1);
if (!::GetVolumePathNameW(pathW.c_str(), volumePath.get(), (path.length() + 1)))
#if defined Q_OS_HAIKU
return false;
#elif defined(Q_OS_WIN)
const std::wstring pathW = path.toString().toStdWString();
auto volumePath = std::make_unique<wchar_t[]>(pathW.length() + 1);
if (!::GetVolumePathNameW(pathW.c_str(), volumePath.get(), static_cast<DWORD>(pathW.length() + 1)))
return false;
return (::GetDriveTypeW(volumePath.get()) == DRIVE_REMOTE);
#else
QString file = path;
if (!file.endsWith('/'))
file += '/';
file += '.';
const QString file = path.toString() + QLatin1String("/.");
struct statfs buf {};
if (statfs(file.toLocal8Bit().constData(), &buf) != 0)
return false;
@ -398,41 +289,77 @@ bool Utils::Fs::isNetworkFileSystem(const QString &path)
#endif
#endif
}
#endif // Q_OS_HAIKU
QString Utils::Fs::findRootFolder(const QStringList &filePaths)
bool Utils::Fs::copyFile(const Path &from, const Path &to)
{
QString rootFolder;
for (const QString &filePath : filePaths)
{
const auto filePathElements = QStringView(filePath).split(u'/');
// if at least one file has no root folder, no common root folder exists
if (filePathElements.count() <= 1)
return {};
if (rootFolder.isEmpty())
rootFolder = filePathElements.at(0).toString();
else if (rootFolder != filePathElements.at(0))
return {};
}
return rootFolder;
return QFile::copy(from.data(), to.data());
}
void Utils::Fs::stripRootFolder(QStringList &filePaths)
bool Utils::Fs::renameFile(const Path &from, const Path &to)
{
const QString commonRootFolder = findRootFolder(filePaths);
if (commonRootFolder.isEmpty())
return;
for (QString &filePath : filePaths)
filePath = filePath.mid(commonRootFolder.size() + 1);
return QFile::rename(from.data(), to.data());
}
void Utils::Fs::addRootFolder(QStringList &filePaths, const QString &rootFolder)
/**
* Removes the file with the given filePath.
*
* This function will try to fix the file permissions before removing it.
*/
bool Utils::Fs::removeFile(const Path &path)
{
Q_ASSERT(!rootFolder.isEmpty());
if (QFile::remove(path.data()))
return true;
for (QString &filePath : filePaths)
filePath = rootFolder + QLatin1Char('/') + filePath;
QFile file {path.data()};
if (!file.exists())
return true;
// Make sure we have read/write permissions
file.setPermissions(file.permissions() | QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser);
return file.remove();
}
bool Utils::Fs::isReadable(const Path &path)
{
return QFileInfo(path.data()).isReadable();
}
bool Utils::Fs::isWritable(const Path &path)
{
return QFileInfo(path.data()).isWritable();
}
QDateTime Utils::Fs::lastModified(const Path &path)
{
return QFileInfo(path.data()).lastModified();
}
bool Utils::Fs::isDir(const Path &path)
{
return QFileInfo(path.data()).isDir();
}
Path Utils::Fs::toCanonicalPath(const Path &path)
{
return Path(QFileInfo(path.data()).canonicalFilePath());
}
Path Utils::Fs::homePath()
{
return Path(QDir::homePath());
}
bool Utils::Fs::mkdir(const Path &dirPath)
{
return QDir().mkdir(dirPath.data());
}
bool Utils::Fs::mkpath(const Path &dirPath)
{
return QDir().mkpath(dirPath.data());
}
bool Utils::Fs::rmdir(const Path &dirPath)
{
return QDir().rmdir(dirPath.data());
}

View File

@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -34,58 +35,36 @@
#include <QString>
#include "base/pathfwd.h"
class QDateTime;
namespace Utils::Fs
{
/**
* Converts a path to a string suitable for display.
* This function makes sure the directory separator used is consistent
* with the OS being run.
*/
QString toNativePath(const QString &path);
qint64 computePathSize(const Path &path);
qint64 freeDiskSpaceOnPath(const Path &path);
/**
* Converts a path to a string suitable for processing.
* This function makes sure the directory separator used is independent
* from the OS being run so it is the same on all supported platforms.
* Slash ('/') is used as "uniform" directory separator.
*/
QString toUniformPath(const QString &path);
bool isRegularFile(const Path &path);
bool isDir(const Path &path);
bool isReadable(const Path &path);
bool isWritable(const Path &path);
bool isNetworkFileSystem(const Path &path);
QDateTime lastModified(const Path &path);
bool sameFiles(const Path &path1, const Path &path2);
/**
* If `path is relative then resolves it against `basePath`, otherwise returns the `path` itself
*/
QString resolvePath(const QString &relativePath, const QString &basePath);
QString toValidFileName(const QString &name, const QString &pad = QLatin1String(" "));
Path toValidPath(const QString &name, const QString &pad = QLatin1String(" "));
Path toCanonicalPath(const Path &path);
/**
* Returns the file extension part of a file name.
*/
QString fileExtension(const QString &filename);
bool copyFile(const Path &from, const Path &to);
bool renameFile(const Path &from, const Path &to);
bool removeFile(const Path &path);
bool mkdir(const Path &dirPath);
bool mkpath(const Path &dirPath);
bool rmdir(const Path &dirPath);
void removeDirRecursively(const Path &path);
bool smartRemoveEmptyFolderTree(const Path &path);
QString fileName(const QString &filePath);
QString folderName(const QString &filePath);
qint64 computePathSize(const QString &path);
bool sameFiles(const QString &path1, const QString &path2);
QString toValidFileSystemName(const QString &name, bool allowSeparators = false
, const QString &pad = QLatin1String(" "));
bool isValidFileSystemName(const QString &name, bool allowSeparators = false);
qint64 freeDiskSpaceOnPath(const QString &path);
QString branchPath(const QString &filePath, QString *removed = nullptr);
bool sameFileNames(const QString &first, const QString &second);
QString expandPath(const QString &path);
QString expandPathAbs(const QString &path);
bool isRegularFile(const QString &path);
bool smartRemoveEmptyFolderTree(const QString &path);
bool forceRemove(const QString &filePath);
void removeDirRecursive(const QString &path);
QString tempPath();
QString findRootFolder(const QStringList &filePaths);
void stripRootFolder(QStringList &filePaths);
void addRootFolder(QStringList &filePaths, const QString &name);
#if !defined Q_OS_HAIKU
bool isNetworkFileSystem(const QString &path);
#endif
Path homePath();
Path tempPath();
}

View File

@ -36,6 +36,8 @@
#include <QSaveFile>
#include <QString>
#include "base/path.h"
Utils::IO::FileDeviceOutputIterator::FileDeviceOutputIterator(QFileDevice &device, const int bufferSize)
: m_device {&device}
, m_buffer {std::make_shared<QByteArray>()}
@ -66,17 +68,17 @@ Utils::IO::FileDeviceOutputIterator &Utils::IO::FileDeviceOutputIterator::operat
return *this;
}
nonstd::expected<void, QString> Utils::IO::saveToFile(const QString &path, const QByteArray &data)
nonstd::expected<void, QString> Utils::IO::saveToFile(const Path &path, const QByteArray &data)
{
QSaveFile file {path};
QSaveFile file {path.data()};
if (!file.open(QIODevice::WriteOnly) || (file.write(data) != data.size()) || !file.flush() || !file.commit())
return nonstd::make_unexpected(file.errorString());
return {};
}
nonstd::expected<void, QString> Utils::IO::saveToFile(const QString &path, const lt::entry &data)
nonstd::expected<void, QString> Utils::IO::saveToFile(const Path &path, const lt::entry &data)
{
QSaveFile file {path};
QSaveFile file {path.data()};
if (!file.open(QIODevice::WriteOnly))
return nonstd::make_unexpected(file.errorString());

View File

@ -34,6 +34,7 @@
#include <libtorrent/fwd.hpp>
#include "base/3rdparty/expected.hpp"
#include "base/pathfwd.h"
class QByteArray;
class QFileDevice;
@ -80,6 +81,6 @@ namespace Utils::IO
int m_bufferSize;
};
nonstd::expected<void, QString> saveToFile(const QString &path, const QByteArray &data);
nonstd::expected<void, QString> saveToFile(const QString &path, const lt::entry &data);
nonstd::expected<void, QString> saveToFile(const Path &path, const QByteArray &data);
nonstd::expected<void, QString> saveToFile(const Path &path, const lt::entry &data);
}

View File

@ -61,6 +61,7 @@
#include <QDBusInterface>
#endif
#include "base/path.h"
#include "base/types.h"
#include "base/unicodestrings.h"
#include "base/utils/fs.h"
@ -292,9 +293,9 @@ qlonglong Utils::Misc::sizeInBytes(qreal size, const Utils::Misc::SizeUnit unit)
return size;
}
bool Utils::Misc::isPreviewable(const QString &filename)
bool Utils::Misc::isPreviewable(const Path &filePath)
{
const QString mime = QMimeDatabase().mimeTypeForFile(filename, QMimeDatabase::MatchExtension).name();
const QString mime = QMimeDatabase().mimeTypeForFile(filePath.data(), QMimeDatabase::MatchExtension).name();
if (mime.startsWith(QLatin1String("audio"), Qt::CaseInsensitive)
|| mime.startsWith(QLatin1String("video"), Qt::CaseInsensitive))
@ -304,50 +305,50 @@ bool Utils::Misc::isPreviewable(const QString &filename)
const QSet<QString> multimediaExtensions =
{
"3GP",
"AAC",
"AC3",
"AIF",
"AIFC",
"AIFF",
"ASF",
"AU",
"AVI",
"FLAC",
"FLV",
"M3U",
"M4A",
"M4P",
"M4V",
"MID",
"MKV",
"MOV",
"MP2",
"MP3",
"MP4",
"MPC",
"MPE",
"MPEG",
"MPG",
"MPP",
"OGG",
"OGM",
"OGV",
"QT",
"RA",
"RAM",
"RM",
"RMV",
"RMVB",
"SWA",
"SWF",
"TS",
"VOB",
"WAV",
"WMA",
"WMV"
".3GP",
".AAC",
".AC3",
".AIF",
".AIFC",
".AIFF",
".ASF",
".AU",
".AVI",
".FLAC",
".FLV",
".M3U",
".M4A",
".M4P",
".M4V",
".MID",
".MKV",
".MOV",
".MP2",
".MP3",
".MP4",
".MPC",
".MPE",
".MPEG",
".MPG",
".MPP",
".OGG",
".OGM",
".OGV",
".QT",
".RA",
".RAM",
".RM",
".RMV",
".RMVB",
".SWA",
".SWF",
".TS",
".VOB",
".WAV",
".WMA",
".WMV"
};
return multimediaExtensions.contains(Utils::Fs::fileExtension(filename).toUpper());
return multimediaExtensions.contains(filePath.extension().toUpper());
}
QString Utils::Misc::userFriendlyDuration(const qlonglong seconds, const qlonglong maxCap)

View File

@ -37,6 +37,8 @@
#include <QString>
#include "base/pathfwd.h"
enum class ShutdownDialogAction;
/* Miscellaneous functions that can be useful */
@ -77,7 +79,7 @@ namespace Utils::Misc
int friendlyUnitPrecision(SizeUnit unit);
qint64 sizeInBytes(qreal size, SizeUnit unit);
bool isPreviewable(const QString &filename);
bool isPreviewable(const Path &filePath);
// Take a number of seconds and return a user-friendly
// time duration like "1d 2h 10m".

View File

@ -30,6 +30,7 @@
#include <QFile>
#include "base/path.h"
#include "base/unicodestrings.h"
#include "base/utils/misc.h"
#include "base/version.h"
@ -70,7 +71,7 @@ AboutDialog::AboutDialog(QWidget *parent)
, tr("Bug Tracker:"));
m_ui->labelAbout->setText(aboutText);
m_ui->labelMascot->setPixmap(Utils::Gui::scaledPixmap(":/icons/mascot.png", this));
m_ui->labelMascot->setPixmap(Utils::Gui::scaledPixmap(Path(":/icons/mascot.png"), this));
// Thanks
QFile thanksfile(":/thanks.html");

View File

@ -81,7 +81,7 @@ namespace
class FileStorageAdaptor final : public BitTorrent::AbstractFileStorage
{
public:
FileStorageAdaptor(const BitTorrent::TorrentInfo &torrentInfo, QStringList &filePaths)
FileStorageAdaptor(const BitTorrent::TorrentInfo &torrentInfo, PathList &filePaths)
: m_torrentInfo {torrentInfo}
, m_filePaths {filePaths}
{
@ -99,16 +99,16 @@ namespace
return m_torrentInfo.fileSize(index);
}
QString filePath(const int index) const override
Path filePath(const int index) const override
{
Q_ASSERT((index >= 0) && (index < filesCount()));
return (m_filePaths.isEmpty() ? m_torrentInfo.filePath(index) : m_filePaths.at(index));
}
void renameFile(const int index, const QString &newFilePath) override
void renameFile(const int index, const Path &newFilePath) override
{
Q_ASSERT((index >= 0) && (index < filesCount()));
const QString currentFilePath = filePath(index);
const Path currentFilePath = filePath(index);
if (currentFilePath == newFilePath)
return;
@ -120,22 +120,21 @@ namespace
private:
const BitTorrent::TorrentInfo &m_torrentInfo;
QStringList &m_filePaths;
PathList &m_filePaths;
};
// savePath is a folder, not an absolute file path
int indexOfPath(const FileSystemPathComboEdit *fsPathEdit, const QString &savePath)
int indexOfPath(const FileSystemPathComboEdit *fsPathEdit, const Path &savePath)
{
const QDir saveDir {savePath};
for (int i = 0; i < fsPathEdit->count(); ++i)
{
if (QDir(fsPathEdit->item(i)) == saveDir)
if (fsPathEdit->item(i) == savePath)
return i;
}
return -1;
}
void setPath(FileSystemPathComboEdit *fsPathEdit, const QString &newPath)
void setPath(FileSystemPathComboEdit *fsPathEdit, const Path &newPath)
{
int existingIndex = indexOfPath(fsPathEdit, newPath);
if (existingIndex < 0)
@ -148,16 +147,18 @@ namespace
fsPathEdit->setCurrentIndex(existingIndex);
}
void updatePathHistory(const QString &settingsKey, const QString &path, const int maxLength)
void updatePathHistory(const QString &settingsKey, const Path &path, const int maxLength)
{
// Add last used save path to the front of history
auto pathList = settings()->loadValue<QStringList>(settingsKey);
const int selectedSavePathIndex = pathList.indexOf(path);
const int selectedSavePathIndex = pathList.indexOf(path.toString());
if (selectedSavePathIndex > -1)
pathList.move(selectedSavePathIndex, 0);
else
pathList.prepend(path);
pathList.prepend(path.toString());
settings()->storeValue(settingsKey, QStringList(pathList.mid(0, maxLength)));
}
}
@ -325,7 +326,7 @@ void AddNewTorrentDialog::show(const QString &source, const BitTorrent::AddTorre
return;
}
const BitTorrent::MagnetUri magnetUri(source);
const BitTorrent::MagnetUri magnetUri {source};
const bool isLoaded = magnetUri.isValid()
? dlg->loadMagnet(magnetUri)
: dlg->loadTorrentFile(source);
@ -341,18 +342,18 @@ void AddNewTorrentDialog::show(const QString &source, QWidget *parent)
show(source, BitTorrent::AddTorrentParams(), parent);
}
bool AddNewTorrentDialog::loadTorrentFile(const QString &torrentPath)
bool AddNewTorrentDialog::loadTorrentFile(const QString &source)
{
const QString decodedPath = torrentPath.startsWith("file://", Qt::CaseInsensitive)
? QUrl::fromEncoded(torrentPath.toLocal8Bit()).toLocalFile()
: torrentPath;
const Path decodedPath {source.startsWith("file://", Qt::CaseInsensitive)
? QUrl::fromEncoded(source.toLocal8Bit()).toLocalFile()
: source};
const nonstd::expected<BitTorrent::TorrentInfo, QString> result = BitTorrent::TorrentInfo::loadFromFile(decodedPath);
if (!result)
{
RaisedMessageBox::critical(this, tr("Invalid torrent")
, tr("Failed to load the torrent: %1.\nError: %2", "Don't remove the '\n' characters. They insert a newline.")
.arg(Utils::Fs::toNativePath(decodedPath), result.error()));
.arg(decodedPath.toString(), result.error()));
return false;
}
@ -489,7 +490,7 @@ void AddNewTorrentDialog::updateDiskSpaceLabel()
m_ui->labelSizeData->setText(sizeString);
}
void AddNewTorrentDialog::onSavePathChanged(const QString &newPath)
void AddNewTorrentDialog::onSavePathChanged(const Path &newPath)
{
Q_UNUSED(newPath);
// Remember index
@ -497,7 +498,7 @@ void AddNewTorrentDialog::onSavePathChanged(const QString &newPath)
updateDiskSpaceLabel();
}
void AddNewTorrentDialog::onDownloadPathChanged(const QString &newPath)
void AddNewTorrentDialog::onDownloadPathChanged(const Path &newPath)
{
Q_UNUSED(newPath);
// Remember index
@ -521,11 +522,11 @@ void AddNewTorrentDialog::categoryChanged(int index)
const auto *btSession = BitTorrent::Session::instance();
const QString categoryName = m_ui->categoryComboBox->currentText();
const QString savePath = btSession->categorySavePath(categoryName);
m_ui->savePath->setSelectedPath(Utils::Fs::toNativePath(savePath));
const Path savePath = btSession->categorySavePath(categoryName);
m_ui->savePath->setSelectedPath(savePath);
const QString downloadPath = btSession->categoryDownloadPath(categoryName);
m_ui->downloadPath->setSelectedPath(Utils::Fs::toNativePath(downloadPath));
const Path downloadPath = btSession->categoryDownloadPath(categoryName);
m_ui->downloadPath->setSelectedPath(downloadPath);
m_ui->groupBoxDownloadPath->setChecked(!m_ui->downloadPath->selectedPath().isEmpty());
@ -545,7 +546,7 @@ void AddNewTorrentDialog::contentLayoutChanged(const int index)
const auto contentLayout = ((index == 0)
? BitTorrent::detectContentLayout(m_torrentInfo.filePaths())
: static_cast<BitTorrent::TorrentContentLayout>(index));
BitTorrent::applyContentLayout(m_torrentParams.filePaths, contentLayout, Utils::Fs::findRootFolder(m_torrentInfo.filePaths()));
BitTorrent::applyContentLayout(m_torrentParams.filePaths, contentLayout, Path::findRootFolder(m_torrentInfo.filePaths()));
m_contentModel->model()->setupModelData(FileStorageAdaptor(m_torrentInfo, m_torrentParams.filePaths));
m_contentModel->model()->updateFilesPriorities(filePriorities);
@ -574,7 +575,7 @@ void AddNewTorrentDialog::saveTorrentFile()
if (!path.endsWith(torrentFileExtension, Qt::CaseInsensitive))
path += torrentFileExtension;
const nonstd::expected<void, QString> result = m_torrentInfo.saveToFile(path);
const nonstd::expected<void, QString> result = m_torrentInfo.saveToFile(Path(path));
if (!result)
{
QMessageBox::critical(this, tr("I/O Error")
@ -597,7 +598,7 @@ void AddNewTorrentDialog::populateSavePaths()
if (savePathHistory.size() > 0)
{
for (const QString &path : savePathHistory)
m_ui->savePath->addItem(path);
m_ui->savePath->addItem(Path(path));
}
else
{
@ -628,7 +629,7 @@ void AddNewTorrentDialog::populateSavePaths()
if (downloadPathHistory.size() > 0)
{
for (const QString &path : downloadPathHistory)
m_ui->downloadPath->addItem(path);
m_ui->downloadPath->addItem(Path(path));
}
else
{
@ -806,14 +807,14 @@ void AddNewTorrentDialog::accept()
m_torrentParams.useAutoTMM = useAutoTMM;
if (!useAutoTMM)
{
const QString savePath = m_ui->savePath->selectedPath();
const Path savePath = m_ui->savePath->selectedPath();
m_torrentParams.savePath = savePath;
updatePathHistory(KEY_SAVEPATHHISTORY, savePath, savePathHistoryLength());
m_torrentParams.useDownloadPath = m_ui->groupBoxDownloadPath->isChecked();
if (m_torrentParams.useDownloadPath)
{
const QString downloadPath = m_ui->downloadPath->selectedPath();
const Path downloadPath = m_ui->downloadPath->selectedPath();
m_torrentParams.downloadPath = downloadPath;
updatePathHistory(KEY_DOWNLOADPATHHISTORY, downloadPath, savePathHistoryLength());
}
@ -907,7 +908,7 @@ void AddNewTorrentDialog::setupTreeview()
: static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex()));
if (m_torrentParams.filePaths.isEmpty())
m_torrentParams.filePaths = m_torrentInfo.filePaths();
BitTorrent::applyContentLayout(m_torrentParams.filePaths, contentLayout, Utils::Fs::findRootFolder(m_torrentInfo.filePaths()));
BitTorrent::applyContentLayout(m_torrentParams.filePaths, contentLayout, Path::findRootFolder(m_torrentInfo.filePaths()));
// List files in torrent
m_contentModel->model()->setupModelData(FileStorageAdaptor(m_torrentInfo, m_torrentParams.filePaths));
if (const QByteArray state = m_storeTreeHeaderState; !state.isEmpty())
@ -981,12 +982,12 @@ void AddNewTorrentDialog::TMMChanged(int index)
m_ui->savePath->blockSignals(true);
m_ui->savePath->clear();
const QString savePath = session->categorySavePath(m_ui->categoryComboBox->currentText());
const Path savePath = session->categorySavePath(m_ui->categoryComboBox->currentText());
m_ui->savePath->addItem(savePath);
m_ui->downloadPath->blockSignals(true);
m_ui->downloadPath->clear();
const QString downloadPath = session->categoryDownloadPath(m_ui->categoryComboBox->currentText());
const Path downloadPath = session->categoryDownloadPath(m_ui->categoryComboBox->currentText());
m_ui->downloadPath->addItem(downloadPath);
m_ui->groupBoxDownloadPath->blockSignals(true);

View File

@ -81,8 +81,8 @@ private slots:
void displayContentTreeMenu();
void displayColumnHeaderMenu();
void updateDiskSpaceLabel();
void onSavePathChanged(const QString &newPath);
void onDownloadPathChanged(const QString &newPath);
void onSavePathChanged(const Path &newPath);
void onDownloadPathChanged(const Path &newPath);
void onUseDownloadPathChanged(bool checked);
void updateMetadata(const BitTorrent::TorrentInfo &metadata);
void handleDownloadFinished(const Net::DownloadResult &downloadResult);
@ -97,7 +97,7 @@ private slots:
private:
explicit AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inParams, QWidget *parent);
bool loadTorrentFile(const QString &torrentPath);
bool loadTorrentFile(const QString &source);
bool loadTorrentImpl();
bool loadMagnet(const BitTorrent::MagnetUri &magnetUri);
void populateSavePaths();

View File

@ -28,7 +28,7 @@
#include "autoexpandabledialog.h"
#include "base/utils/fs.h"
#include "base/path.h"
#include "ui_autoexpandabledialog.h"
#include "utils.h"
@ -58,9 +58,9 @@ QString AutoExpandableDialog::getText(QWidget *parent, const QString &title, con
d.m_ui->textEdit->selectAll();
if (excludeExtension)
{
const QString extension = Utils::Fs::fileExtension(text);
const QString extension = Path(text).extension();
if (!extension.isEmpty())
d.m_ui->textEdit->setSelection(0, (text.length() - extension.length() - 1));
d.m_ui->textEdit->setSelection(0, (text.length() - extension.length()));
}
bool res = d.exec();

View File

@ -82,7 +82,7 @@ class FileSystemPathEdit::FileSystemPathEditPrivate
QToolButton *m_browseBtn;
QString m_fileNameFilter;
Mode m_mode;
QString m_lastSignaledPath;
Path m_lastSignaledPath;
QString m_dialogCaption;
Private::FileSystemPathValidator *m_validator;
};
@ -112,31 +112,32 @@ void FileSystemPathEdit::FileSystemPathEditPrivate::browseActionTriggered()
{
Q_Q(FileSystemPathEdit);
const QFileInfo fileInfo {q->selectedPath()};
const QString directory = (m_mode == FileSystemPathEdit::Mode::DirectoryOpen) || (m_mode == FileSystemPathEdit::Mode::DirectorySave)
? fileInfo.absoluteFilePath()
: fileInfo.absolutePath();
QString filter = q->fileNameFilter();
const Path currentDirectory = (m_mode == FileSystemPathEdit::Mode::DirectoryOpen) || (m_mode == FileSystemPathEdit::Mode::DirectorySave)
? q->selectedPath()
: q->selectedPath().parentPath();
const Path initialDirectory = currentDirectory.isAbsolute() ? currentDirectory : (Utils::Fs::homePath() / currentDirectory);
QString selectedPath;
QString filter = q->fileNameFilter();
QString newPath;
switch (m_mode)
{
case FileSystemPathEdit::Mode::FileOpen:
selectedPath = QFileDialog::getOpenFileName(q, dialogCaptionOrDefault(), directory, filter);
newPath = QFileDialog::getOpenFileName(q, dialogCaptionOrDefault(), initialDirectory.data(), filter);
break;
case FileSystemPathEdit::Mode::FileSave:
selectedPath = QFileDialog::getSaveFileName(q, dialogCaptionOrDefault(), directory, filter, &filter);
newPath = QFileDialog::getSaveFileName(q, dialogCaptionOrDefault(), initialDirectory.data(), filter, &filter);
break;
case FileSystemPathEdit::Mode::DirectoryOpen:
case FileSystemPathEdit::Mode::DirectorySave:
selectedPath = QFileDialog::getExistingDirectory(q, dialogCaptionOrDefault(),
directory, QFileDialog::DontResolveSymlinks | QFileDialog::ShowDirsOnly);
newPath = QFileDialog::getExistingDirectory(q, dialogCaptionOrDefault(),
initialDirectory.data(), QFileDialog::DontResolveSymlinks | QFileDialog::ShowDirsOnly);
break;
default:
throw std::logic_error("Unknown FileSystemPathEdit mode");
}
if (!selectedPath.isEmpty())
q->setEditWidgetText(Utils::Fs::toNativePath(selectedPath));
if (!newPath.isEmpty())
q->setSelectedPath(Path(newPath));
}
QString FileSystemPathEdit::FileSystemPathEditPrivate::dialogCaptionOrDefault() const
@ -202,16 +203,18 @@ FileSystemPathEdit::~FileSystemPathEdit()
delete d_ptr;
}
QString FileSystemPathEdit::selectedPath() const
Path FileSystemPathEdit::selectedPath() const
{
return Utils::Fs::toUniformPath(editWidgetText());
return Path(editWidgetText());
}
void FileSystemPathEdit::setSelectedPath(const QString &val)
void FileSystemPathEdit::setSelectedPath(const Path &val)
{
Q_D(FileSystemPathEdit);
setEditWidgetText(Utils::Fs::toNativePath(val));
d->m_editor->widget()->setToolTip(val);
const QString nativePath = val.toString();
setEditWidgetText(nativePath);
d->m_editor->widget()->setToolTip(nativePath);
}
QString FileSystemPathEdit::fileNameFilter() const
@ -251,13 +254,13 @@ void FileSystemPathEdit::setFileNameFilter(const QString &val)
#endif
}
QString FileSystemPathEdit::placeholder() const
Path FileSystemPathEdit::placeholder() const
{
Q_D(const FileSystemPathEdit);
return d->m_editor->placeholder();
}
void FileSystemPathEdit::setPlaceholder(const QString &val)
void FileSystemPathEdit::setPlaceholder(const Path &val)
{
Q_D(FileSystemPathEdit);
d->m_editor->setPlaceholder(val);
@ -278,7 +281,8 @@ void FileSystemPathEdit::setBriefBrowseButtonCaption(bool brief)
void FileSystemPathEdit::onPathEdited()
{
Q_D(FileSystemPathEdit);
QString newPath = selectedPath();
const Path newPath = selectedPath();
if (newPath != d->m_lastSignaledPath)
{
emit selectedPathChanged(newPath);
@ -360,19 +364,19 @@ int FileSystemPathComboEdit::count() const
return editWidget<WidgetType>()->count();
}
QString FileSystemPathComboEdit::item(int index) const
Path FileSystemPathComboEdit::item(int index) const
{
return Utils::Fs::toUniformPath(editWidget<WidgetType>()->itemText(index));
return Path(editWidget<WidgetType>()->itemText(index));
}
void FileSystemPathComboEdit::addItem(const QString &text)
void FileSystemPathComboEdit::addItem(const Path &path)
{
editWidget<WidgetType>()->addItem(Utils::Fs::toNativePath(text));
editWidget<WidgetType>()->addItem(path.toString());
}
void FileSystemPathComboEdit::insertItem(int index, const QString &text)
void FileSystemPathComboEdit::insertItem(int index, const Path &path)
{
editWidget<WidgetType>()->insertItem(index, Utils::Fs::toNativePath(text));
editWidget<WidgetType>()->insertItem(index, path.toString());
}
int FileSystemPathComboEdit::currentIndex() const

View File

@ -30,6 +30,8 @@
#include <QWidget>
#include "base/path.h"
namespace Private
{
class FileComboEdit;
@ -45,7 +47,7 @@ class FileSystemPathEdit : public QWidget
{
Q_OBJECT
Q_PROPERTY(Mode mode READ mode WRITE setMode)
Q_PROPERTY(QString selectedPath READ selectedPath WRITE setSelectedPath NOTIFY selectedPathChanged)
Q_PROPERTY(Path selectedPath READ selectedPath WRITE setSelectedPath NOTIFY selectedPathChanged)
Q_PROPERTY(QString fileNameFilter READ fileNameFilter WRITE setFileNameFilter)
Q_PROPERTY(QString dialogCaption READ dialogCaption WRITE setDialogCaption)
@ -64,14 +66,14 @@ public:
Mode mode() const;
void setMode(Mode mode);
QString selectedPath() const;
void setSelectedPath(const QString &val);
Path selectedPath() const;
void setSelectedPath(const Path &val);
QString fileNameFilter() const;
void setFileNameFilter(const QString &val);
QString placeholder() const;
void setPlaceholder(const QString &val);
Path placeholder() const;
void setPlaceholder(const Path &val);
/// The browse button caption is "..." if true, and "Browse" otherwise
bool briefBrowseButtonCaption() const;
@ -83,7 +85,7 @@ public:
virtual void clear() = 0;
signals:
void selectedPathChanged(const QString &path);
void selectedPathChanged(const Path &path);
protected:
explicit FileSystemPathEdit(Private::FileEditorWithCompletion *editor, QWidget *parent);
@ -136,9 +138,9 @@ public:
void clear() override;
int count() const;
QString item(int index) const;
void addItem(const QString &text);
void insertItem(int index, const QString &text);
Path item(int index) const;
void addItem(const Path &path);
void insertItem(int index, const Path &path);
int currentIndex() const;
void setCurrentIndex(int index);

View File

@ -37,6 +37,8 @@
#include <QStringList>
#include <QStyle>
#include "base/path.h"
// -------------------- FileSystemPathValidator ----------------------------------------
Private::FileSystemPathValidator::FileSystemPathValidator(QObject *parent)
: QValidator(parent)
@ -149,7 +151,7 @@ QValidator::State Private::FileSystemPathValidator::validate(const QList<QString
const QStringView componentPath = pathComponents[i];
if (componentPath.isEmpty()) continue;
m_lastTestResult = testPath(pathComponents[i], isFinalPath);
m_lastTestResult = testPath(Path(pathComponents[i].toString()), isFinalPath);
if (m_lastTestResult != TestResult::OK)
{
m_lastTestedPath = componentPath.toString();
@ -161,9 +163,9 @@ QValidator::State Private::FileSystemPathValidator::validate(const QList<QString
}
Private::FileSystemPathValidator::TestResult
Private::FileSystemPathValidator::testPath(const QStringView path, bool pathIsComplete) const
Private::FileSystemPathValidator::testPath(const Path &path, bool pathIsComplete) const
{
QFileInfo fi(path.toString());
QFileInfo fi {path.data()};
if (m_existingOnly && !fi.exists())
return TestResult::DoesNotExist;
@ -240,14 +242,14 @@ void Private::FileLineEdit::setValidator(QValidator *validator)
QLineEdit::setValidator(validator);
}
QString Private::FileLineEdit::placeholder() const
Path Private::FileLineEdit::placeholder() const
{
return placeholderText();
return Path(placeholderText());
}
void Private::FileLineEdit::setPlaceholder(const QString &val)
void Private::FileLineEdit::setPlaceholder(const Path &val)
{
setPlaceholderText(val);
setPlaceholderText(val.toString());
}
QWidget *Private::FileLineEdit::widget()
@ -356,14 +358,14 @@ void Private::FileComboEdit::setValidator(QValidator *validator)
lineEdit()->setValidator(validator);
}
QString Private::FileComboEdit::placeholder() const
Path Private::FileComboEdit::placeholder() const
{
return lineEdit()->placeholderText();
return Path(lineEdit()->placeholderText());
}
void Private::FileComboEdit::setPlaceholder(const QString &val)
void Private::FileComboEdit::setPlaceholder(const Path &val)
{
lineEdit()->setPlaceholderText(val);
lineEdit()->setPlaceholderText(val.toString());
}
void Private::FileComboEdit::setFilenameFilters(const QStringList &filters)

View File

@ -34,6 +34,8 @@
#include <QtContainerFwd>
#include <QValidator>
#include "base/pathfwd.h"
class QAction;
class QCompleter;
class QContextMenuEvent;
@ -84,7 +86,7 @@ namespace Private
QValidator::State validate(const QList<QStringView> &pathComponents, bool strict,
int firstComponentToTest, int lastComponentToTest) const;
TestResult testPath(QStringView path, bool pathIsComplete) const;
TestResult testPath(const Path &path, bool pathIsComplete) const;
bool m_strictMode;
bool m_existingOnly;
@ -105,8 +107,8 @@ namespace Private
virtual void setFilenameFilters(const QStringList &filters) = 0;
virtual void setBrowseAction(QAction *action) = 0;
virtual void setValidator(QValidator *validator) = 0;
virtual QString placeholder() const = 0;
virtual void setPlaceholder(const QString &val) = 0;
virtual Path placeholder() const = 0;
virtual void setPlaceholder(const Path &val) = 0;
virtual QWidget *widget() = 0;
};
@ -123,8 +125,8 @@ namespace Private
void setFilenameFilters(const QStringList &filters) override;
void setBrowseAction(QAction *action) override;
void setValidator(QValidator *validator) override;
QString placeholder() const override;
void setPlaceholder(const QString &val) override;
Path placeholder() const override;
void setPlaceholder(const Path &val) override;
QWidget *widget() override;
protected:
@ -153,8 +155,8 @@ namespace Private
void setFilenameFilters(const QStringList &filters) override;
void setBrowseAction(QAction *action) override;
void setValidator(QValidator *validator) override;
QString placeholder() const override;
void setPlaceholder(const QString &val) override;
Path placeholder() const override;
void setPlaceholder(const Path &val) override;
QWidget *widget() override;
protected:

View File

@ -30,7 +30,7 @@
#include <objc/objc.h>
#include <QSet>
#include "base/pathfwd.h"
class QPixmap;
class QSize;
@ -41,7 +41,7 @@ namespace MacUtils
QPixmap pixmapForExtension(const QString &ext, const QSize &size);
void overrideDockClickHandler(bool (*dockClickHandler)(id, SEL, ...));
void displayNotification(const QString &title, const QString &message);
void openFiles(const QSet<QString> &pathsList);
void openFiles(const PathList &pathList);
QString badgeLabelText();
void setBadgeLabelText(const QString &text);

View File

@ -32,9 +32,11 @@
#include <objc/message.h>
#include <QPixmap>
#include <QSet>
#include <QSize>
#include <QString>
#include <QVector>
#include "base/path.h"
QImage qt_mac_toQImage(CGImageRef image);
@ -95,14 +97,14 @@ namespace MacUtils
}
}
void openFiles(const QSet<QString> &pathsList)
void openFiles(const PathList &pathList)
{
@autoreleasepool
{
NSMutableArray *pathURLs = [NSMutableArray arrayWithCapacity:pathsList.size()];
NSMutableArray *pathURLs = [NSMutableArray arrayWithCapacity:pathList.size()];
for (const auto &path : pathsList)
[pathURLs addObject:[NSURL fileURLWithPath:path.toNSString()]];
for (const auto &path : pathList)
[pathURLs addObject:[NSURL fileURLWithPath:path.toString().toNSString()]];
[[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:pathURLs];
}

View File

@ -58,6 +58,7 @@
#include "base/bittorrent/sessionstatus.h"
#include "base/global.h"
#include "base/net/downloadmanager.h"
#include "base/path.h"
#include "base/preferences.h"
#include "base/rss/rss_folder.h"
#include "base/rss/rss_session.h"
@ -1250,10 +1251,10 @@ void MainWindow::closeEvent(QCloseEvent *e)
// Display window to create a torrent
void MainWindow::on_actionCreateTorrent_triggered()
{
createTorrentTriggered();
createTorrentTriggered({});
}
void MainWindow::createTorrentTriggered(const QString &path)
void MainWindow::createTorrentTriggered(const Path &path)
{
if (m_createTorrentDlg)
{
@ -1261,7 +1262,9 @@ void MainWindow::createTorrentTriggered(const QString &path)
m_createTorrentDlg->activateWindow();
}
else
{
m_createTorrentDlg = new TorrentCreatorDialog(this, path);
}
}
bool MainWindow::event(QEvent *e)
@ -1367,7 +1370,7 @@ void MainWindow::dropEvent(QDropEvent *event)
// Create torrent
for (const QString &file : asConst(otherFiles))
{
createTorrentTriggered(file);
createTorrentTriggered(Path(file));
// currently only handle the first entry
// this is a stub that can be expanded later to create many torrents at once
@ -1423,7 +1426,7 @@ void MainWindow::on_actionOpen_triggered()
// Open File Open Dialog
// Note: it is possible to select more than one file
const QStringList pathsList =
QFileDialog::getOpenFileNames(this, tr("Open Torrent Files"), pref->getMainLastDir(),
QFileDialog::getOpenFileNames(this, tr("Open Torrent Files"), pref->getMainLastDir().data(),
tr("Torrent Files") + " (*" + C_TORRENT_FILE_EXTENSION + ')');
if (pathsList.isEmpty())
@ -1440,9 +1443,9 @@ void MainWindow::on_actionOpen_triggered()
}
// Save last dir to remember it
QString topDir = Utils::Fs::toUniformPath(pathsList.at(0));
topDir = topDir.left(topDir.lastIndexOf('/'));
pref->setMainLastDir(topDir);
const Path topDir {pathsList.at(0)};
const Path parentDir = topDir.parentPath();
pref->setMainLastDir(parentDir.isEmpty() ? topDir : parentDir);
}
void MainWindow::activate()
@ -2110,9 +2113,9 @@ void MainWindow::pythonDownloadFinished(const Net::DownloadResult &result)
QProcess installer;
qDebug("Launching Python installer in passive mode...");
const QString exePath = result.filePath + QLatin1String(".exe");
QFile::rename(result.filePath, exePath);
installer.start(Utils::Fs::toNativePath(exePath), {"/passive"});
const Path exePath = result.filePath + ".exe";
Utils::Fs::renameFile(result.filePath, exePath);
installer.start(exePath.toString(), {"/passive"});
// Wait for setup to complete
installer.waitForFinished(10 * 60 * 1000);
@ -2122,7 +2125,7 @@ void MainWindow::pythonDownloadFinished(const Net::DownloadResult &result)
qDebug("Setup should be complete!");
// Delete temp file
Utils::Fs::forceRemove(exePath);
Utils::Fs::removeFile(exePath);
// Reload search engine
if (Utils::ForeignApps::pythonInfo().isSupportedVersion())

View File

@ -212,7 +212,7 @@ private:
bool event(QEvent *e) override;
void displayRSSTab(bool enable);
void displaySearchTab(bool enable);
void createTorrentTriggered(const QString &path = {});
void createTorrentTriggered(const Path &path);
void showStatusBar(bool show);
Ui::MainWindow *m_ui;

View File

@ -48,6 +48,7 @@
#include "base/net/dnsupdater.h"
#include "base/net/portforwarder.h"
#include "base/net/proxyconfigurationmanager.h"
#include "base/path.h"
#include "base/preferences.h"
#include "base/rss/rss_autodownloader.h"
#include "base/rss/rss_session.h"
@ -492,9 +493,9 @@ OptionsDialog::OptionsDialog(QWidget *parent)
connect(m_ui->checkWebUIUPnP, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkWebUiHttps, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->textWebUIHttpsCert, &FileSystemPathLineEdit::selectedPathChanged, this, &ThisType::enableApplyButton);
connect(m_ui->textWebUIHttpsCert, &FileSystemPathLineEdit::selectedPathChanged, this, [this](const QString &s) { webUIHttpsCertChanged(s, ShowError::Show); });
connect(m_ui->textWebUIHttpsCert, &FileSystemPathLineEdit::selectedPathChanged, this, [this](const Path &path) { webUIHttpsCertChanged(path, ShowError::Show); });
connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, &ThisType::enableApplyButton);
connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, [this](const QString &s) { webUIHttpsKeyChanged(s, ShowError::Show); });
connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, [this](const Path &path) { webUIHttpsKeyChanged(path, ShowError::Show); });
connect(m_ui->textWebUiUsername, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
connect(m_ui->textWebUiPassword, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
connect(m_ui->checkBypassLocalAuth, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
@ -732,7 +733,7 @@ void OptionsDialog::saveOptions()
auto session = BitTorrent::Session::instance();
// Downloads preferences
session->setSavePath(Utils::Fs::expandPathAbs(m_ui->textSavePath->selectedPath()));
session->setSavePath(Path(m_ui->textSavePath->selectedPath()));
session->setSubcategoriesEnabled(m_ui->checkUseSubcategories->isChecked());
session->setUseCategoryPathsInManualMode(m_ui->checkUseCategoryPaths->isChecked());
session->setAutoTMMDisabledByDefault(m_ui->comboSavingMode->currentIndex() == 0);
@ -740,7 +741,7 @@ void OptionsDialog::saveOptions()
session->setDisableAutoTMMWhenCategorySavePathChanged(m_ui->comboCategoryChanged->currentIndex() == 1);
session->setDisableAutoTMMWhenDefaultSavePathChanged(m_ui->comboCategoryDefaultPathChanged->currentIndex() == 1);
session->setDownloadPathEnabled(m_ui->checkUseDownloadPath->isChecked());
session->setDownloadPath(Utils::Fs::expandPathAbs(m_ui->textDownloadPath->selectedPath()));
session->setDownloadPath(m_ui->textDownloadPath->selectedPath());
session->setAppendExtensionEnabled(m_ui->checkAppendqB->isChecked());
session->setPreallocationEnabled(preAllocateAllFiles());
pref->disableRecursiveDownload(!m_ui->checkRecursiveDownload->isChecked());
@ -1008,14 +1009,12 @@ void OptionsDialog::loadOptions()
m_ui->comboCategoryDefaultPathChanged->setCurrentIndex(session->isDisableAutoTMMWhenDefaultSavePathChanged());
m_ui->checkUseDownloadPath->setChecked(session->isDownloadPathEnabled());
m_ui->textDownloadPath->setEnabled(m_ui->checkUseDownloadPath->isChecked());
m_ui->textDownloadPath->setEnabled(m_ui->checkUseDownloadPath->isChecked());
m_ui->textDownloadPath->setSelectedPath(Utils::Fs::toNativePath(session->downloadPath()));
m_ui->textDownloadPath->setSelectedPath(session->downloadPath());
m_ui->checkAppendqB->setChecked(session->isAppendExtensionEnabled());
m_ui->checkPreallocateAll->setChecked(session->isPreallocationEnabled());
m_ui->checkRecursiveDownload->setChecked(!pref->recursiveDownloadDisabled());
strValue = session->torrentExportDirectory();
if (strValue.isEmpty())
if (session->torrentExportDirectory().isEmpty())
{
// Disable
m_ui->checkExportDir->setChecked(false);
@ -1026,11 +1025,10 @@ void OptionsDialog::loadOptions()
// Enable
m_ui->checkExportDir->setChecked(true);
m_ui->textExportDir->setEnabled(true);
m_ui->textExportDir->setSelectedPath(strValue);
m_ui->textExportDir->setSelectedPath(session->torrentExportDirectory());
}
strValue = session->finishedTorrentExportDirectory();
if (strValue.isEmpty())
if (session->finishedTorrentExportDirectory().isEmpty())
{
// Disable
m_ui->checkExportDirFin->setChecked(false);
@ -1041,7 +1039,7 @@ void OptionsDialog::loadOptions()
// Enable
m_ui->checkExportDirFin->setChecked(true);
m_ui->textExportDirFin->setEnabled(true);
m_ui->textExportDirFin->setSelectedPath(strValue);
m_ui->textExportDirFin->setSelectedPath(session->finishedTorrentExportDirectory());
}
m_ui->groupMailNotification->setChecked(pref->isMailNotificationEnabled());
@ -1644,25 +1642,25 @@ void OptionsDialog::setLocale(const QString &localeStr)
m_ui->comboI18n->setCurrentIndex(index);
}
QString OptionsDialog::getTorrentExportDir() const
Path OptionsDialog::getTorrentExportDir() const
{
if (m_ui->checkExportDir->isChecked())
return Utils::Fs::expandPathAbs(m_ui->textExportDir->selectedPath());
return m_ui->textExportDir->selectedPath();
return {};
}
QString OptionsDialog::getFinishedTorrentExportDir() const
Path OptionsDialog::getFinishedTorrentExportDir() const
{
if (m_ui->checkExportDirFin->isChecked())
return Utils::Fs::expandPathAbs(m_ui->textExportDirFin->selectedPath());
return m_ui->textExportDirFin->selectedPath();
return {};
}
void OptionsDialog::on_addWatchedFolderButton_clicked()
{
Preferences *const pref = Preferences::instance();
const QString dir = QFileDialog::getExistingDirectory(this, tr("Select folder to monitor"),
Utils::Fs::toNativePath(Utils::Fs::folderName(pref->getScanDirsLastPath())));
const Path dir {QFileDialog::getExistingDirectory(
this, tr("Select folder to monitor"), pref->getScanDirsLastPath().parentPath().toString())};
if (dir.isEmpty())
return;
@ -1739,19 +1737,8 @@ void OptionsDialog::editWatchedFolderOptions(const QModelIndex &index)
dialog->open();
}
QString OptionsDialog::askForExportDir(const QString &currentExportPath)
{
QDir currentExportDir(Utils::Fs::expandPathAbs(currentExportPath));
QString dir;
if (!currentExportPath.isEmpty() && currentExportDir.exists())
dir = QFileDialog::getExistingDirectory(this, tr("Choose export directory"), currentExportDir.absolutePath());
else
dir = QFileDialog::getExistingDirectory(this, tr("Choose export directory"), QDir::homePath());
return dir;
}
// Return Filter object to apply to BT session
QString OptionsDialog::getFilter() const
Path OptionsDialog::getFilter() const
{
return m_ui->textFilterPath->selectedPath();
}
@ -1773,7 +1760,7 @@ QString OptionsDialog::webUiPassword() const
return m_ui->textWebUiPassword->text();
}
void OptionsDialog::webUIHttpsCertChanged(const QString &path, const ShowError showError)
void OptionsDialog::webUIHttpsCertChanged(const Path &path, const ShowError showError)
{
m_ui->textWebUIHttpsCert->setSelectedPath(path);
m_ui->lblSslCertStatus->setPixmap(Utils::Gui::scaledPixmapSvg(UIThemeManager::instance()->getIconPath(QLatin1String("security-low")), this, 24));
@ -1781,7 +1768,7 @@ void OptionsDialog::webUIHttpsCertChanged(const QString &path, const ShowError s
if (path.isEmpty())
return;
QFile file(path);
QFile file {path.data()};
if (!file.open(QIODevice::ReadOnly))
{
if (showError == ShowError::Show)
@ -1799,7 +1786,7 @@ void OptionsDialog::webUIHttpsCertChanged(const QString &path, const ShowError s
m_ui->lblSslCertStatus->setPixmap(Utils::Gui::scaledPixmapSvg(UIThemeManager::instance()->getIconPath(QLatin1String("security-high")), this, 24));
}
void OptionsDialog::webUIHttpsKeyChanged(const QString &path, const ShowError showError)
void OptionsDialog::webUIHttpsKeyChanged(const Path &path, const ShowError showError)
{
m_ui->textWebUIHttpsKey->setSelectedPath(path);
m_ui->lblSslKeyStatus->setPixmap(Utils::Gui::scaledPixmapSvg(UIThemeManager::instance()->getIconPath(QLatin1String("security-low")), this, 24));
@ -1807,7 +1794,7 @@ void OptionsDialog::webUIHttpsKeyChanged(const QString &path, const ShowError sh
if (path.isEmpty())
return;
QFile file(path);
QFile file {path.data()};
if (!file.open(QIODevice::ReadOnly))
{
if (showError == ShowError::Show)
@ -1843,7 +1830,7 @@ void OptionsDialog::on_IpFilterRefreshBtn_clicked()
// Updating program preferences
BitTorrent::Session *const session = BitTorrent::Session::instance();
session->setIPFilteringEnabled(true);
session->setIPFilterFile(""); // forcing Session reload filter file
session->setIPFilterFile({}); // forcing Session reload filter file
session->setIPFilterFile(getFilter());
connect(session, &BitTorrent::Session::IPFilterParsed, this, &OptionsDialog::handleIPFilterParsed);
setCursor(QCursor(Qt::WaitCursor));
@ -1887,7 +1874,7 @@ bool OptionsDialog::webUIAuthenticationOk()
bool OptionsDialog::isAlternativeWebUIPathValid()
{
if (m_ui->groupAltWebUI->isChecked() && m_ui->textWebUIRootFolder->selectedPath().trimmed().isEmpty())
if (m_ui->groupAltWebUI->isChecked() && m_ui->textWebUIRootFolder->selectedPath().isEmpty())
{
QMessageBox::warning(this, tr("Location Error"), tr("The alternative Web UI files location cannot be blank."));
return false;

View File

@ -30,6 +30,7 @@
#include <QDialog>
#include "base/pathfwd.h"
#include "base/settingvalue.h"
class QCloseEvent;
@ -112,8 +113,8 @@ private slots:
void on_removeWatchedFolderButton_clicked();
void on_registerDNSBtn_clicked();
void setLocale(const QString &localeStr);
void webUIHttpsCertChanged(const QString &path, ShowError showError);
void webUIHttpsKeyChanged(const QString &path, ShowError showError);
void webUIHttpsCertChanged(const Path &path, ShowError showError);
void webUIHttpsKeyChanged(const Path &path, ShowError showError);
private:
// Methods
@ -136,9 +137,8 @@ private:
bool preAllocateAllFiles() const;
bool useAdditionDialog() const;
bool addTorrentsInPause() const;
QString getTorrentExportDir() const;
QString getFinishedTorrentExportDir() const;
QString askForExportDir(const QString &currentExportPath);
Path getTorrentExportDir() const;
Path getFinishedTorrentExportDir() const;
// Connection options
int getPort() const;
bool isUPnPEnabled() const;
@ -162,7 +162,7 @@ private:
Net::ProxyType getProxyType() const;
// IP Filter
bool isIPFilteringEnabled() const;
QString getFilter() const;
Path getFilter() const;
// Queueing system
bool isQueueingSystemEnabled() const;
int getMaxActiveDownloads() const;

View File

@ -93,12 +93,12 @@ PreviewSelectDialog::PreviewSelectDialog(QWidget *parent, const BitTorrent::Torr
const QVector<qreal> fp = torrent->filesProgress();
for (int i = 0; i < torrent->filesCount(); ++i)
{
const QString fileName = Utils::Fs::fileName(torrent->filePath(i));
if (Utils::Misc::isPreviewable(fileName))
const Path filePath = torrent->filePath(i);
if (Utils::Misc::isPreviewable(filePath))
{
int row = m_previewListModel->rowCount();
m_previewListModel->insertRow(row);
m_previewListModel->setData(m_previewListModel->index(row, NAME), fileName);
m_previewListModel->setData(m_previewListModel->index(row, NAME), filePath.filename());
m_previewListModel->setData(m_previewListModel->index(row, SIZE), torrent->fileSize(i));
m_previewListModel->setData(m_previewListModel->index(row, PROGRESS), fp[i]);
m_previewListModel->setData(m_previewListModel->index(row, FILE_INDEX), i);
@ -133,14 +133,14 @@ void PreviewSelectDialog::previewButtonClicked()
// Only one file should be selected
const int fileIndex = selectedIndexes.at(0).data().toInt();
const QString path = QDir(m_torrent->actualStorageLocation()).absoluteFilePath(m_torrent->actualFilePath(fileIndex));
const Path path = m_torrent->actualStorageLocation() / m_torrent->actualFilePath(fileIndex);
// File
if (!QFile::exists(path))
if (!path.exists())
{
const bool isSingleFile = (m_previewListModel->rowCount() == 1);
QWidget *parent = isSingleFile ? this->parentWidget() : this;
QMessageBox::critical(parent, tr("Preview impossible")
, tr("Sorry, we can't preview this file: \"%1\".").arg(Utils::Fs::toNativePath(path)));
, tr("Sorry, we can't preview this file: \"%1\".").arg(path.toString()));
if (isSingleFile)
reject();
return;

View File

@ -30,6 +30,7 @@
#include <QDialog>
#include "base/path.h"
#include "base/settingvalue.h"
class QStandardItemModel;
@ -38,6 +39,7 @@ namespace BitTorrent
{
class Torrent;
}
namespace Ui
{
class PreviewSelectDialog;
@ -64,7 +66,7 @@ public:
~PreviewSelectDialog();
signals:
void readyToPreviewFile(QString) const;
void readyToPreviewFile(const Path &filePath) const;
private slots:
void previewButtonClicked();

View File

@ -470,9 +470,11 @@ void PeerListWidget::updatePeer(const BitTorrent::Torrent *torrent, const BitTor
setModelData(row, PeerListColumns::TOT_UP, totalUp, peer.totalUpload(), intDataTextAlignment);
setModelData(row, PeerListColumns::RELEVANCE, (Utils::String::fromDouble(peer.relevance() * 100, 1) + '%'), peer.relevance(), intDataTextAlignment);
const QStringList downloadingFiles {torrent->hasMetadata()
? torrent->info().filesForPiece(peer.downloadingPieceIndex())
: QStringList()};
const PathList filePaths = torrent->info().filesForPiece(peer.downloadingPieceIndex());
QStringList downloadingFiles;
downloadingFiles.reserve(filePaths.size());
for (const Path &filePath : filePaths)
downloadingFiles.append(filePath.toString());
const QString downloadingFilesDisplayValue = downloadingFiles.join(';');
setModelData(row, PeerListColumns::DOWNLOADING_PIECE, downloadingFilesDisplayValue, downloadingFilesDisplayValue, {}, downloadingFiles.join(QLatin1Char('\n')));

View File

@ -37,9 +37,10 @@
#include <QTextStream>
#include <QToolTip>
#include "base/indexrange.h"
#include "base/bittorrent/torrent.h"
#include "base/bittorrent/torrentinfo.h"
#include "base/indexrange.h"
#include "base/path.h"
#include "base/utils/misc.h"
namespace
@ -53,9 +54,8 @@ namespace
{
public:
PieceIndexToImagePos(const BitTorrent::TorrentInfo &torrentInfo, const QImage &image)
: m_bytesPerPixel
{((image.width() > 0) && (torrentInfo.totalSize() >= image.width()))
? torrentInfo.totalSize() / image.width() : -1}
: m_bytesPerPixel {((image.width() > 0) && (torrentInfo.totalSize() >= image.width()))
? torrentInfo.totalSize() / image.width() : -1}
, m_torrentInfo {torrentInfo}
{
if ((m_bytesPerPixel > 0) && (m_bytesPerPixel < 10))
@ -100,9 +100,9 @@ namespace
m_stream << "</table>";
}
void operator()(const QString &size, const QString &path)
void operator()(const QString &size, const Path &path)
{
m_stream << R"(<tr><td style="white-space:nowrap">)" << size << "</td><td>" << path << "</td></tr>";
m_stream << R"(<tr><td style="white-space:nowrap">)" << size << "</td><td>" << path.toString() << "</td></tr>";
}
private:
@ -282,7 +282,7 @@ void PiecesBar::showToolTip(const QHelpEvent *e)
for (int f : files)
{
const QString filePath {torrentInfo.filePath(f)};
const Path filePath = torrentInfo.filePath(f);
renderer(Utils::Misc::friendlyUnit(torrentInfo.fileSize(f)), filePath);
}
stream << "</body></html>";

View File

@ -31,7 +31,6 @@
#include <QClipboard>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QHeaderView>
#include <QListWidgetItem>
#include <QMenu>
@ -45,6 +44,7 @@
#include "base/bittorrent/infohash.h"
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrent.h"
#include "base/path.h"
#include "base/preferences.h"
#include "base/unicodestrings.h"
#include "base/utils/fs.h"
@ -333,7 +333,7 @@ QTreeView *PropertiesWidget::getFilesList() const
void PropertiesWidget::updateSavePath(BitTorrent::Torrent *const torrent)
{
if (torrent == m_torrent)
m_ui->labelSavePathVal->setText(Utils::Fs::toNativePath(m_torrent->savePath()));
m_ui->labelSavePathVal->setText(m_torrent->savePath().toString());
}
void PropertiesWidget::loadTrackers(BitTorrent::Torrent *const torrent)
@ -593,25 +593,22 @@ void PropertiesWidget::loadUrlSeeds()
}
}
QString PropertiesWidget::getFullPath(const QModelIndex &index) const
Path PropertiesWidget::getFullPath(const QModelIndex &index) const
{
const QDir saveDir {m_torrent->actualStorageLocation()};
if (m_propListModel->itemType(index) == TorrentContentModelItem::FileType)
{
const int fileIdx = m_propListModel->getFileIndex(index);
const QString filename {m_torrent->actualFilePath(fileIdx)};
const QString fullPath {Utils::Fs::expandPath(saveDir.absoluteFilePath(filename))};
const Path fullPath = m_torrent->actualStorageLocation() / m_torrent->actualFilePath(fileIdx);
return fullPath;
}
// folder type
const QModelIndex nameIndex {index.sibling(index.row(), TorrentContentModelItem::COL_NAME)};
QString folderPath {nameIndex.data().toString()};
Path folderPath {nameIndex.data().toString()};
for (QModelIndex modelIdx = m_propListModel->parent(nameIndex); modelIdx.isValid(); modelIdx = modelIdx.parent())
folderPath.prepend(modelIdx.data().toString() + '/');
folderPath = Path(modelIdx.data().toString()) / folderPath;
const QString fullPath {Utils::Fs::expandPath(saveDir.absoluteFilePath(folderPath))};
const Path fullPath = m_torrent->actualStorageLocation() / folderPath;
return fullPath;
}
@ -626,7 +623,7 @@ void PropertiesWidget::openItem(const QModelIndex &index) const
void PropertiesWidget::openParentFolder(const QModelIndex &index) const
{
const QString path = getFullPath(index);
const Path path = getFullPath(index);
m_torrent->flushCache(); // Flush data
#ifdef Q_OS_MACOS
MacUtils::openFiles({path});

View File

@ -31,6 +31,8 @@
#include <QList>
#include <QWidget>
#include "base/pathfwd.h"
class QPushButton;
class QTreeView;
@ -108,7 +110,7 @@ private:
QPushButton *getButtonFromIndex(int index);
void applyPriorities();
void openParentFolder(const QModelIndex &index) const;
QString getFullPath(const QModelIndex &index) const;
Path getFullPath(const QModelIndex &index) const;
Ui::PropertiesWidget *m_ui;
BitTorrent::Torrent *m_torrent;

View File

@ -40,6 +40,7 @@
#include "base/bittorrent/session.h"
#include "base/global.h"
#include "base/path.h"
#include "base/preferences.h"
#include "base/rss/rss_article.h"
#include "base/rss/rss_autodownloader.h"
@ -47,7 +48,6 @@
#include "base/rss/rss_folder.h"
#include "base/rss/rss_session.h"
#include "base/utils/compare.h"
#include "base/utils/fs.h"
#include "base/utils/io.h"
#include "base/utils/string.h"
#include "gui/autoexpandabledialog.h"
@ -261,7 +261,7 @@ void AutomatedRssDownloader::updateRuleDefinitionBox()
else
m_ui->lineEFilter->clear();
m_ui->checkBoxSaveDiffDir->setChecked(!m_currentRule.savePath().isEmpty());
m_ui->lineSavePath->setSelectedPath(Utils::Fs::toNativePath(m_currentRule.savePath()));
m_ui->lineSavePath->setSelectedPath(m_currentRule.savePath());
m_ui->checkRegex->blockSignals(true);
m_ui->checkRegex->setChecked(m_currentRule.useRegex());
m_ui->checkRegex->blockSignals(false);
@ -346,7 +346,7 @@ void AutomatedRssDownloader::updateEditedRule()
m_currentRule.setMustContain(m_ui->lineContains->text());
m_currentRule.setMustNotContain(m_ui->lineNotContains->text());
m_currentRule.setEpisodeFilter(m_ui->lineEFilter->text());
m_currentRule.setSavePath(m_ui->checkBoxSaveDiffDir->isChecked() ? m_ui->lineSavePath->selectedPath() : "");
m_currentRule.setSavePath(m_ui->checkBoxSaveDiffDir->isChecked() ? m_ui->lineSavePath->selectedPath() : Path());
m_currentRule.setCategory(m_ui->comboCategory->currentText());
std::optional<bool> addPaused;
if (m_ui->comboAddPaused->currentIndex() == 1)
@ -429,9 +429,10 @@ void AutomatedRssDownloader::on_exportBtn_clicked()
}
QString selectedFilter {m_formatFilterJSON};
QString path = QFileDialog::getSaveFileName(
Path path {QFileDialog::getSaveFileName(
this, tr("Export RSS rules"), QDir::homePath()
, QString::fromLatin1("%1;;%2").arg(m_formatFilterJSON, m_formatFilterLegacy), &selectedFilter);
, QString::fromLatin1("%1;;%2").arg(m_formatFilterJSON, m_formatFilterLegacy), &selectedFilter)};
if (path.isEmpty()) return;
const RSS::AutoDownloader::RulesFileFormat format
@ -443,12 +444,12 @@ void AutomatedRssDownloader::on_exportBtn_clicked()
if (format == RSS::AutoDownloader::RulesFileFormat::JSON)
{
if (!path.endsWith(EXT_JSON, Qt::CaseInsensitive))
if (!path.hasExtension(EXT_JSON))
path += EXT_JSON;
}
else
{
if (!path.endsWith(EXT_LEGACY, Qt::CaseInsensitive))
if (!path.hasExtension(EXT_LEGACY))
path += EXT_LEGACY;
}
@ -464,13 +465,13 @@ void AutomatedRssDownloader::on_exportBtn_clicked()
void AutomatedRssDownloader::on_importBtn_clicked()
{
QString selectedFilter {m_formatFilterJSON};
QString path = QFileDialog::getOpenFileName(
this, tr("Import RSS rules"), QDir::homePath()
, QString::fromLatin1("%1;;%2").arg(m_formatFilterJSON, m_formatFilterLegacy), &selectedFilter);
if (path.isEmpty() || !QFile::exists(path))
const Path path {QFileDialog::getOpenFileName(
this, tr("Import RSS rules"), QDir::homePath()
, QString::fromLatin1("%1;;%2").arg(m_formatFilterJSON, m_formatFilterLegacy), &selectedFilter)};
if (!path.exists())
return;
QFile file {path};
QFile file {path.data()};
if (!file.open(QIODevice::ReadOnly))
{
QMessageBox::critical(

Some files were not shown because too many files have changed in this diff Show More