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 <QAtomicInt>
#include <QDebug> #include <QDebug>
#include <QDir>
#include <QLibraryInfo> #include <QLibraryInfo>
#include <QProcess> #include <QProcess>
@ -80,6 +79,7 @@
#include "base/torrentfileswatcher.h" #include "base/torrentfileswatcher.h"
#include "base/utils/compare.h" #include "base/utils/compare.h"
#include "base/utils/fs.h" #include "base/utils/fs.h"
#include "base/path.h"
#include "base/utils/misc.h" #include "base/utils/misc.h"
#include "base/version.h" #include "base/version.h"
#include "applicationinstancemanager.h" #include "applicationinstancemanager.h"
@ -105,7 +105,7 @@ namespace
const QString LOG_FOLDER = QStringLiteral("logs"); const QString LOG_FOLDER = QStringLiteral("logs");
const QChar PARAMS_SEPARATOR = QLatin1Char('|'); 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 MIN_FILELOG_SIZE = 1024; // 1KiB
const int MAX_FILELOG_SIZE = 1000 * 1024 * 1024; // 1000MiB const int MAX_FILELOG_SIZE = 1000 * 1024 * 1024; // 1000MiB
@ -143,16 +143,16 @@ Application::Application(int &argc, char **argv)
QPixmapCache::setCacheLimit(PIXMAP_CACHE_SIZE); QPixmapCache::setCacheLimit(PIXMAP_CACHE_SIZE);
#endif #endif
const bool portableModeEnabled = m_commandLineArgs.profileDir.isEmpty() const auto portableProfilePath = Path(QCoreApplication::applicationDirPath()) / DEFAULT_PORTABLE_MODE_PROFILE_DIR;
&& QDir(QCoreApplication::applicationDirPath()).exists(DEFAULT_PORTABLE_MODE_PROFILE_DIR); const bool portableModeEnabled = m_commandLineArgs.profileDir.isEmpty() && portableProfilePath.exists();
const QString profileDir = portableModeEnabled const Path profileDir = portableModeEnabled
? QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(DEFAULT_PORTABLE_MODE_PROFILE_DIR) ? portableProfilePath
: m_commandLineArgs.profileDir; : m_commandLineArgs.profileDir;
Profile::initInstance(profileDir, m_commandLineArgs.configurationName, Profile::initInstance(profileDir, m_commandLineArgs.configurationName,
(m_commandLineArgs.relativeFastresumePaths || portableModeEnabled)); (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(); Logger::initInstance();
SettingsStorage::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)); Logger::instance()->addMessage(tr("qBittorrent %1 started", "qBittorrent v3.2.0alpha started").arg(QBT_VERSION));
if (portableModeEnabled) 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) 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 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 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; 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) if (m_fileLogger)
m_fileLogger->changePath(path); m_fileLogger->changePath(path);
@ -327,16 +327,16 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
break; break;
case u'D': case u'D':
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
program.replace(i, 2, chopPathSep(Utils::Fs::toNativePath(torrent->savePath()))); program.replace(i, 2, chopPathSep(torrent->savePath().toString()));
#else #else
program.replace(i, 2, Utils::Fs::toNativePath(torrent->savePath())); program.replace(i, 2, torrent->savePath().toString());
#endif #endif
break; break;
case u'F': case u'F':
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
program.replace(i, 2, chopPathSep(Utils::Fs::toNativePath(torrent->contentPath()))); program.replace(i, 2, chopPathSep(torrent->contentPath().toString()));
#else #else
program.replace(i, 2, Utils::Fs::toNativePath(torrent->contentPath())); program.replace(i, 2, torrent->contentPath().toString());
#endif #endif
break; break;
case u'G': case u'G':
@ -359,9 +359,9 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
break; break;
case u'R': case u'R':
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
program.replace(i, 2, chopPathSep(Utils::Fs::toNativePath(torrent->rootPath()))); program.replace(i, 2, chopPathSep(torrent->rootPath().toString()));
#else #else
program.replace(i, 2, Utils::Fs::toNativePath(torrent->rootPath())); program.replace(i, 2, torrent->rootPath().toString());
#endif #endif
break; break;
case u'T': case u'T':
@ -439,7 +439,7 @@ void Application::sendNotificationEmail(const BitTorrent::Torrent *torrent)
// Prepare mail content // Prepare mail content
const QString content = tr("Torrent name: %1").arg(torrent->name()) + '\n' const QString content = tr("Torrent name: %1").arg(torrent->name()) + '\n'
+ tr("Torrent size: %1").arg(Utils::Misc::friendlyUnit(torrent->wantedSize())) + '\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") + 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" .arg(Utils::Misc::userFriendlyDuration(torrent->activeTime())) + "\n\n\n"
+ tr("Thank you for using qBittorrent.") + '\n'; + tr("Thank you for using qBittorrent.") + '\n';
@ -545,7 +545,7 @@ void Application::processParams(const QStringList &params)
if (param.startsWith(QLatin1String("@savePath="))) if (param.startsWith(QLatin1String("@savePath=")))
{ {
torrentParams.savePath = param.mid(10); torrentParams.savePath = Path(param.mid(10));
continue; continue;
} }
@ -821,7 +821,7 @@ void Application::cleanup()
Logger::freeInstance(); Logger::freeInstance();
IconProvider::freeInstance(); IconProvider::freeInstance();
SearchPluginManager::freeInstance(); SearchPluginManager::freeInstance();
Utils::Fs::removeDirRecursive(Utils::Fs::tempPath()); Utils::Fs::removeDirRecursively(Utils::Fs::tempPath());
#ifndef DISABLE_GUI #ifndef DISABLE_GUI
if (m_window) if (m_window)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -58,14 +58,14 @@ namespace BitTorrent
Q_DISABLE_COPY_MOVE(Worker) Q_DISABLE_COPY_MOVE(Worker)
public: public:
explicit Worker(const QDir &resumeDataDir); explicit Worker(const Path &resumeDataDir);
void store(const TorrentID &id, const LoadTorrentParams &resumeData) const; void store(const TorrentID &id, const LoadTorrentParams &resumeData) const;
void remove(const TorrentID &id) const; void remove(const TorrentID &id) const;
void storeQueue(const QVector<TorrentID> &queue) const; void storeQueue(const QVector<TorrentID> &queue) const;
private: 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} : ResumeDataStorage {parent}
, m_resumeDataDir {path} , m_resumeDataPath {path}
, m_ioThread {new QThread {this}} , 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\"") throw RuntimeError(tr("Cannot create torrent resume folder: \"%1\"")
.arg(Utils::Fs::toNativePath(m_resumeDataDir.absolutePath()))}; .arg(m_resumeDataPath.toString()));
} }
const QRegularExpression filenamePattern {QLatin1String("^([A-Fa-f0-9]{40})\\.fastresume$")}; 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()); m_registeredTorrents.reserve(filenames.size());
for (const QString &filename : filenames) for (const QString &filename : filenames)
@ -112,7 +114,7 @@ BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const QString &pa
m_registeredTorrents.append(TorrentID::fromString(rxMatch.captured(1))); 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(); 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 std::optional<BitTorrent::LoadTorrentParams> BitTorrent::BencodeResumeDataStorage::load(const TorrentID &id) const
{ {
const QString idString = id.toString(); const QString idString = id.toString();
const QString fastresumePath = m_resumeDataDir.absoluteFilePath(QString::fromLatin1("%1.fastresume").arg(idString)); const Path fastresumePath = m_resumeDataPath / Path(idString + QLatin1String(".fastresume"));
const QString torrentFilePath = m_resumeDataDir.absoluteFilePath(QString::fromLatin1("%1.torrent").arg(idString)); const Path torrentFilePath = m_resumeDataPath / Path(idString + QLatin1String(".torrent"));
QFile resumeDataFile {fastresumePath}; QFile resumeDataFile {fastresumePath.data()};
if (!resumeDataFile.open(QIODevice::ReadOnly)) 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; return std::nullopt;
} }
QFile metadataFile {torrentFilePath}; QFile metadataFile {torrentFilePath.data()};
if (metadataFile.exists() && !metadataFile.open(QIODevice::ReadOnly)) 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; 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.seedingTimeLimit = root.dict_find_int_value("qBt-seedingTimeLimit", Torrent::USE_GLOBAL_SEEDING_TIME);
torrentParams.savePath = Profile::instance()->fromPortablePath( 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(); torrentParams.useAutoTMM = torrentParams.savePath.isEmpty();
if (!torrentParams.useAutoTMM) if (!torrentParams.useAutoTMM)
{ {
torrentParams.downloadPath = Profile::instance()->fromPortablePath( 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. // 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; lt::add_torrent_params &p = torrentParams.ltAddTorrentParams;
p = lt::read_resume_data(root, ec); 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) 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()) if (!queueFile.exists())
return; 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} : m_resumeDataDir {resumeDataDir}
{ {
} }
@ -315,7 +318,8 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co
{ {
// We need to adjust native libtorrent resume data // We need to adjust native libtorrent resume data
lt::add_torrent_params p = resumeData.ltAddTorrentParams; 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) if (resumeData.stopped)
{ {
p.flags |= lt::torrent_flags::paused; 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("created by"));
metadataDict.insert(dataDict.extract("comment")); 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); const nonstd::expected<void, QString> result = Utils::IO::saveToFile(torrentFilepath, metadata);
if (!result) if (!result)
{ {
LogMsg(tr("Couldn't save torrent metadata to '%1'. Error: %2.") 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; return;
} }
} }
@ -370,26 +374,26 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co
if (!resumeData.useAutoTMM) if (!resumeData.useAutoTMM)
{ {
data["qBt-savePath"] = Profile::instance()->toPortablePath(resumeData.savePath).toStdString(); data["qBt-savePath"] = Profile::instance()->toPortablePath(resumeData.savePath).data().toStdString();
data["qBt-downloadPath"] = Profile::instance()->toPortablePath(resumeData.downloadPath).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); const nonstd::expected<void, QString> result = Utils::IO::saveToFile(resumeFilepath, data);
if (!result) if (!result)
{ {
LogMsg(tr("Couldn't save torrent resume data to '%1'. Error: %2.") 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 void BitTorrent::BencodeResumeDataStorage::Worker::remove(const TorrentID &id) const
{ {
const QString resumeFilename = QString::fromLatin1("%1.fastresume").arg(id.toString()); const Path resumeFilename {QString::fromLatin1("%1.fastresume").arg(id.toString())};
Utils::Fs::forceRemove(m_resumeDataDir.absoluteFilePath(resumeFilename)); Utils::Fs::removeFile(m_resumeDataDir / resumeFilename);
const QString torrentFilename = QString::fromLatin1("%1.torrent").arg(id.toString()); const Path torrentFilename {QString::fromLatin1("%1.torrent").arg(id.toString())};
Utils::Fs::forceRemove(m_resumeDataDir.absoluteFilePath(torrentFilename)); Utils::Fs::removeFile(m_resumeDataDir / torrentFilename);
} }
void BitTorrent::BencodeResumeDataStorage::Worker::storeQueue(const QVector<TorrentID> &queue) const 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) for (const BitTorrent::TorrentID &torrentID : queue)
data += (torrentID.toString().toLatin1() + '\n'); 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); const nonstd::expected<void, QString> result = Utils::IO::saveToFile(filepath, data);
if (!result) if (!result)
{ {
LogMsg(tr("Couldn't save data to '%1'. Error: %2") 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 <QDir>
#include <QVector> #include <QVector>
#include "base/path.h"
#include "resumedatastorage.h" #include "resumedatastorage.h"
class QByteArray; class QByteArray;
@ -44,7 +45,7 @@ namespace BitTorrent
Q_DISABLE_COPY_MOVE(BencodeResumeDataStorage) Q_DISABLE_COPY_MOVE(BencodeResumeDataStorage)
public: public:
explicit BencodeResumeDataStorage(const QString &path, QObject *parent = nullptr); explicit BencodeResumeDataStorage(const Path &path, QObject *parent = nullptr);
~BencodeResumeDataStorage() override; ~BencodeResumeDataStorage() override;
QVector<TorrentID> registeredTorrents() const override; QVector<TorrentID> registeredTorrents() const override;
@ -54,10 +55,10 @@ namespace BitTorrent
void storeQueue(const QVector<TorrentID> &queue) const override; void storeQueue(const QVector<TorrentID> &queue) const override;
private: private:
void loadQueue(const QString &queueFilename); void loadQueue(const Path &queueFilename);
std::optional<LoadTorrentParams> loadTorrentResumeData(const QByteArray &data, const QByteArray &metadata) const; std::optional<LoadTorrentParams> loadTorrentResumeData(const QByteArray &data, const QByteArray &metadata) const;
const QDir m_resumeDataDir; const Path m_resumeDataPath;
QVector<TorrentID> m_registeredTorrents; QVector<TorrentID> m_registeredTorrents;
QThread *m_ioThread = nullptr; 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) BitTorrent::CategoryOptions BitTorrent::CategoryOptions::fromJSON(const QJsonObject &jsonObj)
{ {
CategoryOptions options; CategoryOptions options;
options.savePath = jsonObj.value(OPTION_SAVEPATH).toString(); options.savePath = Path(jsonObj.value(OPTION_SAVEPATH).toString());
const QJsonValue downloadPathValue = jsonObj.value(OPTION_DOWNLOADPATH); const QJsonValue downloadPathValue = jsonObj.value(OPTION_DOWNLOADPATH);
if (downloadPathValue.isBool()) if (downloadPathValue.isBool())
options.downloadPath = {downloadPathValue.toBool(), {}}; options.downloadPath = {downloadPathValue.toBool(), {}};
else if (downloadPathValue.isString()) else if (downloadPathValue.isString())
options.downloadPath = {true, downloadPathValue.toString()}; options.downloadPath = {true, Path(downloadPathValue.toString())};
return options; return options;
} }
@ -54,13 +54,13 @@ QJsonObject BitTorrent::CategoryOptions::toJSON() const
if (downloadPath) if (downloadPath)
{ {
if (downloadPath->enabled) if (downloadPath->enabled)
downloadPathValue = downloadPath->path; downloadPathValue = downloadPath->path.data();
else else
downloadPathValue = false; downloadPathValue = false;
} }
return { return {
{OPTION_SAVEPATH, savePath}, {OPTION_SAVEPATH, savePath.data()},
{OPTION_DOWNLOADPATH, downloadPathValue} {OPTION_DOWNLOADPATH, downloadPathValue}
}; };
} }

View File

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

View File

@ -30,8 +30,6 @@
#include <libtorrent/download_priority.hpp> #include <libtorrent/download_priority.hpp>
#include <QDir>
#include "base/utils/fs.h" #include "base/utils/fs.h"
#include "common.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); 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] = m_storageData[storageHolder] =
{ {
savePath savePath,
, storageParams.mapped_files ? *storageParams.mapped_files : storageParams.files storageParams.mapped_files ? *storageParams.mapped_files : storageParams.files,
, storageParams.priorities}; storageParams.priorities
};
return storageHolder; 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 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) , 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) if (flags == lt::move_flags_t::dont_replace)
handleCompleteFiles(storage, newSavePath); handleCompleteFiles(storage, newSavePath);
@ -192,9 +191,8 @@ void CustomDiskIOThread::settings_updated()
m_nativeDiskIO->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 StorageData storageData = m_storageData[storage];
const lt::file_storage &fileStorage = storageData.files; const lt::file_storage &fileStorage = storageData.files;
for (const lt::file_index_t fileIndex : fileStorage.file_range()) 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 // ignore pad files
if (fileStorage.pad_file_at(fileIndex)) continue; if (fileStorage.pad_file_at(fileIndex)) continue;
const QString filePath = QString::fromStdString(fileStorage.file_path(fileIndex)); const Path filePath {fileStorage.file_path(fileIndex)};
if (filePath.endsWith(QB_EXT)) if (filePath.hasExtension(QB_EXT))
{ {
const QString completeFilePath = filePath.left(filePath.size() - QB_EXT.size()); const Path incompleteFilePath = savePath / filePath;
QFile completeFile {saveDir.absoluteFilePath(completeFilePath)}; Path completeFilePath = incompleteFilePath;
if (completeFile.exists()) completeFilePath.removeExtension();
if (completeFilePath.exists())
{ {
QFile incompleteFile {saveDir.absoluteFilePath(filePath)}; Utils::Fs::removeFile(incompleteFilePath);
incompleteFile.remove(); Utils::Fs::renameFile(completeFilePath, incompleteFilePath);
completeFile.rename(incompleteFile.fileName());
} }
} }
} }
@ -230,7 +228,7 @@ lt::storage_interface *customStorageConstructor(const lt::storage_params &params
CustomStorage::CustomStorage(const lt::storage_params &params, lt::file_pool &filePool) CustomStorage::CustomStorage(const lt::storage_params &params, lt::file_pool &filePool)
: lt::default_storage {params, 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) 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) if (flags == lt::move_flags_t::dont_replace)
handleCompleteFiles(newSavePath); handleCompleteFiles(newSavePath);
@ -260,10 +258,8 @@ lt::status_t CustomStorage::move_storage(const std::string &savePath, lt::move_f
return ret; return ret;
} }
void CustomStorage::handleCompleteFiles(const QString &savePath) void CustomStorage::handleCompleteFiles(const Path &savePath)
{ {
const QDir saveDir {savePath};
const lt::file_storage &fileStorage = files(); const lt::file_storage &fileStorage = files();
for (const lt::file_index_t fileIndex : fileStorage.file_range()) for (const lt::file_index_t fileIndex : fileStorage.file_range())
{ {
@ -274,16 +270,16 @@ void CustomStorage::handleCompleteFiles(const QString &savePath)
// ignore pad files // ignore pad files
if (fileStorage.pad_file_at(fileIndex)) continue; if (fileStorage.pad_file_at(fileIndex)) continue;
const QString filePath = QString::fromStdString(fileStorage.file_path(fileIndex)); const Path filePath {fileStorage.file_path(fileIndex)};
if (filePath.endsWith(QB_EXT)) if (filePath.hasExtension(QB_EXT))
{ {
const QString completeFilePath = filePath.left(filePath.size() - QB_EXT.size()); const Path incompleteFilePath = savePath / filePath;
QFile completeFile {saveDir.absoluteFilePath(completeFilePath)}; Path completeFilePath = incompleteFilePath;
if (completeFile.exists()) completeFilePath.removeExtension();
if (completeFilePath.exists())
{ {
QFile incompleteFile {saveDir.absoluteFilePath(filePath)}; Utils::Fs::removeFile(incompleteFilePath);
incompleteFile.remove(); Utils::Fs::renameFile(completeFilePath, incompleteFilePath);
completeFile.rename(incompleteFile.fileName());
} }
} }
} }

View File

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

View File

@ -49,6 +49,7 @@
#include "base/exceptions.h" #include "base/exceptions.h"
#include "base/global.h" #include "base/global.h"
#include "base/logger.h" #include "base/logger.h"
#include "base/path.h"
#include "base/profile.h" #include "base/profile.h"
#include "base/utils/fs.h" #include "base/utils/fs.h"
#include "base/utils/string.h" #include "base/utils/string.h"
@ -173,7 +174,7 @@ namespace BitTorrent
Q_DISABLE_COPY_MOVE(Worker) Q_DISABLE_COPY_MOVE(Worker)
public: public:
Worker(const QString &dbPath, const QString &dbConnectionName); Worker(const Path &dbPath, const QString &dbConnectionName);
void openDatabase() const; void openDatabase() const;
void closeDatabase() const; void closeDatabase() const;
@ -183,19 +184,19 @@ namespace BitTorrent
void storeQueue(const QVector<TorrentID> &queue) const; void storeQueue(const QVector<TorrentID> &queue) const;
private: private:
const QString m_path; const Path m_path;
const QString m_connectionName; const QString m_connectionName;
}; };
} }
BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const QString &dbPath, QObject *parent) BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject *parent)
: ResumeDataStorage {parent} : ResumeDataStorage {parent}
, m_ioThread {new QThread(this)} , 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); auto db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), DB_CONNECTION_NAME);
db.setDatabaseName(dbPath); db.setDatabaseName(dbPath.data());
if (!db.open()) if (!db.open())
throw RuntimeError(db.lastError().text()); 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.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
resumeData.savePath = Profile::instance()->fromPortablePath( 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(); resumeData.useAutoTMM = resumeData.savePath.isEmpty();
if (!resumeData.useAutoTMM) if (!resumeData.useAutoTMM)
{ {
resumeData.downloadPath = Profile::instance()->fromPortablePath( 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(); 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; lt::error_code ec;
const lt::bdecode_node root = lt::bdecode(allData, 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; lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
p = lt::read_resume_data(root, ec); 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; 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_path {dbPath}
, m_connectionName {dbConnectionName} , m_connectionName {dbConnectionName}
{ {
@ -494,7 +493,7 @@ BitTorrent::DBResumeDataStorage::Worker::Worker(const QString &dbPath, const QSt
void BitTorrent::DBResumeDataStorage::Worker::openDatabase() const void BitTorrent::DBResumeDataStorage::Worker::openDatabase() const
{ {
auto db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), m_connectionName); auto db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), m_connectionName);
db.setDatabaseName(m_path); db.setDatabaseName(m_path.data());
if (!db.open()) if (!db.open())
throw RuntimeError(db.lastError().text()); 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 // We need to adjust native libtorrent resume data
lt::add_torrent_params p = resumeData.ltAddTorrentParams; 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) if (resumeData.stopped)
{ {
p.flags |= lt::torrent_flags::paused; p.flags |= lt::torrent_flags::paused;
@ -603,8 +603,8 @@ void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const L
if (!resumeData.useAutoTMM) if (!resumeData.useAutoTMM)
{ {
query.bindValue(DB_COLUMN_TARGET_SAVE_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.savePath)); 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)); query.bindValue(DB_COLUMN_DOWNLOAD_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.downloadPath).data());
} }
query.bindValue(DB_COLUMN_RESUMEDATA.placeholder, bencodedResumeData); query.bindValue(DB_COLUMN_RESUMEDATA.placeholder, bencodedResumeData);

View File

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

View File

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

View File

@ -30,6 +30,8 @@
#include <QObject> #include <QObject>
#include "base/path.h"
namespace BitTorrent namespace BitTorrent
{ {
class TorrentID; class TorrentID;
@ -44,9 +46,9 @@ public:
FileSearcher() = default; FileSearcher() = default;
public slots: public slots:
void search(const BitTorrent::TorrentID &id, const QStringList &originalFileNames void search(const BitTorrent::TorrentID &id, const PathList &originalFileNames
, const QString &savePath, const QString &downloadPath); , const Path &savePath, const Path &downloadPath);
signals: 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 FilterParserThread::parseDATFilterFile()
{ {
int ruleCount = 0; int ruleCount = 0;
QFile file(m_filePath); QFile file {m_filePath.data()};
if (!file.exists()) return ruleCount; if (!file.exists()) return ruleCount;
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
@ -288,7 +288,7 @@ int FilterParserThread::parseDATFilterFile()
int FilterParserThread::parseP2PFilterFile() int FilterParserThread::parseP2PFilterFile()
{ {
int ruleCount = 0; int ruleCount = 0;
QFile file(m_filePath); QFile file {m_filePath.data()};
if (!file.exists()) return ruleCount; if (!file.exists()) return ruleCount;
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
@ -469,7 +469,7 @@ int FilterParserThread::getlineInStream(QDataStream &stream, std::string &name,
int FilterParserThread::parseP2BFilterFile() int FilterParserThread::parseP2BFilterFile()
{ {
int ruleCount = 0; int ruleCount = 0;
QFile file(m_filePath); QFile file {m_filePath.data()};
if (!file.exists()) return ruleCount; if (!file.exists()) return ruleCount;
if (!file.open(QIODevice::ReadOnly)) if (!file.open(QIODevice::ReadOnly))
@ -592,7 +592,7 @@ int FilterParserThread::parseP2BFilterFile()
// * eMule IP list (DAT): http://wiki.phoenixlabs.org/wiki/DAT_Format // * eMule IP list (DAT): http://wiki.phoenixlabs.org/wiki/DAT_Format
// * PeerGuardian Text (P2P): http://wiki.phoenixlabs.org/wiki/P2P_Format // * PeerGuardian Text (P2P): http://wiki.phoenixlabs.org/wiki/P2P_Format
// * PeerGuardian Binary (P2B): http://wiki.phoenixlabs.org/wiki/P2B_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()) if (isRunning())
{ {
@ -617,17 +617,17 @@ void FilterParserThread::run()
{ {
qDebug("Processing filter file"); qDebug("Processing filter file");
int ruleCount = 0; int ruleCount = 0;
if (m_filePath.endsWith(".p2p", Qt::CaseInsensitive)) if (m_filePath.hasExtension(QLatin1String(".p2p")))
{ {
// PeerGuardian p2p file // PeerGuardian p2p file
ruleCount = parseP2PFilterFile(); ruleCount = parseP2PFilterFile();
} }
else if (m_filePath.endsWith(".p2b", Qt::CaseInsensitive)) else if (m_filePath.hasExtension(QLatin1String(".p2b")))
{ {
// PeerGuardian p2b file // PeerGuardian p2b file
ruleCount = parseP2BFilterFile(); ruleCount = parseP2BFilterFile();
} }
else if (m_filePath.endsWith(".dat", Qt::CaseInsensitive)) else if (m_filePath.hasExtension(QLatin1String(".dat")))
{ {
// eMule DAT format // eMule DAT format
ruleCount = parseDATFilterFile(); ruleCount = parseDATFilterFile();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -32,36 +32,35 @@
namespace namespace
{ {
QString removeExtension(const QString &fileName) Path removeExtension(const Path &fileName)
{ {
const QString extension = Utils::Fs::fileExtension(fileName); Path result = fileName;
return extension.isEmpty() result.removeExtension();
? fileName return result;
: fileName.chopped(extension.size() + 1);
} }
} }
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() return (rootFolder.isEmpty()
? TorrentContentLayout::NoSubfolder ? TorrentContentLayout::NoSubfolder
: TorrentContentLayout::Subfolder); : 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()); Q_ASSERT(!filePaths.isEmpty());
switch (contentLayout) switch (contentLayout)
{ {
case TorrentContentLayout::Subfolder: case TorrentContentLayout::Subfolder:
if (Utils::Fs::findRootFolder(filePaths).isEmpty()) if (Path::findRootFolder(filePaths).isEmpty())
Utils::Fs::addRootFolder(filePaths, !rootFolder.isEmpty() ? rootFolder : removeExtension(filePaths.at(0))); Path::addRootFolder(filePaths, !rootFolder.isEmpty() ? rootFolder : removeExtension(filePaths.at(0)));
break; break;
case TorrentContentLayout::NoSubfolder: case TorrentContentLayout::NoSubfolder:
Utils::Fs::stripRootFolder(filePaths); Path::stripRootFolder(filePaths);
break; break;
default: default:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,13 +26,15 @@
* exception statement from your version. * exception statement from your version.
*/ */
#include "geoipdatabase.h"
#include <QDateTime> #include <QDateTime>
#include <QDebug> #include <QDebug>
#include <QFile> #include <QFile>
#include <QHostAddress> #include <QHostAddress>
#include <QVariant> #include <QVariant>
#include "geoipdatabase.h" #include "base/path.h"
namespace 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; GeoIPDatabase *db = nullptr;
QFile file(filename); QFile file {filename.data()};
if (file.size() > MAX_FILE_SIZE) if (file.size() > MAX_FILE_SIZE)
{ {
error = tr("Unsupported database file size."); error = tr("Unsupported database file size.");

View File

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

View File

@ -30,7 +30,6 @@
#include "geoipmanager.h" #include "geoipmanager.h"
#include <QDateTime> #include <QDateTime>
#include <QDir>
#include <QHostAddress> #include <QHostAddress>
#include <QLocale> #include <QLocale>
@ -43,9 +42,9 @@
#include "downloadmanager.h" #include "downloadmanager.h"
#include "geoipdatabase.h" #include "geoipdatabase.h"
static const QString DATABASE_URL = QStringLiteral("https://download.db-ip.com/free/dbip-country-lite-%1.mmdb.gz"); const QString DATABASE_URL = QStringLiteral("https://download.db-ip.com/free/dbip-country-lite-%1.mmdb.gz");
static const char GEODB_FOLDER[] = "GeoDB"; const char GEODB_FOLDER[] = "GeoDB";
static const char GEODB_FILENAME[] = "dbip-country-lite.mmdb"; const char GEODB_FILENAME[] = "dbip-country-lite.mmdb";
using namespace Net; using namespace Net;
@ -88,17 +87,21 @@ void GeoIPManager::loadDatabase()
delete m_geoIPDatabase; delete m_geoIPDatabase;
m_geoIPDatabase = nullptr; m_geoIPDatabase = nullptr;
const QString filepath = Utils::Fs::expandPathAbs( const Path filepath = specialFolderLocation(SpecialFolder::Data)
QString::fromLatin1("%1/%2/%3").arg(specialFolderLocation(SpecialFolder::Data), GEODB_FOLDER, GEODB_FILENAME)); / Path(GEODB_FOLDER) / Path(GEODB_FILENAME);
QString error; QString error;
m_geoIPDatabase = GeoIPDatabase::load(filepath, error); m_geoIPDatabase = GeoIPDatabase::load(filepath, error);
if (m_geoIPDatabase) if (m_geoIPDatabase)
{
Logger::instance()->addMessage(tr("IP geolocation database loaded. Type: %1. Build time: %2.") Logger::instance()->addMessage(tr("IP geolocation database loaded. Type: %1. Build time: %2.")
.arg(m_geoIPDatabase->type(), m_geoIPDatabase->buildEpoch().toString()), .arg(m_geoIPDatabase->type(), m_geoIPDatabase->buildEpoch().toString()),
Log::INFO); Log::INFO);
}
else else
{
Logger::instance()->addMessage(tr("Couldn't load IP geolocation database. Reason: %1").arg(error), Log::WARNING); Logger::instance()->addMessage(tr("Couldn't load IP geolocation database. Reason: %1").arg(error), Log::WARNING);
}
manageDatabaseUpdate(); manageDatabaseUpdate();
} }
@ -445,14 +448,13 @@ void GeoIPManager::downloadFinished(const DownloadResult &result)
delete m_geoIPDatabase; delete m_geoIPDatabase;
m_geoIPDatabase = geoIPDatabase; m_geoIPDatabase = geoIPDatabase;
LogMsg(tr("IP geolocation database loaded. Type: %1. Build time: %2.") LogMsg(tr("IP geolocation database loaded. Type: %1. Build time: %2.")
.arg(m_geoIPDatabase->type(), m_geoIPDatabase->buildEpoch().toString()), .arg(m_geoIPDatabase->type(), m_geoIPDatabase->buildEpoch().toString())
Log::INFO); , Log::INFO);
const QString targetPath = Utils::Fs::expandPathAbs( const Path targetPath = specialFolderLocation(SpecialFolder::Data) / Path(GEODB_FOLDER);
QDir(specialFolderLocation(SpecialFolder::Data)).absoluteFilePath(GEODB_FOLDER)); if (!targetPath.exists())
if (!QDir(targetPath).exists()) Utils::Fs::mkpath(targetPath);
QDir().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); const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, data);
if (result) 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 "algorithm.h"
#include "global.h" #include "global.h"
#include "path.h"
#include "profile.h" #include "profile.h"
#include "settingsstorage.h" #include "settingsstorage.h"
#include "utils/fs.h" #include "utils/fs.h"
@ -85,11 +86,11 @@ namespace
} }
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
QString makeProfileID(const QString &profilePath, const QString &profileName) QString makeProfileID(const Path &profilePath, const QString &profileName)
{ {
return profilePath.isEmpty() return profilePath.isEmpty()
? profileName ? profileName
: profileName + QLatin1Char('@') + Utils::Fs::toValidFileSystemName(profilePath, false, {}); : profileName + QLatin1Char('@') + Utils::Fs::toValidFileName(profilePath.data(), {});
} }
#endif #endif
} }
@ -137,12 +138,12 @@ void Preferences::setUseCustomUITheme(const bool use)
setValue("Preferences/General/UseCustomUITheme", 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); setValue("Preferences/General/CustomUIThemePath", path);
} }
@ -336,7 +337,7 @@ void Preferences::setPreventFromSuspendWhenSeeding(const bool b)
bool Preferences::WinStartup() const bool Preferences::WinStartup() const
{ {
const QString profileName = Profile::instance()->profileName(); 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 QString profileID = makeProfileID(profilePath, profileName);
const QSettings settings {"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat}; 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) void Preferences::setWinStartup(const bool b)
{ {
const QString profileName = Profile::instance()->profileName(); 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 QString profileID = makeProfileID(profilePath, profileName);
QSettings settings {"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat}; QSettings settings {"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat};
if (b) if (b)
@ -354,7 +355,7 @@ void Preferences::setWinStartup(const bool b)
const QString configuration = Profile::instance()->configurationName(); const QString configuration = Profile::instance()->configurationName();
const auto cmd = QString::fromLatin1(R"("%1" "--profile=%2" "--configuration=%3")") 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); settings.setValue(profileID, cmd);
} }
else else
@ -365,24 +366,14 @@ void Preferences::setWinStartup(const bool b)
#endif // Q_OS_WIN #endif // Q_OS_WIN
// Downloads // 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)); setValue("Preferences/Downloads/ScanDirsLastPath", 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));
} }
bool Preferences::isMailNotificationEnabled() const bool Preferences::isMailNotificationEnabled() const
@ -737,22 +728,22 @@ void Preferences::setWebUiHttpsEnabled(const bool enabled)
setValue("Preferences/WebUI/HTTPS/Enabled", 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); 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); setValue("Preferences/WebUI/HTTPS/KeyPath", path);
} }
@ -767,12 +758,12 @@ void Preferences::setAltWebUiEnabled(const bool enabled)
setValue("Preferences/WebUI/AlternativeUIEnabled", 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); setValue("Preferences/WebUI/RootFolder", path);
} }
@ -1050,14 +1041,14 @@ bool Preferences::isMagnetLinkAssocSet()
const QSettings settings("HKEY_CURRENT_USER\\Software\\Classes", QSettings::NativeFormat); const QSettings settings("HKEY_CURRENT_USER\\Software\\Classes", QSettings::NativeFormat);
// Check magnet link assoc // 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); const QRegularExpressionMatch exeRegMatch = QRegularExpression("\"([^\"]+)\".*").match(shellCommand);
if (!exeRegMatch.hasMatch()) if (!exeRegMatch.hasMatch())
return false; return false;
const QString assocExe = exeRegMatch.captured(1); const Path assocExe {exeRegMatch.captured(1)};
if (assocExe.compare(Utils::Fs::toNativePath(qApp->applicationFilePath()), Qt::CaseInsensitive) != 0) if (assocExe != Path(qApp->applicationFilePath()))
return false; return false;
return true; return true;
@ -1090,15 +1081,16 @@ void Preferences::setMagnetLinkAssoc(const bool set)
// Magnet association // Magnet association
if (set) if (set)
{ {
const QString commandStr = '"' + qApp->applicationFilePath() + "\" \"%1\""; const QString applicationFilePath = Path(qApp->applicationFilePath()).toString();
const QString iconStr = '"' + qApp->applicationFilePath() + "\",1"; const QString commandStr = '"' + applicationFilePath + "\" \"%1\"";
const QString iconStr = '"' + applicationFilePath + "\",1";
settings.setValue("magnet/Default", "URL:Magnet link"); settings.setValue("magnet/Default", "URL:Magnet link");
settings.setValue("magnet/Content Type", "application/x-magnet"); settings.setValue("magnet/Content Type", "application/x-magnet");
settings.setValue("magnet/URL Protocol", ""); 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/Default", "open");
settings.setValue("magnet/shell/open/command/Default", Utils::Fs::toNativePath(commandStr)); settings.setValue("magnet/shell/open/command/Default", commandStr);
} }
else if (isMagnetLinkAssocSet()) else if (isMagnetLinkAssocSet())
{ {
@ -1294,12 +1286,12 @@ void Preferences::setMainVSplitterState(const QByteArray &state)
#endif #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); setValue("MainWindow/LastDir", path);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -35,6 +35,7 @@
#include <QVariant> #include <QVariant>
#include "base/bittorrent/torrentcontentlayout.h" #include "base/bittorrent/torrentcontentlayout.h"
#include "base/pathfwd.h"
class QDateTime; class QDateTime;
class QJsonObject; class QJsonObject;
@ -77,8 +78,8 @@ namespace RSS
QStringList previouslyMatchedEpisodes() const; QStringList previouslyMatchedEpisodes() const;
void setPreviouslyMatchedEpisodes(const QStringList &previouslyMatchedEpisodes); void setPreviouslyMatchedEpisodes(const QStringList &previouslyMatchedEpisodes);
QString savePath() const; Path savePath() const;
void setSavePath(const QString &savePath); void setSavePath(const Path &savePath);
std::optional<bool> addPaused() const; std::optional<bool> addPaused() const;
void setAddPaused(std::optional<bool> addPaused); void setAddPaused(std::optional<bool> addPaused);
std::optional<BitTorrent::TorrentContentLayout> torrentContentLayout() const; 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) , m_url(url)
{ {
const auto uidHex = QString::fromLatin1(m_uid.toRfc4122().toHex()); 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) // Move to new file naming scheme (since v4.1.2)
const QString legacyFilename const QString legacyFilename = Utils::Fs::toValidFileName(m_url, QLatin1String("_")) + QLatin1String(".json");
{Utils::Fs::toValidFileSystemName(m_url, false, QLatin1String("_")) const Path storageDir = m_session->dataFileStorage()->storageDir();
+ QLatin1String(".json")}; const Path dataFilePath = storageDir / m_dataFileName;
const QDir storageDir {m_session->dataFileStorage()->storageDir()}; if (!dataFilePath.exists())
if (!QFile::exists(storageDir.absoluteFilePath(m_dataFileName))) Utils::Fs::renameFile((storageDir / Path(legacyFilename)), dataFilePath);
QFile::rename(storageDir.absoluteFilePath(legacyFilename), storageDir.absoluteFilePath(m_dataFileName));
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 = new Private::Parser(m_lastBuildDate);
m_parser->moveToThread(m_session->workingThread()); m_parser->moveToThread(m_session->workingThread());
@ -139,7 +138,7 @@ void Feed::refresh()
m_downloadHandler = Net::DownloadManager::instance()->download(m_url); m_downloadHandler = Net::DownloadManager::instance()->download(m_url);
connect(m_downloadHandler, &Net::DownloadHandler::finished, this, &Feed::handleDownloadFinished); connect(m_downloadHandler, &Net::DownloadHandler::finished, this, &Feed::handleDownloadFinished);
if (!QFile::exists(m_iconPath)) if (!m_iconPath.exists())
downloadIcon(); downloadIcon();
m_isLoading = true; m_isLoading = true;
@ -262,7 +261,7 @@ void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result)
void Feed::load() void Feed::load()
{ {
QFile file(m_session->dataFileStorage()->storageDir().absoluteFilePath(m_dataFileName)); QFile file {(m_session->dataFileStorage()->storageDir() / m_dataFileName).data()};
if (!file.exists()) if (!file.exists())
{ {
@ -278,7 +277,7 @@ void Feed::load()
else else
{ {
LogMsg(tr("Couldn't read RSS Session data from %1. Error: %2") 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); , Log::WARNING);
} }
} }
@ -500,7 +499,7 @@ int Feed::updateArticles(const QList<QVariantHash> &loadedArticles)
return newArticlesCount; return newArticlesCount;
} }
QString Feed::iconPath() const Path Feed::iconPath() const
{ {
return m_iconPath; return m_iconPath;
} }
@ -549,8 +548,8 @@ void Feed::handleArticleRead(Article *article)
void Feed::cleanup() void Feed::cleanup()
{ {
Utils::Fs::forceRemove(m_session->dataFileStorage()->storageDir().absoluteFilePath(m_dataFileName)); Utils::Fs::removeFile(m_session->dataFileStorage()->storageDir() / m_dataFileName);
Utils::Fs::forceRemove(m_iconPath); Utils::Fs::removeFile(m_iconPath);
} }
void Feed::timerEvent(QTimerEvent *event) void Feed::timerEvent(QTimerEvent *event)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,7 +31,7 @@
#include "settingvalue.h" #include "settingvalue.h"
#include "utils/fs.h" #include "utils/fs.h"
FileGuard::FileGuard(const QString &path) FileGuard::FileGuard(const Path &path)
: m_path {path} : m_path {path}
, m_remove {true} , m_remove {true}
{ {
@ -45,17 +45,17 @@ void FileGuard::setAutoRemove(const bool remove) noexcept
FileGuard::~FileGuard() FileGuard::~FileGuard()
{ {
if (m_remove && !m_path.isEmpty()) 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) TorrentFileGuard::TorrentFileGuard(const Path &path, const TorrentFileGuard::AutoDeleteMode mode)
: FileGuard {mode != Never ? path : QString()} : FileGuard {mode != Never ? path : Path()}
, m_mode {mode} , m_mode {mode}
, m_wasAdded {false} , m_wasAdded {false}
{ {
} }
TorrentFileGuard::TorrentFileGuard(const QString &path) TorrentFileGuard::TorrentFileGuard(const Path &path)
: TorrentFileGuard {path, autoDeleteMode()} : TorrentFileGuard {path, autoDeleteMode()}
{ {
} }

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -50,57 +51,17 @@
#include <unistd.h> #include <unistd.h>
#endif #endif
#include <QDateTime>
#include <QDebug> #include <QDebug>
#include <QDir> #include <QDir>
#include <QDirIterator> #include <QDirIterator>
#include <QFile> #include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QMimeDatabase>
#include <QStorageInfo> #include <QStorageInfo>
#include <QRegularExpression> #include <QRegularExpression>
#include "base/global.h" #include "base/global.h"
#include "base/path.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);
}
/** /**
* This function will first check if there are only system cache files, e.g. `Thumbs.db`, * 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. * 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. * 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; return true;
const QStringList deleteFilesList = 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. // travel from the deepest folder and remove anything unwanted on the way out.
QStringList dirList(path + '/'); // get all sub directories paths QStringList dirList(path.data() + '/'); // get all sub directories paths
QDirIterator iter(path, (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories); QDirIterator iter {path.data(), (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories};
while (iter.hasNext()) while (iter.hasNext())
dirList << iter.next() + '/'; dirList << iter.next() + '/';
// sort descending by directory depth // sort descending by directory depth
@ -138,7 +99,7 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path)
for (const QString &p : asConst(dirList)) 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 // A deeper folder may have not been removed in the previous iteration
// so don't remove anything from this folder either. // so don't remove anything from this folder either.
if (!dir.isEmpty(QDir::Dirs | QDir::NoDotAndDotDot)) if (!dir.isEmpty(QDir::Dirs | QDir::NoDotAndDotDot))
@ -156,38 +117,22 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path)
continue; continue;
for (const QString &f : tmpFileList) for (const QString &f : tmpFileList)
forceRemove(p + f); removeFile(Path(p + f));
// remove directory if empty // remove directory if empty
dir.rmdir(p); dir.rmdir(p);
} }
return QDir(path).exists(); return 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();
} }
/** /**
* Removes directory and its content recursively. * Removes directory and its content recursively.
*/ */
void Utils::Fs::removeDirRecursive(const QString &path) void Utils::Fs::removeDirRecursively(const Path &path)
{ {
if (!path.isEmpty()) 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. * 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 // Check if it is a file
const QFileInfo fi(path); const QFileInfo fi {path.data()};
if (!fi.exists()) return -1; if (!fi.exists()) return -1;
if (fi.isFile()) return fi.size(); if (fi.isFile()) return fi.size();
// Compute folder size based on its content // Compute folder size based on its content
qint64 size = 0; 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()) while (iter.hasNext())
{ {
iter.next(); 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. * 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.exists() || !f2.exists()) return false;
if (f1.size() != f2.size()) return false; if (f1.size() != f2.size()) return false;
if (!f1.open(QIODevice::ReadOnly)) 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; 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(); QString validName = name.trimmed();
validName.replace(regex, pad); validName.replace(regex, pad);
qDebug() << "toValidFileSystemName:" << name << "=>" << validName;
return 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) QString validPathStr = name;
const QRegularExpression regex validPathStr.replace(regex, pad);
{allowSeparators
? QLatin1String("[:?\"*<>|]") return Path(validPathStr);
: 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);
} }
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); static const Path path = Path(QDir::tempPath()) / Path(".qBittorrent");
if (ret.endsWith('/')) mkdir(path);
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);
return path; return path;
} }
bool Utils::Fs::isRegularFile(const QString &path) bool Utils::Fs::isRegularFile(const Path &path)
{ {
struct ::stat st; struct ::stat st;
if (::stat(path.toUtf8().constData(), &st) != 0) if (::stat(path.toString().toUtf8().constData(), &st) != 0)
{ {
// analyse erno and log the error // analyse erno and log the error
const auto err = errno; const auto err = errno;
qDebug("Could not get file stats for path '%s'. Error: %s" 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 false;
} }
return (st.st_mode & S_IFMT) == S_IFREG; return (st.st_mode & S_IFMT) == S_IFREG;
} }
#if !defined Q_OS_HAIKU bool Utils::Fs::isNetworkFileSystem(const Path &path)
bool Utils::Fs::isNetworkFileSystem(const QString &path)
{ {
#if defined(Q_OS_WIN) #if defined Q_OS_HAIKU
const std::wstring pathW {path.toStdWString()}; return false;
auto volumePath = std::make_unique<wchar_t[]>(path.length() + 1); #elif defined(Q_OS_WIN)
if (!::GetVolumePathNameW(pathW.c_str(), volumePath.get(), (path.length() + 1))) 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 false;
return (::GetDriveTypeW(volumePath.get()) == DRIVE_REMOTE); return (::GetDriveTypeW(volumePath.get()) == DRIVE_REMOTE);
#else #else
QString file = path; const QString file = path.toString() + QLatin1String("/.");
if (!file.endsWith('/'))
file += '/';
file += '.';
struct statfs buf {}; struct statfs buf {};
if (statfs(file.toLocal8Bit().constData(), &buf) != 0) if (statfs(file.toLocal8Bit().constData(), &buf) != 0)
return false; return false;
@ -398,41 +289,77 @@ bool Utils::Fs::isNetworkFileSystem(const QString &path)
#endif #endif
#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; return QFile::copy(from.data(), to.data());
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;
} }
void Utils::Fs::stripRootFolder(QStringList &filePaths) bool Utils::Fs::renameFile(const Path &from, const Path &to)
{ {
const QString commonRootFolder = findRootFolder(filePaths); return QFile::rename(from.data(), to.data());
if (commonRootFolder.isEmpty())
return;
for (QString &filePath : filePaths)
filePath = filePath.mid(commonRootFolder.size() + 1);
} }
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) QFile file {path.data()};
filePath = rootFolder + QLatin1Char('/') + filePath; 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. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -34,58 +35,36 @@
#include <QString> #include <QString>
#include "base/pathfwd.h"
class QDateTime;
namespace Utils::Fs namespace Utils::Fs
{ {
/** qint64 computePathSize(const Path &path);
* Converts a path to a string suitable for display. qint64 freeDiskSpaceOnPath(const Path &path);
* This function makes sure the directory separator used is consistent
* with the OS being run.
*/
QString toNativePath(const QString &path);
/** bool isRegularFile(const Path &path);
* Converts a path to a string suitable for processing. bool isDir(const Path &path);
* This function makes sure the directory separator used is independent bool isReadable(const Path &path);
* from the OS being run so it is the same on all supported platforms. bool isWritable(const Path &path);
* Slash ('/') is used as "uniform" directory separator. bool isNetworkFileSystem(const Path &path);
*/ QDateTime lastModified(const Path &path);
QString toUniformPath(const QString &path); bool sameFiles(const Path &path1, const Path &path2);
/** QString toValidFileName(const QString &name, const QString &pad = QLatin1String(" "));
* If `path is relative then resolves it against `basePath`, otherwise returns the `path` itself Path toValidPath(const QString &name, const QString &pad = QLatin1String(" "));
*/ Path toCanonicalPath(const Path &path);
QString resolvePath(const QString &relativePath, const QString &basePath);
/** bool copyFile(const Path &from, const Path &to);
* Returns the file extension part of a file name. bool renameFile(const Path &from, const Path &to);
*/ bool removeFile(const Path &path);
QString fileExtension(const QString &filename); 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); Path homePath();
QString folderName(const QString &filePath); Path tempPath();
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
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -30,6 +30,7 @@
#include <QFile> #include <QFile>
#include "base/path.h"
#include "base/unicodestrings.h" #include "base/unicodestrings.h"
#include "base/utils/misc.h" #include "base/utils/misc.h"
#include "base/version.h" #include "base/version.h"
@ -70,7 +71,7 @@ AboutDialog::AboutDialog(QWidget *parent)
, tr("Bug Tracker:")); , tr("Bug Tracker:"));
m_ui->labelAbout->setText(aboutText); 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 // Thanks
QFile thanksfile(":/thanks.html"); QFile thanksfile(":/thanks.html");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -93,12 +93,12 @@ PreviewSelectDialog::PreviewSelectDialog(QWidget *parent, const BitTorrent::Torr
const QVector<qreal> fp = torrent->filesProgress(); const QVector<qreal> fp = torrent->filesProgress();
for (int i = 0; i < torrent->filesCount(); ++i) for (int i = 0; i < torrent->filesCount(); ++i)
{ {
const QString fileName = Utils::Fs::fileName(torrent->filePath(i)); const Path filePath = torrent->filePath(i);
if (Utils::Misc::isPreviewable(fileName)) if (Utils::Misc::isPreviewable(filePath))
{ {
int row = m_previewListModel->rowCount(); int row = m_previewListModel->rowCount();
m_previewListModel->insertRow(row); 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, SIZE), torrent->fileSize(i));
m_previewListModel->setData(m_previewListModel->index(row, PROGRESS), fp[i]); m_previewListModel->setData(m_previewListModel->index(row, PROGRESS), fp[i]);
m_previewListModel->setData(m_previewListModel->index(row, FILE_INDEX), i); m_previewListModel->setData(m_previewListModel->index(row, FILE_INDEX), i);
@ -133,14 +133,14 @@ void PreviewSelectDialog::previewButtonClicked()
// Only one file should be selected // Only one file should be selected
const int fileIndex = selectedIndexes.at(0).data().toInt(); 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 // File
if (!QFile::exists(path)) if (!path.exists())
{ {
const bool isSingleFile = (m_previewListModel->rowCount() == 1); const bool isSingleFile = (m_previewListModel->rowCount() == 1);
QWidget *parent = isSingleFile ? this->parentWidget() : this; QWidget *parent = isSingleFile ? this->parentWidget() : this;
QMessageBox::critical(parent, tr("Preview impossible") 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) if (isSingleFile)
reject(); reject();
return; return;

View File

@ -30,6 +30,7 @@
#include <QDialog> #include <QDialog>
#include "base/path.h"
#include "base/settingvalue.h" #include "base/settingvalue.h"
class QStandardItemModel; class QStandardItemModel;
@ -38,6 +39,7 @@ namespace BitTorrent
{ {
class Torrent; class Torrent;
} }
namespace Ui namespace Ui
{ {
class PreviewSelectDialog; class PreviewSelectDialog;
@ -64,7 +66,7 @@ public:
~PreviewSelectDialog(); ~PreviewSelectDialog();
signals: signals:
void readyToPreviewFile(QString) const; void readyToPreviewFile(const Path &filePath) const;
private slots: private slots:
void previewButtonClicked(); 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::TOT_UP, totalUp, peer.totalUpload(), intDataTextAlignment);
setModelData(row, PeerListColumns::RELEVANCE, (Utils::String::fromDouble(peer.relevance() * 100, 1) + '%'), peer.relevance(), intDataTextAlignment); setModelData(row, PeerListColumns::RELEVANCE, (Utils::String::fromDouble(peer.relevance() * 100, 1) + '%'), peer.relevance(), intDataTextAlignment);
const QStringList downloadingFiles {torrent->hasMetadata() const PathList filePaths = torrent->info().filesForPiece(peer.downloadingPieceIndex());
? torrent->info().filesForPiece(peer.downloadingPieceIndex()) QStringList downloadingFiles;
: QStringList()}; downloadingFiles.reserve(filePaths.size());
for (const Path &filePath : filePaths)
downloadingFiles.append(filePath.toString());
const QString downloadingFilesDisplayValue = downloadingFiles.join(';'); const QString downloadingFilesDisplayValue = downloadingFiles.join(';');
setModelData(row, PeerListColumns::DOWNLOADING_PIECE, downloadingFilesDisplayValue, downloadingFilesDisplayValue, {}, downloadingFiles.join(QLatin1Char('\n'))); setModelData(row, PeerListColumns::DOWNLOADING_PIECE, downloadingFilesDisplayValue, downloadingFilesDisplayValue, {}, downloadingFiles.join(QLatin1Char('\n')));

View File

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

View File

@ -31,7 +31,6 @@
#include <QClipboard> #include <QClipboard>
#include <QDateTime> #include <QDateTime>
#include <QDebug> #include <QDebug>
#include <QDir>
#include <QHeaderView> #include <QHeaderView>
#include <QListWidgetItem> #include <QListWidgetItem>
#include <QMenu> #include <QMenu>
@ -45,6 +44,7 @@
#include "base/bittorrent/infohash.h" #include "base/bittorrent/infohash.h"
#include "base/bittorrent/session.h" #include "base/bittorrent/session.h"
#include "base/bittorrent/torrent.h" #include "base/bittorrent/torrent.h"
#include "base/path.h"
#include "base/preferences.h" #include "base/preferences.h"
#include "base/unicodestrings.h" #include "base/unicodestrings.h"
#include "base/utils/fs.h" #include "base/utils/fs.h"
@ -333,7 +333,7 @@ QTreeView *PropertiesWidget::getFilesList() const
void PropertiesWidget::updateSavePath(BitTorrent::Torrent *const torrent) void PropertiesWidget::updateSavePath(BitTorrent::Torrent *const torrent)
{ {
if (torrent == m_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) 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) if (m_propListModel->itemType(index) == TorrentContentModelItem::FileType)
{ {
const int fileIdx = m_propListModel->getFileIndex(index); const int fileIdx = m_propListModel->getFileIndex(index);
const QString filename {m_torrent->actualFilePath(fileIdx)}; const Path fullPath = m_torrent->actualStorageLocation() / m_torrent->actualFilePath(fileIdx);
const QString fullPath {Utils::Fs::expandPath(saveDir.absoluteFilePath(filename))};
return fullPath; return fullPath;
} }
// folder type // folder type
const QModelIndex nameIndex {index.sibling(index.row(), TorrentContentModelItem::COL_NAME)}; 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()) 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; return fullPath;
} }
@ -626,7 +623,7 @@ void PropertiesWidget::openItem(const QModelIndex &index) const
void PropertiesWidget::openParentFolder(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 m_torrent->flushCache(); // Flush data
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
MacUtils::openFiles({path}); MacUtils::openFiles({path});

View File

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

View File

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

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