diff --git a/src/app/application.cpp b/src/app/application.cpp index 6e58d352c..a92e4b6ca 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -45,7 +45,6 @@ #include #include -#include #include #include @@ -80,6 +79,7 @@ #include "base/torrentfileswatcher.h" #include "base/utils/compare.h" #include "base/utils/fs.h" +#include "base/path.h" #include "base/utils/misc.h" #include "base/version.h" #include "applicationinstancemanager.h" @@ -105,7 +105,7 @@ namespace const QString LOG_FOLDER = QStringLiteral("logs"); const QChar PARAMS_SEPARATOR = QLatin1Char('|'); - const QString DEFAULT_PORTABLE_MODE_PROFILE_DIR = QStringLiteral("profile"); + const Path DEFAULT_PORTABLE_MODE_PROFILE_DIR {QStringLiteral("profile")}; const int MIN_FILELOG_SIZE = 1024; // 1KiB const int MAX_FILELOG_SIZE = 1000 * 1024 * 1024; // 1000MiB @@ -143,16 +143,16 @@ Application::Application(int &argc, char **argv) QPixmapCache::setCacheLimit(PIXMAP_CACHE_SIZE); #endif - const bool portableModeEnabled = m_commandLineArgs.profileDir.isEmpty() - && QDir(QCoreApplication::applicationDirPath()).exists(DEFAULT_PORTABLE_MODE_PROFILE_DIR); + const auto portableProfilePath = Path(QCoreApplication::applicationDirPath()) / DEFAULT_PORTABLE_MODE_PROFILE_DIR; + const bool portableModeEnabled = m_commandLineArgs.profileDir.isEmpty() && portableProfilePath.exists(); - const QString profileDir = portableModeEnabled - ? QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(DEFAULT_PORTABLE_MODE_PROFILE_DIR) + const Path profileDir = portableModeEnabled + ? portableProfilePath : m_commandLineArgs.profileDir; Profile::initInstance(profileDir, m_commandLineArgs.configurationName, (m_commandLineArgs.relativeFastresumePaths || portableModeEnabled)); - m_instanceManager = new ApplicationInstanceManager {Profile::instance()->location(SpecialFolder::Config), this}; + m_instanceManager = new ApplicationInstanceManager(Profile::instance()->location(SpecialFolder::Config), this); Logger::initInstance(); SettingsStorage::initInstance(); @@ -175,13 +175,13 @@ Application::Application(int &argc, char **argv) Logger::instance()->addMessage(tr("qBittorrent %1 started", "qBittorrent v3.2.0alpha started").arg(QBT_VERSION)); if (portableModeEnabled) { - Logger::instance()->addMessage(tr("Running in portable mode. Auto detected profile folder at: %1").arg(profileDir)); + Logger::instance()->addMessage(tr("Running in portable mode. Auto detected profile folder at: %1").arg(profileDir.toString())); if (m_commandLineArgs.relativeFastresumePaths) Logger::instance()->addMessage(tr("Redundant command line flag detected: \"%1\". Portable mode implies relative fastresume.").arg("--relative-fastresume"), Log::WARNING); // to avoid translating the `--relative-fastresume` string } else { - Logger::instance()->addMessage(tr("Using config directory: %1").arg(Profile::instance()->location(SpecialFolder::Config))); + Logger::instance()->addMessage(tr("Using config directory: %1").arg(Profile::instance()->location(SpecialFolder::Config).toString())); } } @@ -218,12 +218,12 @@ void Application::setFileLoggerEnabled(const bool value) m_storeFileLoggerEnabled = value; } -QString Application::fileLoggerPath() const +Path Application::fileLoggerPath() const { - return m_storeFileLoggerPath.get(QDir(specialFolderLocation(SpecialFolder::Data)).absoluteFilePath(LOG_FOLDER)); + return m_storeFileLoggerPath.get(specialFolderLocation(SpecialFolder::Data) / Path(LOG_FOLDER)); } -void Application::setFileLoggerPath(const QString &path) +void Application::setFileLoggerPath(const Path &path) { if (m_fileLogger) m_fileLogger->changePath(path); @@ -327,16 +327,16 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const break; case u'D': #if defined(Q_OS_WIN) - program.replace(i, 2, chopPathSep(Utils::Fs::toNativePath(torrent->savePath()))); + program.replace(i, 2, chopPathSep(torrent->savePath().toString())); #else - program.replace(i, 2, Utils::Fs::toNativePath(torrent->savePath())); + program.replace(i, 2, torrent->savePath().toString()); #endif break; case u'F': #if defined(Q_OS_WIN) - program.replace(i, 2, chopPathSep(Utils::Fs::toNativePath(torrent->contentPath()))); + program.replace(i, 2, chopPathSep(torrent->contentPath().toString())); #else - program.replace(i, 2, Utils::Fs::toNativePath(torrent->contentPath())); + program.replace(i, 2, torrent->contentPath().toString()); #endif break; case u'G': @@ -359,9 +359,9 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const break; case u'R': #if defined(Q_OS_WIN) - program.replace(i, 2, chopPathSep(Utils::Fs::toNativePath(torrent->rootPath()))); + program.replace(i, 2, chopPathSep(torrent->rootPath().toString())); #else - program.replace(i, 2, Utils::Fs::toNativePath(torrent->rootPath())); + program.replace(i, 2, torrent->rootPath().toString()); #endif break; case u'T': @@ -439,7 +439,7 @@ void Application::sendNotificationEmail(const BitTorrent::Torrent *torrent) // Prepare mail content const QString content = tr("Torrent name: %1").arg(torrent->name()) + '\n' + tr("Torrent size: %1").arg(Utils::Misc::friendlyUnit(torrent->wantedSize())) + '\n' - + tr("Save path: %1").arg(torrent->savePath()) + "\n\n" + + tr("Save path: %1").arg(torrent->savePath().toString()) + "\n\n" + tr("The torrent was downloaded in %1.", "The torrent was downloaded in 1 hour and 20 seconds") .arg(Utils::Misc::userFriendlyDuration(torrent->activeTime())) + "\n\n\n" + tr("Thank you for using qBittorrent.") + '\n'; @@ -545,7 +545,7 @@ void Application::processParams(const QStringList ¶ms) if (param.startsWith(QLatin1String("@savePath="))) { - torrentParams.savePath = param.mid(10); + torrentParams.savePath = Path(param.mid(10)); continue; } @@ -821,7 +821,7 @@ void Application::cleanup() Logger::freeInstance(); IconProvider::freeInstance(); SearchPluginManager::freeInstance(); - Utils::Fs::removeDirRecursive(Utils::Fs::tempPath()); + Utils::Fs::removeDirRecursively(Utils::Fs::tempPath()); #ifndef DISABLE_GUI if (m_window) diff --git a/src/app/application.h b/src/app/application.h index 13df0e196..9980e80b7 100644 --- a/src/app/application.h +++ b/src/app/application.h @@ -47,6 +47,7 @@ class QSessionManager; using BaseApplication = QCoreApplication; #endif // DISABLE_GUI +#include "base/path.h" #include "base/settingvalue.h" #include "base/types.h" #include "cmdoptions.h" @@ -91,8 +92,8 @@ public: // FileLogger properties bool isFileLoggerEnabled() const; void setFileLoggerEnabled(bool value); - QString fileLoggerPath() const; - void setFileLoggerPath(const QString &path); + Path fileLoggerPath() const; + void setFileLoggerPath(const Path &path); bool isFileLoggerBackup() const; void setFileLoggerBackup(bool value); bool isFileLoggerDeleteOld() const; @@ -152,5 +153,5 @@ private: SettingValue m_storeFileLoggerMaxSize; SettingValue m_storeFileLoggerAge; SettingValue m_storeFileLoggerAgeType; - SettingValue m_storeFileLoggerPath; + SettingValue m_storeFileLoggerPath; }; diff --git a/src/app/applicationinstancemanager.cpp b/src/app/applicationinstancemanager.cpp index 6a15f0471..e91bd99ab 100644 --- a/src/app/applicationinstancemanager.cpp +++ b/src/app/applicationinstancemanager.cpp @@ -37,18 +37,19 @@ #include #endif +#include "base/path.h" #include "qtlocalpeer/qtlocalpeer.h" -ApplicationInstanceManager::ApplicationInstanceManager(const QString &instancePath, QObject *parent) +ApplicationInstanceManager::ApplicationInstanceManager(const Path &instancePath, QObject *parent) : QObject {parent} - , m_peer {new QtLocalPeer {instancePath, this}} + , m_peer {new QtLocalPeer(instancePath.data(), this)} , m_isFirstInstance {!m_peer->isClient()} { connect(m_peer, &QtLocalPeer::messageReceived, this, &ApplicationInstanceManager::messageReceived); #ifdef Q_OS_WIN - const QString sharedMemoryKey = instancePath + QLatin1String {"/shared-memory"}; - auto sharedMem = new QSharedMemory {sharedMemoryKey, this}; + const QString sharedMemoryKey = instancePath.data() + QLatin1String("/shared-memory"); + auto sharedMem = new QSharedMemory(sharedMemoryKey, this); if (m_isFirstInstance) { // First instance creates shared memory and store PID diff --git a/src/app/applicationinstancemanager.h b/src/app/applicationinstancemanager.h index 91ad7c2fd..4a04fd1c7 100644 --- a/src/app/applicationinstancemanager.h +++ b/src/app/applicationinstancemanager.h @@ -30,6 +30,8 @@ #include +#include "base/pathfwd.h" + class QtLocalPeer; class ApplicationInstanceManager final : public QObject @@ -38,7 +40,7 @@ class ApplicationInstanceManager final : public QObject Q_DISABLE_COPY_MOVE(ApplicationInstanceManager) public: - explicit ApplicationInstanceManager(const QString &instancePath, QObject *parent = nullptr); + explicit ApplicationInstanceManager(const Path &instancePath, QObject *parent = nullptr); bool isFirstInstance() const; diff --git a/src/app/cmdoptions.cpp b/src/app/cmdoptions.cpp index 5baee490a..0c7a089f2 100644 --- a/src/app/cmdoptions.cpp +++ b/src/app/cmdoptions.cpp @@ -372,7 +372,7 @@ QStringList QBtCommandLineParameters::paramList() const // torrent paths or URLs. if (!savePath.isEmpty()) - result.append(QLatin1String("@savePath=") + savePath); + result.append(QLatin1String("@savePath=") + savePath.data()); if (addPaused.has_value()) result.append(*addPaused ? QLatin1String {"@addPaused=1"} : QLatin1String {"@addPaused=0"}); @@ -438,7 +438,7 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args) #endif else if (arg == PROFILE_OPTION) { - result.profileDir = PROFILE_OPTION.value(arg); + result.profileDir = Path(PROFILE_OPTION.value(arg)); } else if (arg == RELATIVE_FASTRESUME) { @@ -450,7 +450,7 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args) } else if (arg == SAVE_PATH_OPTION) { - result.savePath = SAVE_PATH_OPTION.value(arg); + result.savePath = Path(SAVE_PATH_OPTION.value(arg)); } else if (arg == PAUSED_OPTION) { diff --git a/src/app/cmdoptions.h b/src/app/cmdoptions.h index 46d84b66e..77fb67b77 100644 --- a/src/app/cmdoptions.h +++ b/src/app/cmdoptions.h @@ -36,6 +36,7 @@ #include #include "base/exceptions.h" +#include "base/path.h" class QProcessEnvironment; @@ -58,9 +59,9 @@ struct QBtCommandLineParameters std::optional addPaused; std::optional skipDialog; QStringList torrents; - QString profileDir; + Path profileDir; QString configurationName; - QString savePath; + Path savePath; QString category; QString unknownParameter; diff --git a/src/app/filelogger.cpp b/src/app/filelogger.cpp index b8ad5e012..46a2afb93 100644 --- a/src/app/filelogger.cpp +++ b/src/app/filelogger.cpp @@ -44,7 +44,9 @@ namespace const std::chrono::seconds FLUSH_INTERVAL {2}; } -FileLogger::FileLogger(const QString &path, const bool backup, const int maxSize, const bool deleteOld, const int age, const FileLogAgeType ageType) +FileLogger::FileLogger(const Path &path, const bool backup + , const int maxSize, const bool deleteOld, const int age + , const FileLogAgeType ageType) : m_backup(backup) , m_maxSize(maxSize) { @@ -68,26 +70,25 @@ FileLogger::~FileLogger() closeLogFile(); } -void FileLogger::changePath(const QString &newPath) +void FileLogger::changePath(const Path &newPath) { - const QDir dir(newPath); - dir.mkpath(newPath); - const QString tmpPath = dir.absoluteFilePath("qbittorrent.log"); + // compare paths as strings to perform case sensitive comparison on all the platforms + if (newPath.data() == m_path.parentPath().data()) + return; - if (tmpPath != m_path) - { - m_path = tmpPath; + closeLogFile(); - closeLogFile(); - m_logFile.setFileName(m_path); - openLogFile(); - } + m_path = newPath / Path("qbittorrent.log"); + m_logFile.setFileName(m_path.data()); + + Utils::Fs::mkpath(newPath); + openLogFile(); } void FileLogger::deleteOld(const int age, const FileLogAgeType ageType) { const QDateTime date = QDateTime::currentDateTime(); - const QDir dir(Utils::Fs::branchPath(m_path)); + const QDir dir {m_path.parentPath().data()}; const QFileInfoList fileList = dir.entryInfoList(QStringList("qbittorrent.log.bak*") , (QDir::Files | QDir::Writable), (QDir::Time | QDir::Reversed)); @@ -107,7 +108,7 @@ void FileLogger::deleteOld(const int age, const FileLogAgeType ageType) } if (modificationDate > date) break; - Utils::Fs::forceRemove(file.absoluteFilePath()); + Utils::Fs::removeFile(Path(file.absoluteFilePath())); } } @@ -151,15 +152,15 @@ void FileLogger::addLogMessage(const Log::Msg &msg) { closeLogFile(); int counter = 0; - QString backupLogFilename = m_path + ".bak"; + Path backupLogFilename = m_path + ".bak"; - while (QFile::exists(backupLogFilename)) + while (backupLogFilename.exists()) { ++counter; backupLogFilename = m_path + ".bak" + QString::number(counter); } - QFile::rename(m_path, backupLogFilename); + Utils::Fs::renameFile(m_path, backupLogFilename); openLogFile(); } else diff --git a/src/app/filelogger.h b/src/app/filelogger.h index b62ae2979..739cd93a8 100644 --- a/src/app/filelogger.h +++ b/src/app/filelogger.h @@ -32,6 +32,8 @@ #include #include +#include "base/path.h" + namespace Log { struct Msg; @@ -50,10 +52,10 @@ public: YEARS }; - FileLogger(const QString &path, bool backup, int maxSize, bool deleteOld, int age, FileLogAgeType ageType); + FileLogger(const Path &path, bool backup, int maxSize, bool deleteOld, int age, FileLogAgeType ageType); ~FileLogger(); - void changePath(const QString &newPath); + void changePath(const Path &newPath); void deleteOld(int age, FileLogAgeType ageType); void setBackup(bool value); void setMaxSize(int value); @@ -66,7 +68,7 @@ private: void openLogFile(); void closeLogFile(); - QString m_path; + Path m_path; bool m_backup; int m_maxSize; QFile m_logFile; diff --git a/src/app/upgrade.cpp b/src/app/upgrade.cpp index a2ebe61fb..1a991839d 100644 --- a/src/app/upgrade.cpp +++ b/src/app/upgrade.cpp @@ -48,7 +48,7 @@ namespace void exportWebUIHttpsFiles() { - const auto migrate = [](const QString &oldKey, const QString &newKey, const QString &savePath) + const auto migrate = [](const QString &oldKey, const QString &newKey, const Path &savePath) { SettingsStorage *settingsStorage {SettingsStorage::instance()}; const auto oldData {settingsStorage->loadValue(oldKey)}; @@ -61,24 +61,24 @@ namespace const nonstd::expected result = Utils::IO::saveToFile(savePath, oldData); if (!result) { - LogMsg(errorMsgFormat.arg(savePath, result.error()) , Log::WARNING); + LogMsg(errorMsgFormat.arg(savePath.toString(), result.error()) , Log::WARNING); return; } settingsStorage->storeValue(newKey, savePath); settingsStorage->removeValue(oldKey); - LogMsg(QObject::tr("Migrated preferences: WebUI https, exported data to file: \"%1\"").arg(savePath) + LogMsg(QObject::tr("Migrated preferences: WebUI https, exported data to file: \"%1\"").arg(savePath.toString()) , Log::INFO); }; - const QString configPath {specialFolderLocation(SpecialFolder::Config)}; + const Path configPath = specialFolderLocation(SpecialFolder::Config); migrate(QLatin1String("Preferences/WebUI/HTTPS/Certificate") , QLatin1String("Preferences/WebUI/HTTPS/CertificatePath") - , Utils::Fs::toNativePath(configPath + QLatin1String("/WebUICertificate.crt"))); + , (configPath / Path("WebUICertificate.crt"))); migrate(QLatin1String("Preferences/WebUI/HTTPS/Key") , QLatin1String("Preferences/WebUI/HTTPS/KeyPath") - , Utils::Fs::toNativePath(configPath + QLatin1String("/WebUIPrivateKey.pem"))); + , (configPath / Path("WebUIPrivateKey.pem"))); } void upgradeTorrentContentLayout() diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 49b3bf336..6e9386cc7 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -62,6 +62,8 @@ add_library(qbt_base STATIC net/reverseresolution.h net/smtp.h orderedset.h + path.h + pathfwd.h preferences.h profile.h profile_p.h @@ -144,6 +146,7 @@ add_library(qbt_base STATIC net/proxyconfigurationmanager.cpp net/reverseresolution.cpp net/smtp.cpp + path.cpp preferences.cpp profile.cpp profile_p.cpp diff --git a/src/base/asyncfilestorage.cpp b/src/base/asyncfilestorage.cpp index ee3379d72..128353ca6 100644 --- a/src/base/asyncfilestorage.cpp +++ b/src/base/asyncfilestorage.cpp @@ -31,21 +31,22 @@ #include #include +#include "base/utils/fs.h" #include "base/utils/io.h" -AsyncFileStorage::AsyncFileStorage(const QString &storageFolderPath, QObject *parent) +AsyncFileStorage::AsyncFileStorage(const Path &storageFolderPath, QObject *parent) : QObject(parent) , m_storageDir(storageFolderPath) - , m_lockFile(m_storageDir.absoluteFilePath(QStringLiteral("storage.lock"))) + , m_lockFile((m_storageDir / Path(QStringLiteral("storage.lock"))).data()) { - if (!m_storageDir.mkpath(m_storageDir.absolutePath())) - throw AsyncFileStorageError - {tr("Could not create directory '%1'.") - .arg(m_storageDir.absolutePath())}; + Q_ASSERT(m_storageDir.isAbsolute()); + + if (!Utils::Fs::mkpath(m_storageDir)) + throw AsyncFileStorageError(tr("Could not create directory '%1'.").arg(m_storageDir.toString())); // TODO: This folder locking approach does not work for UNIX systems. Implement it. if (!m_lockFile.open(QFile::WriteOnly)) - throw AsyncFileStorageError {m_lockFile.errorString()}; + throw AsyncFileStorageError(m_lockFile.errorString()); } AsyncFileStorage::~AsyncFileStorage() @@ -54,21 +55,21 @@ AsyncFileStorage::~AsyncFileStorage() m_lockFile.remove(); } -void AsyncFileStorage::store(const QString &fileName, const QByteArray &data) +void AsyncFileStorage::store(const Path &filePath, const QByteArray &data) { - QMetaObject::invokeMethod(this, [this, data, fileName]() { store_impl(fileName, data); } + QMetaObject::invokeMethod(this, [this, data, filePath]() { store_impl(filePath, data); } , Qt::QueuedConnection); } -QDir AsyncFileStorage::storageDir() const +Path AsyncFileStorage::storageDir() const { return m_storageDir; } -void AsyncFileStorage::store_impl(const QString &fileName, const QByteArray &data) +void AsyncFileStorage::store_impl(const Path &fileName, const QByteArray &data) { - const QString filePath = m_storageDir.absoluteFilePath(fileName); - qDebug() << "AsyncFileStorage: Saving data to" << filePath; + const Path filePath = m_storageDir / fileName; + qDebug() << "AsyncFileStorage: Saving data to" << filePath.toString(); const nonstd::expected result = Utils::IO::saveToFile(filePath, data); if (!result) diff --git a/src/base/asyncfilestorage.h b/src/base/asyncfilestorage.h index 496aa4b9d..2943ee212 100644 --- a/src/base/asyncfilestorage.h +++ b/src/base/asyncfilestorage.h @@ -28,11 +28,11 @@ #pragma once -#include #include #include #include "base/exceptions.h" +#include "base/path.h" class AsyncFileStorageError : public RuntimeError { @@ -46,19 +46,19 @@ class AsyncFileStorage : public QObject Q_DISABLE_COPY_MOVE(AsyncFileStorage) public: - explicit AsyncFileStorage(const QString &storageFolderPath, QObject *parent = nullptr); + explicit AsyncFileStorage(const Path &storageFolderPath, QObject *parent = nullptr); ~AsyncFileStorage() override; - void store(const QString &fileName, const QByteArray &data); + void store(const Path &filePath, const QByteArray &data); - QDir storageDir() const; + Path storageDir() const; signals: - void failed(const QString &fileName, const QString &errorString); + void failed(const Path &filePath, const QString &errorString); private: - Q_INVOKABLE void store_impl(const QString &fileName, const QByteArray &data); + Q_INVOKABLE void store_impl(const Path &fileName, const QByteArray &data); - QDir m_storageDir; + Path m_storageDir; QFile m_lockFile; }; diff --git a/src/base/base.pri b/src/base/base.pri index 169844c5e..9b5baeb9d 100644 --- a/src/base/base.pri +++ b/src/base/base.pri @@ -61,6 +61,8 @@ HEADERS += \ $$PWD/net/reverseresolution.h \ $$PWD/net/smtp.h \ $$PWD/orderedset.h \ + $$PWD/path.h \ + $$PWD/pathfwd.h \ $$PWD/preferences.h \ $$PWD/profile.h \ $$PWD/profile_p.h \ @@ -144,6 +146,7 @@ SOURCES += \ $$PWD/net/proxyconfigurationmanager.cpp \ $$PWD/net/reverseresolution.cpp \ $$PWD/net/smtp.cpp \ + $$PWD/path.cpp \ $$PWD/preferences.cpp \ $$PWD/profile.cpp \ $$PWD/profile_p.cpp \ diff --git a/src/base/bittorrent/abstractfilestorage.cpp b/src/base/bittorrent/abstractfilestorage.cpp index e48101a1f..c55ef8e52 100644 --- a/src/base/bittorrent/abstractfilestorage.cpp +++ b/src/base/bittorrent/abstractfilestorage.cpp @@ -33,93 +33,63 @@ #include #include "base/exceptions.h" +#include "base/path.h" #include "base/utils/fs.h" -#if defined(Q_OS_WIN) -const Qt::CaseSensitivity CASE_SENSITIVITY {Qt::CaseInsensitive}; -#else -const Qt::CaseSensitivity CASE_SENSITIVITY {Qt::CaseSensitive}; -#endif - -namespace +void BitTorrent::AbstractFileStorage::renameFile(const Path &oldPath, const Path &newPath) { - bool areSameFileNames(QString first, QString second) - { - return QString::compare(first, second, CASE_SENSITIVITY) == 0; - } -} - -void BitTorrent::AbstractFileStorage::renameFile(const QString &oldPath, const QString &newPath) -{ - if (!Utils::Fs::isValidFileSystemName(oldPath, true)) - throw RuntimeError {tr("The old path is invalid: '%1'.").arg(oldPath)}; - if (!Utils::Fs::isValidFileSystemName(newPath, true)) - throw RuntimeError {tr("The new path is invalid: '%1'.").arg(newPath)}; - - const QString oldFilePath = Utils::Fs::toUniformPath(oldPath); - if (oldFilePath.endsWith(QLatin1Char {'/'})) - throw RuntimeError {tr("Invalid file path: '%1'.").arg(oldFilePath)}; - - const QString newFilePath = Utils::Fs::toUniformPath(newPath); - if (newFilePath.endsWith(QLatin1Char {'/'})) - throw RuntimeError {tr("Invalid file path: '%1'.").arg(newFilePath)}; - if (QDir().isAbsolutePath(newFilePath)) - throw RuntimeError {tr("Absolute path isn't allowed: '%1'.").arg(newFilePath)}; + if (!oldPath.isValid()) + throw RuntimeError(tr("The old path is invalid: '%1'.").arg(oldPath.toString())); + if (!newPath.isValid()) + throw RuntimeError(tr("The new path is invalid: '%1'.").arg(newPath.toString())); + if (newPath.isAbsolute()) + throw RuntimeError(tr("Absolute path isn't allowed: '%1'.").arg(newPath.toString())); int renamingFileIndex = -1; for (int i = 0; i < filesCount(); ++i) { - const QString path = filePath(i); + const Path path = filePath(i); - if ((renamingFileIndex < 0) && areSameFileNames(path, oldFilePath)) + if ((renamingFileIndex < 0) && (path == oldPath)) renamingFileIndex = i; - else if (areSameFileNames(path, newFilePath)) - throw RuntimeError {tr("The file already exists: '%1'.").arg(newFilePath)}; + else if (path == newPath) + throw RuntimeError(tr("The file already exists: '%1'.").arg(newPath.toString())); } if (renamingFileIndex < 0) - throw RuntimeError {tr("No such file: '%1'.").arg(oldFilePath)}; + throw RuntimeError(tr("No such file: '%1'.").arg(oldPath.toString())); - renameFile(renamingFileIndex, newFilePath); + renameFile(renamingFileIndex, newPath); } -void BitTorrent::AbstractFileStorage::renameFolder(const QString &oldPath, const QString &newPath) +void BitTorrent::AbstractFileStorage::renameFolder(const Path &oldFolderPath, const Path &newFolderPath) { - if (!Utils::Fs::isValidFileSystemName(oldPath, true)) - throw RuntimeError {tr("The old path is invalid: '%1'.").arg(oldPath)}; - if (!Utils::Fs::isValidFileSystemName(newPath, true)) - throw RuntimeError {tr("The new path is invalid: '%1'.").arg(newPath)}; - - const auto cleanFolderPath = [](const QString &path) -> QString - { - const QString uniformPath = Utils::Fs::toUniformPath(path); - return (uniformPath.endsWith(QLatin1Char {'/'}) ? uniformPath : uniformPath + QLatin1Char {'/'}); - }; - - const QString oldFolderPath = cleanFolderPath(oldPath); - const QString newFolderPath = cleanFolderPath(newPath); - if (QDir().isAbsolutePath(newFolderPath)) - throw RuntimeError {tr("Absolute path isn't allowed: '%1'.").arg(newFolderPath)}; + if (!oldFolderPath.isValid()) + throw RuntimeError(tr("The old path is invalid: '%1'.").arg(oldFolderPath.toString())); + if (!newFolderPath.isValid()) + throw RuntimeError(tr("The new path is invalid: '%1'.").arg(newFolderPath.toString())); + if (newFolderPath.isAbsolute()) + throw RuntimeError(tr("Absolute path isn't allowed: '%1'.").arg(newFolderPath.toString())); QVector renamingFileIndexes; renamingFileIndexes.reserve(filesCount()); for (int i = 0; i < filesCount(); ++i) { - const QString path = filePath(i); + const Path path = filePath(i); - if (path.startsWith(oldFolderPath, CASE_SENSITIVITY)) + if (path.hasAncestor(oldFolderPath)) renamingFileIndexes.append(i); - else if (path.startsWith(newFolderPath, CASE_SENSITIVITY)) - throw RuntimeError {tr("The folder already exists: '%1'.").arg(newFolderPath)}; + else if (path.hasAncestor(newFolderPath)) + throw RuntimeError(tr("The folder already exists: '%1'.").arg(newFolderPath.toString())); } if (renamingFileIndexes.isEmpty()) - throw RuntimeError {tr("No such folder: '%1'.").arg(oldFolderPath)}; + throw RuntimeError(tr("No such folder: '%1'.").arg(oldFolderPath.toString())); for (const int index : renamingFileIndexes) { - const QString newFilePath = newFolderPath + filePath(index).mid(oldFolderPath.size()); + const Path newFilePath = newFolderPath / oldFolderPath.relativePathOf(filePath(index)); renameFile(index, newFilePath); } } diff --git a/src/base/bittorrent/abstractfilestorage.h b/src/base/bittorrent/abstractfilestorage.h index 74afda94f..f6f8eec92 100644 --- a/src/base/bittorrent/abstractfilestorage.h +++ b/src/base/bittorrent/abstractfilestorage.h @@ -31,6 +31,8 @@ #include #include +#include "base/pathfwd.h" + class QString; namespace BitTorrent @@ -43,12 +45,12 @@ namespace BitTorrent virtual ~AbstractFileStorage() = default; virtual int filesCount() const = 0; - virtual QString filePath(int index) const = 0; + virtual Path filePath(int index) const = 0; virtual qlonglong fileSize(int index) const = 0; - virtual void renameFile(int index, const QString &name) = 0; + virtual void renameFile(int index, const Path &newPath) = 0; - void renameFile(const QString &oldPath, const QString &newPath); - void renameFolder(const QString &oldPath, const QString &newPath); + void renameFile(const Path &oldPath, const Path &newPath); + void renameFolder(const Path &oldFolderPath, const Path &newFolderPath); }; } diff --git a/src/base/bittorrent/addtorrentparams.h b/src/base/bittorrent/addtorrentparams.h index 78bb6f96b..80d9839b8 100644 --- a/src/base/bittorrent/addtorrentparams.h +++ b/src/base/bittorrent/addtorrentparams.h @@ -34,6 +34,7 @@ #include #include +#include "base/path.h" #include "base/tagset.h" #include "torrent.h" #include "torrentcontentlayout.h" @@ -47,14 +48,14 @@ namespace BitTorrent QString name; QString category; TagSet tags; - QString savePath; + Path savePath; std::optional useDownloadPath; - QString downloadPath; + Path downloadPath; bool sequential = false; bool firstLastPiecePriority = false; bool addForced = false; std::optional addPaused; - QStringList filePaths; // used if TorrentInfo is set + PathList filePaths; // used if TorrentInfo is set QVector filePriorities; // used if TorrentInfo is set bool skipChecking = false; std::optional contentLayout; diff --git a/src/base/bittorrent/bencoderesumedatastorage.cpp b/src/base/bittorrent/bencoderesumedatastorage.cpp index ad593a01e..6f3f5cb0d 100644 --- a/src/base/bittorrent/bencoderesumedatastorage.cpp +++ b/src/base/bittorrent/bencoderesumedatastorage.cpp @@ -58,14 +58,14 @@ namespace BitTorrent Q_DISABLE_COPY_MOVE(Worker) public: - explicit Worker(const QDir &resumeDataDir); + explicit Worker(const Path &resumeDataDir); void store(const TorrentID &id, const LoadTorrentParams &resumeData) const; void remove(const TorrentID &id) const; void storeQueue(const QVector &queue) const; private: - const QDir m_resumeDataDir; + const Path m_resumeDataDir; }; } @@ -89,20 +89,22 @@ namespace } } -BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const QString &path, QObject *parent) +BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const Path &path, QObject *parent) : ResumeDataStorage {parent} - , m_resumeDataDir {path} + , m_resumeDataPath {path} , m_ioThread {new QThread {this}} - , m_asyncWorker {new Worker {m_resumeDataDir}} + , m_asyncWorker {new Worker(m_resumeDataPath)} { - if (!m_resumeDataDir.exists() && !m_resumeDataDir.mkpath(m_resumeDataDir.absolutePath())) + Q_ASSERT(path.isAbsolute()); + + if (!m_resumeDataPath.exists() && !Utils::Fs::mkpath(m_resumeDataPath)) { - throw RuntimeError {tr("Cannot create torrent resume folder: \"%1\"") - .arg(Utils::Fs::toNativePath(m_resumeDataDir.absolutePath()))}; + throw RuntimeError(tr("Cannot create torrent resume folder: \"%1\"") + .arg(m_resumeDataPath.toString())); } const QRegularExpression filenamePattern {QLatin1String("^([A-Fa-f0-9]{40})\\.fastresume$")}; - const QStringList filenames = m_resumeDataDir.entryList(QStringList(QLatin1String("*.fastresume")), QDir::Files, QDir::Unsorted); + const QStringList filenames = QDir(m_resumeDataPath.data()).entryList(QStringList(QLatin1String("*.fastresume")), QDir::Files, QDir::Unsorted); m_registeredTorrents.reserve(filenames.size()); for (const QString &filename : filenames) @@ -112,7 +114,7 @@ BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const QString &pa m_registeredTorrents.append(TorrentID::fromString(rxMatch.captured(1))); } - loadQueue(m_resumeDataDir.absoluteFilePath(QLatin1String("queue"))); + loadQueue(m_resumeDataPath / Path("queue")); qDebug() << "Registered torrents count: " << m_registeredTorrents.size(); @@ -135,20 +137,20 @@ QVector BitTorrent::BencodeResumeDataStorage::registeredT std::optional BitTorrent::BencodeResumeDataStorage::load(const TorrentID &id) const { const QString idString = id.toString(); - const QString fastresumePath = m_resumeDataDir.absoluteFilePath(QString::fromLatin1("%1.fastresume").arg(idString)); - const QString torrentFilePath = m_resumeDataDir.absoluteFilePath(QString::fromLatin1("%1.torrent").arg(idString)); + const Path fastresumePath = m_resumeDataPath / Path(idString + QLatin1String(".fastresume")); + const Path torrentFilePath = m_resumeDataPath / Path(idString + QLatin1String(".torrent")); - QFile resumeDataFile {fastresumePath}; + QFile resumeDataFile {fastresumePath.data()}; if (!resumeDataFile.open(QIODevice::ReadOnly)) { - LogMsg(tr("Cannot read file %1: %2").arg(fastresumePath, resumeDataFile.errorString()), Log::WARNING); + LogMsg(tr("Cannot read file %1: %2").arg(fastresumePath.toString(), resumeDataFile.errorString()), Log::WARNING); return std::nullopt; } - QFile metadataFile {torrentFilePath}; + QFile metadataFile {torrentFilePath.data()}; if (metadataFile.exists() && !metadataFile.open(QIODevice::ReadOnly)) { - LogMsg(tr("Cannot read file %1: %2").arg(torrentFilePath, metadataFile.errorString()), Log::WARNING); + LogMsg(tr("Cannot read file %1: %2").arg(torrentFilePath.toString(), metadataFile.errorString()), Log::WARNING); return std::nullopt; } @@ -178,12 +180,12 @@ std::optional BitTorrent::BencodeResumeDataStorag torrentParams.seedingTimeLimit = root.dict_find_int_value("qBt-seedingTimeLimit", Torrent::USE_GLOBAL_SEEDING_TIME); torrentParams.savePath = Profile::instance()->fromPortablePath( - Utils::Fs::toUniformPath(fromLTString(root.dict_find_string_value("qBt-savePath")))); + Path(fromLTString(root.dict_find_string_value("qBt-savePath")))); torrentParams.useAutoTMM = torrentParams.savePath.isEmpty(); if (!torrentParams.useAutoTMM) { torrentParams.downloadPath = Profile::instance()->fromPortablePath( - Utils::Fs::toUniformPath(fromLTString(root.dict_find_string_value("qBt-downloadPath")))); + Path(fromLTString(root.dict_find_string_value("qBt-downloadPath")))); } // TODO: The following code is deprecated. Replace with the commented one after several releases in 4.4.x. @@ -224,7 +226,8 @@ std::optional BitTorrent::BencodeResumeDataStorag lt::add_torrent_params &p = torrentParams.ltAddTorrentParams; p = lt::read_resume_data(root, ec); - p.save_path = Profile::instance()->fromPortablePath(fromLTString(p.save_path)).toStdString(); + p.save_path = Profile::instance()->fromPortablePath( + Path(fromLTString(p.save_path))).toString().toStdString(); if (p.flags & lt::torrent_flags::stop_when_ready) { @@ -273,9 +276,9 @@ void BitTorrent::BencodeResumeDataStorage::storeQueue(const QVector & }); } -void BitTorrent::BencodeResumeDataStorage::loadQueue(const QString &queueFilename) +void BitTorrent::BencodeResumeDataStorage::loadQueue(const Path &queueFilename) { - QFile queueFile {queueFilename}; + QFile queueFile {queueFilename.data()}; if (!queueFile.exists()) return; @@ -306,7 +309,7 @@ void BitTorrent::BencodeResumeDataStorage::loadQueue(const QString &queueFilenam } } -BitTorrent::BencodeResumeDataStorage::Worker::Worker(const QDir &resumeDataDir) +BitTorrent::BencodeResumeDataStorage::Worker::Worker(const Path &resumeDataDir) : m_resumeDataDir {resumeDataDir} { } @@ -315,7 +318,8 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co { // We need to adjust native libtorrent resume data lt::add_torrent_params p = resumeData.ltAddTorrentParams; - p.save_path = Profile::instance()->toPortablePath(QString::fromStdString(p.save_path)).toStdString(); + p.save_path = Profile::instance()->toPortablePath(Path(p.save_path)) + .toString().toStdString(); if (resumeData.stopped) { p.flags |= lt::torrent_flags::paused; @@ -349,12 +353,12 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co metadataDict.insert(dataDict.extract("created by")); metadataDict.insert(dataDict.extract("comment")); - const QString torrentFilepath = m_resumeDataDir.absoluteFilePath(QString::fromLatin1("%1.torrent").arg(id.toString())); + const Path torrentFilepath = m_resumeDataDir / Path(QString::fromLatin1("%1.torrent").arg(id.toString())); const nonstd::expected result = Utils::IO::saveToFile(torrentFilepath, metadata); if (!result) { LogMsg(tr("Couldn't save torrent metadata to '%1'. Error: %2.") - .arg(torrentFilepath, result.error()), Log::CRITICAL); + .arg(torrentFilepath.toString(), result.error()), Log::CRITICAL); return; } } @@ -370,26 +374,26 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co if (!resumeData.useAutoTMM) { - data["qBt-savePath"] = Profile::instance()->toPortablePath(resumeData.savePath).toStdString(); - data["qBt-downloadPath"] = Profile::instance()->toPortablePath(resumeData.downloadPath).toStdString(); + data["qBt-savePath"] = Profile::instance()->toPortablePath(resumeData.savePath).data().toStdString(); + data["qBt-downloadPath"] = Profile::instance()->toPortablePath(resumeData.downloadPath).data().toStdString(); } - const QString resumeFilepath = m_resumeDataDir.absoluteFilePath(QString::fromLatin1("%1.fastresume").arg(id.toString())); + const Path resumeFilepath = m_resumeDataDir / Path(QString::fromLatin1("%1.fastresume").arg(id.toString())); const nonstd::expected result = Utils::IO::saveToFile(resumeFilepath, data); if (!result) { LogMsg(tr("Couldn't save torrent resume data to '%1'. Error: %2.") - .arg(resumeFilepath, result.error()), Log::CRITICAL); + .arg(resumeFilepath.toString(), result.error()), Log::CRITICAL); } } void BitTorrent::BencodeResumeDataStorage::Worker::remove(const TorrentID &id) const { - const QString resumeFilename = QString::fromLatin1("%1.fastresume").arg(id.toString()); - Utils::Fs::forceRemove(m_resumeDataDir.absoluteFilePath(resumeFilename)); + const Path resumeFilename {QString::fromLatin1("%1.fastresume").arg(id.toString())}; + Utils::Fs::removeFile(m_resumeDataDir / resumeFilename); - const QString torrentFilename = QString::fromLatin1("%1.torrent").arg(id.toString()); - Utils::Fs::forceRemove(m_resumeDataDir.absoluteFilePath(torrentFilename)); + const Path torrentFilename {QString::fromLatin1("%1.torrent").arg(id.toString())}; + Utils::Fs::removeFile(m_resumeDataDir / torrentFilename); } void BitTorrent::BencodeResumeDataStorage::Worker::storeQueue(const QVector &queue) const @@ -399,11 +403,11 @@ void BitTorrent::BencodeResumeDataStorage::Worker::storeQueue(const QVector result = Utils::IO::saveToFile(filepath, data); if (!result) { LogMsg(tr("Couldn't save data to '%1'. Error: %2") - .arg(filepath, result.error()), Log::CRITICAL); + .arg(filepath.toString(), result.error()), Log::CRITICAL); } } diff --git a/src/base/bittorrent/bencoderesumedatastorage.h b/src/base/bittorrent/bencoderesumedatastorage.h index 41f3325d6..b7f558927 100644 --- a/src/base/bittorrent/bencoderesumedatastorage.h +++ b/src/base/bittorrent/bencoderesumedatastorage.h @@ -31,6 +31,7 @@ #include #include +#include "base/path.h" #include "resumedatastorage.h" class QByteArray; @@ -44,7 +45,7 @@ namespace BitTorrent Q_DISABLE_COPY_MOVE(BencodeResumeDataStorage) public: - explicit BencodeResumeDataStorage(const QString &path, QObject *parent = nullptr); + explicit BencodeResumeDataStorage(const Path &path, QObject *parent = nullptr); ~BencodeResumeDataStorage() override; QVector registeredTorrents() const override; @@ -54,10 +55,10 @@ namespace BitTorrent void storeQueue(const QVector &queue) const override; private: - void loadQueue(const QString &queueFilename); + void loadQueue(const Path &queueFilename); std::optional loadTorrentResumeData(const QByteArray &data, const QByteArray &metadata) const; - const QDir m_resumeDataDir; + const Path m_resumeDataPath; QVector m_registeredTorrents; QThread *m_ioThread = nullptr; diff --git a/src/base/bittorrent/categoryoptions.cpp b/src/base/bittorrent/categoryoptions.cpp index 3efc0cbf9..94470f8ca 100644 --- a/src/base/bittorrent/categoryoptions.cpp +++ b/src/base/bittorrent/categoryoptions.cpp @@ -37,13 +37,13 @@ const QString OPTION_DOWNLOADPATH {QStringLiteral("download_path")}; BitTorrent::CategoryOptions BitTorrent::CategoryOptions::fromJSON(const QJsonObject &jsonObj) { CategoryOptions options; - options.savePath = jsonObj.value(OPTION_SAVEPATH).toString(); + options.savePath = Path(jsonObj.value(OPTION_SAVEPATH).toString()); const QJsonValue downloadPathValue = jsonObj.value(OPTION_DOWNLOADPATH); if (downloadPathValue.isBool()) options.downloadPath = {downloadPathValue.toBool(), {}}; else if (downloadPathValue.isString()) - options.downloadPath = {true, downloadPathValue.toString()}; + options.downloadPath = {true, Path(downloadPathValue.toString())}; return options; } @@ -54,13 +54,13 @@ QJsonObject BitTorrent::CategoryOptions::toJSON() const if (downloadPath) { if (downloadPath->enabled) - downloadPathValue = downloadPath->path; + downloadPathValue = downloadPath->path.data(); else downloadPathValue = false; } return { - {OPTION_SAVEPATH, savePath}, + {OPTION_SAVEPATH, savePath.data()}, {OPTION_DOWNLOADPATH, downloadPathValue} }; } diff --git a/src/base/bittorrent/categoryoptions.h b/src/base/bittorrent/categoryoptions.h index 61de80fa8..6f834ea48 100644 --- a/src/base/bittorrent/categoryoptions.h +++ b/src/base/bittorrent/categoryoptions.h @@ -32,6 +32,8 @@ #include +#include "base/path.h" + class QJsonObject; namespace BitTorrent @@ -41,10 +43,10 @@ namespace BitTorrent struct DownloadPathOption { bool enabled; - QString path; + Path path; }; - QString savePath; + Path savePath; std::optional downloadPath; static CategoryOptions fromJSON(const QJsonObject &jsonObj); diff --git a/src/base/bittorrent/customstorage.cpp b/src/base/bittorrent/customstorage.cpp index 8c14edeb5..e6c24785b 100644 --- a/src/base/bittorrent/customstorage.cpp +++ b/src/base/bittorrent/customstorage.cpp @@ -30,8 +30,6 @@ #include -#include - #include "base/utils/fs.h" #include "common.h" @@ -53,12 +51,13 @@ lt::storage_holder CustomDiskIOThread::new_torrent(const lt::storage_params &sto { lt::storage_holder storageHolder = m_nativeDiskIO->new_torrent(storageParams, torrent); - const QString savePath = Utils::Fs::expandPathAbs(QString::fromStdString(storageParams.path)); + const Path savePath {storageParams.path}; m_storageData[storageHolder] = { - savePath - , storageParams.mapped_files ? *storageParams.mapped_files : storageParams.files - , storageParams.priorities}; + savePath, + storageParams.mapped_files ? *storageParams.mapped_files : storageParams.files, + storageParams.priorities + }; return storageHolder; } @@ -99,7 +98,7 @@ void CustomDiskIOThread::async_hash2(lt::storage_index_t storage, lt::piece_inde void CustomDiskIOThread::async_move_storage(lt::storage_index_t storage, std::string path, lt::move_flags_t flags , std::function handler) { - const QString newSavePath {Utils::Fs::expandPathAbs(QString::fromStdString(path))}; + const Path newSavePath {path}; if (flags == lt::move_flags_t::dont_replace) handleCompleteFiles(storage, newSavePath); @@ -192,9 +191,8 @@ void CustomDiskIOThread::settings_updated() m_nativeDiskIO->settings_updated(); } -void CustomDiskIOThread::handleCompleteFiles(lt::storage_index_t storage, const QString &savePath) +void CustomDiskIOThread::handleCompleteFiles(lt::storage_index_t storage, const Path &savePath) { - const QDir saveDir {savePath}; const StorageData storageData = m_storageData[storage]; const lt::file_storage &fileStorage = storageData.files; for (const lt::file_index_t fileIndex : fileStorage.file_range()) @@ -206,16 +204,16 @@ void CustomDiskIOThread::handleCompleteFiles(lt::storage_index_t storage, const // ignore pad files if (fileStorage.pad_file_at(fileIndex)) continue; - const QString filePath = QString::fromStdString(fileStorage.file_path(fileIndex)); - if (filePath.endsWith(QB_EXT)) + const Path filePath {fileStorage.file_path(fileIndex)}; + if (filePath.hasExtension(QB_EXT)) { - const QString completeFilePath = filePath.left(filePath.size() - QB_EXT.size()); - QFile completeFile {saveDir.absoluteFilePath(completeFilePath)}; - if (completeFile.exists()) + const Path incompleteFilePath = savePath / filePath; + Path completeFilePath = incompleteFilePath; + completeFilePath.removeExtension(); + if (completeFilePath.exists()) { - QFile incompleteFile {saveDir.absoluteFilePath(filePath)}; - incompleteFile.remove(); - completeFile.rename(incompleteFile.fileName()); + Utils::Fs::removeFile(incompleteFilePath); + Utils::Fs::renameFile(completeFilePath, incompleteFilePath); } } } @@ -230,7 +228,7 @@ lt::storage_interface *customStorageConstructor(const lt::storage_params ¶ms CustomStorage::CustomStorage(const lt::storage_params ¶ms, lt::file_pool &filePool) : lt::default_storage {params, filePool} - , m_savePath {Utils::Fs::expandPathAbs(QString::fromStdString(params.path))} + , m_savePath {params.path} { } @@ -248,7 +246,7 @@ void CustomStorage::set_file_priority(lt::aux::vector +#include "base/path.h" + #ifdef QBT_USES_LIBTORRENT2 #include #include @@ -86,13 +88,13 @@ public: void settings_updated() override; private: - void handleCompleteFiles(libtorrent::storage_index_t storage, const QString &savePath); + void handleCompleteFiles(libtorrent::storage_index_t storage, const Path &savePath); std::unique_ptr m_nativeDiskIO; struct StorageData { - QString savePath; + Path savePath; lt::file_storage files; lt::aux::vector filePriorities; }; @@ -113,9 +115,9 @@ public: lt::status_t move_storage(const std::string &savePath, lt::move_flags_t flags, lt::storage_error &ec) override; private: - void handleCompleteFiles(const QString &savePath); + void handleCompleteFiles(const Path &savePath); lt::aux::vector m_filePriorities; - QString m_savePath; + Path m_savePath; }; #endif diff --git a/src/base/bittorrent/dbresumedatastorage.cpp b/src/base/bittorrent/dbresumedatastorage.cpp index 8b5e09b0c..b0b866d58 100644 --- a/src/base/bittorrent/dbresumedatastorage.cpp +++ b/src/base/bittorrent/dbresumedatastorage.cpp @@ -49,6 +49,7 @@ #include "base/exceptions.h" #include "base/global.h" #include "base/logger.h" +#include "base/path.h" #include "base/profile.h" #include "base/utils/fs.h" #include "base/utils/string.h" @@ -173,7 +174,7 @@ namespace BitTorrent Q_DISABLE_COPY_MOVE(Worker) public: - Worker(const QString &dbPath, const QString &dbConnectionName); + Worker(const Path &dbPath, const QString &dbConnectionName); void openDatabase() const; void closeDatabase() const; @@ -183,19 +184,19 @@ namespace BitTorrent void storeQueue(const QVector &queue) const; private: - const QString m_path; + const Path m_path; const QString m_connectionName; }; } -BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const QString &dbPath, QObject *parent) +BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject *parent) : ResumeDataStorage {parent} , m_ioThread {new QThread(this)} { - const bool needCreateDB = !QFile::exists(dbPath); + const bool needCreateDB = !dbPath.exists(); auto db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), DB_CONNECTION_NAME); - db.setDatabaseName(dbPath); + db.setDatabaseName(dbPath.data()); if (!db.open()) throw RuntimeError(db.lastError().text()); @@ -308,12 +309,12 @@ std::optional BitTorrent::DBResumeDataStorage::lo resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool(); resumeData.savePath = Profile::instance()->fromPortablePath( - Utils::Fs::toUniformPath(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString())); + Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString())); resumeData.useAutoTMM = resumeData.savePath.isEmpty(); if (!resumeData.useAutoTMM) { resumeData.downloadPath = Profile::instance()->fromPortablePath( - Utils::Fs::toUniformPath(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString())); + Path(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString())); } const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray(); @@ -325,13 +326,11 @@ std::optional BitTorrent::DBResumeDataStorage::lo lt::error_code ec; const lt::bdecode_node root = lt::bdecode(allData, ec); - resumeData.downloadPath = Profile::instance()->fromPortablePath( - Utils::Fs::toUniformPath(fromLTString(root.dict_find_string_value("qBt-downloadPath")))); - lt::add_torrent_params &p = resumeData.ltAddTorrentParams; p = lt::read_resume_data(root, ec); - p.save_path = Profile::instance()->fromPortablePath(fromLTString(p.save_path)).toStdString(); + p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path))) + .toString().toStdString(); return resumeData; } @@ -485,7 +484,7 @@ void BitTorrent::DBResumeDataStorage::updateDBFromVersion1() const } } -BitTorrent::DBResumeDataStorage::Worker::Worker(const QString &dbPath, const QString &dbConnectionName) +BitTorrent::DBResumeDataStorage::Worker::Worker(const Path &dbPath, const QString &dbConnectionName) : m_path {dbPath} , m_connectionName {dbConnectionName} { @@ -494,7 +493,7 @@ BitTorrent::DBResumeDataStorage::Worker::Worker(const QString &dbPath, const QSt void BitTorrent::DBResumeDataStorage::Worker::openDatabase() const { auto db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), m_connectionName); - db.setDatabaseName(m_path); + db.setDatabaseName(m_path.data()); if (!db.open()) throw RuntimeError(db.lastError().text()); } @@ -508,7 +507,8 @@ void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const L { // We need to adjust native libtorrent resume data lt::add_torrent_params p = resumeData.ltAddTorrentParams; - p.save_path = Profile::instance()->toPortablePath(QString::fromStdString(p.save_path)).toStdString(); + p.save_path = Profile::instance()->toPortablePath(Path(p.save_path)) + .toString().toStdString(); if (resumeData.stopped) { p.flags |= lt::torrent_flags::paused; @@ -603,8 +603,8 @@ void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const L if (!resumeData.useAutoTMM) { - query.bindValue(DB_COLUMN_TARGET_SAVE_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.savePath)); - query.bindValue(DB_COLUMN_DOWNLOAD_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.downloadPath)); + query.bindValue(DB_COLUMN_TARGET_SAVE_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.savePath).data()); + query.bindValue(DB_COLUMN_DOWNLOAD_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.downloadPath).data()); } query.bindValue(DB_COLUMN_RESUMEDATA.placeholder, bencodedResumeData); diff --git a/src/base/bittorrent/dbresumedatastorage.h b/src/base/bittorrent/dbresumedatastorage.h index df784aec3..b8b2a149e 100644 --- a/src/base/bittorrent/dbresumedatastorage.h +++ b/src/base/bittorrent/dbresumedatastorage.h @@ -28,6 +28,7 @@ #pragma once +#include "base/pathfwd.h" #include "resumedatastorage.h" class QThread; @@ -40,7 +41,7 @@ namespace BitTorrent Q_DISABLE_COPY_MOVE(DBResumeDataStorage) public: - explicit DBResumeDataStorage(const QString &dbPath, QObject *parent = nullptr); + explicit DBResumeDataStorage(const Path &dbPath, QObject *parent = nullptr); ~DBResumeDataStorage() override; QVector registeredTorrents() const override; diff --git a/src/base/bittorrent/filesearcher.cpp b/src/base/bittorrent/filesearcher.cpp index 63209c0f1..fbf7070b2 100644 --- a/src/base/bittorrent/filesearcher.cpp +++ b/src/base/bittorrent/filesearcher.cpp @@ -27,37 +27,33 @@ */ #include "filesearcher.h" - -#include - #include "base/bittorrent/common.h" #include "base/bittorrent/infohash.h" -void FileSearcher::search(const BitTorrent::TorrentID &id, const QStringList &originalFileNames - , const QString &savePath, const QString &downloadPath) +void FileSearcher::search(const BitTorrent::TorrentID &id, const PathList &originalFileNames + , const Path &savePath, const Path &downloadPath) { - const auto findInDir = [](const QString &dirPath, QStringList &fileNames) -> bool + const auto findInDir = [](const Path &dirPath, PathList &fileNames) -> bool { - const QDir dir {dirPath}; bool found = false; - for (QString &fileName : fileNames) + for (Path &fileName : fileNames) { - if (dir.exists(fileName)) + if ((dirPath / fileName).exists()) { found = true; } - else if (dir.exists(fileName + QB_EXT)) + else if ((dirPath / fileName + QB_EXT).exists()) { found = true; - fileName += QB_EXT; + fileName = fileName + QB_EXT; } } return found; }; - QString usedPath = savePath; - QStringList adjustedFileNames = originalFileNames; + Path usedPath = savePath; + PathList adjustedFileNames = originalFileNames; const bool found = findInDir(usedPath, adjustedFileNames); if (!found && !downloadPath.isEmpty()) { diff --git a/src/base/bittorrent/filesearcher.h b/src/base/bittorrent/filesearcher.h index f167d8fa1..bcdc6bdfe 100644 --- a/src/base/bittorrent/filesearcher.h +++ b/src/base/bittorrent/filesearcher.h @@ -30,6 +30,8 @@ #include +#include "base/path.h" + namespace BitTorrent { class TorrentID; @@ -44,9 +46,9 @@ public: FileSearcher() = default; public slots: - void search(const BitTorrent::TorrentID &id, const QStringList &originalFileNames - , const QString &savePath, const QString &downloadPath); + void search(const BitTorrent::TorrentID &id, const PathList &originalFileNames + , const Path &savePath, const Path &downloadPath); signals: - void searchFinished(const BitTorrent::TorrentID &id, const QString &savePath, const QStringList &fileNames); + void searchFinished(const BitTorrent::TorrentID &id, const Path &savePath, const PathList &fileNames); }; diff --git a/src/base/bittorrent/filterparserthread.cpp b/src/base/bittorrent/filterparserthread.cpp index 05e2b2719..6e1eec2c1 100644 --- a/src/base/bittorrent/filterparserthread.cpp +++ b/src/base/bittorrent/filterparserthread.cpp @@ -124,7 +124,7 @@ FilterParserThread::~FilterParserThread() int FilterParserThread::parseDATFilterFile() { int ruleCount = 0; - QFile file(m_filePath); + QFile file {m_filePath.data()}; if (!file.exists()) return ruleCount; if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) @@ -288,7 +288,7 @@ int FilterParserThread::parseDATFilterFile() int FilterParserThread::parseP2PFilterFile() { int ruleCount = 0; - QFile file(m_filePath); + QFile file {m_filePath.data()}; if (!file.exists()) return ruleCount; if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) @@ -469,7 +469,7 @@ int FilterParserThread::getlineInStream(QDataStream &stream, std::string &name, int FilterParserThread::parseP2BFilterFile() { int ruleCount = 0; - QFile file(m_filePath); + QFile file {m_filePath.data()}; if (!file.exists()) return ruleCount; if (!file.open(QIODevice::ReadOnly)) @@ -592,7 +592,7 @@ int FilterParserThread::parseP2BFilterFile() // * eMule IP list (DAT): http://wiki.phoenixlabs.org/wiki/DAT_Format // * PeerGuardian Text (P2P): http://wiki.phoenixlabs.org/wiki/P2P_Format // * PeerGuardian Binary (P2B): http://wiki.phoenixlabs.org/wiki/P2B_Format -void FilterParserThread::processFilterFile(const QString &filePath) +void FilterParserThread::processFilterFile(const Path &filePath) { if (isRunning()) { @@ -617,17 +617,17 @@ void FilterParserThread::run() { qDebug("Processing filter file"); int ruleCount = 0; - if (m_filePath.endsWith(".p2p", Qt::CaseInsensitive)) + if (m_filePath.hasExtension(QLatin1String(".p2p"))) { // PeerGuardian p2p file ruleCount = parseP2PFilterFile(); } - else if (m_filePath.endsWith(".p2b", Qt::CaseInsensitive)) + else if (m_filePath.hasExtension(QLatin1String(".p2b"))) { // PeerGuardian p2b file ruleCount = parseP2BFilterFile(); } - else if (m_filePath.endsWith(".dat", Qt::CaseInsensitive)) + else if (m_filePath.hasExtension(QLatin1String(".dat"))) { // eMule DAT format ruleCount = parseDATFilterFile(); diff --git a/src/base/bittorrent/filterparserthread.h b/src/base/bittorrent/filterparserthread.h index c5fc522c0..28a209a3a 100644 --- a/src/base/bittorrent/filterparserthread.h +++ b/src/base/bittorrent/filterparserthread.h @@ -32,16 +32,19 @@ #include +#include "base/path.h" + class QDataStream; class FilterParserThread final : public QThread { Q_OBJECT + Q_DISABLE_COPY_MOVE(FilterParserThread) public: FilterParserThread(QObject *parent = nullptr); ~FilterParserThread(); - void processFilterFile(const QString &filePath); + void processFilterFile(const Path &filePath); lt::ip_filter IPfilter(); signals: @@ -60,6 +63,6 @@ private: int parseP2BFilterFile(); bool m_abort; - QString m_filePath; + Path m_filePath; lt::ip_filter m_filter; }; diff --git a/src/base/bittorrent/loadtorrentparams.h b/src/base/bittorrent/loadtorrentparams.h index dcb090be9..4892bece8 100644 --- a/src/base/bittorrent/loadtorrentparams.h +++ b/src/base/bittorrent/loadtorrentparams.h @@ -32,6 +32,7 @@ #include +#include "base/path.h" #include "base/tagset.h" #include "torrent.h" #include "torrentcontentlayout.h" @@ -45,8 +46,8 @@ namespace BitTorrent QString name; QString category; TagSet tags; - QString savePath; - QString downloadPath; + Path savePath; + Path downloadPath; TorrentContentLayout contentLayout = TorrentContentLayout::Original; TorrentOperatingMode operatingMode = TorrentOperatingMode::AutoManaged; bool useAutoTMM = false; diff --git a/src/base/bittorrent/session.cpp b/src/base/bittorrent/session.cpp index 1d7454033..92c6fd9f4 100644 --- a/src/base/bittorrent/session.cpp +++ b/src/base/bittorrent/session.cpp @@ -108,7 +108,7 @@ using namespace BitTorrent; -const QString CATEGORIES_FILE_NAME {QStringLiteral("categories.json")}; +const Path CATEGORIES_FILE_NAME {"categories.json"}; namespace { @@ -394,8 +394,8 @@ Session::Session(QObject *parent) , clampValue(SeedChokingAlgorithm::RoundRobin, SeedChokingAlgorithm::AntiLeech)) , m_storedTags(BITTORRENT_SESSION_KEY("Tags")) , m_maxRatioAction(BITTORRENT_SESSION_KEY("MaxRatioAction"), Pause) - , m_savePath(BITTORRENT_SESSION_KEY("DefaultSavePath"), specialFolderLocation(SpecialFolder::Downloads), Utils::Fs::toUniformPath) - , m_downloadPath(BITTORRENT_SESSION_KEY("TempPath"), specialFolderLocation(SpecialFolder::Downloads) + QLatin1String("/temp"), Utils::Fs::toUniformPath) + , m_savePath(BITTORRENT_SESSION_KEY("DefaultSavePath"), specialFolderLocation(SpecialFolder::Downloads)) + , m_downloadPath(BITTORRENT_SESSION_KEY("TempPath"), (savePath() / Path("temp"))) , m_isDownloadPathEnabled(BITTORRENT_SESSION_KEY("TempPathEnabled"), false) , m_isSubcategoriesEnabled(BITTORRENT_SESSION_KEY("SubcategoriesEnabled"), false) , m_useCategoryPathsInManualMode(BITTORRENT_SESSION_KEY("UseCategoryPathsInManualMode"), false) @@ -594,36 +594,34 @@ void Session::setPreallocationEnabled(const bool enabled) m_isPreallocationEnabled = enabled; } -QString Session::torrentExportDirectory() const +Path Session::torrentExportDirectory() const { - return Utils::Fs::toUniformPath(m_torrentExportDirectory); + return m_torrentExportDirectory; } -void Session::setTorrentExportDirectory(QString path) +void Session::setTorrentExportDirectory(const Path &path) { - path = Utils::Fs::toUniformPath(path); if (path != torrentExportDirectory()) m_torrentExportDirectory = path; } -QString Session::finishedTorrentExportDirectory() const +Path Session::finishedTorrentExportDirectory() const { - return Utils::Fs::toUniformPath(m_finishedTorrentExportDirectory); + return m_finishedTorrentExportDirectory; } -void Session::setFinishedTorrentExportDirectory(QString path) +void Session::setFinishedTorrentExportDirectory(const Path &path) { - path = Utils::Fs::toUniformPath(path); if (path != finishedTorrentExportDirectory()) m_finishedTorrentExportDirectory = path; } -QString Session::savePath() const +Path Session::savePath() const { return m_savePath; } -QString Session::downloadPath() const +Path Session::downloadPath() const { return m_downloadPath; } @@ -667,20 +665,20 @@ CategoryOptions Session::categoryOptions(const QString &categoryName) const return m_categories.value(categoryName); } -QString Session::categorySavePath(const QString &categoryName) const +Path Session::categorySavePath(const QString &categoryName) const { - const QString basePath = savePath(); + const Path basePath = savePath(); if (categoryName.isEmpty()) return basePath; - QString path = m_categories.value(categoryName).savePath; + Path path = m_categories.value(categoryName).savePath; if (path.isEmpty()) // use implicit save path - path = Utils::Fs::toValidFileSystemName(categoryName, true); + path = Utils::Fs::toValidPath(categoryName); - return (QDir::isAbsolutePath(path) ? path : Utils::Fs::resolvePath(path, basePath)); + return (path.isAbsolute() ? path : (basePath / path)); } -QString Session::categoryDownloadPath(const QString &categoryName) const +Path Session::categoryDownloadPath(const QString &categoryName) const { const CategoryOptions categoryOptions = m_categories.value(categoryName); const CategoryOptions::DownloadPathOption downloadPathOption = @@ -688,15 +686,15 @@ QString Session::categoryDownloadPath(const QString &categoryName) const if (!downloadPathOption.enabled) return {}; - const QString basePath = downloadPath(); + const Path basePath = downloadPath(); if (categoryName.isEmpty()) return basePath; - const QString path = (!downloadPathOption.path.isEmpty() + const Path path = (!downloadPathOption.path.isEmpty() ? downloadPathOption.path - : Utils::Fs::toValidFileSystemName(categoryName, true)); // use implicit download path + : Utils::Fs::toValidPath(categoryName)); // use implicit download path - return (QDir::isAbsolutePath(path) ? path : Utils::Fs::resolvePath(path, basePath)); + return (path.isAbsolute() ? path : (basePath / path)); } bool Session::addCategory(const QString &name, const CategoryOptions &options) @@ -1722,7 +1720,7 @@ void Session::handleDownloadFinished(const Net::DownloadResult &result) } } -void Session::fileSearchFinished(const TorrentID &id, const QString &savePath, const QStringList &fileNames) +void Session::fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames) { TorrentImpl *torrent = m_torrents.value(id); if (torrent) @@ -1739,11 +1737,11 @@ void Session::fileSearchFinished(const TorrentID &id, const QString &savePath, c lt::add_torrent_params &p = params.ltAddTorrentParams; - p.save_path = Utils::Fs::toNativePath(savePath).toStdString(); + p.save_path = savePath.toString().toStdString(); const TorrentInfo torrentInfo {*p.ti}; const auto nativeIndexes = torrentInfo.nativeIndexes(); for (int i = 0; i < fileNames.size(); ++i) - p.renamed_files[nativeIndexes[i]] = fileNames[i].toStdString(); + p.renamed_files[nativeIndexes[i]] = fileNames[i].toString().toStdString(); loadTorrent(params); } @@ -1811,7 +1809,7 @@ bool Session::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption // Remove it from session if (deleteOption == DeleteTorrent) { - m_removingTorrents[torrent->id()] = {torrent->name(), "", deleteOption}; + m_removingTorrents[torrent->id()] = {torrent->name(), {}, deleteOption}; const lt::torrent_handle nativeHandle {torrent->nativeHandle()}; const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end() @@ -2033,8 +2031,9 @@ bool Session::addTorrent(const QString &source, const AddTorrentParams ¶ms) if (magnetUri.isValid()) return addTorrent(magnetUri, params); - TorrentFileGuard guard {source}; - const nonstd::expected loadResult = TorrentInfo::loadFromFile(source); + const Path path {source}; + TorrentFileGuard guard {path}; + const nonstd::expected loadResult = TorrentInfo::loadFromFile(path); if (!loadResult) { LogMsg(tr("Couldn't load torrent: %1").arg(loadResult.error()), Log::WARNING); @@ -2079,27 +2078,27 @@ LoadTorrentParams Session::initLoadTorrentParams(const AddTorrentParams &addTorr if (!loadTorrentParams.useAutoTMM) { - if (QDir::isAbsolutePath(addTorrentParams.savePath)) + if (addTorrentParams.savePath.isAbsolute()) { loadTorrentParams.savePath = addTorrentParams.savePath; } else { - const QString basePath = useCategoryPathsInManualMode() ? categorySavePath(loadTorrentParams.category) : savePath(); - loadTorrentParams.savePath = Utils::Fs::resolvePath(addTorrentParams.savePath, basePath); + const Path basePath = useCategoryPathsInManualMode() ? categorySavePath(loadTorrentParams.category) : savePath(); + loadTorrentParams.savePath = basePath / addTorrentParams.savePath; } const bool useDownloadPath = addTorrentParams.useDownloadPath.value_or(isDownloadPathEnabled()); if (useDownloadPath) { - if (QDir::isAbsolutePath(addTorrentParams.downloadPath)) + if (addTorrentParams.downloadPath.isAbsolute()) { loadTorrentParams.downloadPath = addTorrentParams.downloadPath; } else { - const QString basePath = useCategoryPathsInManualMode() ? categoryDownloadPath(loadTorrentParams.category) : downloadPath(); - loadTorrentParams.downloadPath = Utils::Fs::resolvePath(addTorrentParams.downloadPath, basePath); + const Path basePath = useCategoryPathsInManualMode() ? categoryDownloadPath(loadTorrentParams.category) : downloadPath(); + loadTorrentParams.downloadPath = basePath / addTorrentParams.downloadPath; } } } @@ -2165,7 +2164,7 @@ bool Session::addTorrent_impl(const std::variant &source bool isFindingIncompleteFiles = false; const bool useAutoTMM = loadTorrentParams.useAutoTMM; - const QString actualSavePath = useAutoTMM ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath; + const Path actualSavePath = useAutoTMM ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath; if (hasMetadata) { @@ -2175,16 +2174,16 @@ bool Session::addTorrent_impl(const std::variant &source const TorrentContentLayout contentLayout = ((loadTorrentParams.contentLayout == TorrentContentLayout::Original) ? detectContentLayout(torrentInfo.filePaths()) : loadTorrentParams.contentLayout); - QStringList filePaths = (!addTorrentParams.filePaths.isEmpty() ? addTorrentParams.filePaths : torrentInfo.filePaths()); - applyContentLayout(filePaths, contentLayout, Utils::Fs::findRootFolder(torrentInfo.filePaths())); + PathList filePaths = (!addTorrentParams.filePaths.isEmpty() ? addTorrentParams.filePaths : torrentInfo.filePaths()); + applyContentLayout(filePaths, contentLayout, Path::findRootFolder(torrentInfo.filePaths())); // if torrent name wasn't explicitly set we handle the case of // initial renaming of torrent content and rename torrent accordingly if (loadTorrentParams.name.isEmpty()) { - QString contentName = Utils::Fs::findRootFolder(filePaths); + QString contentName = Path::findRootFolder(filePaths).toString(); if (contentName.isEmpty() && (filePaths.size() == 1)) - contentName = Utils::Fs::fileName(filePaths.at(0)); + contentName = filePaths.at(0).filename(); if (!contentName.isEmpty() && (contentName != torrentInfo.name())) loadTorrentParams.name = contentName; @@ -2192,7 +2191,7 @@ bool Session::addTorrent_impl(const std::variant &source if (!loadTorrentParams.hasSeedStatus) { - const QString actualDownloadPath = useAutoTMM + const Path actualDownloadPath = useAutoTMM ? categoryDownloadPath(loadTorrentParams.category) : loadTorrentParams.downloadPath; findIncompleteFiles(torrentInfo, actualSavePath, actualDownloadPath, filePaths); isFindingIncompleteFiles = true; @@ -2202,7 +2201,7 @@ bool Session::addTorrent_impl(const std::variant &source if (!filePaths.isEmpty()) { for (int index = 0; index < addTorrentParams.filePaths.size(); ++index) - p.renamed_files[nativeIndexes[index]] = Utils::Fs::toNativePath(addTorrentParams.filePaths.at(index)).toStdString(); + p.renamed_files[nativeIndexes[index]] = addTorrentParams.filePaths.at(index).toString().toStdString(); } Q_ASSERT(p.file_priorities.empty()); @@ -2224,7 +2223,7 @@ bool Session::addTorrent_impl(const std::variant &source loadTorrentParams.name = QString::fromStdString(p.name); } - p.save_path = Utils::Fs::toNativePath(actualSavePath).toStdString(); + p.save_path = actualSavePath.toString().toStdString(); p.upload_limit = addTorrentParams.uploadLimit; p.download_limit = addTorrentParams.downloadLimit; @@ -2288,13 +2287,13 @@ bool Session::loadTorrent(LoadTorrentParams params) return true; } -void Session::findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath - , const QString &downloadPath, const QStringList &filePaths) const +void Session::findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath + , const Path &downloadPath, const PathList &filePaths) const { Q_ASSERT(filePaths.isEmpty() || (filePaths.size() == torrentInfo.filesCount())); const auto searchId = TorrentID::fromInfoHash(torrentInfo.infoHash()); - const QStringList originalFileNames = (filePaths.isEmpty() ? torrentInfo.filePaths() : filePaths); + const PathList originalFileNames = (filePaths.isEmpty() ? torrentInfo.filePaths() : filePaths); QMetaObject::invokeMethod(m_fileSearcher, [=]() { m_fileSearcher->search(searchId, originalFileNames, savePath, downloadPath); @@ -2333,8 +2332,8 @@ bool Session::downloadMetadata(const MagnetUri &magnetUri) p.max_connections = maxConnectionsPerTorrent(); p.max_uploads = maxUploadsPerTorrent(); - const QString savePath = Utils::Fs::tempPath() + id.toString(); - p.save_path = Utils::Fs::toNativePath(savePath).toStdString(); + const Path savePath = Utils::Fs::tempPath() / Path(id.toString()); + p.save_path = savePath.toString().toStdString(); // Forced start p.flags &= ~lt::torrent_flags::paused; @@ -2360,28 +2359,27 @@ bool Session::downloadMetadata(const MagnetUri &magnetUri) return true; } -void Session::exportTorrentFile(const TorrentInfo &torrentInfo, const QString &folderPath, const QString &baseName) +void Session::exportTorrentFile(const TorrentInfo &torrentInfo, const Path &folderPath, const QString &baseName) { - const QString validName = Utils::Fs::toValidFileSystemName(baseName); - QString torrentExportFilename = QString::fromLatin1("%1.torrent").arg(validName); - const QDir exportDir {folderPath}; - if (exportDir.exists() || exportDir.mkpath(exportDir.absolutePath())) - { - QString newTorrentPath = exportDir.absoluteFilePath(torrentExportFilename); - int counter = 0; - while (QFile::exists(newTorrentPath)) - { - // Append number to torrent name to make it unique - torrentExportFilename = QString::fromLatin1("%1 %2.torrent").arg(validName).arg(++counter); - newTorrentPath = exportDir.absoluteFilePath(torrentExportFilename); - } + if (!folderPath.exists() && !Utils::Fs::mkpath(folderPath)) + return; - const nonstd::expected result = torrentInfo.saveToFile(newTorrentPath); - if (!result) - { - LogMsg(tr("Couldn't export torrent metadata file '%1'. Reason: %2.") - .arg(newTorrentPath, result.error()), Log::WARNING); - } + const QString validName = Utils::Fs::toValidFileName(baseName); + QString torrentExportFilename = QString::fromLatin1("%1.torrent").arg(validName); + Path newTorrentPath = folderPath / Path(torrentExportFilename); + int counter = 0; + while (newTorrentPath.exists()) + { + // Append number to torrent name to make it unique + torrentExportFilename = QString::fromLatin1("%1 %2.torrent").arg(validName).arg(++counter); + newTorrentPath = folderPath / Path(torrentExportFilename); + } + + const nonstd::expected result = torrentInfo.saveToFile(newTorrentPath); + if (!result) + { + LogMsg(tr("Couldn't export torrent metadata file '%1'. Reason: %2.") + .arg(newTorrentPath.toString(), result.error()), Log::WARNING); } } @@ -2455,13 +2453,13 @@ void Session::removeTorrentsQueue() const m_resumeDataStorage->storeQueue({}); } -void Session::setSavePath(const QString &path) +void Session::setSavePath(const Path &path) { - const QString baseSavePath = specialFolderLocation(SpecialFolder::Downloads); - const QString resolvedPath = (QDir::isAbsolutePath(path) ? path : Utils::Fs::resolvePath(path, baseSavePath)); - if (resolvedPath == m_savePath) return; + const auto newPath = (path.isAbsolute() ? path : (specialFolderLocation(SpecialFolder::Downloads) / path)); + if (newPath == m_savePath) + return; - m_savePath = resolvedPath; + m_savePath = newPath; if (isDisableAutoTMMWhenDefaultSavePathChanged()) { @@ -2475,13 +2473,12 @@ void Session::setSavePath(const QString &path) } } -void Session::setDownloadPath(const QString &path) +void Session::setDownloadPath(const Path &path) { - const QString baseDownloadPath = specialFolderLocation(SpecialFolder::Downloads) + QLatin1String("/temp"); - const QString resolvedPath = (QDir::isAbsolutePath(path) ? path : Utils::Fs::resolvePath(path, baseDownloadPath)); - if (resolvedPath != m_downloadPath) + const Path newPath = (path.isAbsolute() ? path : (savePath() / Path("temp") / path)); + if (newPath != m_downloadPath) { - m_downloadPath = resolvedPath; + m_downloadPath = newPath; for (TorrentImpl *const torrent : asConst(m_torrents)) torrent->handleDownloadPathChanged(); @@ -2953,14 +2950,13 @@ void Session::setIPFilteringEnabled(const bool enabled) } } -QString Session::IPFilterFile() const +Path Session::IPFilterFile() const { - return Utils::Fs::toUniformPath(m_IPFilterFile); + return m_IPFilterFile; } -void Session::setIPFilterFile(QString path) +void Session::setIPFilterFile(const Path &path) { - path = Utils::Fs::toUniformPath(path); if (path != IPFilterFile()) { m_IPFilterFile = path; @@ -3994,13 +3990,13 @@ void Session::handleTorrentFinished(TorrentImpl *const torrent) qDebug("Checking if the torrent contains torrent files to download"); // Check if there are torrent files inside - for (const QString &torrentRelpath : asConst(torrent->filePaths())) + for (const Path &torrentRelpath : asConst(torrent->filePaths())) { - if (torrentRelpath.endsWith(".torrent", Qt::CaseInsensitive)) + if (torrentRelpath.hasExtension(QLatin1String(".torrent"))) { qDebug("Found possible recursive torrent download."); - const QString torrentFullpath = torrent->actualStorageLocation() + '/' + torrentRelpath; - qDebug("Full subtorrent path is %s", qUtf8Printable(torrentFullpath)); + const Path torrentFullpath = torrent->actualStorageLocation() / torrentRelpath; + qDebug("Full subtorrent path is %s", qUtf8Printable(torrentFullpath.toString())); if (TorrentInfo::loadFromFile(torrentFullpath)) { qDebug("emitting recursiveTorrentDownloadPossible()"); @@ -4010,7 +4006,7 @@ void Session::handleTorrentFinished(TorrentImpl *const torrent) else { qDebug("Caught error loading torrent"); - LogMsg(tr("Unable to decode '%1' torrent file.").arg(Utils::Fs::toNativePath(torrentFullpath)), Log::CRITICAL); + LogMsg(tr("Unable to decode '%1' torrent file.").arg(torrentFullpath.toString()), Log::CRITICAL); } } } @@ -4047,12 +4043,12 @@ void Session::handleTorrentTrackerError(TorrentImpl *const torrent, const QStrin emit trackerError(torrent, trackerUrl); } -bool Session::addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newPath, const MoveStorageMode mode) +bool Session::addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, const MoveStorageMode mode) { Q_ASSERT(torrent); const lt::torrent_handle torrentHandle = torrent->nativeHandle(); - const QString currentLocation = Utils::Fs::toNativePath(torrent->actualStorageLocation()); + const Path currentLocation = torrent->actualStorageLocation(); if (m_moveStorageQueue.size() > 1) { @@ -4065,7 +4061,7 @@ bool Session::addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newP if (iter != m_moveStorageQueue.end()) { // remove existing inactive job - LogMsg(tr("Cancelled moving \"%1\" from \"%2\" to \"%3\".").arg(torrent->name(), currentLocation, iter->path)); + LogMsg(tr("Cancelled moving \"%1\" from \"%2\" to \"%3\".").arg(torrent->name(), currentLocation.toString(), iter->path.toString())); iter = m_moveStorageQueue.erase(iter); iter = std::find_if(iter, m_moveStorageQueue.end(), [&torrentHandle](const MoveStorageJob &job) @@ -4082,26 +4078,26 @@ bool Session::addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newP { // if there is active job for this torrent prevent creating meaningless // job that will move torrent to the same location as current one - if (QDir {m_moveStorageQueue.first().path} == QDir {newPath}) + if (m_moveStorageQueue.first().path == newPath) { LogMsg(tr("Couldn't enqueue move of \"%1\" to \"%2\". Torrent is currently moving to the same destination location.") - .arg(torrent->name(), newPath)); + .arg(torrent->name(), newPath.toString())); return false; } } else { - if (QDir {currentLocation} == QDir {newPath}) + if (currentLocation == newPath) { LogMsg(tr("Couldn't enqueue move of \"%1\" from \"%2\" to \"%3\". Both paths point to the same location.") - .arg(torrent->name(), currentLocation, newPath)); + .arg(torrent->name(), currentLocation.toString(), newPath.toString())); return false; } } const MoveStorageJob moveStorageJob {torrentHandle, newPath, mode}; m_moveStorageQueue << moveStorageJob; - LogMsg(tr("Enqueued to move \"%1\" from \"%2\" to \"%3\".").arg(torrent->name(), currentLocation, newPath)); + LogMsg(tr("Enqueued to move \"%1\" from \"%2\" to \"%3\".").arg(torrent->name(), currentLocation.toString(), newPath.toString())); if (m_moveStorageQueue.size() == 1) moveTorrentStorage(moveStorageJob); @@ -4118,9 +4114,9 @@ void Session::moveTorrentStorage(const MoveStorageJob &job) const #endif const TorrentImpl *torrent = m_torrents.value(id); const QString torrentName = (torrent ? torrent->name() : id.toString()); - LogMsg(tr("Moving \"%1\" to \"%2\"...").arg(torrentName, job.path)); + LogMsg(tr("Moving \"%1\" to \"%2\"...").arg(torrentName, job.path.toString())); - job.torrentHandle.move_storage(job.path.toUtf8().constData() + job.torrentHandle.move_storage(job.path.toString().toStdString() , ((job.mode == MoveStorageMode::Overwrite) ? lt::move_flags_t::always_replace_files : lt::move_flags_t::dont_replace)); } @@ -4164,13 +4160,13 @@ void Session::storeCategories() const jsonObj[categoryName] = categoryOptions.toJSON(); } - const QString path = QDir(specialFolderLocation(SpecialFolder::Config)).absoluteFilePath(CATEGORIES_FILE_NAME); + const Path path = specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME; const QByteArray data = QJsonDocument(jsonObj).toJson(); const nonstd::expected result = Utils::IO::saveToFile(path, data); if (!result) { LogMsg(tr("Couldn't store Categories configuration to %1. Error: %2") - .arg(path, result.error()), Log::WARNING); + .arg(path.toString(), result.error()), Log::WARNING); } } @@ -4181,7 +4177,7 @@ void Session::upgradeCategories() { const QString categoryName = it.key(); CategoryOptions categoryOptions; - categoryOptions.savePath = it.value().toString(); + categoryOptions.savePath = Path(it.value().toString()); m_categories[categoryName] = categoryOptions; } @@ -4192,7 +4188,7 @@ void Session::loadCategories() { m_categories.clear(); - QFile confFile {QDir(specialFolderLocation(SpecialFolder::Config)).absoluteFilePath(CATEGORIES_FILE_NAME)}; + QFile confFile {(specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME).data()}; if (!confFile.exists()) { // TODO: Remove the following upgrade code in v4.5 @@ -4308,14 +4304,14 @@ void Session::recursiveTorrentDownload(const TorrentID &id) TorrentImpl *const torrent = m_torrents.value(id); if (!torrent) return; - for (const QString &torrentRelpath : asConst(torrent->filePaths())) + for (const Path &torrentRelpath : asConst(torrent->filePaths())) { - if (torrentRelpath.endsWith(QLatin1String(".torrent"))) + if (torrentRelpath.hasExtension(QLatin1String(".torrent"))) { LogMsg(tr("Recursive download of file '%1' embedded in torrent '%2'" , "Recursive download of 'test.torrent' embedded in torrent 'test2'") - .arg(Utils::Fs::toNativePath(torrentRelpath), torrent->name())); - const QString torrentFullpath = torrent->savePath() + '/' + torrentRelpath; + .arg(torrentRelpath.toString(), torrent->name())); + const Path torrentFullpath = torrent->savePath() / torrentRelpath; AddTorrentParams params; // Passing the save path along to the sub torrent file @@ -4343,9 +4339,8 @@ void Session::startUpTorrents() { qDebug("Initializing torrents resume data storage..."); - const QString dbPath = Utils::Fs::expandPathAbs( - specialFolderLocation(SpecialFolder::Data) + QLatin1String("/torrents.db")); - const bool dbStorageExists = QFile::exists(dbPath); + const Path dbPath = specialFolderLocation(SpecialFolder::Data) / Path("torrents.db"); + const bool dbStorageExists = dbPath.exists(); ResumeDataStorage *startupStorage = nullptr; if (resumeDataStorageType() == ResumeDataStorageType::SQLite) @@ -4354,15 +4349,13 @@ void Session::startUpTorrents() if (!dbStorageExists) { - const QString dataPath = Utils::Fs::expandPathAbs( - specialFolderLocation(SpecialFolder::Data) + QLatin1String("/BT_backup")); + const Path dataPath = specialFolderLocation(SpecialFolder::Data) / Path("BT_backup"); startupStorage = new BencodeResumeDataStorage(dataPath, this); } } else { - const QString dataPath = Utils::Fs::expandPathAbs( - specialFolderLocation(SpecialFolder::Data) + QLatin1String("/BT_backup")); + const Path dataPath = specialFolderLocation(SpecialFolder::Data) / Path("BT_backup"); m_resumeDataStorage = new BencodeResumeDataStorage(dataPath, this); if (dbStorageExists) @@ -4491,7 +4484,7 @@ void Session::startUpTorrents() { delete startupStorage; if (resumeDataStorageType() == ResumeDataStorageType::Legacy) - Utils::Fs::forceRemove(dbPath); + Utils::Fs::removeFile(dbPath); if (isQueueingSystemEnabled()) m_resumeDataStorage->storeQueue(queue); @@ -5064,7 +5057,7 @@ void Session::handleStorageMovedAlert(const lt::storage_moved_alert *p) const MoveStorageJob ¤tJob = m_moveStorageQueue.first(); Q_ASSERT(currentJob.torrentHandle == p->handle); - const QString newPath {p->storage_path()}; + const Path newPath {QString::fromUtf8(p->storage_path())}; Q_ASSERT(newPath == currentJob.path); #ifdef QBT_USES_LIBTORRENT2 @@ -5075,7 +5068,7 @@ void Session::handleStorageMovedAlert(const lt::storage_moved_alert *p) TorrentImpl *torrent = m_torrents.value(id); const QString torrentName = (torrent ? torrent->name() : id.toString()); - LogMsg(tr("\"%1\" is successfully moved to \"%2\".").arg(torrentName, newPath)); + LogMsg(tr("\"%1\" is successfully moved to \"%2\".").arg(torrentName, newPath.toString())); handleMoveTorrentStorageJobFinished(); } @@ -5098,7 +5091,7 @@ void Session::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert const QString currentLocation = QString::fromStdString(p->handle.status(lt::torrent_handle::query_save_path).save_path); const QString errorMessage = QString::fromStdString(p->message()); LogMsg(tr("Failed to move \"%1\" from \"%2\" to \"%3\". Reason: %4.") - .arg(torrentName, currentLocation, currentJob.path, errorMessage), Log::CRITICAL); + .arg(torrentName, currentLocation, currentJob.path.toString(), errorMessage), Log::CRITICAL); handleMoveTorrentStorageJobFinished(); } diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index 2a49c937b..6ff71559d 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -43,6 +43,7 @@ #include #include +#include "base/path.h" #include "base/settingvalue.h" #include "base/types.h" #include "addtorrentparams.h" @@ -202,7 +203,7 @@ namespace BitTorrent } disk; }; - class Session : public QObject + class Session final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(Session) @@ -212,10 +213,10 @@ namespace BitTorrent static void freeInstance(); static Session *instance(); - QString savePath() const; - void setSavePath(const QString &path); - QString downloadPath() const; - void setDownloadPath(const QString &path); + Path savePath() const; + void setSavePath(const Path &path); + Path downloadPath() const; + void setDownloadPath(const Path &path); bool isDownloadPathEnabled() const; void setDownloadPathEnabled(bool enabled); @@ -225,8 +226,8 @@ namespace BitTorrent QStringList categories() const; CategoryOptions categoryOptions(const QString &categoryName) const; - QString categorySavePath(const QString &categoryName) const; - QString categoryDownloadPath(const QString &categoryName) const; + Path categorySavePath(const QString &categoryName) const; + Path categoryDownloadPath(const QString &categoryName) const; bool addCategory(const QString &name, const CategoryOptions &options = {}); bool editCategory(const QString &name, const CategoryOptions &options); bool removeCategory(const QString &name); @@ -283,10 +284,10 @@ namespace BitTorrent void setRefreshInterval(int value); bool isPreallocationEnabled() const; void setPreallocationEnabled(bool enabled); - QString torrentExportDirectory() const; - void setTorrentExportDirectory(QString path); - QString finishedTorrentExportDirectory() const; - void setFinishedTorrentExportDirectory(QString path); + Path torrentExportDirectory() const; + void setTorrentExportDirectory(const Path &path); + Path finishedTorrentExportDirectory() const; + void setFinishedTorrentExportDirectory(const Path &path); int globalDownloadSpeedLimit() const; void setGlobalDownloadSpeedLimit(int limit); @@ -329,8 +330,8 @@ namespace BitTorrent void setAdditionalTrackers(const QString &trackers); bool isIPFilteringEnabled() const; void setIPFilteringEnabled(bool enabled); - QString IPFilterFile() const; - void setIPFilterFile(QString path); + Path IPFilterFile() const; + void setIPFilterFile(const Path &path); bool announceToAllTrackers() const; void setAnnounceToAllTrackers(bool val); bool announceToAllTiers() const; @@ -501,10 +502,10 @@ namespace BitTorrent void handleTorrentTrackerWarning(TorrentImpl *const torrent, const QString &trackerUrl); void handleTorrentTrackerError(TorrentImpl *const torrent, const QString &trackerUrl); - bool addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newPath, MoveStorageMode mode); + bool addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, MoveStorageMode mode); - void findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath - , const QString &downloadPath, const QStringList &filePaths = {}) const; + void findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath + , const Path &downloadPath, const PathList &filePaths = {}) const; signals: void allTorrentsFinished(); @@ -553,7 +554,7 @@ namespace BitTorrent void handleIPFilterParsed(int ruleCount); void handleIPFilterError(); void handleDownloadFinished(const Net::DownloadResult &result); - void fileSearchFinished(const TorrentID &id, const QString &savePath, const QStringList &fileNames); + void fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames); #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) // Session reconfiguration triggers @@ -565,14 +566,14 @@ namespace BitTorrent struct MoveStorageJob { lt::torrent_handle torrentHandle; - QString path; + Path path; MoveStorageMode mode; }; struct RemovingTorrentData { QString name; - QString pathToRemove; + Path pathToRemove; DeleteOption deleteOption; }; @@ -611,7 +612,7 @@ namespace BitTorrent bool addTorrent_impl(const std::variant &source, const AddTorrentParams &addTorrentParams); void updateSeedingLimitTimer(); - void exportTorrentFile(const TorrentInfo &torrentInfo, const QString &folderPath, const QString &baseName); + void exportTorrentFile(const TorrentInfo &torrentInfo, const Path &folderPath, const QString &baseName); void handleAlert(const lt::alert *a); void dispatchTorrentAlert(const lt::alert *a); @@ -663,7 +664,7 @@ namespace BitTorrent CachedSettingValue m_isPeXEnabled; CachedSettingValue m_isIPFilteringEnabled; CachedSettingValue m_isTrackerFilteringEnabled; - CachedSettingValue m_IPFilterFile; + CachedSettingValue m_IPFilterFile; CachedSettingValue m_announceToAllTrackers; CachedSettingValue m_announceToAllTiers; CachedSettingValue m_asyncIOThreads; @@ -721,8 +722,8 @@ namespace BitTorrent CachedSettingValue m_isAppendExtensionEnabled; CachedSettingValue m_refreshInterval; CachedSettingValue m_isPreallocationEnabled; - CachedSettingValue m_torrentExportDirectory; - CachedSettingValue m_finishedTorrentExportDirectory; + CachedSettingValue m_torrentExportDirectory; + CachedSettingValue m_finishedTorrentExportDirectory; CachedSettingValue m_globalDownloadSpeedLimit; CachedSettingValue m_globalUploadSpeedLimit; CachedSettingValue m_altGlobalDownloadSpeedLimit; @@ -740,8 +741,8 @@ namespace BitTorrent CachedSettingValue m_seedChokingAlgorithm; CachedSettingValue m_storedTags; CachedSettingValue m_maxRatioAction; - CachedSettingValue m_savePath; - CachedSettingValue m_downloadPath; + CachedSettingValue m_savePath; + CachedSettingValue m_downloadPath; CachedSettingValue m_isDownloadPathEnabled; CachedSettingValue m_isSubcategoriesEnabled; CachedSettingValue m_useCategoryPathsInManualMode; diff --git a/src/base/bittorrent/torrent.h b/src/base/bittorrent/torrent.h index 337b935f9..aaa548d70 100644 --- a/src/base/bittorrent/torrent.h +++ b/src/base/bittorrent/torrent.h @@ -29,10 +29,11 @@ #pragma once +#include #include #include -#include +#include "base/pathfwd.h" #include "base/tagset.h" #include "abstractfilestorage.h" @@ -169,13 +170,13 @@ namespace BitTorrent virtual bool isAutoTMMEnabled() const = 0; virtual void setAutoTMMEnabled(bool enabled) = 0; - virtual QString savePath() const = 0; - virtual void setSavePath(const QString &savePath) = 0; - virtual QString downloadPath() const = 0; - virtual void setDownloadPath(const QString &downloadPath) = 0; - virtual QString actualStorageLocation() const = 0; - virtual QString rootPath() const = 0; - virtual QString contentPath() const = 0; + virtual Path savePath() const = 0; + virtual void setSavePath(const Path &savePath) = 0; + virtual Path downloadPath() const = 0; + virtual void setDownloadPath(const Path &downloadPath) = 0; + virtual Path actualStorageLocation() const = 0; + virtual Path rootPath() const = 0; + virtual Path contentPath() const = 0; virtual QString category() const = 0; virtual bool belongsToCategory(const QString &category) const = 0; virtual bool setCategory(const QString &category) = 0; @@ -193,8 +194,8 @@ namespace BitTorrent virtual qreal ratioLimit() const = 0; virtual int seedingTimeLimit() const = 0; - virtual QString actualFilePath(int index) const = 0; - virtual QStringList filePaths() const = 0; + virtual Path actualFilePath(int index) const = 0; + virtual PathList filePaths() const = 0; virtual QVector filePriorities() const = 0; virtual TorrentInfo info() const = 0; diff --git a/src/base/bittorrent/torrentcontentlayout.cpp b/src/base/bittorrent/torrentcontentlayout.cpp index bb4c4a42b..8b3611ddd 100644 --- a/src/base/bittorrent/torrentcontentlayout.cpp +++ b/src/base/bittorrent/torrentcontentlayout.cpp @@ -32,36 +32,35 @@ namespace { - QString removeExtension(const QString &fileName) + Path removeExtension(const Path &fileName) { - const QString extension = Utils::Fs::fileExtension(fileName); - return extension.isEmpty() - ? fileName - : fileName.chopped(extension.size() + 1); + Path result = fileName; + result.removeExtension(); + return result; } } -BitTorrent::TorrentContentLayout BitTorrent::detectContentLayout(const QStringList &filePaths) +BitTorrent::TorrentContentLayout BitTorrent::detectContentLayout(const PathList &filePaths) { - const QString rootFolder = Utils::Fs::findRootFolder(filePaths); + const Path rootFolder = Path::findRootFolder(filePaths); return (rootFolder.isEmpty() ? TorrentContentLayout::NoSubfolder : TorrentContentLayout::Subfolder); } -void BitTorrent::applyContentLayout(QStringList &filePaths, const BitTorrent::TorrentContentLayout contentLayout, const QString &rootFolder) +void BitTorrent::applyContentLayout(PathList &filePaths, const BitTorrent::TorrentContentLayout contentLayout, const Path &rootFolder) { Q_ASSERT(!filePaths.isEmpty()); switch (contentLayout) { case TorrentContentLayout::Subfolder: - if (Utils::Fs::findRootFolder(filePaths).isEmpty()) - Utils::Fs::addRootFolder(filePaths, !rootFolder.isEmpty() ? rootFolder : removeExtension(filePaths.at(0))); + if (Path::findRootFolder(filePaths).isEmpty()) + Path::addRootFolder(filePaths, !rootFolder.isEmpty() ? rootFolder : removeExtension(filePaths.at(0))); break; case TorrentContentLayout::NoSubfolder: - Utils::Fs::stripRootFolder(filePaths); + Path::stripRootFolder(filePaths); break; default: diff --git a/src/base/bittorrent/torrentcontentlayout.h b/src/base/bittorrent/torrentcontentlayout.h index a93a6b2e1..9ebb35e2c 100644 --- a/src/base/bittorrent/torrentcontentlayout.h +++ b/src/base/bittorrent/torrentcontentlayout.h @@ -28,8 +28,11 @@ #pragma once +#include #include +#include "base/path.h" + namespace BitTorrent { // Using `Q_ENUM_NS()` without a wrapper namespace in our case is not advised @@ -49,6 +52,6 @@ namespace BitTorrent Q_ENUM_NS(TorrentContentLayout) } - TorrentContentLayout detectContentLayout(const QStringList &filePaths); - void applyContentLayout(QStringList &filePaths, TorrentContentLayout contentLayout, const QString &rootFolder = {}); + TorrentContentLayout detectContentLayout(const PathList &filePaths); + void applyContentLayout(PathList &filePaths, TorrentContentLayout contentLayout, const Path &rootFolder = {}); } diff --git a/src/base/bittorrent/torrentcreatorthread.cpp b/src/base/bittorrent/torrentcreatorthread.cpp index 80b6cc9b2..2aca5e2e5 100644 --- a/src/base/bittorrent/torrentcreatorthread.cpp +++ b/src/base/bittorrent/torrentcreatorthread.cpp @@ -52,7 +52,7 @@ namespace // name starts with a . bool fileFilter(const std::string &f) { - return !Utils::Fs::fileName(QString::fromStdString(f)).startsWith('.'); + return !Path(f).filename().startsWith('.'); } #ifdef QBT_USES_LIBTORRENT2 @@ -108,50 +108,50 @@ void TorrentCreatorThread::run() try { - const QString parentPath = Utils::Fs::branchPath(m_params.inputPath) + '/'; + const Path parentPath = m_params.inputPath.parentPath(); const Utils::Compare::NaturalLessThan naturalLessThan {}; // Adding files to the torrent lt::file_storage fs; - if (QFileInfo(m_params.inputPath).isFile()) + if (QFileInfo(m_params.inputPath.data()).isFile()) { - lt::add_files(fs, Utils::Fs::toNativePath(m_params.inputPath).toStdString(), fileFilter); + lt::add_files(fs, m_params.inputPath.toString().toStdString(), fileFilter); } else { // need to sort the file names by natural sort order - QStringList dirs = {m_params.inputPath}; + QStringList dirs = {m_params.inputPath.data()}; - QDirIterator dirIter {m_params.inputPath, (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories}; + QDirIterator dirIter {m_params.inputPath.data(), (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories}; while (dirIter.hasNext()) { dirIter.next(); - dirs += dirIter.filePath(); + dirs.append(dirIter.filePath()); } std::sort(dirs.begin(), dirs.end(), naturalLessThan); QStringList fileNames; QHash fileSizeMap; - for (const auto &dir : asConst(dirs)) + for (const QString &dir : asConst(dirs)) { QStringList tmpNames; // natural sort files within each dir - QDirIterator fileIter(dir, QDir::Files); + QDirIterator fileIter {dir, QDir::Files}; while (fileIter.hasNext()) { fileIter.next(); - const QString relFilePath = fileIter.filePath().mid(parentPath.length()); - tmpNames += relFilePath; - fileSizeMap[relFilePath] = fileIter.fileInfo().size(); + const auto relFilePath = parentPath.relativePathOf(Path(fileIter.filePath())); + tmpNames.append(relFilePath.toString()); + fileSizeMap[tmpNames.last()] = fileIter.fileInfo().size(); } std::sort(tmpNames.begin(), tmpNames.end(), naturalLessThan); fileNames += tmpNames; } - for (const auto &fileName : asConst(fileNames)) + for (const QString &fileName : asConst(fileNames)) fs.add_file(fileName.toStdString(), fileSizeMap[fileName]); } @@ -182,7 +182,7 @@ void TorrentCreatorThread::run() } // calculate the hash for all pieces - lt::set_piece_hashes(newTorrent, Utils::Fs::toNativePath(parentPath).toStdString() + lt::set_piece_hashes(newTorrent, parentPath.toString().toStdString() , [this, &newTorrent](const lt::piece_index_t n) { checkInterruptionRequested(); @@ -225,16 +225,16 @@ void TorrentCreatorThread::run() } #ifdef QBT_USES_LIBTORRENT2 -int TorrentCreatorThread::calculateTotalPieces(const QString &inputPath, const int pieceSize, const TorrentFormat torrentFormat) +int TorrentCreatorThread::calculateTotalPieces(const Path &inputPath, const int pieceSize, const TorrentFormat torrentFormat) #else -int TorrentCreatorThread::calculateTotalPieces(const QString &inputPath, const int pieceSize, const bool isAlignmentOptimized, const int paddedFileSizeLimit) +int TorrentCreatorThread::calculateTotalPieces(const Path &inputPath, const int pieceSize, const bool isAlignmentOptimized, const int paddedFileSizeLimit) #endif { if (inputPath.isEmpty()) return 0; lt::file_storage fs; - lt::add_files(fs, Utils::Fs::toNativePath(inputPath).toStdString(), fileFilter); + lt::add_files(fs, inputPath.toString().toStdString(), fileFilter); #ifdef QBT_USES_LIBTORRENT2 return lt::create_torrent {fs, pieceSize, toNativeTorrentFormatFlag(torrentFormat)}.num_pieces(); diff --git a/src/base/bittorrent/torrentcreatorthread.h b/src/base/bittorrent/torrentcreatorthread.h index 4b77c53a5..ec09c8a4c 100644 --- a/src/base/bittorrent/torrentcreatorthread.h +++ b/src/base/bittorrent/torrentcreatorthread.h @@ -31,6 +31,8 @@ #include #include +#include "base/path.h" + namespace BitTorrent { #ifdef QBT_USES_LIBTORRENT2 @@ -52,8 +54,8 @@ namespace BitTorrent int paddedFileSizeLimit; #endif int pieceSize; - QString inputPath; - QString savePath; + Path inputPath; + Path savePath; QString comment; QString source; QStringList trackers; @@ -72,15 +74,15 @@ namespace BitTorrent void create(const TorrentCreatorParams ¶ms); #ifdef QBT_USES_LIBTORRENT2 - static int calculateTotalPieces(const QString &inputPath, const int pieceSize, const TorrentFormat torrentFormat); + static int calculateTotalPieces(const Path &inputPath, const int pieceSize, const TorrentFormat torrentFormat); #else - static int calculateTotalPieces(const QString &inputPath + static int calculateTotalPieces(const Path &inputPath , const int pieceSize, const bool isAlignmentOptimized, int paddedFileSizeLimit); #endif signals: void creationFailure(const QString &msg); - void creationSuccess(const QString &path, const QString &branchPath); + void creationSuccess(const Path &path, const Path &branchPath); void updateProgress(int progress); private: diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 645a5e761..413e15aa2 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -45,7 +45,6 @@ #endif #include -#include #include #include #include @@ -282,8 +281,10 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession { const lt::file_index_t nativeIndex = m_torrentInfo.nativeIndexes().at(i); m_indexMap[nativeIndex] = i; - const QString filePath = Utils::Fs::toUniformPath(QString::fromStdString(fileStorage.file_path(nativeIndex))); - m_filePaths.append(filePath.endsWith(QB_EXT, Qt::CaseInsensitive) ? filePath.chopped(QB_EXT.size()) : filePath); + Path filePath {fileStorage.file_path(nativeIndex)}; + if (filePath.hasExtension(QB_EXT)) + filePath.removeExtension(); + m_filePaths.append(filePath); } } @@ -295,24 +296,22 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession // TODO: Remove the following upgrade code in v4.4 // == BEGIN UPGRADE CODE == - const QString spath = actualStorageLocation(); + const Path spath = actualStorageLocation(); for (int i = 0; i < filesCount(); ++i) { - const QString filepath = filePath(i); + const Path filepath = filePath(i); // Move "unwanted" files back to their original folder - const QString parentRelPath = Utils::Fs::branchPath(filepath); - if (QDir(parentRelPath).dirName() == ".unwanted") + const Path parentRelPath = filepath.parentPath(); + if (parentRelPath.filename() == QLatin1String(".unwanted")) { - const QString oldName = Utils::Fs::fileName(filepath); - const QString newRelPath = Utils::Fs::branchPath(parentRelPath); - if (newRelPath.isEmpty()) - renameFile(i, oldName); - else - renameFile(i, QDir(newRelPath).filePath(oldName)); + const QString oldName = filepath.filename(); + const Path newRelPath = parentRelPath.parentPath(); + renameFile(i, (newRelPath / Path(oldName))); // Remove .unwanted directory if empty - qDebug() << "Attempting to remove \".unwanted\" folder at " << QDir(spath + '/' + newRelPath).absoluteFilePath(".unwanted"); - QDir(spath + '/' + newRelPath).rmdir(".unwanted"); + const Path newPath = spath / newRelPath; + qDebug() << "Attempting to remove \".unwanted\" folder at " << (newPath / Path(".unwanted")).toString(); + Utils::Fs::rmdir(newPath / Path(".unwanted")); } } // == END UPGRADE CODE == @@ -396,18 +395,18 @@ QString TorrentImpl::currentTracker() const return QString::fromStdString(m_nativeStatus.current_tracker); } -QString TorrentImpl::savePath() const +Path TorrentImpl::savePath() const { return isAutoTMMEnabled() ? m_session->categorySavePath(category()) : m_savePath; } -void TorrentImpl::setSavePath(const QString &path) +void TorrentImpl::setSavePath(const Path &path) { Q_ASSERT(!isAutoTMMEnabled()); - const QString basePath = m_session->useCategoryPathsInManualMode() + const Path basePath = m_session->useCategoryPathsInManualMode() ? m_session->categorySavePath(category()) : m_session->savePath(); - const QString resolvedPath = (QDir::isAbsolutePath(path) ? path : Utils::Fs::resolvePath(path, basePath)); + const Path resolvedPath = (path.isAbsolute() ? path : (basePath / path)); if (resolvedPath == savePath()) return; @@ -420,18 +419,18 @@ void TorrentImpl::setSavePath(const QString &path) moveStorage(savePath(), MoveStorageMode::KeepExistingFiles); } -QString TorrentImpl::downloadPath() const +Path TorrentImpl::downloadPath() const { return isAutoTMMEnabled() ? m_session->categoryDownloadPath(category()) : m_downloadPath; } -void TorrentImpl::setDownloadPath(const QString &path) +void TorrentImpl::setDownloadPath(const Path &path) { Q_ASSERT(!isAutoTMMEnabled()); - const QString basePath = m_session->useCategoryPathsInManualMode() + const Path basePath = m_session->useCategoryPathsInManualMode() ? m_session->categoryDownloadPath(category()) : m_session->downloadPath(); - const QString resolvedPath = ((path.isEmpty() || QDir::isAbsolutePath(path)) ? path : Utils::Fs::resolvePath(path, basePath)); + const Path resolvedPath = (path.isEmpty() || path.isAbsolute()) ? path : (basePath / path); if (resolvedPath == m_downloadPath) return; @@ -444,27 +443,27 @@ void TorrentImpl::setDownloadPath(const QString &path) moveStorage((m_downloadPath.isEmpty() ? savePath() : m_downloadPath), MoveStorageMode::KeepExistingFiles); } -QString TorrentImpl::rootPath() const +Path TorrentImpl::rootPath() const { if (!hasMetadata()) return {}; - const QString relativeRootPath = Utils::Fs::findRootFolder(filePaths()); + const Path relativeRootPath = Path::findRootFolder(filePaths()); if (relativeRootPath.isEmpty()) return {}; - return QDir(actualStorageLocation()).absoluteFilePath(relativeRootPath); + return (actualStorageLocation() / relativeRootPath); } -QString TorrentImpl::contentPath() const +Path TorrentImpl::contentPath() const { if (!hasMetadata()) return {}; if (filesCount() == 1) - return QDir(actualStorageLocation()).absoluteFilePath(filePath(0)); + return (actualStorageLocation() / filePath(0)); - const QString rootPath = this->rootPath(); + const Path rootPath = this->rootPath(); return (rootPath.isEmpty() ? actualStorageLocation() : rootPath); } @@ -491,9 +490,9 @@ void TorrentImpl::setAutoTMMEnabled(bool enabled) adjustStorageLocation(); } -QString TorrentImpl::actualStorageLocation() const +Path TorrentImpl::actualStorageLocation() const { - return Utils::Fs::toUniformPath(QString::fromStdString(m_nativeStatus.save_path)); + return Path(m_nativeStatus.save_path); } void TorrentImpl::setAutoManaged(const bool enable) @@ -799,16 +798,15 @@ int TorrentImpl::seedingTimeLimit() const return m_seedingTimeLimit; } -QString TorrentImpl::filePath(const int index) const +Path TorrentImpl::filePath(const int index) const { return m_filePaths.at(index); } -QString TorrentImpl::actualFilePath(const int index) const +Path TorrentImpl::actualFilePath(const int index) const { const auto nativeIndex = m_torrentInfo.nativeIndexes().at(index); - const std::string filePath = m_nativeHandle.torrent_file()->files().file_path(nativeIndex); - return Utils::Fs::toUniformPath(QString::fromStdString(filePath)); + return Path(m_nativeHandle.torrent_file()->files().file_path(nativeIndex)); } qlonglong TorrentImpl::fileSize(const int index) const @@ -816,7 +814,7 @@ qlonglong TorrentImpl::fileSize(const int index) const return m_torrentInfo.fileSize(index); } -QStringList TorrentImpl::filePaths() const +PathList TorrentImpl::filePaths() const { return m_filePaths; } @@ -1481,12 +1479,12 @@ void TorrentImpl::applyFirstLastPiecePriority(const bool enabled, const QVector< m_nativeHandle.prioritize_pieces(piecePriorities); } -void TorrentImpl::fileSearchFinished(const QString &savePath, const QStringList &fileNames) +void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileNames) { endReceivedMetadataHandling(savePath, fileNames); } -void TorrentImpl::endReceivedMetadataHandling(const QString &savePath, const QStringList &fileNames) +void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathList &fileNames) { Q_ASSERT(m_filePaths.isEmpty()); Q_ASSERT(m_indexMap.isEmpty()); @@ -1502,11 +1500,14 @@ void TorrentImpl::endReceivedMetadataHandling(const QString &savePath, const QSt const auto nativeIndex = nativeIndexes.at(i); m_indexMap[nativeIndex] = i; - const QString filePath = fileNames.at(i); - m_filePaths.append(filePath.endsWith(QB_EXT, Qt::CaseInsensitive) ? filePath.chopped(QB_EXT.size()) : filePath); - p.renamed_files[nativeIndex] = filePath.toStdString(); + Path filePath = fileNames.at(i); + p.renamed_files[nativeIndex] = filePath.toString().toStdString(); + + if (filePath.hasExtension(QB_EXT)) + filePath.removeExtension(); + m_filePaths.append(filePath); } - p.save_path = Utils::Fs::toNativePath(savePath).toStdString(); + p.save_path = savePath.toString().toStdString(); p.ti = metadata; const int internalFilesCount = p.ti->files().num_files(); // including .pad files @@ -1613,19 +1614,20 @@ void TorrentImpl::resume(const TorrentOperatingMode mode) } } -void TorrentImpl::moveStorage(const QString &newPath, const MoveStorageMode mode) +void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageMode mode) { - if (m_session->addMoveTorrentStorageJob(this, Utils::Fs::toNativePath(newPath), mode)) + if (m_session->addMoveTorrentStorageJob(this, newPath, mode)) { m_storageIsMoving = true; updateStatus(); } } -void TorrentImpl::renameFile(const int index, const QString &path) +void TorrentImpl::renameFile(const int index, const Path &path) { ++m_renameCount; - m_nativeHandle.rename_file(m_torrentInfo.nativeIndexes().at(index), Utils::Fs::toNativePath(path).toStdString()); + m_nativeHandle.rename_file(m_torrentInfo.nativeIndexes().at(index) + , path.toString().toStdString()); } void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus) @@ -1790,7 +1792,7 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p) TorrentInfo metadata = TorrentInfo(*m_nativeHandle.torrent_file()); - QStringList filePaths = metadata.filePaths(); + PathList filePaths = metadata.filePaths(); applyContentLayout(filePaths, m_contentLayout); m_session->findIncompleteFiles(metadata, savePath(), downloadPath(), filePaths); } @@ -1876,37 +1878,23 @@ void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p) // Remove empty leftover folders // For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will // be removed if they are empty - const QString oldFilePath = m_filePaths.at(fileIndex); - const QString newFilePath = Utils::Fs::toUniformPath(p->new_name()); + const Path oldFilePath = m_filePaths.at(fileIndex); + const Path newFilePath {QString(p->new_name())}; // Check if ".!qB" extension was just added or removed - if ((oldFilePath != newFilePath) && (oldFilePath != newFilePath.chopped(QB_EXT.size()))) + // We should compare path in a case sensitive manner even on case insensitive + // platforms since it can be renamed by only changing case of some character(s) + if ((oldFilePath.data() != newFilePath.data()) + && ((oldFilePath + QB_EXT) != newFilePath)) { m_filePaths[fileIndex] = newFilePath; - QList oldPathParts = QStringView(oldFilePath).split('/', Qt::SkipEmptyParts); - oldPathParts.removeLast(); // drop file name part - QList newPathParts = QStringView(newFilePath).split('/', Qt::SkipEmptyParts); - newPathParts.removeLast(); // drop file name part - -#if defined(Q_OS_WIN) - const Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive; -#else - const Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive; -#endif - - int pathIdx = 0; - while ((pathIdx < oldPathParts.size()) && (pathIdx < newPathParts.size())) + Path oldParentPath = oldFilePath.parentPath(); + const Path commonBasePath = Path::commonPath(oldParentPath, newFilePath.parentPath()); + while (oldParentPath != commonBasePath) { - if (oldPathParts[pathIdx].compare(newPathParts[pathIdx], caseSensitivity) != 0) - break; - ++pathIdx; - } - - for (int i = (oldPathParts.size() - 1); i >= pathIdx; --i) - { - QDir().rmdir(savePath() + Utils::String::join(oldPathParts, QString::fromLatin1("/"))); - oldPathParts.removeLast(); + Utils::Fs::rmdir(actualStorageLocation() / oldParentPath); + oldParentPath = oldParentPath.parentPath(); } } @@ -1923,7 +1911,7 @@ void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert Q_ASSERT(fileIndex >= 0); LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"") - .arg(name(), filePath(fileIndex), QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING); + .arg(name(), filePath(fileIndex).toString(), QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING); --m_renameCount; while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) @@ -1939,11 +1927,11 @@ void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p) const int fileIndex = m_indexMap.value(p->index, -1); Q_ASSERT(fileIndex >= 0); - const QString path = filePath(fileIndex); - const QString actualPath = actualFilePath(fileIndex); + const Path path = filePath(fileIndex); + const Path actualPath = actualFilePath(fileIndex); if (actualPath != path) { - qDebug("Renaming %s to %s", qUtf8Printable(actualPath), qUtf8Printable(path)); + qDebug("Renaming %s to %s", qUtf8Printable(actualPath.toString()), qUtf8Printable(path.toString())); renameFile(fileIndex, path); } } @@ -2064,18 +2052,17 @@ void TorrentImpl::manageIncompleteFiles() for (int i = 0; i < filesCount(); ++i) { - const QString path = filePath(i); + const Path path = filePath(i); const auto nativeIndex = m_torrentInfo.nativeIndexes().at(i); - const QString actualPath = Utils::Fs::toUniformPath( - QString::fromStdString(nativeFiles.file_path(nativeIndex))); + const Path actualPath {nativeFiles.file_path(nativeIndex)}; if (isAppendExtensionEnabled && (fileSize(i) > 0) && (fp[i] < 1)) { - const QString wantedPath = path + QB_EXT; + const Path wantedPath = path + QB_EXT; if (actualPath != wantedPath) { - qDebug() << "Renaming" << actualPath << "to" << wantedPath; + qDebug() << "Renaming" << actualPath.toString() << "to" << wantedPath.toString(); renameFile(i, wantedPath); } } @@ -2083,7 +2070,7 @@ void TorrentImpl::manageIncompleteFiles() { if (actualPath != path) { - qDebug() << "Renaming" << actualPath << "to" << path; + qDebug() << "Renaming" << actualPath.toString() << "to" << path.toString(); renameFile(i, path); } } @@ -2092,12 +2079,12 @@ void TorrentImpl::manageIncompleteFiles() void TorrentImpl::adjustStorageLocation() { - const QString downloadPath = this->downloadPath(); + const Path downloadPath = this->downloadPath(); const bool isFinished = isSeed() || m_hasSeedStatus; - const QDir targetDir {((isFinished || downloadPath.isEmpty()) ? savePath() : downloadPath)}; + const Path targetPath = ((isFinished || downloadPath.isEmpty()) ? savePath() : downloadPath); - if ((targetDir != QDir(actualStorageLocation())) || isMoveInProgress()) - moveStorage(targetDir.absolutePath(), MoveStorageMode::Overwrite); + if ((targetPath != actualStorageLocation()) || isMoveInProgress()) + moveStorage(targetPath, MoveStorageMode::Overwrite); } lt::torrent_handle TorrentImpl::nativeHandle() const diff --git a/src/base/bittorrent/torrentimpl.h b/src/base/bittorrent/torrentimpl.h index c665c897a..190e6793e 100644 --- a/src/base/bittorrent/torrentimpl.h +++ b/src/base/bittorrent/torrentimpl.h @@ -46,6 +46,7 @@ #include #include +#include "base/path.h" #include "base/tagset.h" #include "infohash.h" #include "speedmonitor.h" @@ -102,13 +103,13 @@ namespace BitTorrent bool isAutoTMMEnabled() const override; void setAutoTMMEnabled(bool enabled) override; - QString savePath() const override; - void setSavePath(const QString &path) override; - QString downloadPath() const override; - void setDownloadPath(const QString &path) override; - QString actualStorageLocation() const override; - QString rootPath() const override; - QString contentPath() const override; + Path savePath() const override; + void setSavePath(const Path &path) override; + Path downloadPath() const override; + void setDownloadPath(const Path &path) override; + Path actualStorageLocation() const override; + Path rootPath() const override; + Path contentPath() const override; QString category() const override; bool belongsToCategory(const QString &category) const override; bool setCategory(const QString &category) override; @@ -127,10 +128,10 @@ namespace BitTorrent qreal ratioLimit() const override; int seedingTimeLimit() const override; - QString filePath(int index) const override; - QString actualFilePath(int index) const override; + Path filePath(int index) const override; + Path actualFilePath(int index) const override; qlonglong fileSize(int index) const override; - QStringList filePaths() const override; + PathList filePaths() const override; QVector filePriorities() const override; TorrentInfo info() const override; @@ -205,7 +206,7 @@ namespace BitTorrent void forceReannounce(int index = -1) override; void forceDHTAnnounce() override; void forceRecheck() override; - void renameFile(int index, const QString &path) override; + void renameFile(int index, const Path &path) override; void prioritizeFiles(const QVector &priorities) override; void setRatioLimit(qreal limit) override; void setSeedingTimeLimit(int limit) override; @@ -237,7 +238,7 @@ namespace BitTorrent void handleAppendExtensionToggled(); void saveResumeData(); void handleMoveStorageJobFinished(bool hasOutstandingJob); - void fileSearchFinished(const QString &savePath, const QStringList &fileNames); + void fileSearchFinished(const Path &savePath, const PathList &fileNames); private: using EventTrigger = std::function; @@ -271,12 +272,12 @@ namespace BitTorrent void setAutoManaged(bool enable); void adjustStorageLocation(); - void moveStorage(const QString &newPath, MoveStorageMode mode); + void moveStorage(const Path &newPath, MoveStorageMode mode); void manageIncompleteFiles(); void applyFirstLastPiecePriority(bool enabled, const QVector &updatedFilePrio = {}); void prepareResumeData(const lt::add_torrent_params ¶ms); - void endReceivedMetadataHandling(const QString &savePath, const QStringList &fileNames); + void endReceivedMetadataHandling(const Path &savePath, const PathList &fileNames); void reload(); Session *const m_session; @@ -285,7 +286,7 @@ namespace BitTorrent lt::torrent_status m_nativeStatus; TorrentState m_state = TorrentState::Unknown; TorrentInfo m_torrentInfo; - QStringList m_filePaths; + PathList m_filePaths; QHash m_indexMap; SpeedMonitor m_speedMonitor; @@ -304,8 +305,8 @@ namespace BitTorrent // Persistent data QString m_name; - QString m_savePath; - QString m_downloadPath; + Path m_savePath; + Path m_downloadPath; QString m_category; TagSet m_tags; qreal m_ratioLimit; diff --git a/src/base/bittorrent/torrentinfo.cpp b/src/base/bittorrent/torrentinfo.cpp index e46e60b07..10b93f7fa 100644 --- a/src/base/bittorrent/torrentinfo.cpp +++ b/src/base/bittorrent/torrentinfo.cpp @@ -99,16 +99,16 @@ nonstd::expected TorrentInfo::load(const QByteArray &data) if (ec) return nonstd::make_unexpected(QString::fromStdString(ec.message())); - lt::torrent_info nativeInfo {node, ec}; + const lt::torrent_info nativeInfo {node, ec}; if (ec) return nonstd::make_unexpected(QString::fromStdString(ec.message())); return TorrentInfo(nativeInfo); } -nonstd::expected TorrentInfo::loadFromFile(const QString &path) noexcept +nonstd::expected TorrentInfo::loadFromFile(const Path &path) noexcept { - QFile file {path}; + QFile file {path.data()}; if (!file.open(QIODevice::ReadOnly)) return nonstd::make_unexpected(file.errorString()); @@ -133,7 +133,7 @@ nonstd::expected TorrentInfo::loadFromFile(const QString & return load(data); } -nonstd::expected TorrentInfo::saveToFile(const QString &path) const +nonstd::expected TorrentInfo::saveToFile(const Path &path) const { if (!isValid()) return nonstd::make_unexpected(tr("Invalid metadata")); @@ -236,17 +236,16 @@ int TorrentInfo::piecesCount() const return m_nativeInfo->num_pieces(); } -QString TorrentInfo::filePath(const int index) const +Path TorrentInfo::filePath(const int index) const { if (!isValid()) return {}; - return Utils::Fs::toUniformPath( - QString::fromStdString(m_nativeInfo->orig_files().file_path(m_nativeIndexes[index]))); + return Path(m_nativeInfo->orig_files().file_path(m_nativeIndexes[index])); } -QStringList TorrentInfo::filePaths() const +PathList TorrentInfo::filePaths() const { - QStringList list; + PathList list; list.reserve(filesCount()); for (int i = 0; i < filesCount(); ++i) list << filePath(i); @@ -312,15 +311,15 @@ QByteArray TorrentInfo::metadata() const #endif } -QStringList TorrentInfo::filesForPiece(const int pieceIndex) const +PathList TorrentInfo::filesForPiece(const int pieceIndex) const { // no checks here because fileIndicesForPiece() will return an empty list const QVector fileIndices = fileIndicesForPiece(pieceIndex); - QStringList res; + PathList res; res.reserve(fileIndices.size()); - std::transform(fileIndices.begin(), fileIndices.end(), std::back_inserter(res), - [this](int i) { return filePath(i); }); + std::transform(fileIndices.begin(), fileIndices.end(), std::back_inserter(res) + , [this](int i) { return filePath(i); }); return res; } @@ -359,15 +358,15 @@ QVector TorrentInfo::pieceHashes() const return hashes; } -TorrentInfo::PieceRange TorrentInfo::filePieces(const QString &file) const +TorrentInfo::PieceRange TorrentInfo::filePieces(const Path &filePath) const { if (!isValid()) // if we do not check here the debug message will be printed, which would be not correct return {}; - const int index = fileIndex(file); + const int index = fileIndex(filePath); if (index == -1) { - qDebug() << "Filename" << file << "was not found in torrent" << name(); + qDebug() << "Filename" << filePath.toString() << "was not found in torrent" << name(); return {}; } return filePieces(index); @@ -396,13 +395,13 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(const int fileIndex) const return makeInterval(beginIdx, endIdx); } -int TorrentInfo::fileIndex(const QString &fileName) const +int TorrentInfo::fileIndex(const Path &filePath) const { // the check whether the object is valid is not needed here // because if filesCount() returns -1 the loop exits immediately for (int i = 0; i < filesCount(); ++i) { - if (fileName == filePath(i)) + if (filePath == this->filePath(i)) return i; } diff --git a/src/base/bittorrent/torrentinfo.h b/src/base/bittorrent/torrentinfo.h index 62d073ef5..2099dc687 100644 --- a/src/base/bittorrent/torrentinfo.h +++ b/src/base/bittorrent/torrentinfo.h @@ -35,6 +35,7 @@ #include "base/3rdparty/expected.hpp" #include "base/indexrange.h" +#include "base/pathfwd.h" #include "torrentcontentlayout.h" class QByteArray; @@ -58,8 +59,8 @@ namespace BitTorrent explicit TorrentInfo(const lt::torrent_info &nativeInfo); static nonstd::expected load(const QByteArray &data) noexcept; - static nonstd::expected loadFromFile(const QString &path) noexcept; - nonstd::expected saveToFile(const QString &path) const; + static nonstd::expected loadFromFile(const Path &path) noexcept; + nonstd::expected saveToFile(const Path &path) const; TorrentInfo &operator=(const TorrentInfo &other); @@ -75,21 +76,21 @@ namespace BitTorrent int pieceLength() const; int pieceLength(int index) const; int piecesCount() const; - QString filePath(int index) const; - QStringList filePaths() const; + Path filePath(int index) const; + PathList filePaths() const; qlonglong fileSize(int index) const; qlonglong fileOffset(int index) const; QVector trackers() const; QVector urlSeeds() const; QByteArray metadata() const; - QStringList filesForPiece(int pieceIndex) const; + PathList filesForPiece(int pieceIndex) const; QVector fileIndicesForPiece(int pieceIndex) const; QVector pieceHashes() const; using PieceRange = IndexRange; // returns pair of the first and the last pieces into which // the given file extends (maybe partially). - PieceRange filePieces(const QString &file) const; + PieceRange filePieces(const Path &filePath) const; PieceRange filePieces(int fileIndex) const; std::shared_ptr nativeInfo() const; @@ -97,7 +98,7 @@ namespace BitTorrent private: // returns file index or -1 if fileName is not found - int fileIndex(const QString &fileName) const; + int fileIndex(const Path &filePath) const; TorrentContentLayout contentLayout() const; std::shared_ptr m_nativeInfo; diff --git a/src/base/iconprovider.cpp b/src/base/iconprovider.cpp index f5dad49f7..edbbcca04 100644 --- a/src/base/iconprovider.cpp +++ b/src/base/iconprovider.cpp @@ -29,7 +29,7 @@ #include "iconprovider.h" -#include +#include "base/path.h" IconProvider::IconProvider(QObject *parent) : QObject(parent) @@ -55,14 +55,14 @@ IconProvider *IconProvider::instance() return m_instance; } -QString IconProvider::getIconPath(const QString &iconId) const +Path IconProvider::getIconPath(const QString &iconId) const { // there are a few icons not available in svg - const QString pathSvg = ":/icons/" + iconId + ".svg"; - if (QFileInfo::exists(pathSvg)) + const Path pathSvg {":/icons/" + iconId + ".svg"}; + if (pathSvg.exists()) return pathSvg; - const QString pathPng = ":/icons/" + iconId + ".png"; + const Path pathPng {":/icons/" + iconId + ".png"}; return pathPng; } diff --git a/src/base/iconprovider.h b/src/base/iconprovider.h index 3450186b2..33b44bf2d 100644 --- a/src/base/iconprovider.h +++ b/src/base/iconprovider.h @@ -31,6 +31,8 @@ #include +#include "base/pathfwd.h" + class QString; class IconProvider : public QObject @@ -42,7 +44,7 @@ public: static void freeInstance(); static IconProvider *instance(); - virtual QString getIconPath(const QString &iconId) const; + virtual Path getIconPath(const QString &iconId) const; protected: explicit IconProvider(QObject *parent = nullptr); diff --git a/src/base/net/downloadhandlerimpl.cpp b/src/base/net/downloadhandlerimpl.cpp index 75a0318bd..807b19740 100644 --- a/src/base/net/downloadhandlerimpl.cpp +++ b/src/base/net/downloadhandlerimpl.cpp @@ -42,14 +42,14 @@ const int MAX_REDIRECTIONS = 20; // the common value for web browsers namespace { - nonstd::expected saveToTempFile(const QByteArray &data) + nonstd::expected saveToTempFile(const QByteArray &data) { - QTemporaryFile file {Utils::Fs::tempPath()}; + QTemporaryFile file {Utils::Fs::tempPath().data()}; if (!file.open() || (file.write(data) != data.length()) || !file.flush()) return nonstd::make_unexpected(file.errorString()); file.setAutoRemove(false); - return file.fileName(); + return Path(file.fileName()); } } @@ -127,10 +127,10 @@ void DownloadHandlerImpl::processFinishedDownload() if (m_downloadRequest.saveToFile()) { - const QString destinationPath = m_downloadRequest.destFileName(); + const Path destinationPath = m_downloadRequest.destFileName(); if (destinationPath.isEmpty()) { - const nonstd::expected result = saveToTempFile(m_result.data); + const nonstd::expected result = saveToTempFile(m_result.data); if (result) m_result.filePath = result.value(); else diff --git a/src/base/net/downloadmanager.cpp b/src/base/net/downloadmanager.cpp index aaf8089f9..86e6b89f9 100644 --- a/src/base/net/downloadmanager.cpp +++ b/src/base/net/downloadmanager.cpp @@ -348,12 +348,12 @@ Net::DownloadRequest &Net::DownloadRequest::saveToFile(const bool value) return *this; } -QString Net::DownloadRequest::destFileName() const +Path Net::DownloadRequest::destFileName() const { return m_destFileName; } -Net::DownloadRequest &Net::DownloadRequest::destFileName(const QString &value) +Net::DownloadRequest &Net::DownloadRequest::destFileName(const Path &value) { m_destFileName = value; return *this; diff --git a/src/base/net/downloadmanager.h b/src/base/net/downloadmanager.h index 22f133191..2dea1c162 100644 --- a/src/base/net/downloadmanager.h +++ b/src/base/net/downloadmanager.h @@ -35,6 +35,8 @@ #include #include +#include "base/path.h" + class QNetworkCookie; class QNetworkReply; class QSslError; @@ -81,15 +83,15 @@ namespace Net // if saveToFile is set, the file is saved in destFileName // (deprecated) if destFileName is not provided, the file will be saved // in a temporary file, the name of file is set in DownloadResult::filePath - QString destFileName() const; - DownloadRequest &destFileName(const QString &value); + Path destFileName() const; + DownloadRequest &destFileName(const Path &value); private: QString m_url; QString m_userAgent; qint64 m_limit = 0; bool m_saveToFile = false; - QString m_destFileName; + Path m_destFileName; }; struct DownloadResult @@ -98,7 +100,7 @@ namespace Net DownloadStatus status; QString errorString; QByteArray data; - QString filePath; + Path filePath; QString magnet; }; diff --git a/src/base/net/geoipdatabase.cpp b/src/base/net/geoipdatabase.cpp index b55e3d8f8..c802ae0cb 100644 --- a/src/base/net/geoipdatabase.cpp +++ b/src/base/net/geoipdatabase.cpp @@ -26,13 +26,15 @@ * exception statement from your version. */ +#include "geoipdatabase.h" + #include #include #include #include #include -#include "geoipdatabase.h" +#include "base/path.h" namespace { @@ -84,10 +86,10 @@ GeoIPDatabase::GeoIPDatabase(const quint32 size) { } -GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error) +GeoIPDatabase *GeoIPDatabase::load(const Path &filename, QString &error) { GeoIPDatabase *db = nullptr; - QFile file(filename); + QFile file {filename.data()}; if (file.size() > MAX_FILE_SIZE) { error = tr("Unsupported database file size."); diff --git a/src/base/net/geoipdatabase.h b/src/base/net/geoipdatabase.h index 55d13e371..f0b2b6400 100644 --- a/src/base/net/geoipdatabase.h +++ b/src/base/net/geoipdatabase.h @@ -28,11 +28,15 @@ #pragma once -#include #include +#include +#include +#include +#include + +#include "base/pathfwd.h" class QByteArray; -class QDateTime; class QHostAddress; class QString; @@ -43,7 +47,7 @@ class GeoIPDatabase Q_DECLARE_TR_FUNCTIONS(GeoIPDatabase) public: - static GeoIPDatabase *load(const QString &filename, QString &error); + static GeoIPDatabase *load(const Path &filename, QString &error); static GeoIPDatabase *load(const QByteArray &data, QString &error); ~GeoIPDatabase(); diff --git a/src/base/net/geoipmanager.cpp b/src/base/net/geoipmanager.cpp index 449b3b49f..18ddd04f9 100644 --- a/src/base/net/geoipmanager.cpp +++ b/src/base/net/geoipmanager.cpp @@ -30,7 +30,6 @@ #include "geoipmanager.h" #include -#include #include #include @@ -43,9 +42,9 @@ #include "downloadmanager.h" #include "geoipdatabase.h" -static const QString DATABASE_URL = QStringLiteral("https://download.db-ip.com/free/dbip-country-lite-%1.mmdb.gz"); -static const char GEODB_FOLDER[] = "GeoDB"; -static const char GEODB_FILENAME[] = "dbip-country-lite.mmdb"; +const QString DATABASE_URL = QStringLiteral("https://download.db-ip.com/free/dbip-country-lite-%1.mmdb.gz"); +const char GEODB_FOLDER[] = "GeoDB"; +const char GEODB_FILENAME[] = "dbip-country-lite.mmdb"; using namespace Net; @@ -88,17 +87,21 @@ void GeoIPManager::loadDatabase() delete m_geoIPDatabase; m_geoIPDatabase = nullptr; - const QString filepath = Utils::Fs::expandPathAbs( - QString::fromLatin1("%1/%2/%3").arg(specialFolderLocation(SpecialFolder::Data), GEODB_FOLDER, GEODB_FILENAME)); + const Path filepath = specialFolderLocation(SpecialFolder::Data) + / Path(GEODB_FOLDER) / Path(GEODB_FILENAME); QString error; m_geoIPDatabase = GeoIPDatabase::load(filepath, error); if (m_geoIPDatabase) + { Logger::instance()->addMessage(tr("IP geolocation database loaded. Type: %1. Build time: %2.") - .arg(m_geoIPDatabase->type(), m_geoIPDatabase->buildEpoch().toString()), - Log::INFO); + .arg(m_geoIPDatabase->type(), m_geoIPDatabase->buildEpoch().toString()), + Log::INFO); + } else + { Logger::instance()->addMessage(tr("Couldn't load IP geolocation database. Reason: %1").arg(error), Log::WARNING); + } manageDatabaseUpdate(); } @@ -445,14 +448,13 @@ void GeoIPManager::downloadFinished(const DownloadResult &result) delete m_geoIPDatabase; m_geoIPDatabase = geoIPDatabase; LogMsg(tr("IP geolocation database loaded. Type: %1. Build time: %2.") - .arg(m_geoIPDatabase->type(), m_geoIPDatabase->buildEpoch().toString()), - Log::INFO); - const QString targetPath = Utils::Fs::expandPathAbs( - QDir(specialFolderLocation(SpecialFolder::Data)).absoluteFilePath(GEODB_FOLDER)); - if (!QDir(targetPath).exists()) - QDir().mkpath(targetPath); + .arg(m_geoIPDatabase->type(), m_geoIPDatabase->buildEpoch().toString()) + , Log::INFO); + const Path targetPath = specialFolderLocation(SpecialFolder::Data) / Path(GEODB_FOLDER); + if (!targetPath.exists()) + Utils::Fs::mkpath(targetPath); - const auto path = QString::fromLatin1("%1/%2").arg(targetPath, GEODB_FILENAME); + const auto path = targetPath / Path(GEODB_FILENAME); const nonstd::expected result = Utils::IO::saveToFile(path, data); if (result) { diff --git a/src/base/path.cpp b/src/base/path.cpp new file mode 100644 index 000000000..33c04eb5c --- /dev/null +++ b/src/base/path.cpp @@ -0,0 +1,326 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2022 Vladimir Golovnev + * Copyright (C) 2012 Christophe Dumez + * + * 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 +#include +#include +#include +#include +#include + +#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(); + +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 leftPathItems = QStringView(left.m_pathStr).split(u'/'); + const QList 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); +} diff --git a/src/base/path.h b/src/base/path.h new file mode 100644 index 000000000..a7ac3cb02 --- /dev/null +++ b/src/base/path.h @@ -0,0 +1,100 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2022 Vladimir Golovnev + * Copyright (C) 2012 Christophe Dumez + * + * 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 +#include + +#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); diff --git a/src/base/pathfwd.h b/src/base/pathfwd.h new file mode 100644 index 000000000..52fec4d48 --- /dev/null +++ b/src/base/pathfwd.h @@ -0,0 +1,35 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2022 Vladimir Golovnev + * + * 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 + +class Path; + +using PathList = QList; diff --git a/src/base/preferences.cpp b/src/base/preferences.cpp index ded4e7f86..ac764c776 100644 --- a/src/base/preferences.cpp +++ b/src/base/preferences.cpp @@ -54,6 +54,7 @@ #include "algorithm.h" #include "global.h" +#include "path.h" #include "profile.h" #include "settingsstorage.h" #include "utils/fs.h" @@ -85,11 +86,11 @@ namespace } #ifdef Q_OS_WIN - QString makeProfileID(const QString &profilePath, const QString &profileName) + QString makeProfileID(const Path &profilePath, const QString &profileName) { return profilePath.isEmpty() ? profileName - : profileName + QLatin1Char('@') + Utils::Fs::toValidFileSystemName(profilePath, false, {}); + : profileName + QLatin1Char('@') + Utils::Fs::toValidFileName(profilePath.data(), {}); } #endif } @@ -137,12 +138,12 @@ void Preferences::setUseCustomUITheme(const bool use) setValue("Preferences/General/UseCustomUITheme", use); } -QString Preferences::customUIThemePath() const +Path Preferences::customUIThemePath() const { - return value("Preferences/General/CustomUIThemePath"); + return value("Preferences/General/CustomUIThemePath"); } -void Preferences::setCustomUIThemePath(const QString &path) +void Preferences::setCustomUIThemePath(const Path &path) { setValue("Preferences/General/CustomUIThemePath", path); } @@ -336,7 +337,7 @@ void Preferences::setPreventFromSuspendWhenSeeding(const bool b) bool Preferences::WinStartup() const { const QString profileName = Profile::instance()->profileName(); - const QString profilePath = Profile::instance()->rootPath(); + const Path profilePath = Profile::instance()->rootPath(); const QString profileID = makeProfileID(profilePath, profileName); const QSettings settings {"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat}; @@ -346,7 +347,7 @@ bool Preferences::WinStartup() const void Preferences::setWinStartup(const bool b) { const QString profileName = Profile::instance()->profileName(); - const QString profilePath = Profile::instance()->rootPath(); + const Path profilePath = Profile::instance()->rootPath(); const QString profileID = makeProfileID(profilePath, profileName); QSettings settings {"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat}; if (b) @@ -354,7 +355,7 @@ void Preferences::setWinStartup(const bool b) const QString configuration = Profile::instance()->configurationName(); const auto cmd = QString::fromLatin1(R"("%1" "--profile=%2" "--configuration=%3")") - .arg(Utils::Fs::toNativePath(qApp->applicationFilePath()), profilePath, configuration); + .arg(Path(qApp->applicationFilePath()).toString(), profilePath.toString(), configuration); settings.setValue(profileID, cmd); } else @@ -365,24 +366,14 @@ void Preferences::setWinStartup(const bool b) #endif // Q_OS_WIN // Downloads -QString Preferences::lastLocationPath() const +Path Preferences::getScanDirsLastPath() const { - return Utils::Fs::toUniformPath(value("Preferences/Downloads/LastLocationPath")); + return value("Preferences/Downloads/ScanDirsLastPath"); } -void Preferences::setLastLocationPath(const QString &path) +void Preferences::setScanDirsLastPath(const Path &path) { - setValue("Preferences/Downloads/LastLocationPath", Utils::Fs::toUniformPath(path)); -} - -QString Preferences::getScanDirsLastPath() const -{ - return Utils::Fs::toUniformPath(value("Preferences/Downloads/ScanDirsLastPath")); -} - -void Preferences::setScanDirsLastPath(const QString &path) -{ - setValue("Preferences/Downloads/ScanDirsLastPath", Utils::Fs::toUniformPath(path)); + setValue("Preferences/Downloads/ScanDirsLastPath", path); } bool Preferences::isMailNotificationEnabled() const @@ -737,22 +728,22 @@ void Preferences::setWebUiHttpsEnabled(const bool enabled) setValue("Preferences/WebUI/HTTPS/Enabled", enabled); } -QString Preferences::getWebUIHttpsCertificatePath() const +Path Preferences::getWebUIHttpsCertificatePath() const { - return value("Preferences/WebUI/HTTPS/CertificatePath"); + return value("Preferences/WebUI/HTTPS/CertificatePath"); } -void Preferences::setWebUIHttpsCertificatePath(const QString &path) +void Preferences::setWebUIHttpsCertificatePath(const Path &path) { setValue("Preferences/WebUI/HTTPS/CertificatePath", path); } -QString Preferences::getWebUIHttpsKeyPath() const +Path Preferences::getWebUIHttpsKeyPath() const { - return value("Preferences/WebUI/HTTPS/KeyPath"); + return value("Preferences/WebUI/HTTPS/KeyPath"); } -void Preferences::setWebUIHttpsKeyPath(const QString &path) +void Preferences::setWebUIHttpsKeyPath(const Path &path) { setValue("Preferences/WebUI/HTTPS/KeyPath", path); } @@ -767,12 +758,12 @@ void Preferences::setAltWebUiEnabled(const bool enabled) setValue("Preferences/WebUI/AlternativeUIEnabled", enabled); } -QString Preferences::getWebUiRootFolder() const +Path Preferences::getWebUiRootFolder() const { - return value("Preferences/WebUI/RootFolder"); + return value("Preferences/WebUI/RootFolder"); } -void Preferences::setWebUiRootFolder(const QString &path) +void Preferences::setWebUiRootFolder(const Path &path) { setValue("Preferences/WebUI/RootFolder", path); } @@ -1050,14 +1041,14 @@ bool Preferences::isMagnetLinkAssocSet() const QSettings settings("HKEY_CURRENT_USER\\Software\\Classes", QSettings::NativeFormat); // Check magnet link assoc - const QString shellCommand = Utils::Fs::toNativePath(settings.value("magnet/shell/open/command/Default", "").toString()); + const QString shellCommand = settings.value("magnet/shell/open/command/Default", "").toString(); const QRegularExpressionMatch exeRegMatch = QRegularExpression("\"([^\"]+)\".*").match(shellCommand); if (!exeRegMatch.hasMatch()) return false; - const QString assocExe = exeRegMatch.captured(1); - if (assocExe.compare(Utils::Fs::toNativePath(qApp->applicationFilePath()), Qt::CaseInsensitive) != 0) + const Path assocExe {exeRegMatch.captured(1)}; + if (assocExe != Path(qApp->applicationFilePath())) return false; return true; @@ -1090,15 +1081,16 @@ void Preferences::setMagnetLinkAssoc(const bool set) // Magnet association if (set) { - const QString commandStr = '"' + qApp->applicationFilePath() + "\" \"%1\""; - const QString iconStr = '"' + qApp->applicationFilePath() + "\",1"; + const QString applicationFilePath = Path(qApp->applicationFilePath()).toString(); + const QString commandStr = '"' + applicationFilePath + "\" \"%1\""; + const QString iconStr = '"' + applicationFilePath + "\",1"; settings.setValue("magnet/Default", "URL:Magnet link"); settings.setValue("magnet/Content Type", "application/x-magnet"); settings.setValue("magnet/URL Protocol", ""); - settings.setValue("magnet/DefaultIcon/Default", Utils::Fs::toNativePath(iconStr)); + settings.setValue("magnet/DefaultIcon/Default", iconStr); settings.setValue("magnet/shell/Default", "open"); - settings.setValue("magnet/shell/open/command/Default", Utils::Fs::toNativePath(commandStr)); + settings.setValue("magnet/shell/open/command/Default", commandStr); } else if (isMagnetLinkAssocSet()) { @@ -1294,12 +1286,12 @@ void Preferences::setMainVSplitterState(const QByteArray &state) #endif } -QString Preferences::getMainLastDir() const +Path Preferences::getMainLastDir() const { - return value("MainWindow/LastDir", QDir::homePath()); + return value("MainWindow/LastDir", Utils::Fs::homePath()); } -void Preferences::setMainLastDir(const QString &path) +void Preferences::setMainLastDir(const Path &path) { setValue("MainWindow/LastDir", path); } diff --git a/src/base/preferences.h b/src/base/preferences.h index ad0d2235d..8186d5175 100644 --- a/src/base/preferences.h +++ b/src/base/preferences.h @@ -29,10 +29,11 @@ #pragma once -#include #include #include +#include +#include "base/pathfwd.h" #include "base/utils/net.h" class QDateTime; @@ -108,8 +109,8 @@ public: void setLocale(const QString &locale); bool useCustomUITheme() const; void setUseCustomUITheme(bool use); - QString customUIThemePath() const; - void setCustomUIThemePath(const QString &path); + Path customUIThemePath() const; + void setCustomUIThemePath(const Path &path); bool deleteTorrentFilesAsDefault() const; void setDeleteTorrentFilesAsDefault(bool del); bool confirmOnExit() const; @@ -140,10 +141,8 @@ public: #endif // Downloads - QString lastLocationPath() const; - void setLastLocationPath(const QString &path); - QString getScanDirsLastPath() const; - void setScanDirsLastPath(const QString &path); + Path getScanDirsLastPath() const; + void setScanDirsLastPath(const Path &path); bool isMailNotificationEnabled() const; void setMailNotificationEnabled(bool enabled); QString getMailNotificationSender() const; @@ -220,14 +219,14 @@ public: // HTTPS bool isWebUiHttpsEnabled() const; void setWebUiHttpsEnabled(bool enabled); - QString getWebUIHttpsCertificatePath() const; - void setWebUIHttpsCertificatePath(const QString &path); - QString getWebUIHttpsKeyPath() const; - void setWebUIHttpsKeyPath(const QString &path); + Path getWebUIHttpsCertificatePath() const; + void setWebUIHttpsCertificatePath(const Path &path); + Path getWebUIHttpsKeyPath() const; + void setWebUIHttpsKeyPath(const Path &path); bool isAltWebUiEnabled() const; void setAltWebUiEnabled(bool enabled); - QString getWebUiRootFolder() const; - void setWebUiRootFolder(const QString &path); + Path getWebUiRootFolder() const; + void setWebUiRootFolder(const Path &path); // WebUI custom HTTP headers bool isWebUICustomHTTPHeadersEnabled() const; @@ -343,8 +342,8 @@ public: void setMainGeometry(const QByteArray &geometry); QByteArray getMainVSplitterState() const; void setMainVSplitterState(const QByteArray &state); - QString getMainLastDir() const; - void setMainLastDir(const QString &path); + Path getMainLastDir() const; + void setMainLastDir(const Path &path); QByteArray getPeerListState() const; void setPeerListState(const QByteArray &state); QString getPropSplitterSizes() const; diff --git a/src/base/profile.cpp b/src/base/profile.cpp index 289df87b9..0fa3c5b58 100644 --- a/src/base/profile.cpp +++ b/src/base/profile.cpp @@ -29,11 +29,13 @@ #include "profile.h" +#include "base/path.h" +#include "base/utils/fs.h" #include "profile_p.h" Profile *Profile::m_instance = nullptr; -Profile::Profile(const QString &rootProfilePath, const QString &configurationName, const bool convertPathsToProfileRelative) +Profile::Profile(const Path &rootProfilePath, const QString &configurationName, const bool convertPathsToProfileRelative) { if (rootProfilePath.isEmpty()) m_profileImpl = std::make_unique(configurationName); @@ -50,7 +52,7 @@ Profile::Profile(const QString &rootProfilePath, const QString &configurationNam m_pathConverterImpl = std::make_unique(); } -void Profile::initInstance(const QString &rootProfilePath, const QString &configurationName, +void Profile::initInstance(const Path &rootProfilePath, const QString &configurationName, const bool convertPathsToProfileRelative) { if (m_instance) @@ -69,29 +71,29 @@ const Profile *Profile::instance() return m_instance; } -QString Profile::location(const SpecialFolder folder) const +Path Profile::location(const SpecialFolder folder) const { - QString result; switch (folder) { case SpecialFolder::Cache: - result = m_profileImpl->cacheLocation(); - break; + return m_profileImpl->cacheLocation(); + case SpecialFolder::Config: - result = m_profileImpl->configLocation(); - break; + return m_profileImpl->configLocation(); + case SpecialFolder::Data: - result = m_profileImpl->dataLocation(); - break; + return m_profileImpl->dataLocation(); + case SpecialFolder::Downloads: - result = m_profileImpl->downloadLocation(); - break; + return m_profileImpl->downloadLocation(); + } - return result; + Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown SpecialFolder value."); + return {}; } -QString Profile::rootPath() const +Path Profile::rootPath() const { return m_profileImpl->rootPath(); } @@ -113,22 +115,22 @@ SettingsPtr Profile::applicationSettings(const QString &name) const void Profile::ensureDirectoryExists(const SpecialFolder folder) const { - const QString locationPath = location(folder); - if (!locationPath.isEmpty() && !QDir().mkpath(locationPath)) - qFatal("Could not create required directory '%s'", qUtf8Printable(locationPath)); + const Path locationPath = location(folder); + if (!locationPath.isEmpty() && !Utils::Fs::mkpath(locationPath)) + qFatal("Could not create required directory '%s'", qUtf8Printable(locationPath.toString())); } -QString Profile::toPortablePath(const QString &absolutePath) const +Path Profile::toPortablePath(const Path &absolutePath) const { return m_pathConverterImpl->toPortablePath(absolutePath); } -QString Profile::fromPortablePath(const QString &portablePath) const +Path Profile::fromPortablePath(const Path &portablePath) const { return m_pathConverterImpl->fromPortablePath(portablePath); } -QString specialFolderLocation(const SpecialFolder folder) +Path specialFolderLocation(const SpecialFolder folder) { return Profile::instance()->location(folder); } diff --git a/src/base/profile.h b/src/base/profile.h index 0a4ca6ac8..fd5131572 100644 --- a/src/base/profile.h +++ b/src/base/profile.h @@ -33,6 +33,8 @@ #include +#include "base/pathfwd.h" + class QString; namespace Private @@ -54,26 +56,26 @@ enum class SpecialFolder class Profile { public: - static void initInstance(const QString &rootProfilePath, const QString &configurationName, + static void initInstance(const Path &rootProfilePath, const QString &configurationName, bool convertPathsToProfileRelative); static void freeInstance(); static const Profile *instance(); - QString location(SpecialFolder folder) const; + Path location(SpecialFolder folder) const; SettingsPtr applicationSettings(const QString &name) const; - QString rootPath() const; + Path rootPath() const; QString configurationName() const; /// Returns either default name for configuration file (QCoreApplication::applicationName()) /// or the value, supplied via parameters QString profileName() const; - QString toPortablePath(const QString &absolutePath) const; - QString fromPortablePath(const QString &portablePath) const; + Path toPortablePath(const Path &absolutePath) const; + Path fromPortablePath(const Path &portablePath) const; private: - Profile(const QString &rootProfilePath, const QString &configurationName, bool convertPathsToProfileRelative); + Profile(const Path &rootProfilePath, const QString &configurationName, bool convertPathsToProfileRelative); ~Profile() = default; // to generate correct call to ProfilePrivate::~ProfileImpl() void ensureDirectoryExists(SpecialFolder folder) const; @@ -83,4 +85,4 @@ private: static Profile *m_instance; }; -QString specialFolderLocation(SpecialFolder folder); +Path specialFolderLocation(SpecialFolder folder); diff --git a/src/base/profile_p.cpp b/src/base/profile_p.cpp index cd06122ef..44dde0feb 100644 --- a/src/base/profile_p.cpp +++ b/src/base/profile_p.cpp @@ -31,6 +31,8 @@ #include +#include "base/utils/fs.h" + Private::Profile::Profile(const QString &configurationName) : m_configurationName {configurationName} { @@ -56,22 +58,22 @@ Private::DefaultProfile::DefaultProfile(const QString &configurationName) { } -QString Private::DefaultProfile::rootPath() const +Path Private::DefaultProfile::rootPath() const { return {}; } -QString Private::DefaultProfile::basePath() const +Path Private::DefaultProfile::basePath() const { - return QDir::homePath(); + return Utils::Fs::homePath(); } -QString Private::DefaultProfile::cacheLocation() const +Path Private::DefaultProfile::cacheLocation() const { return locationWithConfigurationName(QStandardPaths::CacheLocation); } -QString Private::DefaultProfile::configLocation() const +Path Private::DefaultProfile::configLocation() const { #if defined(Q_OS_WIN) // On Windows QSettings stores files in FOLDERID_RoamingAppData\AppName @@ -81,22 +83,22 @@ QString Private::DefaultProfile::configLocation() const #endif } -QString Private::DefaultProfile::dataLocation() const +Path Private::DefaultProfile::dataLocation() const { #if defined(Q_OS_WIN) || defined (Q_OS_MACOS) return locationWithConfigurationName(QStandardPaths::AppLocalDataLocation); #else // On Linux keep using the legacy directory ~/.local/share/data/ if it exists - const QString legacyDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) - + QLatin1String("/data/") + profileName(); + const Path genericDataPath {QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)}; + const Path profilePath {profileName()}; + const Path legacyDir = genericDataPath / Path("data") / profilePath; - const QString dataDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) - + QLatin1Char('/') + profileName(); + const Path dataDir = genericDataPath / profilePath; - if (!QDir(dataDir).exists() && QDir(legacyDir).exists()) + if (!dataDir.exists() && legacyDir.exists()) { qWarning("The legacy data directory '%s' is used. It is recommended to move its content to '%s'", - qUtf8Printable(legacyDir), qUtf8Printable(dataDir)); + qUtf8Printable(legacyDir.toString()), qUtf8Printable(dataDir.toString())); return legacyDir; } @@ -105,9 +107,9 @@ QString Private::DefaultProfile::dataLocation() const #endif } -QString Private::DefaultProfile::downloadLocation() const +Path Private::DefaultProfile::downloadLocation() const { - return QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); + return Path(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); } SettingsPtr Private::DefaultProfile::applicationSettings(const QString &name) const @@ -119,48 +121,48 @@ SettingsPtr Private::DefaultProfile::applicationSettings(const QString &name) co #endif } -QString Private::DefaultProfile::locationWithConfigurationName(const QStandardPaths::StandardLocation location) const +Path Private::DefaultProfile::locationWithConfigurationName(const QStandardPaths::StandardLocation location) const { - return QStandardPaths::writableLocation(location) + configurationSuffix(); + return Path(QStandardPaths::writableLocation(location) + configurationSuffix()); } -Private::CustomProfile::CustomProfile(const QString &rootPath, const QString &configurationName) +Private::CustomProfile::CustomProfile(const Path &rootPath, const QString &configurationName) : Profile {configurationName} - , m_rootDir {rootPath} - , m_baseDir {m_rootDir.absoluteFilePath(profileName())} - , m_cacheLocation {m_baseDir.absoluteFilePath(QLatin1String("cache"))} - , m_configLocation {m_baseDir.absoluteFilePath(QLatin1String("config"))} - , m_dataLocation {m_baseDir.absoluteFilePath(QLatin1String("data"))} - , m_downloadLocation {m_baseDir.absoluteFilePath(QLatin1String("downloads"))} + , m_rootPath {rootPath} + , m_basePath {m_rootPath / Path(profileName())} + , m_cacheLocation {m_basePath / Path("cache")} + , m_configLocation {m_basePath / Path("config")} + , m_dataLocation {m_basePath / Path("data")} + , m_downloadLocation {m_basePath / Path("downloads")} { } -QString Private::CustomProfile::rootPath() const +Path Private::CustomProfile::rootPath() const { - return m_rootDir.absolutePath(); + return m_rootPath; } -QString Private::CustomProfile::basePath() const +Path Private::CustomProfile::basePath() const { - return m_baseDir.absolutePath(); + return m_basePath; } -QString Private::CustomProfile::cacheLocation() const +Path Private::CustomProfile::cacheLocation() const { return m_cacheLocation; } -QString Private::CustomProfile::configLocation() const +Path Private::CustomProfile::configLocation() const { return m_configLocation; } -QString Private::CustomProfile::dataLocation() const +Path Private::CustomProfile::dataLocation() const { return m_dataLocation; } -QString Private::CustomProfile::downloadLocation() const +Path Private::CustomProfile::downloadLocation() const { return m_downloadLocation; } @@ -173,48 +175,48 @@ SettingsPtr Private::CustomProfile::applicationSettings(const QString &name) con #else const char CONF_FILE_EXTENSION[] = ".conf"; #endif - const QString settingsFileName {QDir(configLocation()).absoluteFilePath(name + QLatin1String(CONF_FILE_EXTENSION))}; - return SettingsPtr(new QSettings(settingsFileName, QSettings::IniFormat)); + const Path settingsFilePath = configLocation() / Path(name + QLatin1String(CONF_FILE_EXTENSION)); + return SettingsPtr(new QSettings(settingsFilePath.data(), QSettings::IniFormat)); } -QString Private::NoConvertConverter::fromPortablePath(const QString &portablePath) const +Path Private::NoConvertConverter::fromPortablePath(const Path &portablePath) const { return portablePath; } -QString Private::NoConvertConverter::toPortablePath(const QString &path) const +Path Private::NoConvertConverter::toPortablePath(const Path &path) const { return path; } -Private::Converter::Converter(const QString &basePath) - : m_baseDir {basePath} +Private::Converter::Converter(const Path &basePath) + : m_basePath {basePath} { - m_baseDir.makeAbsolute(); + Q_ASSERT(basePath.isAbsolute()); } -QString Private::Converter::toPortablePath(const QString &path) const +Path Private::Converter::toPortablePath(const Path &path) const { - if (path.isEmpty() || m_baseDir.path().isEmpty()) + if (path.isEmpty()) return path; #ifdef Q_OS_WIN - if (QDir::isAbsolutePath(path)) + if (path.isAbsolute()) { - const QChar driveLeter = path[0].toUpper(); - const QChar baseDriveLetter = m_baseDir.path()[0].toUpper(); - const bool onSameDrive = (driveLeter.category() == QChar::Letter_Uppercase) && (driveLeter == baseDriveLetter); + const QChar driveLetter = path.data()[0].toUpper(); + const QChar baseDriveLetter = m_basePath.data()[0].toUpper(); + const bool onSameDrive = (driveLetter.category() == QChar::Letter_Uppercase) && (driveLetter == baseDriveLetter); if (!onSameDrive) return path; } #endif - return m_baseDir.relativeFilePath(path); + return m_basePath.relativePathOf(path); } -QString Private::Converter::fromPortablePath(const QString &portablePath) const +Path Private::Converter::fromPortablePath(const Path &portablePath) const { - if (portablePath.isEmpty() || QDir::isAbsolutePath(portablePath)) + if (portablePath.isEmpty() || portablePath.isAbsolute()) return portablePath; - return QDir::cleanPath(m_baseDir.absoluteFilePath(portablePath)); + return m_basePath / portablePath; } diff --git a/src/base/profile_p.h b/src/base/profile_p.h index dea94e2e9..73293a4bf 100644 --- a/src/base/profile_p.h +++ b/src/base/profile_p.h @@ -29,10 +29,10 @@ #pragma once -#include #include -#include "base/profile.h" +#include "base/path.h" +#include "profile.h" namespace Private { @@ -41,17 +41,17 @@ namespace Private public: virtual ~Profile() = default; - virtual QString rootPath() const = 0; + virtual Path rootPath() const = 0; /** * @brief The base path against to which portable (relative) paths are resolved */ - virtual QString basePath() const = 0; + virtual Path basePath() const = 0; - virtual QString cacheLocation() const = 0; - virtual QString configLocation() const = 0; - virtual QString dataLocation() const = 0; - virtual QString downloadLocation() const = 0; + virtual Path cacheLocation() const = 0; + virtual Path configLocation() const = 0; + virtual Path dataLocation() const = 0; + virtual Path downloadLocation() const = 0; virtual SettingsPtr applicationSettings(const QString &name) const = 0; @@ -77,12 +77,12 @@ namespace Private public: explicit DefaultProfile(const QString &configurationName); - QString rootPath() const override; - QString basePath() const override; - QString cacheLocation() const override; - QString configLocation() const override; - QString dataLocation() const override; - QString downloadLocation() const override; + Path rootPath() const override; + Path basePath() const override; + Path cacheLocation() const override; + Path configLocation() const override; + Path dataLocation() const override; + Path downloadLocation() const override; SettingsPtr applicationSettings(const QString &name) const override; private: @@ -92,55 +92,55 @@ namespace Private * @param location location kind * @return QStandardPaths::writableLocation(location) / configurationName() */ - QString locationWithConfigurationName(QStandardPaths::StandardLocation location) const; + Path locationWithConfigurationName(QStandardPaths::StandardLocation location) const; }; /// Custom tree: creates directories under the specified root directory class CustomProfile final : public Profile { public: - CustomProfile(const QString &rootPath, const QString &configurationName); + CustomProfile(const Path &rootPath, const QString &configurationName); - QString rootPath() const override; - QString basePath() const override; - QString cacheLocation() const override; - QString configLocation() const override; - QString dataLocation() const override; - QString downloadLocation() const override; + Path rootPath() const override; + Path basePath() const override; + Path cacheLocation() const override; + Path configLocation() const override; + Path dataLocation() const override; + Path downloadLocation() const override; SettingsPtr applicationSettings(const QString &name) const override; private: - const QDir m_rootDir; - const QDir m_baseDir; - const QString m_cacheLocation; - const QString m_configLocation; - const QString m_dataLocation; - const QString m_downloadLocation; + const Path m_rootPath; + const Path m_basePath; + const Path m_cacheLocation; + const Path m_configLocation; + const Path m_dataLocation; + const Path m_downloadLocation; }; class PathConverter { public: - virtual QString toPortablePath(const QString &path) const = 0; - virtual QString fromPortablePath(const QString &portablePath) const = 0; + virtual Path toPortablePath(const Path &path) const = 0; + virtual Path fromPortablePath(const Path &portablePath) const = 0; virtual ~PathConverter() = default; }; class NoConvertConverter final : public PathConverter { public: - QString toPortablePath(const QString &path) const override; - QString fromPortablePath(const QString &portablePath) const override; + Path toPortablePath(const Path &path) const override; + Path fromPortablePath(const Path &portablePath) const override; }; class Converter final : public PathConverter { public: - explicit Converter(const QString &basePath); - QString toPortablePath(const QString &path) const override; - QString fromPortablePath(const QString &portablePath) const override; + explicit Converter(const Path &basePath); + Path toPortablePath(const Path &path) const override; + Path fromPortablePath(const Path &portablePath) const override; private: - QDir m_baseDir; + Path m_basePath; }; } diff --git a/src/base/rss/rss_autodownloader.cpp b/src/base/rss/rss_autodownloader.cpp index 49345b5ff..12a632726 100644 --- a/src/base/rss/rss_autodownloader.cpp +++ b/src/base/rss/rss_autodownloader.cpp @@ -58,8 +58,8 @@ struct ProcessingJob QVariantHash articleData; }; -const QString ConfFolderName(QStringLiteral("rss")); -const QString RulesFileName(QStringLiteral("download_rules.json")); +const QString CONF_FOLDER_NAME {QStringLiteral("rss")}; +const QString RULES_FILE_NAME {QStringLiteral("download_rules.json")}; namespace { @@ -107,17 +107,16 @@ AutoDownloader::AutoDownloader() Q_ASSERT(!m_instance); // only one instance is allowed m_instance = this; - m_fileStorage = new AsyncFileStorage( - Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Config) + QLatin1Char('/') + ConfFolderName)); + m_fileStorage = new AsyncFileStorage(specialFolderLocation(SpecialFolder::Config) / Path(CONF_FOLDER_NAME)); if (!m_fileStorage) throw RuntimeError(tr("Directory for RSS AutoDownloader data is unavailable.")); m_fileStorage->moveToThread(m_ioThread); connect(m_ioThread, &QThread::finished, m_fileStorage, &AsyncFileStorage::deleteLater); - connect(m_fileStorage, &AsyncFileStorage::failed, [](const QString &fileName, const QString &errorString) + connect(m_fileStorage, &AsyncFileStorage::failed, [](const Path &fileName, const QString &errorString) { LogMsg(tr("Couldn't save RSS AutoDownloader data in %1. Error: %2") - .arg(fileName, errorString), Log::CRITICAL); + .arg(fileName.toString(), errorString), Log::CRITICAL); }); m_ioThread->start(); @@ -414,7 +413,7 @@ void AutoDownloader::processJob(const QSharedPointer &job) void AutoDownloader::load() { - QFile rulesFile(m_fileStorage->storageDir().absoluteFilePath(RulesFileName)); + QFile rulesFile {(m_fileStorage->storageDir() / Path(RULES_FILE_NAME)).data()}; if (!rulesFile.exists()) loadRulesLegacy(); @@ -463,7 +462,7 @@ void AutoDownloader::store() for (const auto &rule : asConst(m_rules)) jsonObj.insert(rule.name(), rule.toJsonObject()); - m_fileStorage->store(RulesFileName, QJsonDocument(jsonObj).toJson()); + m_fileStorage->store(Path(RULES_FILE_NAME), QJsonDocument(jsonObj).toJson()); } void AutoDownloader::storeDeferred() diff --git a/src/base/rss/rss_autodownloadrule.cpp b/src/base/rss/rss_autodownloadrule.cpp index d78bb3541..65abd5afd 100644 --- a/src/base/rss/rss_autodownloadrule.cpp +++ b/src/base/rss/rss_autodownloadrule.cpp @@ -41,6 +41,7 @@ #include #include "base/global.h" +#include "base/path.h" #include "base/preferences.h" #include "base/utils/fs.h" #include "base/utils/string.h" @@ -132,7 +133,7 @@ namespace RSS int ignoreDays = 0; QDateTime lastMatch; - QString savePath; + Path savePath; QString category; std::optional addPaused; std::optional contentLayout; @@ -466,7 +467,7 @@ QJsonObject AutoDownloadRule::toJsonObject() const , {Str_MustNotContain, mustNotContain()} , {Str_EpisodeFilter, episodeFilter()} , {Str_AffectedFeeds, QJsonArray::fromStringList(feedURLs())} - , {Str_SavePath, savePath()} + , {Str_SavePath, savePath().toString()} , {Str_AssignedCategory, assignedCategory()} , {Str_LastMatch, lastMatch().toString(Qt::RFC2822Date)} , {Str_IgnoreDays, ignoreDays()} @@ -485,7 +486,7 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co rule.setMustNotContain(jsonObj.value(Str_MustNotContain).toString()); rule.setEpisodeFilter(jsonObj.value(Str_EpisodeFilter).toString()); rule.setEnabled(jsonObj.value(Str_Enabled).toBool(true)); - rule.setSavePath(jsonObj.value(Str_SavePath).toString()); + rule.setSavePath(Path(jsonObj.value(Str_SavePath).toString())); rule.setCategory(jsonObj.value(Str_AssignedCategory).toString()); rule.setAddPaused(toOptionalBool(jsonObj.value(Str_AddPaused))); @@ -546,7 +547,7 @@ QVariantHash AutoDownloadRule::toLegacyDict() const return {{"name", name()}, {"must_contain", mustContain()}, {"must_not_contain", mustNotContain()}, - {"save_path", savePath()}, + {"save_path", savePath().toString()}, {"affected_feeds", feedURLs()}, {"enabled", isEnabled()}, {"category_assigned", assignedCategory()}, @@ -567,7 +568,7 @@ AutoDownloadRule AutoDownloadRule::fromLegacyDict(const QVariantHash &dict) rule.setEpisodeFilter(dict.value("episode_filter").toString()); rule.setFeedURLs(dict.value("affected_feeds").toStringList()); rule.setEnabled(dict.value("enabled", false).toBool()); - rule.setSavePath(dict.value("save_path").toString()); + rule.setSavePath(Path(dict.value("save_path").toString())); rule.setCategory(dict.value("category_assigned").toString()); rule.setAddPaused(addPausedLegacyToOptionalBool(dict.value("add_paused").toInt())); rule.setLastMatch(dict.value("last_match").toDateTime()); @@ -624,14 +625,14 @@ void AutoDownloadRule::setName(const QString &name) m_dataPtr->name = name; } -QString AutoDownloadRule::savePath() const +Path AutoDownloadRule::savePath() const { return m_dataPtr->savePath; } -void AutoDownloadRule::setSavePath(const QString &savePath) +void AutoDownloadRule::setSavePath(const Path &savePath) { - m_dataPtr->savePath = Utils::Fs::toUniformPath(savePath); + m_dataPtr->savePath = savePath; } std::optional AutoDownloadRule::addPaused() const diff --git a/src/base/rss/rss_autodownloadrule.h b/src/base/rss/rss_autodownloadrule.h index 2af6af810..c7446df6f 100644 --- a/src/base/rss/rss_autodownloadrule.h +++ b/src/base/rss/rss_autodownloadrule.h @@ -35,6 +35,7 @@ #include #include "base/bittorrent/torrentcontentlayout.h" +#include "base/pathfwd.h" class QDateTime; class QJsonObject; @@ -77,8 +78,8 @@ namespace RSS QStringList previouslyMatchedEpisodes() const; void setPreviouslyMatchedEpisodes(const QStringList &previouslyMatchedEpisodes); - QString savePath() const; - void setSavePath(const QString &savePath); + Path savePath() const; + void setSavePath(const Path &savePath); std::optional addPaused() const; void setAddPaused(std::optional addPaused); std::optional torrentContentLayout() const; diff --git a/src/base/rss/rss_feed.cpp b/src/base/rss/rss_feed.cpp index 7563c7b02..63763fb55 100644 --- a/src/base/rss/rss_feed.cpp +++ b/src/base/rss/rss_feed.cpp @@ -68,17 +68,16 @@ Feed::Feed(const QUuid &uid, const QString &url, const QString &path, Session *s , m_url(url) { const auto uidHex = QString::fromLatin1(m_uid.toRfc4122().toHex()); - m_dataFileName = uidHex + QLatin1String(".json"); + m_dataFileName = Path(uidHex + QLatin1String(".json")); // Move to new file naming scheme (since v4.1.2) - const QString legacyFilename - {Utils::Fs::toValidFileSystemName(m_url, false, QLatin1String("_")) - + QLatin1String(".json")}; - const QDir storageDir {m_session->dataFileStorage()->storageDir()}; - if (!QFile::exists(storageDir.absoluteFilePath(m_dataFileName))) - QFile::rename(storageDir.absoluteFilePath(legacyFilename), storageDir.absoluteFilePath(m_dataFileName)); + const QString legacyFilename = Utils::Fs::toValidFileName(m_url, QLatin1String("_")) + QLatin1String(".json"); + const Path storageDir = m_session->dataFileStorage()->storageDir(); + const Path dataFilePath = storageDir / m_dataFileName; + if (!dataFilePath.exists()) + Utils::Fs::renameFile((storageDir / Path(legacyFilename)), dataFilePath); - m_iconPath = Utils::Fs::toUniformPath(storageDir.absoluteFilePath(uidHex + QLatin1String(".ico"))); + m_iconPath = storageDir / Path(uidHex + QLatin1String(".ico")); m_parser = new Private::Parser(m_lastBuildDate); m_parser->moveToThread(m_session->workingThread()); @@ -139,7 +138,7 @@ void Feed::refresh() m_downloadHandler = Net::DownloadManager::instance()->download(m_url); connect(m_downloadHandler, &Net::DownloadHandler::finished, this, &Feed::handleDownloadFinished); - if (!QFile::exists(m_iconPath)) + if (!m_iconPath.exists()) downloadIcon(); m_isLoading = true; @@ -262,7 +261,7 @@ void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result) void Feed::load() { - QFile file(m_session->dataFileStorage()->storageDir().absoluteFilePath(m_dataFileName)); + QFile file {(m_session->dataFileStorage()->storageDir() / m_dataFileName).data()}; if (!file.exists()) { @@ -278,7 +277,7 @@ void Feed::load() else { LogMsg(tr("Couldn't read RSS Session data from %1. Error: %2") - .arg(m_dataFileName, file.errorString()) + .arg(m_dataFileName.toString(), file.errorString()) , Log::WARNING); } } @@ -500,7 +499,7 @@ int Feed::updateArticles(const QList &loadedArticles) return newArticlesCount; } -QString Feed::iconPath() const +Path Feed::iconPath() const { return m_iconPath; } @@ -549,8 +548,8 @@ void Feed::handleArticleRead(Article *article) void Feed::cleanup() { - Utils::Fs::forceRemove(m_session->dataFileStorage()->storageDir().absoluteFilePath(m_dataFileName)); - Utils::Fs::forceRemove(m_iconPath); + Utils::Fs::removeFile(m_session->dataFileStorage()->storageDir() / m_dataFileName); + Utils::Fs::removeFile(m_iconPath); } void Feed::timerEvent(QTimerEvent *event) diff --git a/src/base/rss/rss_feed.h b/src/base/rss/rss_feed.h index 5156a12d3..584c6b2d6 100644 --- a/src/base/rss/rss_feed.h +++ b/src/base/rss/rss_feed.h @@ -35,6 +35,7 @@ #include #include +#include "base/path.h" #include "rss_item.h" class AsyncFileStorage; @@ -79,7 +80,7 @@ namespace RSS bool hasError() const; bool isLoading() const; Article *articleByGUID(const QString &guid) const; - QString iconPath() const; + Path iconPath() const; QJsonValue toJsonValue(bool withData = false) const override; @@ -122,8 +123,8 @@ namespace RSS QHash m_articles; QList
m_articlesByDate; int m_unreadCount = 0; - QString m_iconPath; - QString m_dataFileName; + Path m_iconPath; + Path m_dataFileName; QBasicTimer m_savingTimer; bool m_dirty = false; Net::DownloadHandler *m_downloadHandler = nullptr; diff --git a/src/base/rss/rss_session.cpp b/src/base/rss/rss_session.cpp index f5806e568..0ed96572b 100644 --- a/src/base/rss/rss_session.cpp +++ b/src/base/rss/rss_session.cpp @@ -49,9 +49,9 @@ #include "rss_item.h" const int MsecsPerMin = 60000; -const QString ConfFolderName(QStringLiteral("rss")); -const QString DataFolderName(QStringLiteral("rss/articles")); -const QString FeedsFileName(QStringLiteral("feeds.json")); +const QString CONF_FOLDER_NAME(QStringLiteral("rss")); +const QString DATA_FOLDER_NAME(QStringLiteral("rss/articles")); +const QString FEEDS_FILE_NAME(QStringLiteral("feeds.json")); using namespace RSS; @@ -66,24 +66,22 @@ Session::Session() Q_ASSERT(!m_instance); // only one instance is allowed m_instance = this; - m_confFileStorage = new AsyncFileStorage( - Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Config) + QLatin1Char('/') + ConfFolderName)); + m_confFileStorage = new AsyncFileStorage(specialFolderLocation(SpecialFolder::Config) / Path(CONF_FOLDER_NAME)); m_confFileStorage->moveToThread(m_workingThread); connect(m_workingThread, &QThread::finished, m_confFileStorage, &AsyncFileStorage::deleteLater); - connect(m_confFileStorage, &AsyncFileStorage::failed, [](const QString &fileName, const QString &errorString) + connect(m_confFileStorage, &AsyncFileStorage::failed, [](const Path &fileName, const QString &errorString) { - Logger::instance()->addMessage(QString("Couldn't save RSS Session configuration in %1. Error: %2") - .arg(fileName, errorString), Log::WARNING); + LogMsg(tr("Couldn't save RSS Session configuration in %1. Error: %2") + .arg(fileName.toString(), errorString), Log::WARNING); }); - m_dataFileStorage = new AsyncFileStorage( - Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + QLatin1Char('/') + DataFolderName)); + m_dataFileStorage = new AsyncFileStorage(specialFolderLocation(SpecialFolder::Data) / Path(DATA_FOLDER_NAME)); m_dataFileStorage->moveToThread(m_workingThread); connect(m_workingThread, &QThread::finished, m_dataFileStorage, &AsyncFileStorage::deleteLater); - connect(m_dataFileStorage, &AsyncFileStorage::failed, [](const QString &fileName, const QString &errorString) + connect(m_dataFileStorage, &AsyncFileStorage::failed, [](const Path &fileName, const QString &errorString) { - Logger::instance()->addMessage(QString("Couldn't save RSS Session data in %1. Error: %2") - .arg(fileName, errorString), Log::WARNING); + LogMsg(tr("Couldn't save RSS Session data in %1. Error: %2") + .arg(fileName.toString(), errorString), Log::WARNING); }); m_itemsByPath.insert("", new Folder); // root folder @@ -233,7 +231,7 @@ Item *Session::itemByPath(const QString &path) const void Session::load() { - QFile itemsFile(m_confFileStorage->storageDir().absoluteFilePath(FeedsFileName)); + QFile itemsFile {(m_confFileStorage->storageDir() / Path(FEEDS_FILE_NAME)).data()}; if (!itemsFile.exists()) { loadLegacy(); @@ -372,7 +370,8 @@ void Session::loadLegacy() void Session::store() { - m_confFileStorage->store(FeedsFileName, QJsonDocument(rootFolder()->toJsonValue().toObject()).toJson()); + m_confFileStorage->store(Path(FEEDS_FILE_NAME) + , QJsonDocument(rootFolder()->toJsonValue().toObject()).toJson()); } nonstd::expected Session::prepareItemDest(const QString &path) diff --git a/src/base/search/searchdownloadhandler.cpp b/src/base/search/searchdownloadhandler.cpp index 2d781ff7a..b20c64aa2 100644 --- a/src/base/search/searchdownloadhandler.cpp +++ b/src/base/search/searchdownloadhandler.cpp @@ -30,8 +30,9 @@ #include -#include "../utils/foreignapps.h" -#include "../utils/fs.h" +#include "base/path.h" +#include "base/utils/foreignapps.h" +#include "base/utils/fs.h" #include "searchpluginmanager.h" SearchDownloadHandler::SearchDownloadHandler(const QString &siteUrl, const QString &url, SearchPluginManager *manager) @@ -44,7 +45,7 @@ SearchDownloadHandler::SearchDownloadHandler(const QString &siteUrl, const QStri , this, &SearchDownloadHandler::downloadProcessFinished); const QStringList params { - Utils::Fs::toNativePath(m_manager->engineLocation() + "/nova2dl.py"), + (m_manager->engineLocation() / Path("nova2dl.py")).toString(), siteUrl, url }; diff --git a/src/base/search/searchhandler.cpp b/src/base/search/searchhandler.cpp index 7aec0d84d..d92c1752a 100644 --- a/src/base/search/searchhandler.cpp +++ b/src/base/search/searchhandler.cpp @@ -34,6 +34,7 @@ #include #include "base/global.h" +#include "base/path.h" #include "base/utils/foreignapps.h" #include "base/utils/fs.h" #include "searchpluginmanager.h" @@ -67,7 +68,7 @@ SearchHandler::SearchHandler(const QString &pattern, const QString &category, co const QStringList params { - Utils::Fs::toNativePath(m_manager->engineLocation() + "/nova2.py"), + (m_manager->engineLocation() / Path("nova2.py")).toString(), m_usedPlugins.join(','), m_category }; diff --git a/src/base/search/searchpluginmanager.cpp b/src/base/search/searchpluginmanager.cpp index bdc4bafcf..de0701643 100644 --- a/src/base/search/searchpluginmanager.cpp +++ b/src/base/search/searchpluginmanager.cpp @@ -52,30 +52,31 @@ namespace { - void clearPythonCache(const QString &path) + void clearPythonCache(const Path &path) { // remove python cache artifacts in `path` and subdirs - QStringList dirs = {path}; - QDirIterator iter {path, (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories}; + PathList dirs = {path}; + QDirIterator iter {path.data(), (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories}; while (iter.hasNext()) - dirs += iter.next(); + dirs += Path(iter.next()); - for (const QString &dir : asConst(dirs)) + for (const Path &dir : asConst(dirs)) { // python 3: remove "__pycache__" folders - if (dir.endsWith("/__pycache__")) + if (dir.filename() == QLatin1String("__pycache__")) { - Utils::Fs::removeDirRecursive(dir); + Utils::Fs::removeDirRecursively(dir); continue; } // python 2: remove "*.pyc" files - const QStringList files = QDir(dir).entryList(QDir::Files); + const QStringList files = QDir(dir.data()).entryList(QDir::Files); for (const QString &file : files) { - if (file.endsWith(".pyc")) - Utils::Fs::forceRemove(file); + const Path path {file}; + if (path.hasExtension(QLatin1String(".pyc"))) + Utils::Fs::removeFile(path); } } } @@ -210,21 +211,22 @@ void SearchPluginManager::installPlugin(const QString &source) } else { - QString path = source; - if (path.startsWith("file:", Qt::CaseInsensitive)) - path = QUrl(path).toLocalFile(); + const Path path {source.startsWith("file:", Qt::CaseInsensitive) ? QUrl(source).toLocalFile() : source}; - QString pluginName = Utils::Fs::fileName(path); - pluginName.chop(pluginName.size() - pluginName.lastIndexOf('.')); - - if (!path.endsWith(".py", Qt::CaseInsensitive)) - emit pluginInstallationFailed(pluginName, tr("Unknown search engine plugin file format.")); - else + QString pluginName = path.filename(); + if (pluginName.endsWith(".py", Qt::CaseInsensitive)) + { + pluginName.chop(pluginName.size() - pluginName.lastIndexOf('.')); installPlugin_impl(pluginName, path); + } + else + { + emit pluginInstallationFailed(pluginName, tr("Unknown search engine plugin file format.")); + } } } -void SearchPluginManager::installPlugin_impl(const QString &name, const QString &path) +void SearchPluginManager::installPlugin_impl(const QString &name, const Path &path) { const PluginVersion newVersion = getPluginVersion(path); const PluginInfo *plugin = pluginInfo(name); @@ -236,30 +238,31 @@ void SearchPluginManager::installPlugin_impl(const QString &name, const QString } // Process with install - const QString destPath = pluginPath(name); + const Path destPath = pluginPath(name); + const Path backupPath = destPath + ".bak"; bool updated = false; - if (QFile::exists(destPath)) + if (destPath.exists()) { // Backup in case install fails - QFile::copy(destPath, destPath + ".bak"); - Utils::Fs::forceRemove(destPath); + Utils::Fs::copyFile(destPath, backupPath); + Utils::Fs::removeFile(destPath); updated = true; } // Copy the plugin - QFile::copy(path, destPath); + Utils::Fs::copyFile(path, destPath); // Update supported plugins update(); // Check if this was correctly installed if (!m_plugins.contains(name)) { // Remove broken file - Utils::Fs::forceRemove(destPath); + Utils::Fs::removeFile(destPath); LogMsg(tr("Plugin %1 is not supported.").arg(name), Log::INFO); if (updated) { // restore backup - QFile::copy(destPath + ".bak", destPath); - Utils::Fs::forceRemove(destPath + ".bak"); + Utils::Fs::copyFile(backupPath, destPath); + Utils::Fs::removeFile(backupPath); // Update supported plugins update(); emit pluginUpdateFailed(name, tr("Plugin is not supported.")); @@ -275,7 +278,7 @@ void SearchPluginManager::installPlugin_impl(const QString &name, const QString if (updated) { LogMsg(tr("Plugin %1 has been successfully updated.").arg(name), Log::INFO); - Utils::Fs::forceRemove(destPath + ".bak"); + Utils::Fs::removeFile(backupPath); } } } @@ -285,12 +288,11 @@ bool SearchPluginManager::uninstallPlugin(const QString &name) clearPythonCache(engineLocation()); // remove it from hard drive - const QDir pluginsFolder(pluginsLocation()); - QStringList filters; - filters << name + ".*"; - const QStringList files = pluginsFolder.entryList(filters, QDir::Files, QDir::Unsorted); + const Path pluginsPath = pluginsLocation(); + const QStringList filters {name + QLatin1String(".*")}; + const QStringList files = QDir(pluginsPath.data()).entryList(filters, QDir::Files, QDir::Unsorted); for (const QString &file : files) - Utils::Fs::forceRemove(pluginsFolder.absoluteFilePath(file)); + Utils::Fs::removeFile(pluginsPath / Path(file)); // Remove it from supported engines delete m_plugins.take(name); @@ -301,15 +303,17 @@ bool SearchPluginManager::uninstallPlugin(const QString &name) void SearchPluginManager::updateIconPath(PluginInfo *const plugin) { if (!plugin) return; - QString iconPath = QString::fromLatin1("%1/%2.png").arg(pluginsLocation(), plugin->name); - if (QFile::exists(iconPath)) + + const Path pluginsPath = pluginsLocation(); + Path iconPath = pluginsPath / Path(plugin->name + QLatin1String(".png")); + if (iconPath.exists()) { plugin->iconPath = iconPath; } else { - iconPath = QString::fromLatin1("%1/%2.ico").arg(pluginsLocation(), plugin->name); - if (QFile::exists(iconPath)) + iconPath = pluginsPath / Path(plugin->name + QLatin1String(".ico")); + if (iconPath.exists()) plugin->iconPath = iconPath; } } @@ -357,20 +361,18 @@ QString SearchPluginManager::pluginFullName(const QString &pluginName) return pluginInfo(pluginName) ? pluginInfo(pluginName)->fullName : QString(); } -QString SearchPluginManager::pluginsLocation() +Path SearchPluginManager::pluginsLocation() { - return QString::fromLatin1("%1/engines").arg(engineLocation()); + return (engineLocation() / Path("engines")); } -QString SearchPluginManager::engineLocation() +Path SearchPluginManager::engineLocation() { - static QString location; + static Path location; if (location.isEmpty()) { - location = Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + "/nova3"); - - const QDir locationDir(location); - locationDir.mkpath(locationDir.absolutePath()); + location = specialFolderLocation(SpecialFolder::Data) / Path("nova3"); + Utils::Fs::mkpath(location); } return location; @@ -388,12 +390,12 @@ void SearchPluginManager::pluginDownloadFinished(const Net::DownloadResult &resu { if (result.status == Net::DownloadStatus::Success) { - const QString filePath = Utils::Fs::toUniformPath(result.filePath); + const Path filePath = result.filePath; - QString pluginName = Utils::Fs::fileName(result.url); - pluginName.chop(pluginName.size() - pluginName.lastIndexOf('.')); // Remove extension - installPlugin_impl(pluginName, filePath); - Utils::Fs::forceRemove(filePath); + Path pluginPath {QUrl(result.url).path()}; + pluginPath.removeExtension(); // Remove extension + installPlugin_impl(pluginPath.filename(), filePath); + Utils::Fs::removeFile(filePath); } else { @@ -412,37 +414,37 @@ void SearchPluginManager::pluginDownloadFinished(const Net::DownloadResult &resu void SearchPluginManager::updateNova() { // create nova directory if necessary - const QDir searchDir(engineLocation()); + const Path enginePath = engineLocation(); - QFile packageFile(searchDir.absoluteFilePath("__init__.py")); + QFile packageFile {(enginePath / Path("__init__.py")).data()}; packageFile.open(QIODevice::WriteOnly); packageFile.close(); - searchDir.mkdir("engines"); + Utils::Fs::mkdir(enginePath / Path("engines")); - QFile packageFile2(searchDir.absolutePath() + "/engines/__init__.py"); + QFile packageFile2 {(enginePath / Path("engines/__init__.py")).data()}; packageFile2.open(QIODevice::WriteOnly); packageFile2.close(); // Copy search plugin files (if necessary) - const auto updateFile = [](const QString &filename, const bool compareVersion) + const auto updateFile = [&enginePath](const Path &filename, const bool compareVersion) { - const QString filePathBundled = ":/searchengine/nova3/" + filename; - const QString filePathDisk = QDir(engineLocation()).absoluteFilePath(filename); + const Path filePathBundled = Path(":/searchengine/nova3") / filename; + const Path filePathDisk = enginePath / filename; if (compareVersion && (getPluginVersion(filePathBundled) <= getPluginVersion(filePathDisk))) return; - Utils::Fs::forceRemove(filePathDisk); - QFile::copy(filePathBundled, filePathDisk); + Utils::Fs::removeFile(filePathDisk); + Utils::Fs::copyFile(filePathBundled, filePathDisk); }; - updateFile("helpers.py", true); - updateFile("nova2.py", true); - updateFile("nova2dl.py", true); - updateFile("novaprinter.py", true); - updateFile("sgmllib3.py", false); - updateFile("socks.py", false); + updateFile(Path("helpers.py"), true); + updateFile(Path("nova2.py"), true); + updateFile(Path("nova2dl.py"), true); + updateFile(Path("novaprinter.py"), true); + updateFile(Path("sgmllib3.py"), false); + updateFile(Path("socks.py"), false); } void SearchPluginManager::update() @@ -450,7 +452,7 @@ void SearchPluginManager::update() QProcess nova; nova.setProcessEnvironment(QProcessEnvironment::systemEnvironment()); - const QStringList params {Utils::Fs::toNativePath(engineLocation() + "/nova2.py"), "--capabilities"}; + const QStringList params {(engineLocation() / Path("/nova2.py")).toString(), QLatin1String("--capabilities")}; nova.start(Utils::ForeignApps::pythonInfo().executableName, params, QIODevice::ReadOnly); nova.waitForFinished(); @@ -559,14 +561,14 @@ bool SearchPluginManager::isUpdateNeeded(const QString &pluginName, const Plugin return (newVersion > oldVersion); } -QString SearchPluginManager::pluginPath(const QString &name) +Path SearchPluginManager::pluginPath(const QString &name) { - return QString::fromLatin1("%1/%2.py").arg(pluginsLocation(), name); + return (pluginsLocation() / Path(name + QLatin1String(".py"))); } -PluginVersion SearchPluginManager::getPluginVersion(const QString &filePath) +PluginVersion SearchPluginManager::getPluginVersion(const Path &filePath) { - QFile pluginFile(filePath); + QFile pluginFile {filePath.data()}; if (!pluginFile.open(QIODevice::ReadOnly | QIODevice::Text)) return {}; @@ -581,7 +583,7 @@ PluginVersion SearchPluginManager::getPluginVersion(const QString &filePath) return version; LogMsg(tr("Search plugin '%1' contains invalid version string ('%2')") - .arg(Utils::Fs::fileName(filePath), versionStr), Log::MsgType::WARNING); + .arg(filePath.filename(), versionStr), Log::MsgType::WARNING); break; } diff --git a/src/base/search/searchpluginmanager.h b/src/base/search/searchpluginmanager.h index bc7490cb0..0257313d3 100644 --- a/src/base/search/searchpluginmanager.h +++ b/src/base/search/searchpluginmanager.h @@ -33,6 +33,7 @@ #include #include +#include "base/path.h" #include "base/utils/version.h" using PluginVersion = Utils::Version; @@ -50,7 +51,7 @@ struct PluginInfo QString fullName; QString url; QStringList supportedCategories; - QString iconPath; + Path iconPath; bool enabled; }; @@ -85,11 +86,11 @@ public: SearchHandler *startSearch(const QString &pattern, const QString &category, const QStringList &usedPlugins); SearchDownloadHandler *downloadTorrent(const QString &siteUrl, const QString &url); - static PluginVersion getPluginVersion(const QString &filePath); + static PluginVersion getPluginVersion(const Path &filePath); static QString categoryFullName(const QString &categoryName); QString pluginFullName(const QString &pluginName); - static QString pluginsLocation(); - static QString engineLocation(); + static Path pluginsLocation(); + static Path engineLocation(); signals: void pluginEnabled(const QString &name, bool enabled); @@ -106,13 +107,13 @@ private: void update(); void updateNova(); void parseVersionInfo(const QByteArray &info); - void installPlugin_impl(const QString &name, const QString &path); + void installPlugin_impl(const QString &name, const Path &path); bool isUpdateNeeded(const QString &pluginName, PluginVersion newVersion) const; void versionInfoDownloadFinished(const Net::DownloadResult &result); void pluginDownloadFinished(const Net::DownloadResult &result); - static QString pluginPath(const QString &name); + static Path pluginPath(const QString &name); static QPointer m_instance; diff --git a/src/base/settingsstorage.cpp b/src/base/settingsstorage.cpp index a51623bd5..34134f1fb 100644 --- a/src/base/settingsstorage.cpp +++ b/src/base/settingsstorage.cpp @@ -35,6 +35,7 @@ #include "global.h" #include "logger.h" +#include "path.h" #include "profile.h" #include "utils/fs.h" @@ -59,8 +60,8 @@ namespace // there is no other way to get that name except // actually create a QSettings object. // if serialization operation was not successful we return empty string - QString deserialize(const QString &name, QVariantHash &data) const; - QString serialize(const QString &name, const QVariantHash &data) const; + Path deserialize(const QString &name, QVariantHash &data) const; + Path serialize(const QString &name, const QVariantHash &data) const; const QString m_name; }; @@ -156,7 +157,7 @@ QVariantHash TransactionalSettings::read() const { QVariantHash res; - const QString newPath = deserialize(m_name + QLatin1String("_new"), res); + const Path newPath = deserialize(m_name + QLatin1String("_new"), res); if (!newPath.isEmpty()) { // "_new" file is NOT empty // This means that the PC closed either due to power outage @@ -164,15 +165,16 @@ QVariantHash TransactionalSettings::read() const // in their final position. So assume that qbittorrent_new.ini/qbittorrent_new.conf // contains the most recent settings. Logger::instance()->addMessage(QObject::tr("Detected unclean program exit. Using fallback file to restore settings: %1") - .arg(Utils::Fs::toNativePath(newPath)) + .arg(newPath.toString()) , Log::WARNING); - QString finalPath = newPath; - int index = finalPath.lastIndexOf("_new", -1, Qt::CaseInsensitive); - finalPath.remove(index, 4); + QString finalPathStr = newPath.data(); + const int index = finalPathStr.lastIndexOf("_new", -1, Qt::CaseInsensitive); + finalPathStr.remove(index, 4); - Utils::Fs::forceRemove(finalPath); - QFile::rename(newPath, finalPath); + const Path finalPath {finalPathStr}; + Utils::Fs::removeFile(finalPath); + Utils::Fs::renameFile(newPath, finalPath); } else { @@ -189,22 +191,23 @@ bool TransactionalSettings::write(const QVariantHash &data) const // between deleting the file and recreating it. This is a safety measure. // Write everything to qBittorrent_new.ini/qBittorrent_new.conf and if it succeeds // replace qBittorrent.ini/qBittorrent.conf with it. - const QString newPath = serialize(m_name + QLatin1String("_new"), data); + const Path newPath = serialize(m_name + QLatin1String("_new"), data); if (newPath.isEmpty()) { - Utils::Fs::forceRemove(newPath); + Utils::Fs::removeFile(newPath); return false; } - QString finalPath = newPath; - int index = finalPath.lastIndexOf("_new", -1, Qt::CaseInsensitive); - finalPath.remove(index, 4); + QString finalPathStr = newPath.data(); + const int index = finalPathStr.lastIndexOf("_new", -1, Qt::CaseInsensitive); + finalPathStr.remove(index, 4); - Utils::Fs::forceRemove(finalPath); - return QFile::rename(newPath, finalPath); + const Path finalPath {finalPathStr}; + Utils::Fs::removeFile(finalPath); + return Utils::Fs::renameFile(newPath, finalPath); } -QString TransactionalSettings::deserialize(const QString &name, QVariantHash &data) const +Path TransactionalSettings::deserialize(const QString &name, QVariantHash &data) const { SettingsPtr settings = Profile::instance()->applicationSettings(name); @@ -221,10 +224,10 @@ QString TransactionalSettings::deserialize(const QString &name, QVariantHash &da data[key] = value; } - return settings->fileName(); + return Path(settings->fileName()); } -QString TransactionalSettings::serialize(const QString &name, const QVariantHash &data) const +Path TransactionalSettings::serialize(const QString &name, const QVariantHash &data) const { SettingsPtr settings = Profile::instance()->applicationSettings(name); for (auto i = data.begin(); i != data.end(); ++i) @@ -235,7 +238,7 @@ QString TransactionalSettings::serialize(const QString &name, const QVariantHash switch (settings->status()) { case QSettings::NoError: - return settings->fileName(); + return Path(settings->fileName()); case QSettings::AccessError: Logger::instance()->addMessage(QObject::tr("An access error occurred while trying to write the configuration file."), Log::CRITICAL); break; diff --git a/src/base/settingsstorage.h b/src/base/settingsstorage.h index 524f87170..f9263a27f 100644 --- a/src/base/settingsstorage.h +++ b/src/base/settingsstorage.h @@ -36,6 +36,7 @@ #include #include +#include "path.h" #include "utils/string.h" template @@ -68,6 +69,11 @@ public: const typename T::Int value = loadValue(key, static_cast(defaultValue)); return T {value}; } + else if constexpr (std::is_same_v) + { + const auto value = loadValue(key, defaultValue.toString()); + return Path(value); + } else if constexpr (std::is_same_v) { // fast path for loading QVariant @@ -88,8 +94,10 @@ public: storeValueImpl(key, Utils::String::fromEnum(value)); else if constexpr (IsQFlags::value) storeValueImpl(key, static_cast(value)); + else if constexpr (std::is_same_v) + storeValueImpl(key, value.toString()); else - storeValueImpl(key, value); + storeValueImpl(key, QVariant::fromValue(value)); } void removeValue(const QString &key); diff --git a/src/base/torrentfileguard.cpp b/src/base/torrentfileguard.cpp index 1cb908f0f..8bff9eee6 100644 --- a/src/base/torrentfileguard.cpp +++ b/src/base/torrentfileguard.cpp @@ -31,7 +31,7 @@ #include "settingvalue.h" #include "utils/fs.h" -FileGuard::FileGuard(const QString &path) +FileGuard::FileGuard(const Path &path) : m_path {path} , m_remove {true} { @@ -45,17 +45,17 @@ void FileGuard::setAutoRemove(const bool remove) noexcept FileGuard::~FileGuard() { if (m_remove && !m_path.isEmpty()) - Utils::Fs::forceRemove(m_path); // forceRemove() checks for file existence + Utils::Fs::removeFile(m_path); // removeFile() checks for file existence } -TorrentFileGuard::TorrentFileGuard(const QString &path, const TorrentFileGuard::AutoDeleteMode mode) - : FileGuard {mode != Never ? path : QString()} +TorrentFileGuard::TorrentFileGuard(const Path &path, const TorrentFileGuard::AutoDeleteMode mode) + : FileGuard {mode != Never ? path : Path()} , m_mode {mode} , m_wasAdded {false} { } -TorrentFileGuard::TorrentFileGuard(const QString &path) +TorrentFileGuard::TorrentFileGuard(const Path &path) : TorrentFileGuard {path, autoDeleteMode()} { } diff --git a/src/base/torrentfileguard.h b/src/base/torrentfileguard.h index da389b86a..3da84d658 100644 --- a/src/base/torrentfileguard.h +++ b/src/base/torrentfileguard.h @@ -31,20 +31,22 @@ #include #include +#include "base/path.h" + template class SettingValue; /// Utility class to defer file deletion class FileGuard { public: - explicit FileGuard(const QString &path = {}); + explicit FileGuard(const Path &path = {}); ~FileGuard(); /// Cancels or re-enables deferred file deletion void setAutoRemove(bool remove) noexcept; private: - QString m_path; + Path m_path; bool m_remove; }; @@ -55,7 +57,7 @@ class TorrentFileGuard : private FileGuard Q_GADGET public: - explicit TorrentFileGuard(const QString &path = {}); + explicit TorrentFileGuard(const Path &path = {}); ~TorrentFileGuard(); /// marks the torrent file as loaded (added) into the BitTorrent::Session @@ -74,7 +76,7 @@ public: static void setAutoDeleteMode(AutoDeleteMode mode); private: - TorrentFileGuard(const QString &path, AutoDeleteMode mode); + TorrentFileGuard(const Path &path, AutoDeleteMode mode); static SettingValue &autoDeleteModeSetting(); Q_ENUM(AutoDeleteMode) diff --git a/src/base/torrentfileswatcher.cpp b/src/base/torrentfileswatcher.cpp index 38347432b..8e558a62b 100644 --- a/src/base/torrentfileswatcher.cpp +++ b/src/base/torrentfileswatcher.cpp @@ -137,9 +137,9 @@ namespace BitTorrent::AddTorrentParams params; params.category = jsonObj.value(PARAM_CATEGORY).toString(); params.tags = parseTagSet(jsonObj.value(PARAM_TAGS).toArray()); - params.savePath = jsonObj.value(PARAM_SAVEPATH).toString(); + params.savePath = Path(jsonObj.value(PARAM_SAVEPATH).toString()); params.useDownloadPath = getOptionalBool(jsonObj, PARAM_USEDOWNLOADPATH); - params.downloadPath = jsonObj.value(PARAM_DOWNLOADPATH).toString(); + params.downloadPath = Path(jsonObj.value(PARAM_DOWNLOADPATH).toString()); params.addForced = (getEnum(jsonObj, PARAM_OPERATINGMODE) == BitTorrent::TorrentOperatingMode::Forced); params.addPaused = getOptionalBool(jsonObj, PARAM_STOPPED); params.skipChecking = jsonObj.value(PARAM_SKIPCHECKING).toBool(); @@ -158,8 +158,8 @@ namespace QJsonObject jsonObj { {PARAM_CATEGORY, params.category}, {PARAM_TAGS, serializeTagSet(params.tags)}, - {PARAM_SAVEPATH, params.savePath}, - {PARAM_DOWNLOADPATH, params.downloadPath}, + {PARAM_SAVEPATH, params.savePath.data()}, + {PARAM_DOWNLOADPATH, params.downloadPath.data()}, {PARAM_OPERATINGMODE, Utils::String::fromEnum(params.addForced ? BitTorrent::TorrentOperatingMode::Forced : BitTorrent::TorrentOperatingMode::AutoManaged)}, {PARAM_SKIPCHECKING, params.skipChecking}, @@ -208,8 +208,8 @@ public: Worker(); public slots: - void setWatchedFolder(const QString &path, const TorrentFilesWatcher::WatchedFolderOptions &options); - void removeWatchedFolder(const QString &path); + void setWatchedFolder(const Path &path, const TorrentFilesWatcher::WatchedFolderOptions &options); + void removeWatchedFolder(const Path &path); signals: void magnetFound(const BitTorrent::MagnetUri &magnetURI, const BitTorrent::AddTorrentParams &addTorrentParams); @@ -217,21 +217,21 @@ signals: private: void onTimeout(); - void scheduleWatchedFolderProcessing(const QString &path); - void processWatchedFolder(const QString &path); - void processFolder(const QString &path, const QString &watchedFolderPath, const TorrentFilesWatcher::WatchedFolderOptions &options); + void scheduleWatchedFolderProcessing(const Path &path); + void processWatchedFolder(const Path &path); + void processFolder(const Path &path, const Path &watchedFolderPath, const TorrentFilesWatcher::WatchedFolderOptions &options); void processFailedTorrents(); - void addWatchedFolder(const QString &watchedFolderID, const TorrentFilesWatcher::WatchedFolderOptions &options); - void updateWatchedFolder(const QString &watchedFolderID, const TorrentFilesWatcher::WatchedFolderOptions &options); + void addWatchedFolder(const Path &path, const TorrentFilesWatcher::WatchedFolderOptions &options); + void updateWatchedFolder(const Path &path, const TorrentFilesWatcher::WatchedFolderOptions &options); QFileSystemWatcher *m_watcher = nullptr; QTimer *m_watchTimer = nullptr; - QHash m_watchedFolders; - QSet m_watchedByTimeoutFolders; + QHash m_watchedFolders; + QSet m_watchedByTimeoutFolders; // Failed torrents QTimer *m_retryTorrentTimer = nullptr; - QHash> m_failedTorrents; + QHash> m_failedTorrents; }; TorrentFilesWatcher *TorrentFilesWatcher::m_instance = nullptr; @@ -274,20 +274,9 @@ TorrentFilesWatcher::~TorrentFilesWatcher() delete m_asyncWorker; } -QString TorrentFilesWatcher::makeCleanPath(const QString &path) -{ - if (path.isEmpty()) - throw InvalidArgument(tr("Watched folder path cannot be empty.")); - - if (QDir::isRelativePath(path)) - throw InvalidArgument(tr("Watched folder path cannot be relative.")); - - return QDir::cleanPath(path); -} - void TorrentFilesWatcher::load() { - QFile confFile {QDir(specialFolderLocation(SpecialFolder::Config)).absoluteFilePath(CONF_FILE_NAME)}; + QFile confFile {(specialFolderLocation(SpecialFolder::Config) / Path(CONF_FILE_NAME)).data()}; if (!confFile.exists()) { loadLegacy(); @@ -320,7 +309,7 @@ void TorrentFilesWatcher::load() const QJsonObject jsonObj = jsonDoc.object(); for (auto it = jsonObj.constBegin(); it != jsonObj.constEnd(); ++it) { - const QString &watchedFolder = it.key(); + const Path watchedFolder {it.key()}; const WatchedFolderOptions options = parseWatchedFolderOptions(it.value().toObject()); try { @@ -337,13 +326,13 @@ void TorrentFilesWatcher::loadLegacy() { const auto dirs = SettingsStorage::instance()->loadValue("Preferences/Downloads/ScanDirsV2"); - for (auto i = dirs.cbegin(); i != dirs.cend(); ++i) + for (auto it = dirs.cbegin(); it != dirs.cend(); ++it) { - const QString watchedFolder = i.key(); + const Path watchedFolder {it.key()}; BitTorrent::AddTorrentParams params; - if (i.value().type() == QVariant::Int) + if (it.value().type() == QVariant::Int) { - if (i.value().toInt() == 0) + if (it.value().toInt() == 0) { params.savePath = watchedFolder; params.useAutoTMM = false; @@ -351,7 +340,7 @@ void TorrentFilesWatcher::loadLegacy() } else { - const QString customSavePath = i.value().toString(); + const Path customSavePath {it.value().toString()}; params.savePath = customSavePath; params.useAutoTMM = false; } @@ -375,56 +364,60 @@ void TorrentFilesWatcher::store() const QJsonObject jsonObj; for (auto it = m_watchedFolders.cbegin(); it != m_watchedFolders.cend(); ++it) { - const QString &watchedFolder = it.key(); + const Path &watchedFolder = it.key(); const WatchedFolderOptions &options = it.value(); - jsonObj[watchedFolder] = serializeWatchedFolderOptions(options); + jsonObj[watchedFolder.data()] = serializeWatchedFolderOptions(options); } - const QString path = QDir(specialFolderLocation(SpecialFolder::Config)).absoluteFilePath(CONF_FILE_NAME); + const Path path = specialFolderLocation(SpecialFolder::Config) / Path(CONF_FILE_NAME); const QByteArray data = QJsonDocument(jsonObj).toJson(); const nonstd::expected result = Utils::IO::saveToFile(path, data); if (!result) { LogMsg(tr("Couldn't store Watched Folders configuration to %1. Error: %2") - .arg(path, result.error()), Log::WARNING); + .arg(path.toString(), result.error()), Log::WARNING); } } -QHash TorrentFilesWatcher::folders() const +QHash TorrentFilesWatcher::folders() const { return m_watchedFolders; } -void TorrentFilesWatcher::setWatchedFolder(const QString &path, const WatchedFolderOptions &options) +void TorrentFilesWatcher::setWatchedFolder(const Path &path, const WatchedFolderOptions &options) { doSetWatchedFolder(path, options); store(); } -void TorrentFilesWatcher::doSetWatchedFolder(const QString &path, const WatchedFolderOptions &options) +void TorrentFilesWatcher::doSetWatchedFolder(const Path &path, const WatchedFolderOptions &options) { - const QString cleanPath = makeCleanPath(path); - m_watchedFolders[cleanPath] = options; + if (path.isEmpty()) + throw InvalidArgument(tr("Watched folder Path cannot be empty.")); + + if (path.isRelative()) + throw InvalidArgument(tr("Watched folder Path cannot be relative.")); + + m_watchedFolders[path] = options; QMetaObject::invokeMethod(m_asyncWorker, [this, path, options]() { m_asyncWorker->setWatchedFolder(path, options); }); - emit watchedFolderSet(cleanPath, options); + emit watchedFolderSet(path, options); } -void TorrentFilesWatcher::removeWatchedFolder(const QString &path) +void TorrentFilesWatcher::removeWatchedFolder(const Path &path) { - const QString cleanPath = makeCleanPath(path); - if (m_watchedFolders.remove(cleanPath)) + if (m_watchedFolders.remove(path)) { - QMetaObject::invokeMethod(m_asyncWorker, [this, cleanPath]() + QMetaObject::invokeMethod(m_asyncWorker, [this, path]() { - m_asyncWorker->removeWatchedFolder(cleanPath); + m_asyncWorker->removeWatchedFolder(path); }); - emit watchedFolderRemoved(cleanPath); + emit watchedFolderRemoved(path); store(); } @@ -447,7 +440,10 @@ TorrentFilesWatcher::Worker::Worker() , m_watchTimer {new QTimer(this)} , m_retryTorrentTimer {new QTimer(this)} { - connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &Worker::scheduleWatchedFolderProcessing); + connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, [this](const QString &path) + { + scheduleWatchedFolderProcessing(Path(path)); + }); connect(m_watchTimer, &QTimer::timeout, this, &Worker::onTimeout); connect(m_retryTorrentTimer, &QTimer::timeout, this, &Worker::processFailedTorrents); @@ -455,11 +451,11 @@ TorrentFilesWatcher::Worker::Worker() void TorrentFilesWatcher::Worker::onTimeout() { - for (const QString &path : asConst(m_watchedByTimeoutFolders)) + for (const Path &path : asConst(m_watchedByTimeoutFolders)) processWatchedFolder(path); } -void TorrentFilesWatcher::Worker::setWatchedFolder(const QString &path, const TorrentFilesWatcher::WatchedFolderOptions &options) +void TorrentFilesWatcher::Worker::setWatchedFolder(const Path &path, const TorrentFilesWatcher::WatchedFolderOptions &options) { if (m_watchedFolders.contains(path)) updateWatchedFolder(path, options); @@ -467,11 +463,11 @@ void TorrentFilesWatcher::Worker::setWatchedFolder(const QString &path, const To addWatchedFolder(path, options); } -void TorrentFilesWatcher::Worker::removeWatchedFolder(const QString &path) +void TorrentFilesWatcher::Worker::removeWatchedFolder(const Path &path) { m_watchedFolders.remove(path); - m_watcher->removePath(path); + m_watcher->removePath(path.data()); m_watchedByTimeoutFolders.remove(path); if (m_watchedByTimeoutFolders.isEmpty()) m_watchTimer->stop(); @@ -481,7 +477,7 @@ void TorrentFilesWatcher::Worker::removeWatchedFolder(const QString &path) m_retryTorrentTimer->stop(); } -void TorrentFilesWatcher::Worker::scheduleWatchedFolderProcessing(const QString &path) +void TorrentFilesWatcher::Worker::scheduleWatchedFolderProcessing(const Path &path) { QTimer::singleShot(2000, this, [this, path]() { @@ -489,7 +485,7 @@ void TorrentFilesWatcher::Worker::scheduleWatchedFolderProcessing(const QString }); } -void TorrentFilesWatcher::Worker::processWatchedFolder(const QString &path) +void TorrentFilesWatcher::Worker::processWatchedFolder(const Path &path) { const TorrentFilesWatcher::WatchedFolderOptions options = m_watchedFolders.value(path); processFolder(path, path, options); @@ -498,34 +494,32 @@ void TorrentFilesWatcher::Worker::processWatchedFolder(const QString &path) m_retryTorrentTimer->start(WATCH_INTERVAL); } -void TorrentFilesWatcher::Worker::processFolder(const QString &path, const QString &watchedFolderPath +void TorrentFilesWatcher::Worker::processFolder(const Path &path, const Path &watchedFolderPath , const TorrentFilesWatcher::WatchedFolderOptions &options) { - const QDir watchedDir {watchedFolderPath}; - - QDirIterator dirIter {path, {"*.torrent", "*.magnet"}, QDir::Files}; + QDirIterator dirIter {path.data(), {"*.torrent", "*.magnet"}, QDir::Files}; while (dirIter.hasNext()) { - const QString filePath = dirIter.next(); + const Path filePath {dirIter.next()}; BitTorrent::AddTorrentParams addTorrentParams = options.addTorrentParams; if (path != watchedFolderPath) { - const QString subdirPath = watchedDir.relativeFilePath(path); + const Path subdirPath = watchedFolderPath.relativePathOf(path); const bool useAutoTMM = addTorrentParams.useAutoTMM.value_or(!BitTorrent::Session::instance()->isAutoTMMDisabledByDefault()); if (useAutoTMM) { addTorrentParams.category = addTorrentParams.category.isEmpty() - ? subdirPath : (addTorrentParams.category + QLatin1Char('/') + subdirPath); + ? subdirPath.data() : (addTorrentParams.category + QLatin1Char('/') + subdirPath.data()); } else { - addTorrentParams.savePath = QDir::cleanPath(QDir(addTorrentParams.savePath).filePath(subdirPath)); + addTorrentParams.savePath = addTorrentParams.savePath / subdirPath; } } - if (filePath.endsWith(QLatin1String(".magnet"), Qt::CaseInsensitive)) + if (filePath.hasExtension(QLatin1String(".magnet"))) { - QFile file {filePath}; + QFile file {filePath.data()}; if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream str {&file}; @@ -533,7 +527,7 @@ void TorrentFilesWatcher::Worker::processFolder(const QString &path, const QStri emit magnetFound(BitTorrent::MagnetUri(str.readLine()), addTorrentParams); file.close(); - Utils::Fs::forceRemove(filePath); + Utils::Fs::removeFile(filePath); } else { @@ -546,7 +540,7 @@ void TorrentFilesWatcher::Worker::processFolder(const QString &path, const QStri if (result) { emit torrentFound(result.value(), addTorrentParams); - Utils::Fs::forceRemove(filePath); + Utils::Fs::removeFile(filePath); } else { @@ -560,10 +554,10 @@ void TorrentFilesWatcher::Worker::processFolder(const QString &path, const QStri if (options.recursive) { - QDirIterator dirIter {path, (QDir::Dirs | QDir::NoDot | QDir::NoDotDot)}; + QDirIterator dirIter {path.data(), (QDir::Dirs | QDir::NoDot | QDir::NoDotDot)}; while (dirIter.hasNext()) { - const QString folderPath = dirIter.next(); + const Path folderPath {dirIter.next()}; // Skip processing of subdirectory that is explicitly set as watched folder if (!m_watchedFolders.contains(folderPath)) processFolder(folderPath, watchedFolderPath, options); @@ -574,45 +568,43 @@ void TorrentFilesWatcher::Worker::processFolder(const QString &path, const QStri void TorrentFilesWatcher::Worker::processFailedTorrents() { // Check which torrents are still partial - Algorithm::removeIf(m_failedTorrents, [this](const QString &watchedFolderPath, QHash &partialTorrents) + Algorithm::removeIf(m_failedTorrents, [this](const Path &watchedFolderPath, QHash &partialTorrents) { - const QDir dir {watchedFolderPath}; const TorrentFilesWatcher::WatchedFolderOptions options = m_watchedFolders.value(watchedFolderPath); - Algorithm::removeIf(partialTorrents, [this, &dir, &options](const QString &torrentPath, int &value) + Algorithm::removeIf(partialTorrents, [this, &watchedFolderPath, &options](const Path &torrentPath, int &value) { - if (!QFile::exists(torrentPath)) + if (!torrentPath.exists()) return true; const nonstd::expected result = BitTorrent::TorrentInfo::loadFromFile(torrentPath); if (result) { BitTorrent::AddTorrentParams addTorrentParams = options.addTorrentParams; - const QString exactDirPath = QFileInfo(torrentPath).canonicalPath(); - if (exactDirPath != dir.path()) + if (torrentPath != watchedFolderPath) { - const QString subdirPath = dir.relativeFilePath(exactDirPath); + const Path subdirPath = watchedFolderPath.relativePathOf(torrentPath); const bool useAutoTMM = addTorrentParams.useAutoTMM.value_or(!BitTorrent::Session::instance()->isAutoTMMDisabledByDefault()); if (useAutoTMM) { addTorrentParams.category = addTorrentParams.category.isEmpty() - ? subdirPath : (addTorrentParams.category + QLatin1Char('/') + subdirPath); + ? subdirPath.data() : (addTorrentParams.category + QLatin1Char('/') + subdirPath.data()); } else { - addTorrentParams.savePath = QDir(addTorrentParams.savePath).filePath(subdirPath); + addTorrentParams.savePath = addTorrentParams.savePath / subdirPath; } } emit torrentFound(result.value(), addTorrentParams); - Utils::Fs::forceRemove(torrentPath); + Utils::Fs::removeFile(torrentPath); return true; } if (value >= MAX_FAILED_RETRIES) { - LogMsg(tr("Rejecting failed torrent file: %1").arg(torrentPath)); - QFile::rename(torrentPath, torrentPath + ".qbt_rejected"); + LogMsg(tr("Rejecting failed torrent file: %1").arg(torrentPath.toString())); + Utils::Fs::renameFile(torrentPath, (torrentPath + ".qbt_rejected")); return true; } @@ -633,14 +625,10 @@ void TorrentFilesWatcher::Worker::processFailedTorrents() m_retryTorrentTimer->start(WATCH_INTERVAL); } -void TorrentFilesWatcher::Worker::addWatchedFolder(const QString &path, const TorrentFilesWatcher::WatchedFolderOptions &options) +void TorrentFilesWatcher::Worker::addWatchedFolder(const Path &path, const TorrentFilesWatcher::WatchedFolderOptions &options) { -#if !defined Q_OS_HAIKU - // Check if the path points to a network file system or not + // Check if the `path` points to a network file system or not if (Utils::Fs::isNetworkFileSystem(path) || options.recursive) -#else - if (options.recursive) -#endif { m_watchedByTimeoutFolders.insert(path); if (!m_watchTimer->isActive()) @@ -648,27 +636,23 @@ void TorrentFilesWatcher::Worker::addWatchedFolder(const QString &path, const To } else { - m_watcher->addPath(path); + m_watcher->addPath(path.data()); scheduleWatchedFolderProcessing(path); } m_watchedFolders[path] = options; - LogMsg(tr("Watching folder: \"%1\"").arg(Utils::Fs::toNativePath(path))); + LogMsg(tr("Watching folder: \"%1\"").arg(path.toString())); } -void TorrentFilesWatcher::Worker::updateWatchedFolder(const QString &path, const TorrentFilesWatcher::WatchedFolderOptions &options) +void TorrentFilesWatcher::Worker::updateWatchedFolder(const Path &path, const TorrentFilesWatcher::WatchedFolderOptions &options) { const bool recursiveModeChanged = (m_watchedFolders[path].recursive != options.recursive); -#if !defined Q_OS_HAIKU if (recursiveModeChanged && !Utils::Fs::isNetworkFileSystem(path)) -#else - if (recursiveModeChanged) -#endif { if (options.recursive) { - m_watcher->removePath(path); + m_watcher->removePath(path.data()); m_watchedByTimeoutFolders.insert(path); if (!m_watchTimer->isActive()) @@ -680,7 +664,7 @@ void TorrentFilesWatcher::Worker::updateWatchedFolder(const QString &path, const if (m_watchedByTimeoutFolders.isEmpty()) m_watchTimer->stop(); - m_watcher->addPath(path); + m_watcher->addPath(path.data()); scheduleWatchedFolderProcessing(path); } } diff --git a/src/base/torrentfileswatcher.h b/src/base/torrentfileswatcher.h index 5e76967de..7ba6eebdd 100644 --- a/src/base/torrentfileswatcher.h +++ b/src/base/torrentfileswatcher.h @@ -32,6 +32,7 @@ #include #include "base/bittorrent/addtorrentparams.h" +#include "base/path.h" class QThread; @@ -61,15 +62,13 @@ public: static void freeInstance(); static TorrentFilesWatcher *instance(); - static QString makeCleanPath(const QString &path); - - QHash folders() const; - void setWatchedFolder(const QString &path, const WatchedFolderOptions &options); - void removeWatchedFolder(const QString &path); + QHash folders() const; + void setWatchedFolder(const Path &path, const WatchedFolderOptions &options); + void removeWatchedFolder(const Path &path); signals: - void watchedFolderSet(const QString &path, const WatchedFolderOptions &options); - void watchedFolderRemoved(const QString &path); + void watchedFolderSet(const Path &path, const WatchedFolderOptions &options); + void watchedFolderRemoved(const Path &path); private slots: void onMagnetFound(const BitTorrent::MagnetUri &magnetURI, const BitTorrent::AddTorrentParams &addTorrentParams); @@ -83,11 +82,11 @@ private: void loadLegacy(); void store() const; - void doSetWatchedFolder(const QString &path, const WatchedFolderOptions &options); + void doSetWatchedFolder(const Path &path, const WatchedFolderOptions &options); static TorrentFilesWatcher *m_instance; - QHash m_watchedFolders; + QHash m_watchedFolders; QThread *m_ioThread = nullptr; diff --git a/src/base/utils/fs.cpp b/src/base/utils/fs.cpp index b898ef1d7..7234d4232 100644 --- a/src/base/utils/fs.cpp +++ b/src/base/utils/fs.cpp @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2022 Vladimir Golovnev * Copyright (C) 2012 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -50,57 +51,17 @@ #include #endif +#include #include #include #include #include #include -#include #include #include #include "base/global.h" - -QString Utils::Fs::toNativePath(const QString &path) -{ - return QDir::toNativeSeparators(path); -} - -QString Utils::Fs::toUniformPath(const QString &path) -{ - return QDir::fromNativeSeparators(path); -} - -QString Utils::Fs::resolvePath(const QString &relativePath, const QString &basePath) -{ - Q_ASSERT(QDir::isRelativePath(relativePath)); - Q_ASSERT(QDir::isAbsolutePath(basePath)); - - return (relativePath.isEmpty() ? basePath : QDir(basePath).absoluteFilePath(relativePath)); -} - -QString Utils::Fs::fileExtension(const QString &filename) -{ - return QMimeDatabase().suffixForFileName(filename); -} - -QString Utils::Fs::fileName(const QString &filePath) -{ - const QString path = toUniformPath(filePath); - const int slashIndex = path.lastIndexOf('/'); - if (slashIndex == -1) - return path; - return path.mid(slashIndex + 1); -} - -QString Utils::Fs::folderName(const QString &filePath) -{ - const QString path = toUniformPath(filePath); - const int slashIndex = path.lastIndexOf('/'); - if (slashIndex == -1) - return {}; - return path.left(slashIndex); -} +#include "base/path.h" /** * This function will first check if there are only system cache files, e.g. `Thumbs.db`, @@ -111,9 +72,9 @@ QString Utils::Fs::folderName(const QString &filePath) * that only the above mentioned "useless" files exist but before the whole folder is removed. * In this case, the folder will not be removed but the "useless" files will be deleted. */ -bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path) +bool Utils::Fs::smartRemoveEmptyFolderTree(const Path &path) { - if (path.isEmpty() || !QDir(path).exists()) + if (!path.exists()) return true; const QStringList deleteFilesList = @@ -128,8 +89,8 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path) }; // travel from the deepest folder and remove anything unwanted on the way out. - QStringList dirList(path + '/'); // get all sub directories paths - QDirIterator iter(path, (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories); + QStringList dirList(path.data() + '/'); // get all sub directories paths + QDirIterator iter {path.data(), (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories}; while (iter.hasNext()) dirList << iter.next() + '/'; // sort descending by directory depth @@ -138,7 +99,7 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path) for (const QString &p : asConst(dirList)) { - const QDir dir(p); + const QDir dir {p}; // A deeper folder may have not been removed in the previous iteration // so don't remove anything from this folder either. if (!dir.isEmpty(QDir::Dirs | QDir::NoDotAndDotDot)) @@ -156,38 +117,22 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path) continue; for (const QString &f : tmpFileList) - forceRemove(p + f); + removeFile(Path(p + f)); // remove directory if empty dir.rmdir(p); } - return QDir(path).exists(); -} - -/** - * Removes the file with the given filePath. - * - * This function will try to fix the file permissions before removing it. - */ -bool Utils::Fs::forceRemove(const QString &filePath) -{ - QFile f(filePath); - if (!f.exists()) - return true; - // Make sure we have read/write permissions - f.setPermissions(f.permissions() | QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser); - // Remove the file - return f.remove(); + return path.exists(); } /** * Removes directory and its content recursively. */ -void Utils::Fs::removeDirRecursive(const QString &path) +void Utils::Fs::removeDirRecursively(const Path &path) { if (!path.isEmpty()) - QDir(path).removeRecursively(); + QDir(path.data()).removeRecursively(); } /** @@ -196,16 +141,16 @@ void Utils::Fs::removeDirRecursive(const QString &path) * * Returns -1 in case of error. */ -qint64 Utils::Fs::computePathSize(const QString &path) +qint64 Utils::Fs::computePathSize(const Path &path) { // Check if it is a file - const QFileInfo fi(path); + const QFileInfo fi {path.data()}; if (!fi.exists()) return -1; if (fi.isFile()) return fi.size(); // Compute folder size based on its content qint64 size = 0; - QDirIterator iter(path, QDir::Files | QDir::Hidden | QDir::NoSymLinks, QDirIterator::Subdirectories); + QDirIterator iter {path.data(), QDir::Files | QDir::Hidden | QDir::NoSymLinks, QDirIterator::Subdirectories}; while (iter.hasNext()) { iter.next(); @@ -217,9 +162,10 @@ qint64 Utils::Fs::computePathSize(const QString &path) /** * Makes deep comparison of two files to make sure they are identical. */ -bool Utils::Fs::sameFiles(const QString &path1, const QString &path2) +bool Utils::Fs::sameFiles(const Path &path1, const Path &path2) { - QFile f1(path1), f2(path2); + QFile f1 {path1.data()}; + QFile f2 {path2.data()}; if (!f1.exists() || !f2.exists()) return false; if (f1.size() != f2.size()) return false; if (!f1.open(QIODevice::ReadOnly)) return false; @@ -234,120 +180,65 @@ bool Utils::Fs::sameFiles(const QString &path1, const QString &path2) return true; } -QString Utils::Fs::toValidFileSystemName(const QString &name, const bool allowSeparators, const QString &pad) +QString Utils::Fs::toValidFileName(const QString &name, const QString &pad) { - const QRegularExpression regex(allowSeparators ? "[:?\"*<>|]+" : "[\\\\/:?\"*<>|]+"); + const QRegularExpression regex {QLatin1String("[\\\\/:?\"*<>|]+")}; QString validName = name.trimmed(); validName.replace(regex, pad); - qDebug() << "toValidFileSystemName:" << name << "=>" << validName; return validName; } -bool Utils::Fs::isValidFileSystemName(const QString &name, const bool allowSeparators) +Path Utils::Fs::toValidPath(const QString &name, const QString &pad) { - if (name.isEmpty()) return false; + const QRegularExpression regex {QLatin1String("[:?\"*<>|]+")}; -#if defined(Q_OS_WIN) - const QRegularExpression regex - {allowSeparators - ? QLatin1String("[:?\"*<>|]") - : QLatin1String("[\\\\/:?\"*<>|]")}; -#elif defined(Q_OS_MACOS) - const QRegularExpression regex - {allowSeparators - ? QLatin1String("[\\0:]") - : QLatin1String("[\\0/:]")}; -#else - const QRegularExpression regex - {allowSeparators - ? QLatin1String("[\\0]") - : QLatin1String("[\\0/]")}; -#endif - return !name.contains(regex); + QString validPathStr = name; + validPathStr.replace(regex, pad); + + return Path(validPathStr); } -qint64 Utils::Fs::freeDiskSpaceOnPath(const QString &path) +qint64 Utils::Fs::freeDiskSpaceOnPath(const Path &path) { - return QStorageInfo(path).bytesAvailable(); + return QStorageInfo(path.data()).bytesAvailable(); } -QString Utils::Fs::branchPath(const QString &filePath, QString *removed) +Path Utils::Fs::tempPath() { - QString ret = toUniformPath(filePath); - if (ret.endsWith('/')) - ret.chop(1); - const int slashIndex = ret.lastIndexOf('/'); - if (slashIndex >= 0) - { - if (removed) - *removed = ret.mid(slashIndex + 1); - ret = ret.left(slashIndex); - } - return ret; -} - -bool Utils::Fs::sameFileNames(const QString &first, const QString &second) -{ -#if defined(Q_OS_UNIX) || defined(Q_WS_QWS) - return QString::compare(first, second, Qt::CaseSensitive) == 0; -#else - return QString::compare(first, second, Qt::CaseInsensitive) == 0; -#endif -} - -QString Utils::Fs::expandPath(const QString &path) -{ - const QString ret = path.trimmed(); - if (ret.isEmpty()) - return ret; - - return QDir::cleanPath(ret); -} - -QString Utils::Fs::expandPathAbs(const QString &path) -{ - return QDir(expandPath(path)).absolutePath(); -} - -QString Utils::Fs::tempPath() -{ - static const QString path = QDir::tempPath() + "/.qBittorrent/"; - QDir().mkdir(path); + static const Path path = Path(QDir::tempPath()) / Path(".qBittorrent"); + mkdir(path); return path; } -bool Utils::Fs::isRegularFile(const QString &path) +bool Utils::Fs::isRegularFile(const Path &path) { struct ::stat st; - if (::stat(path.toUtf8().constData(), &st) != 0) + if (::stat(path.toString().toUtf8().constData(), &st) != 0) { // analyse erno and log the error const auto err = errno; qDebug("Could not get file stats for path '%s'. Error: %s" - , qUtf8Printable(path), qUtf8Printable(strerror(err))); + , qUtf8Printable(path.toString()), qUtf8Printable(strerror(err))); return false; } return (st.st_mode & S_IFMT) == S_IFREG; } -#if !defined Q_OS_HAIKU -bool Utils::Fs::isNetworkFileSystem(const QString &path) +bool Utils::Fs::isNetworkFileSystem(const Path &path) { -#if defined(Q_OS_WIN) - const std::wstring pathW {path.toStdWString()}; - auto volumePath = std::make_unique(path.length() + 1); - if (!::GetVolumePathNameW(pathW.c_str(), volumePath.get(), (path.length() + 1))) +#if defined Q_OS_HAIKU + return false; +#elif defined(Q_OS_WIN) + const std::wstring pathW = path.toString().toStdWString(); + auto volumePath = std::make_unique(pathW.length() + 1); + if (!::GetVolumePathNameW(pathW.c_str(), volumePath.get(), static_cast(pathW.length() + 1))) return false; return (::GetDriveTypeW(volumePath.get()) == DRIVE_REMOTE); #else - QString file = path; - if (!file.endsWith('/')) - file += '/'; - file += '.'; - + const QString file = path.toString() + QLatin1String("/."); struct statfs buf {}; if (statfs(file.toLocal8Bit().constData(), &buf) != 0) return false; @@ -398,41 +289,77 @@ bool Utils::Fs::isNetworkFileSystem(const QString &path) #endif #endif } -#endif // Q_OS_HAIKU -QString Utils::Fs::findRootFolder(const QStringList &filePaths) +bool Utils::Fs::copyFile(const Path &from, const Path &to) { - QString rootFolder; - for (const QString &filePath : filePaths) - { - const auto filePathElements = QStringView(filePath).split(u'/'); - // if at least one file has no root folder, no common root folder exists - if (filePathElements.count() <= 1) - return {}; - - if (rootFolder.isEmpty()) - rootFolder = filePathElements.at(0).toString(); - else if (rootFolder != filePathElements.at(0)) - return {}; - } - - return rootFolder; + return QFile::copy(from.data(), to.data()); } -void Utils::Fs::stripRootFolder(QStringList &filePaths) +bool Utils::Fs::renameFile(const Path &from, const Path &to) { - const QString commonRootFolder = findRootFolder(filePaths); - if (commonRootFolder.isEmpty()) - return; - - for (QString &filePath : filePaths) - filePath = filePath.mid(commonRootFolder.size() + 1); + return QFile::rename(from.data(), to.data()); } -void Utils::Fs::addRootFolder(QStringList &filePaths, const QString &rootFolder) +/** + * Removes the file with the given filePath. + * + * This function will try to fix the file permissions before removing it. + */ +bool Utils::Fs::removeFile(const Path &path) { - Q_ASSERT(!rootFolder.isEmpty()); + if (QFile::remove(path.data())) + return true; - for (QString &filePath : filePaths) - filePath = rootFolder + QLatin1Char('/') + filePath; + QFile file {path.data()}; + if (!file.exists()) + return true; + + // Make sure we have read/write permissions + file.setPermissions(file.permissions() | QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser); + return file.remove(); +} + +bool Utils::Fs::isReadable(const Path &path) +{ + return QFileInfo(path.data()).isReadable(); +} + +bool Utils::Fs::isWritable(const Path &path) +{ + return QFileInfo(path.data()).isWritable(); +} + +QDateTime Utils::Fs::lastModified(const Path &path) +{ + return QFileInfo(path.data()).lastModified(); +} + +bool Utils::Fs::isDir(const Path &path) +{ + return QFileInfo(path.data()).isDir(); +} + +Path Utils::Fs::toCanonicalPath(const Path &path) +{ + return Path(QFileInfo(path.data()).canonicalFilePath()); +} + +Path Utils::Fs::homePath() +{ + return Path(QDir::homePath()); +} + +bool Utils::Fs::mkdir(const Path &dirPath) +{ + return QDir().mkdir(dirPath.data()); +} + +bool Utils::Fs::mkpath(const Path &dirPath) +{ + return QDir().mkpath(dirPath.data()); +} + +bool Utils::Fs::rmdir(const Path &dirPath) +{ + return QDir().rmdir(dirPath.data()); } diff --git a/src/base/utils/fs.h b/src/base/utils/fs.h index 676fa5b0a..e562eb5e2 100644 --- a/src/base/utils/fs.h +++ b/src/base/utils/fs.h @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2022 Vladimir Golovnev * Copyright (C) 2012 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -34,58 +35,36 @@ #include +#include "base/pathfwd.h" + +class QDateTime; + namespace Utils::Fs { - /** - * Converts a path to a string suitable for display. - * This function makes sure the directory separator used is consistent - * with the OS being run. - */ - QString toNativePath(const QString &path); + qint64 computePathSize(const Path &path); + qint64 freeDiskSpaceOnPath(const Path &path); - /** - * Converts a path to a string suitable for processing. - * This function makes sure the directory separator used is independent - * from the OS being run so it is the same on all supported platforms. - * Slash ('/') is used as "uniform" directory separator. - */ - QString toUniformPath(const QString &path); + bool isRegularFile(const Path &path); + bool isDir(const Path &path); + bool isReadable(const Path &path); + bool isWritable(const Path &path); + bool isNetworkFileSystem(const Path &path); + QDateTime lastModified(const Path &path); + bool sameFiles(const Path &path1, const Path &path2); - /** - * If `path is relative then resolves it against `basePath`, otherwise returns the `path` itself - */ - QString resolvePath(const QString &relativePath, const QString &basePath); + QString toValidFileName(const QString &name, const QString &pad = QLatin1String(" ")); + Path toValidPath(const QString &name, const QString &pad = QLatin1String(" ")); + Path toCanonicalPath(const Path &path); - /** - * Returns the file extension part of a file name. - */ - QString fileExtension(const QString &filename); + bool copyFile(const Path &from, const Path &to); + bool renameFile(const Path &from, const Path &to); + bool removeFile(const Path &path); + bool mkdir(const Path &dirPath); + bool mkpath(const Path &dirPath); + bool rmdir(const Path &dirPath); + void removeDirRecursively(const Path &path); + bool smartRemoveEmptyFolderTree(const Path &path); - QString fileName(const QString &filePath); - QString folderName(const QString &filePath); - qint64 computePathSize(const QString &path); - bool sameFiles(const QString &path1, const QString &path2); - QString toValidFileSystemName(const QString &name, bool allowSeparators = false - , const QString &pad = QLatin1String(" ")); - bool isValidFileSystemName(const QString &name, bool allowSeparators = false); - qint64 freeDiskSpaceOnPath(const QString &path); - QString branchPath(const QString &filePath, QString *removed = nullptr); - bool sameFileNames(const QString &first, const QString &second); - QString expandPath(const QString &path); - QString expandPathAbs(const QString &path); - bool isRegularFile(const QString &path); - - bool smartRemoveEmptyFolderTree(const QString &path); - bool forceRemove(const QString &filePath); - void removeDirRecursive(const QString &path); - - QString tempPath(); - - QString findRootFolder(const QStringList &filePaths); - void stripRootFolder(QStringList &filePaths); - void addRootFolder(QStringList &filePaths, const QString &name); - -#if !defined Q_OS_HAIKU - bool isNetworkFileSystem(const QString &path); -#endif + Path homePath(); + Path tempPath(); } diff --git a/src/base/utils/io.cpp b/src/base/utils/io.cpp index 4a9459770..afb904930 100644 --- a/src/base/utils/io.cpp +++ b/src/base/utils/io.cpp @@ -36,6 +36,8 @@ #include #include +#include "base/path.h" + Utils::IO::FileDeviceOutputIterator::FileDeviceOutputIterator(QFileDevice &device, const int bufferSize) : m_device {&device} , m_buffer {std::make_shared()} @@ -66,17 +68,17 @@ Utils::IO::FileDeviceOutputIterator &Utils::IO::FileDeviceOutputIterator::operat return *this; } -nonstd::expected Utils::IO::saveToFile(const QString &path, const QByteArray &data) +nonstd::expected Utils::IO::saveToFile(const Path &path, const QByteArray &data) { - QSaveFile file {path}; + QSaveFile file {path.data()}; if (!file.open(QIODevice::WriteOnly) || (file.write(data) != data.size()) || !file.flush() || !file.commit()) return nonstd::make_unexpected(file.errorString()); return {}; } -nonstd::expected Utils::IO::saveToFile(const QString &path, const lt::entry &data) +nonstd::expected Utils::IO::saveToFile(const Path &path, const lt::entry &data) { - QSaveFile file {path}; + QSaveFile file {path.data()}; if (!file.open(QIODevice::WriteOnly)) return nonstd::make_unexpected(file.errorString()); diff --git a/src/base/utils/io.h b/src/base/utils/io.h index c7a214aa4..d0be5089a 100644 --- a/src/base/utils/io.h +++ b/src/base/utils/io.h @@ -34,6 +34,7 @@ #include #include "base/3rdparty/expected.hpp" +#include "base/pathfwd.h" class QByteArray; class QFileDevice; @@ -80,6 +81,6 @@ namespace Utils::IO int m_bufferSize; }; - nonstd::expected saveToFile(const QString &path, const QByteArray &data); - nonstd::expected saveToFile(const QString &path, const lt::entry &data); + nonstd::expected saveToFile(const Path &path, const QByteArray &data); + nonstd::expected saveToFile(const Path &path, const lt::entry &data); } diff --git a/src/base/utils/misc.cpp b/src/base/utils/misc.cpp index d72db3cd1..d36ac5667 100644 --- a/src/base/utils/misc.cpp +++ b/src/base/utils/misc.cpp @@ -61,6 +61,7 @@ #include #endif +#include "base/path.h" #include "base/types.h" #include "base/unicodestrings.h" #include "base/utils/fs.h" @@ -292,9 +293,9 @@ qlonglong Utils::Misc::sizeInBytes(qreal size, const Utils::Misc::SizeUnit unit) return size; } -bool Utils::Misc::isPreviewable(const QString &filename) +bool Utils::Misc::isPreviewable(const Path &filePath) { - const QString mime = QMimeDatabase().mimeTypeForFile(filename, QMimeDatabase::MatchExtension).name(); + const QString mime = QMimeDatabase().mimeTypeForFile(filePath.data(), QMimeDatabase::MatchExtension).name(); if (mime.startsWith(QLatin1String("audio"), Qt::CaseInsensitive) || mime.startsWith(QLatin1String("video"), Qt::CaseInsensitive)) @@ -304,50 +305,50 @@ bool Utils::Misc::isPreviewable(const QString &filename) const QSet multimediaExtensions = { - "3GP", - "AAC", - "AC3", - "AIF", - "AIFC", - "AIFF", - "ASF", - "AU", - "AVI", - "FLAC", - "FLV", - "M3U", - "M4A", - "M4P", - "M4V", - "MID", - "MKV", - "MOV", - "MP2", - "MP3", - "MP4", - "MPC", - "MPE", - "MPEG", - "MPG", - "MPP", - "OGG", - "OGM", - "OGV", - "QT", - "RA", - "RAM", - "RM", - "RMV", - "RMVB", - "SWA", - "SWF", - "TS", - "VOB", - "WAV", - "WMA", - "WMV" + ".3GP", + ".AAC", + ".AC3", + ".AIF", + ".AIFC", + ".AIFF", + ".ASF", + ".AU", + ".AVI", + ".FLAC", + ".FLV", + ".M3U", + ".M4A", + ".M4P", + ".M4V", + ".MID", + ".MKV", + ".MOV", + ".MP2", + ".MP3", + ".MP4", + ".MPC", + ".MPE", + ".MPEG", + ".MPG", + ".MPP", + ".OGG", + ".OGM", + ".OGV", + ".QT", + ".RA", + ".RAM", + ".RM", + ".RMV", + ".RMVB", + ".SWA", + ".SWF", + ".TS", + ".VOB", + ".WAV", + ".WMA", + ".WMV" }; - return multimediaExtensions.contains(Utils::Fs::fileExtension(filename).toUpper()); + return multimediaExtensions.contains(filePath.extension().toUpper()); } QString Utils::Misc::userFriendlyDuration(const qlonglong seconds, const qlonglong maxCap) diff --git a/src/base/utils/misc.h b/src/base/utils/misc.h index be0583cdc..f2ad16362 100644 --- a/src/base/utils/misc.h +++ b/src/base/utils/misc.h @@ -37,6 +37,8 @@ #include +#include "base/pathfwd.h" + enum class ShutdownDialogAction; /* Miscellaneous functions that can be useful */ @@ -77,7 +79,7 @@ namespace Utils::Misc int friendlyUnitPrecision(SizeUnit unit); qint64 sizeInBytes(qreal size, SizeUnit unit); - bool isPreviewable(const QString &filename); + bool isPreviewable(const Path &filePath); // Take a number of seconds and return a user-friendly // time duration like "1d 2h 10m". diff --git a/src/gui/aboutdialog.cpp b/src/gui/aboutdialog.cpp index 1631706d8..c8f16e489 100644 --- a/src/gui/aboutdialog.cpp +++ b/src/gui/aboutdialog.cpp @@ -30,6 +30,7 @@ #include +#include "base/path.h" #include "base/unicodestrings.h" #include "base/utils/misc.h" #include "base/version.h" @@ -70,7 +71,7 @@ AboutDialog::AboutDialog(QWidget *parent) , tr("Bug Tracker:")); m_ui->labelAbout->setText(aboutText); - m_ui->labelMascot->setPixmap(Utils::Gui::scaledPixmap(":/icons/mascot.png", this)); + m_ui->labelMascot->setPixmap(Utils::Gui::scaledPixmap(Path(":/icons/mascot.png"), this)); // Thanks QFile thanksfile(":/thanks.html"); diff --git a/src/gui/addnewtorrentdialog.cpp b/src/gui/addnewtorrentdialog.cpp index 4d989bcd3..61a1fb1bf 100644 --- a/src/gui/addnewtorrentdialog.cpp +++ b/src/gui/addnewtorrentdialog.cpp @@ -81,7 +81,7 @@ namespace class FileStorageAdaptor final : public BitTorrent::AbstractFileStorage { public: - FileStorageAdaptor(const BitTorrent::TorrentInfo &torrentInfo, QStringList &filePaths) + FileStorageAdaptor(const BitTorrent::TorrentInfo &torrentInfo, PathList &filePaths) : m_torrentInfo {torrentInfo} , m_filePaths {filePaths} { @@ -99,16 +99,16 @@ namespace return m_torrentInfo.fileSize(index); } - QString filePath(const int index) const override + Path filePath(const int index) const override { Q_ASSERT((index >= 0) && (index < filesCount())); return (m_filePaths.isEmpty() ? m_torrentInfo.filePath(index) : m_filePaths.at(index)); } - void renameFile(const int index, const QString &newFilePath) override + void renameFile(const int index, const Path &newFilePath) override { Q_ASSERT((index >= 0) && (index < filesCount())); - const QString currentFilePath = filePath(index); + const Path currentFilePath = filePath(index); if (currentFilePath == newFilePath) return; @@ -120,22 +120,21 @@ namespace private: const BitTorrent::TorrentInfo &m_torrentInfo; - QStringList &m_filePaths; + PathList &m_filePaths; }; // savePath is a folder, not an absolute file path - int indexOfPath(const FileSystemPathComboEdit *fsPathEdit, const QString &savePath) + int indexOfPath(const FileSystemPathComboEdit *fsPathEdit, const Path &savePath) { - const QDir saveDir {savePath}; for (int i = 0; i < fsPathEdit->count(); ++i) { - if (QDir(fsPathEdit->item(i)) == saveDir) + if (fsPathEdit->item(i) == savePath) return i; } return -1; } - void setPath(FileSystemPathComboEdit *fsPathEdit, const QString &newPath) + void setPath(FileSystemPathComboEdit *fsPathEdit, const Path &newPath) { int existingIndex = indexOfPath(fsPathEdit, newPath); if (existingIndex < 0) @@ -148,16 +147,18 @@ namespace fsPathEdit->setCurrentIndex(existingIndex); } - void updatePathHistory(const QString &settingsKey, const QString &path, const int maxLength) + void updatePathHistory(const QString &settingsKey, const Path &path, const int maxLength) { // Add last used save path to the front of history auto pathList = settings()->loadValue(settingsKey); - const int selectedSavePathIndex = pathList.indexOf(path); + + const int selectedSavePathIndex = pathList.indexOf(path.toString()); if (selectedSavePathIndex > -1) pathList.move(selectedSavePathIndex, 0); else - pathList.prepend(path); + pathList.prepend(path.toString()); + settings()->storeValue(settingsKey, QStringList(pathList.mid(0, maxLength))); } } @@ -325,7 +326,7 @@ void AddNewTorrentDialog::show(const QString &source, const BitTorrent::AddTorre return; } - const BitTorrent::MagnetUri magnetUri(source); + const BitTorrent::MagnetUri magnetUri {source}; const bool isLoaded = magnetUri.isValid() ? dlg->loadMagnet(magnetUri) : dlg->loadTorrentFile(source); @@ -341,18 +342,18 @@ void AddNewTorrentDialog::show(const QString &source, QWidget *parent) show(source, BitTorrent::AddTorrentParams(), parent); } -bool AddNewTorrentDialog::loadTorrentFile(const QString &torrentPath) +bool AddNewTorrentDialog::loadTorrentFile(const QString &source) { - const QString decodedPath = torrentPath.startsWith("file://", Qt::CaseInsensitive) - ? QUrl::fromEncoded(torrentPath.toLocal8Bit()).toLocalFile() - : torrentPath; + const Path decodedPath {source.startsWith("file://", Qt::CaseInsensitive) + ? QUrl::fromEncoded(source.toLocal8Bit()).toLocalFile() + : source}; const nonstd::expected result = BitTorrent::TorrentInfo::loadFromFile(decodedPath); if (!result) { RaisedMessageBox::critical(this, tr("Invalid torrent") , tr("Failed to load the torrent: %1.\nError: %2", "Don't remove the '\n' characters. They insert a newline.") - .arg(Utils::Fs::toNativePath(decodedPath), result.error())); + .arg(decodedPath.toString(), result.error())); return false; } @@ -489,7 +490,7 @@ void AddNewTorrentDialog::updateDiskSpaceLabel() m_ui->labelSizeData->setText(sizeString); } -void AddNewTorrentDialog::onSavePathChanged(const QString &newPath) +void AddNewTorrentDialog::onSavePathChanged(const Path &newPath) { Q_UNUSED(newPath); // Remember index @@ -497,7 +498,7 @@ void AddNewTorrentDialog::onSavePathChanged(const QString &newPath) updateDiskSpaceLabel(); } -void AddNewTorrentDialog::onDownloadPathChanged(const QString &newPath) +void AddNewTorrentDialog::onDownloadPathChanged(const Path &newPath) { Q_UNUSED(newPath); // Remember index @@ -521,11 +522,11 @@ void AddNewTorrentDialog::categoryChanged(int index) const auto *btSession = BitTorrent::Session::instance(); const QString categoryName = m_ui->categoryComboBox->currentText(); - const QString savePath = btSession->categorySavePath(categoryName); - m_ui->savePath->setSelectedPath(Utils::Fs::toNativePath(savePath)); + const Path savePath = btSession->categorySavePath(categoryName); + m_ui->savePath->setSelectedPath(savePath); - const QString downloadPath = btSession->categoryDownloadPath(categoryName); - m_ui->downloadPath->setSelectedPath(Utils::Fs::toNativePath(downloadPath)); + const Path downloadPath = btSession->categoryDownloadPath(categoryName); + m_ui->downloadPath->setSelectedPath(downloadPath); m_ui->groupBoxDownloadPath->setChecked(!m_ui->downloadPath->selectedPath().isEmpty()); @@ -545,7 +546,7 @@ void AddNewTorrentDialog::contentLayoutChanged(const int index) const auto contentLayout = ((index == 0) ? BitTorrent::detectContentLayout(m_torrentInfo.filePaths()) : static_cast(index)); - BitTorrent::applyContentLayout(m_torrentParams.filePaths, contentLayout, Utils::Fs::findRootFolder(m_torrentInfo.filePaths())); + BitTorrent::applyContentLayout(m_torrentParams.filePaths, contentLayout, Path::findRootFolder(m_torrentInfo.filePaths())); m_contentModel->model()->setupModelData(FileStorageAdaptor(m_torrentInfo, m_torrentParams.filePaths)); m_contentModel->model()->updateFilesPriorities(filePriorities); @@ -574,7 +575,7 @@ void AddNewTorrentDialog::saveTorrentFile() if (!path.endsWith(torrentFileExtension, Qt::CaseInsensitive)) path += torrentFileExtension; - const nonstd::expected result = m_torrentInfo.saveToFile(path); + const nonstd::expected result = m_torrentInfo.saveToFile(Path(path)); if (!result) { QMessageBox::critical(this, tr("I/O Error") @@ -597,7 +598,7 @@ void AddNewTorrentDialog::populateSavePaths() if (savePathHistory.size() > 0) { for (const QString &path : savePathHistory) - m_ui->savePath->addItem(path); + m_ui->savePath->addItem(Path(path)); } else { @@ -628,7 +629,7 @@ void AddNewTorrentDialog::populateSavePaths() if (downloadPathHistory.size() > 0) { for (const QString &path : downloadPathHistory) - m_ui->downloadPath->addItem(path); + m_ui->downloadPath->addItem(Path(path)); } else { @@ -806,14 +807,14 @@ void AddNewTorrentDialog::accept() m_torrentParams.useAutoTMM = useAutoTMM; if (!useAutoTMM) { - const QString savePath = m_ui->savePath->selectedPath(); + const Path savePath = m_ui->savePath->selectedPath(); m_torrentParams.savePath = savePath; updatePathHistory(KEY_SAVEPATHHISTORY, savePath, savePathHistoryLength()); m_torrentParams.useDownloadPath = m_ui->groupBoxDownloadPath->isChecked(); if (m_torrentParams.useDownloadPath) { - const QString downloadPath = m_ui->downloadPath->selectedPath(); + const Path downloadPath = m_ui->downloadPath->selectedPath(); m_torrentParams.downloadPath = downloadPath; updatePathHistory(KEY_DOWNLOADPATHHISTORY, downloadPath, savePathHistoryLength()); } @@ -907,7 +908,7 @@ void AddNewTorrentDialog::setupTreeview() : static_cast(m_ui->contentLayoutComboBox->currentIndex())); if (m_torrentParams.filePaths.isEmpty()) m_torrentParams.filePaths = m_torrentInfo.filePaths(); - BitTorrent::applyContentLayout(m_torrentParams.filePaths, contentLayout, Utils::Fs::findRootFolder(m_torrentInfo.filePaths())); + BitTorrent::applyContentLayout(m_torrentParams.filePaths, contentLayout, Path::findRootFolder(m_torrentInfo.filePaths())); // List files in torrent m_contentModel->model()->setupModelData(FileStorageAdaptor(m_torrentInfo, m_torrentParams.filePaths)); if (const QByteArray state = m_storeTreeHeaderState; !state.isEmpty()) @@ -981,12 +982,12 @@ void AddNewTorrentDialog::TMMChanged(int index) m_ui->savePath->blockSignals(true); m_ui->savePath->clear(); - const QString savePath = session->categorySavePath(m_ui->categoryComboBox->currentText()); + const Path savePath = session->categorySavePath(m_ui->categoryComboBox->currentText()); m_ui->savePath->addItem(savePath); m_ui->downloadPath->blockSignals(true); m_ui->downloadPath->clear(); - const QString downloadPath = session->categoryDownloadPath(m_ui->categoryComboBox->currentText()); + const Path downloadPath = session->categoryDownloadPath(m_ui->categoryComboBox->currentText()); m_ui->downloadPath->addItem(downloadPath); m_ui->groupBoxDownloadPath->blockSignals(true); diff --git a/src/gui/addnewtorrentdialog.h b/src/gui/addnewtorrentdialog.h index 7550b20db..3ddfa4d00 100644 --- a/src/gui/addnewtorrentdialog.h +++ b/src/gui/addnewtorrentdialog.h @@ -81,8 +81,8 @@ private slots: void displayContentTreeMenu(); void displayColumnHeaderMenu(); void updateDiskSpaceLabel(); - void onSavePathChanged(const QString &newPath); - void onDownloadPathChanged(const QString &newPath); + void onSavePathChanged(const Path &newPath); + void onDownloadPathChanged(const Path &newPath); void onUseDownloadPathChanged(bool checked); void updateMetadata(const BitTorrent::TorrentInfo &metadata); void handleDownloadFinished(const Net::DownloadResult &downloadResult); @@ -97,7 +97,7 @@ private slots: private: explicit AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inParams, QWidget *parent); - bool loadTorrentFile(const QString &torrentPath); + bool loadTorrentFile(const QString &source); bool loadTorrentImpl(); bool loadMagnet(const BitTorrent::MagnetUri &magnetUri); void populateSavePaths(); diff --git a/src/gui/autoexpandabledialog.cpp b/src/gui/autoexpandabledialog.cpp index af54db1fb..715581353 100644 --- a/src/gui/autoexpandabledialog.cpp +++ b/src/gui/autoexpandabledialog.cpp @@ -28,7 +28,7 @@ #include "autoexpandabledialog.h" -#include "base/utils/fs.h" +#include "base/path.h" #include "ui_autoexpandabledialog.h" #include "utils.h" @@ -58,9 +58,9 @@ QString AutoExpandableDialog::getText(QWidget *parent, const QString &title, con d.m_ui->textEdit->selectAll(); if (excludeExtension) { - const QString extension = Utils::Fs::fileExtension(text); + const QString extension = Path(text).extension(); if (!extension.isEmpty()) - d.m_ui->textEdit->setSelection(0, (text.length() - extension.length() - 1)); + d.m_ui->textEdit->setSelection(0, (text.length() - extension.length())); } bool res = d.exec(); diff --git a/src/gui/fspathedit.cpp b/src/gui/fspathedit.cpp index ed9aceec8..c2cfe115f 100644 --- a/src/gui/fspathedit.cpp +++ b/src/gui/fspathedit.cpp @@ -82,7 +82,7 @@ class FileSystemPathEdit::FileSystemPathEditPrivate QToolButton *m_browseBtn; QString m_fileNameFilter; Mode m_mode; - QString m_lastSignaledPath; + Path m_lastSignaledPath; QString m_dialogCaption; Private::FileSystemPathValidator *m_validator; }; @@ -112,31 +112,32 @@ void FileSystemPathEdit::FileSystemPathEditPrivate::browseActionTriggered() { Q_Q(FileSystemPathEdit); - const QFileInfo fileInfo {q->selectedPath()}; - const QString directory = (m_mode == FileSystemPathEdit::Mode::DirectoryOpen) || (m_mode == FileSystemPathEdit::Mode::DirectorySave) - ? fileInfo.absoluteFilePath() - : fileInfo.absolutePath(); - QString filter = q->fileNameFilter(); + const Path currentDirectory = (m_mode == FileSystemPathEdit::Mode::DirectoryOpen) || (m_mode == FileSystemPathEdit::Mode::DirectorySave) + ? q->selectedPath() + : q->selectedPath().parentPath(); + const Path initialDirectory = currentDirectory.isAbsolute() ? currentDirectory : (Utils::Fs::homePath() / currentDirectory); - QString selectedPath; + QString filter = q->fileNameFilter(); + QString newPath; switch (m_mode) { case FileSystemPathEdit::Mode::FileOpen: - selectedPath = QFileDialog::getOpenFileName(q, dialogCaptionOrDefault(), directory, filter); + newPath = QFileDialog::getOpenFileName(q, dialogCaptionOrDefault(), initialDirectory.data(), filter); break; case FileSystemPathEdit::Mode::FileSave: - selectedPath = QFileDialog::getSaveFileName(q, dialogCaptionOrDefault(), directory, filter, &filter); + newPath = QFileDialog::getSaveFileName(q, dialogCaptionOrDefault(), initialDirectory.data(), filter, &filter); break; case FileSystemPathEdit::Mode::DirectoryOpen: case FileSystemPathEdit::Mode::DirectorySave: - selectedPath = QFileDialog::getExistingDirectory(q, dialogCaptionOrDefault(), - directory, QFileDialog::DontResolveSymlinks | QFileDialog::ShowDirsOnly); + newPath = QFileDialog::getExistingDirectory(q, dialogCaptionOrDefault(), + initialDirectory.data(), QFileDialog::DontResolveSymlinks | QFileDialog::ShowDirsOnly); break; default: throw std::logic_error("Unknown FileSystemPathEdit mode"); } - if (!selectedPath.isEmpty()) - q->setEditWidgetText(Utils::Fs::toNativePath(selectedPath)); + + if (!newPath.isEmpty()) + q->setSelectedPath(Path(newPath)); } QString FileSystemPathEdit::FileSystemPathEditPrivate::dialogCaptionOrDefault() const @@ -202,16 +203,18 @@ FileSystemPathEdit::~FileSystemPathEdit() delete d_ptr; } -QString FileSystemPathEdit::selectedPath() const +Path FileSystemPathEdit::selectedPath() const { - return Utils::Fs::toUniformPath(editWidgetText()); + return Path(editWidgetText()); } -void FileSystemPathEdit::setSelectedPath(const QString &val) +void FileSystemPathEdit::setSelectedPath(const Path &val) { Q_D(FileSystemPathEdit); - setEditWidgetText(Utils::Fs::toNativePath(val)); - d->m_editor->widget()->setToolTip(val); + + const QString nativePath = val.toString(); + setEditWidgetText(nativePath); + d->m_editor->widget()->setToolTip(nativePath); } QString FileSystemPathEdit::fileNameFilter() const @@ -251,13 +254,13 @@ void FileSystemPathEdit::setFileNameFilter(const QString &val) #endif } -QString FileSystemPathEdit::placeholder() const +Path FileSystemPathEdit::placeholder() const { Q_D(const FileSystemPathEdit); return d->m_editor->placeholder(); } -void FileSystemPathEdit::setPlaceholder(const QString &val) +void FileSystemPathEdit::setPlaceholder(const Path &val) { Q_D(FileSystemPathEdit); d->m_editor->setPlaceholder(val); @@ -278,7 +281,8 @@ void FileSystemPathEdit::setBriefBrowseButtonCaption(bool brief) void FileSystemPathEdit::onPathEdited() { Q_D(FileSystemPathEdit); - QString newPath = selectedPath(); + + const Path newPath = selectedPath(); if (newPath != d->m_lastSignaledPath) { emit selectedPathChanged(newPath); @@ -360,19 +364,19 @@ int FileSystemPathComboEdit::count() const return editWidget()->count(); } -QString FileSystemPathComboEdit::item(int index) const +Path FileSystemPathComboEdit::item(int index) const { - return Utils::Fs::toUniformPath(editWidget()->itemText(index)); + return Path(editWidget()->itemText(index)); } -void FileSystemPathComboEdit::addItem(const QString &text) +void FileSystemPathComboEdit::addItem(const Path &path) { - editWidget()->addItem(Utils::Fs::toNativePath(text)); + editWidget()->addItem(path.toString()); } -void FileSystemPathComboEdit::insertItem(int index, const QString &text) +void FileSystemPathComboEdit::insertItem(int index, const Path &path) { - editWidget()->insertItem(index, Utils::Fs::toNativePath(text)); + editWidget()->insertItem(index, path.toString()); } int FileSystemPathComboEdit::currentIndex() const diff --git a/src/gui/fspathedit.h b/src/gui/fspathedit.h index cab9e71b6..2409e893a 100644 --- a/src/gui/fspathedit.h +++ b/src/gui/fspathedit.h @@ -30,6 +30,8 @@ #include +#include "base/path.h" + namespace Private { class FileComboEdit; @@ -45,7 +47,7 @@ class FileSystemPathEdit : public QWidget { Q_OBJECT Q_PROPERTY(Mode mode READ mode WRITE setMode) - Q_PROPERTY(QString selectedPath READ selectedPath WRITE setSelectedPath NOTIFY selectedPathChanged) + Q_PROPERTY(Path selectedPath READ selectedPath WRITE setSelectedPath NOTIFY selectedPathChanged) Q_PROPERTY(QString fileNameFilter READ fileNameFilter WRITE setFileNameFilter) Q_PROPERTY(QString dialogCaption READ dialogCaption WRITE setDialogCaption) @@ -64,14 +66,14 @@ public: Mode mode() const; void setMode(Mode mode); - QString selectedPath() const; - void setSelectedPath(const QString &val); + Path selectedPath() const; + void setSelectedPath(const Path &val); QString fileNameFilter() const; void setFileNameFilter(const QString &val); - QString placeholder() const; - void setPlaceholder(const QString &val); + Path placeholder() const; + void setPlaceholder(const Path &val); /// The browse button caption is "..." if true, and "Browse" otherwise bool briefBrowseButtonCaption() const; @@ -83,7 +85,7 @@ public: virtual void clear() = 0; signals: - void selectedPathChanged(const QString &path); + void selectedPathChanged(const Path &path); protected: explicit FileSystemPathEdit(Private::FileEditorWithCompletion *editor, QWidget *parent); @@ -136,9 +138,9 @@ public: void clear() override; int count() const; - QString item(int index) const; - void addItem(const QString &text); - void insertItem(int index, const QString &text); + Path item(int index) const; + void addItem(const Path &path); + void insertItem(int index, const Path &path); int currentIndex() const; void setCurrentIndex(int index); diff --git a/src/gui/fspathedit_p.cpp b/src/gui/fspathedit_p.cpp index 5565d1cc2..26e54ddd1 100644 --- a/src/gui/fspathedit_p.cpp +++ b/src/gui/fspathedit_p.cpp @@ -37,6 +37,8 @@ #include #include +#include "base/path.h" + // -------------------- FileSystemPathValidator ---------------------------------------- Private::FileSystemPathValidator::FileSystemPathValidator(QObject *parent) : QValidator(parent) @@ -149,7 +151,7 @@ QValidator::State Private::FileSystemPathValidator::validate(const QListsetValidator(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) diff --git a/src/gui/fspathedit_p.h b/src/gui/fspathedit_p.h index 15b1305dd..ee8fc9d41 100644 --- a/src/gui/fspathedit_p.h +++ b/src/gui/fspathedit_p.h @@ -34,6 +34,8 @@ #include #include +#include "base/pathfwd.h" + class QAction; class QCompleter; class QContextMenuEvent; @@ -84,7 +86,7 @@ namespace Private QValidator::State validate(const QList &pathComponents, bool strict, int firstComponentToTest, int lastComponentToTest) const; - TestResult testPath(QStringView path, bool pathIsComplete) const; + TestResult testPath(const Path &path, bool pathIsComplete) const; bool m_strictMode; bool m_existingOnly; @@ -105,8 +107,8 @@ namespace Private virtual void setFilenameFilters(const QStringList &filters) = 0; virtual void setBrowseAction(QAction *action) = 0; virtual void setValidator(QValidator *validator) = 0; - virtual QString placeholder() const = 0; - virtual void setPlaceholder(const QString &val) = 0; + virtual Path placeholder() const = 0; + virtual void setPlaceholder(const Path &val) = 0; virtual QWidget *widget() = 0; }; @@ -123,8 +125,8 @@ namespace Private void setFilenameFilters(const QStringList &filters) override; void setBrowseAction(QAction *action) override; void setValidator(QValidator *validator) override; - QString placeholder() const override; - void setPlaceholder(const QString &val) override; + Path placeholder() const override; + void setPlaceholder(const Path &val) override; QWidget *widget() override; protected: @@ -153,8 +155,8 @@ namespace Private void setFilenameFilters(const QStringList &filters) override; void setBrowseAction(QAction *action) override; void setValidator(QValidator *validator) override; - QString placeholder() const override; - void setPlaceholder(const QString &val) override; + Path placeholder() const override; + void setPlaceholder(const Path &val) override; QWidget *widget() override; protected: diff --git a/src/gui/macutilities.h b/src/gui/macutilities.h index e6ea8c20e..2ad3210bd 100644 --- a/src/gui/macutilities.h +++ b/src/gui/macutilities.h @@ -30,7 +30,7 @@ #include -#include +#include "base/pathfwd.h" class QPixmap; class QSize; @@ -41,7 +41,7 @@ namespace MacUtils QPixmap pixmapForExtension(const QString &ext, const QSize &size); void overrideDockClickHandler(bool (*dockClickHandler)(id, SEL, ...)); void displayNotification(const QString &title, const QString &message); - void openFiles(const QSet &pathsList); + void openFiles(const PathList &pathList); QString badgeLabelText(); void setBadgeLabelText(const QString &text); diff --git a/src/gui/macutilities.mm b/src/gui/macutilities.mm index 60e71cef0..e9ca0fe5a 100644 --- a/src/gui/macutilities.mm +++ b/src/gui/macutilities.mm @@ -32,9 +32,11 @@ #include #include -#include #include #include +#include + +#include "base/path.h" QImage qt_mac_toQImage(CGImageRef image); @@ -95,14 +97,14 @@ namespace MacUtils } } - void openFiles(const QSet &pathsList) + void openFiles(const PathList &pathList) { @autoreleasepool { - NSMutableArray *pathURLs = [NSMutableArray arrayWithCapacity:pathsList.size()]; + NSMutableArray *pathURLs = [NSMutableArray arrayWithCapacity:pathList.size()]; - for (const auto &path : pathsList) - [pathURLs addObject:[NSURL fileURLWithPath:path.toNSString()]]; + for (const auto &path : pathList) + [pathURLs addObject:[NSURL fileURLWithPath:path.toString().toNSString()]]; [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:pathURLs]; } diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 637bc22c1..2f205e3c7 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -58,6 +58,7 @@ #include "base/bittorrent/sessionstatus.h" #include "base/global.h" #include "base/net/downloadmanager.h" +#include "base/path.h" #include "base/preferences.h" #include "base/rss/rss_folder.h" #include "base/rss/rss_session.h" @@ -1250,10 +1251,10 @@ void MainWindow::closeEvent(QCloseEvent *e) // Display window to create a torrent void MainWindow::on_actionCreateTorrent_triggered() { - createTorrentTriggered(); + createTorrentTriggered({}); } -void MainWindow::createTorrentTriggered(const QString &path) +void MainWindow::createTorrentTriggered(const Path &path) { if (m_createTorrentDlg) { @@ -1261,7 +1262,9 @@ void MainWindow::createTorrentTriggered(const QString &path) m_createTorrentDlg->activateWindow(); } else + { m_createTorrentDlg = new TorrentCreatorDialog(this, path); + } } bool MainWindow::event(QEvent *e) @@ -1367,7 +1370,7 @@ void MainWindow::dropEvent(QDropEvent *event) // Create torrent for (const QString &file : asConst(otherFiles)) { - createTorrentTriggered(file); + createTorrentTriggered(Path(file)); // currently only handle the first entry // this is a stub that can be expanded later to create many torrents at once @@ -1423,7 +1426,7 @@ void MainWindow::on_actionOpen_triggered() // Open File Open Dialog // Note: it is possible to select more than one file const QStringList pathsList = - QFileDialog::getOpenFileNames(this, tr("Open Torrent Files"), pref->getMainLastDir(), + QFileDialog::getOpenFileNames(this, tr("Open Torrent Files"), pref->getMainLastDir().data(), tr("Torrent Files") + " (*" + C_TORRENT_FILE_EXTENSION + ')'); if (pathsList.isEmpty()) @@ -1440,9 +1443,9 @@ void MainWindow::on_actionOpen_triggered() } // Save last dir to remember it - QString topDir = Utils::Fs::toUniformPath(pathsList.at(0)); - topDir = topDir.left(topDir.lastIndexOf('/')); - pref->setMainLastDir(topDir); + const Path topDir {pathsList.at(0)}; + const Path parentDir = topDir.parentPath(); + pref->setMainLastDir(parentDir.isEmpty() ? topDir : parentDir); } void MainWindow::activate() @@ -2110,9 +2113,9 @@ void MainWindow::pythonDownloadFinished(const Net::DownloadResult &result) QProcess installer; qDebug("Launching Python installer in passive mode..."); - const QString exePath = result.filePath + QLatin1String(".exe"); - QFile::rename(result.filePath, exePath); - installer.start(Utils::Fs::toNativePath(exePath), {"/passive"}); + const Path exePath = result.filePath + ".exe"; + Utils::Fs::renameFile(result.filePath, exePath); + installer.start(exePath.toString(), {"/passive"}); // Wait for setup to complete installer.waitForFinished(10 * 60 * 1000); @@ -2122,7 +2125,7 @@ void MainWindow::pythonDownloadFinished(const Net::DownloadResult &result) qDebug("Setup should be complete!"); // Delete temp file - Utils::Fs::forceRemove(exePath); + Utils::Fs::removeFile(exePath); // Reload search engine if (Utils::ForeignApps::pythonInfo().isSupportedVersion()) diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h index 907122221..52c5252c8 100644 --- a/src/gui/mainwindow.h +++ b/src/gui/mainwindow.h @@ -212,7 +212,7 @@ private: bool event(QEvent *e) override; void displayRSSTab(bool enable); void displaySearchTab(bool enable); - void createTorrentTriggered(const QString &path = {}); + void createTorrentTriggered(const Path &path); void showStatusBar(bool show); Ui::MainWindow *m_ui; diff --git a/src/gui/optionsdialog.cpp b/src/gui/optionsdialog.cpp index 89f3e21b7..065201a38 100644 --- a/src/gui/optionsdialog.cpp +++ b/src/gui/optionsdialog.cpp @@ -48,6 +48,7 @@ #include "base/net/dnsupdater.h" #include "base/net/portforwarder.h" #include "base/net/proxyconfigurationmanager.h" +#include "base/path.h" #include "base/preferences.h" #include "base/rss/rss_autodownloader.h" #include "base/rss/rss_session.h" @@ -492,9 +493,9 @@ OptionsDialog::OptionsDialog(QWidget *parent) connect(m_ui->checkWebUIUPnP, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkWebUiHttps, &QGroupBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->textWebUIHttpsCert, &FileSystemPathLineEdit::selectedPathChanged, this, &ThisType::enableApplyButton); - connect(m_ui->textWebUIHttpsCert, &FileSystemPathLineEdit::selectedPathChanged, this, [this](const QString &s) { webUIHttpsCertChanged(s, ShowError::Show); }); + connect(m_ui->textWebUIHttpsCert, &FileSystemPathLineEdit::selectedPathChanged, this, [this](const Path &path) { webUIHttpsCertChanged(path, ShowError::Show); }); connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, &ThisType::enableApplyButton); - connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, [this](const QString &s) { webUIHttpsKeyChanged(s, ShowError::Show); }); + connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, [this](const Path &path) { webUIHttpsKeyChanged(path, ShowError::Show); }); connect(m_ui->textWebUiUsername, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); connect(m_ui->textWebUiPassword, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); connect(m_ui->checkBypassLocalAuth, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); @@ -732,7 +733,7 @@ void OptionsDialog::saveOptions() auto session = BitTorrent::Session::instance(); // Downloads preferences - session->setSavePath(Utils::Fs::expandPathAbs(m_ui->textSavePath->selectedPath())); + session->setSavePath(Path(m_ui->textSavePath->selectedPath())); session->setSubcategoriesEnabled(m_ui->checkUseSubcategories->isChecked()); session->setUseCategoryPathsInManualMode(m_ui->checkUseCategoryPaths->isChecked()); session->setAutoTMMDisabledByDefault(m_ui->comboSavingMode->currentIndex() == 0); @@ -740,7 +741,7 @@ void OptionsDialog::saveOptions() session->setDisableAutoTMMWhenCategorySavePathChanged(m_ui->comboCategoryChanged->currentIndex() == 1); session->setDisableAutoTMMWhenDefaultSavePathChanged(m_ui->comboCategoryDefaultPathChanged->currentIndex() == 1); session->setDownloadPathEnabled(m_ui->checkUseDownloadPath->isChecked()); - session->setDownloadPath(Utils::Fs::expandPathAbs(m_ui->textDownloadPath->selectedPath())); + session->setDownloadPath(m_ui->textDownloadPath->selectedPath()); session->setAppendExtensionEnabled(m_ui->checkAppendqB->isChecked()); session->setPreallocationEnabled(preAllocateAllFiles()); pref->disableRecursiveDownload(!m_ui->checkRecursiveDownload->isChecked()); @@ -1008,14 +1009,12 @@ void OptionsDialog::loadOptions() m_ui->comboCategoryDefaultPathChanged->setCurrentIndex(session->isDisableAutoTMMWhenDefaultSavePathChanged()); m_ui->checkUseDownloadPath->setChecked(session->isDownloadPathEnabled()); m_ui->textDownloadPath->setEnabled(m_ui->checkUseDownloadPath->isChecked()); - m_ui->textDownloadPath->setEnabled(m_ui->checkUseDownloadPath->isChecked()); - m_ui->textDownloadPath->setSelectedPath(Utils::Fs::toNativePath(session->downloadPath())); + m_ui->textDownloadPath->setSelectedPath(session->downloadPath()); m_ui->checkAppendqB->setChecked(session->isAppendExtensionEnabled()); m_ui->checkPreallocateAll->setChecked(session->isPreallocationEnabled()); m_ui->checkRecursiveDownload->setChecked(!pref->recursiveDownloadDisabled()); - strValue = session->torrentExportDirectory(); - if (strValue.isEmpty()) + if (session->torrentExportDirectory().isEmpty()) { // Disable m_ui->checkExportDir->setChecked(false); @@ -1026,11 +1025,10 @@ void OptionsDialog::loadOptions() // Enable m_ui->checkExportDir->setChecked(true); m_ui->textExportDir->setEnabled(true); - m_ui->textExportDir->setSelectedPath(strValue); + m_ui->textExportDir->setSelectedPath(session->torrentExportDirectory()); } - strValue = session->finishedTorrentExportDirectory(); - if (strValue.isEmpty()) + if (session->finishedTorrentExportDirectory().isEmpty()) { // Disable m_ui->checkExportDirFin->setChecked(false); @@ -1041,7 +1039,7 @@ void OptionsDialog::loadOptions() // Enable m_ui->checkExportDirFin->setChecked(true); m_ui->textExportDirFin->setEnabled(true); - m_ui->textExportDirFin->setSelectedPath(strValue); + m_ui->textExportDirFin->setSelectedPath(session->finishedTorrentExportDirectory()); } m_ui->groupMailNotification->setChecked(pref->isMailNotificationEnabled()); @@ -1644,25 +1642,25 @@ void OptionsDialog::setLocale(const QString &localeStr) m_ui->comboI18n->setCurrentIndex(index); } -QString OptionsDialog::getTorrentExportDir() const +Path OptionsDialog::getTorrentExportDir() const { if (m_ui->checkExportDir->isChecked()) - return Utils::Fs::expandPathAbs(m_ui->textExportDir->selectedPath()); + return m_ui->textExportDir->selectedPath(); return {}; } -QString OptionsDialog::getFinishedTorrentExportDir() const +Path OptionsDialog::getFinishedTorrentExportDir() const { if (m_ui->checkExportDirFin->isChecked()) - return Utils::Fs::expandPathAbs(m_ui->textExportDirFin->selectedPath()); + return m_ui->textExportDirFin->selectedPath(); return {}; } void OptionsDialog::on_addWatchedFolderButton_clicked() { Preferences *const pref = Preferences::instance(); - const QString dir = QFileDialog::getExistingDirectory(this, tr("Select folder to monitor"), - Utils::Fs::toNativePath(Utils::Fs::folderName(pref->getScanDirsLastPath()))); + const Path dir {QFileDialog::getExistingDirectory( + this, tr("Select folder to monitor"), pref->getScanDirsLastPath().parentPath().toString())}; if (dir.isEmpty()) return; @@ -1739,19 +1737,8 @@ void OptionsDialog::editWatchedFolderOptions(const QModelIndex &index) dialog->open(); } -QString OptionsDialog::askForExportDir(const QString ¤tExportPath) -{ - QDir currentExportDir(Utils::Fs::expandPathAbs(currentExportPath)); - QString dir; - if (!currentExportPath.isEmpty() && currentExportDir.exists()) - dir = QFileDialog::getExistingDirectory(this, tr("Choose export directory"), currentExportDir.absolutePath()); - else - dir = QFileDialog::getExistingDirectory(this, tr("Choose export directory"), QDir::homePath()); - return dir; -} - // Return Filter object to apply to BT session -QString OptionsDialog::getFilter() const +Path OptionsDialog::getFilter() const { return m_ui->textFilterPath->selectedPath(); } @@ -1773,7 +1760,7 @@ QString OptionsDialog::webUiPassword() const return m_ui->textWebUiPassword->text(); } -void OptionsDialog::webUIHttpsCertChanged(const QString &path, const ShowError showError) +void OptionsDialog::webUIHttpsCertChanged(const Path &path, const ShowError showError) { m_ui->textWebUIHttpsCert->setSelectedPath(path); m_ui->lblSslCertStatus->setPixmap(Utils::Gui::scaledPixmapSvg(UIThemeManager::instance()->getIconPath(QLatin1String("security-low")), this, 24)); @@ -1781,7 +1768,7 @@ void OptionsDialog::webUIHttpsCertChanged(const QString &path, const ShowError s if (path.isEmpty()) return; - QFile file(path); + QFile file {path.data()}; if (!file.open(QIODevice::ReadOnly)) { if (showError == ShowError::Show) @@ -1799,7 +1786,7 @@ void OptionsDialog::webUIHttpsCertChanged(const QString &path, const ShowError s m_ui->lblSslCertStatus->setPixmap(Utils::Gui::scaledPixmapSvg(UIThemeManager::instance()->getIconPath(QLatin1String("security-high")), this, 24)); } -void OptionsDialog::webUIHttpsKeyChanged(const QString &path, const ShowError showError) +void OptionsDialog::webUIHttpsKeyChanged(const Path &path, const ShowError showError) { m_ui->textWebUIHttpsKey->setSelectedPath(path); m_ui->lblSslKeyStatus->setPixmap(Utils::Gui::scaledPixmapSvg(UIThemeManager::instance()->getIconPath(QLatin1String("security-low")), this, 24)); @@ -1807,7 +1794,7 @@ void OptionsDialog::webUIHttpsKeyChanged(const QString &path, const ShowError sh if (path.isEmpty()) return; - QFile file(path); + QFile file {path.data()}; if (!file.open(QIODevice::ReadOnly)) { if (showError == ShowError::Show) @@ -1843,7 +1830,7 @@ void OptionsDialog::on_IpFilterRefreshBtn_clicked() // Updating program preferences BitTorrent::Session *const session = BitTorrent::Session::instance(); session->setIPFilteringEnabled(true); - session->setIPFilterFile(""); // forcing Session reload filter file + session->setIPFilterFile({}); // forcing Session reload filter file session->setIPFilterFile(getFilter()); connect(session, &BitTorrent::Session::IPFilterParsed, this, &OptionsDialog::handleIPFilterParsed); setCursor(QCursor(Qt::WaitCursor)); @@ -1887,7 +1874,7 @@ bool OptionsDialog::webUIAuthenticationOk() bool OptionsDialog::isAlternativeWebUIPathValid() { - if (m_ui->groupAltWebUI->isChecked() && m_ui->textWebUIRootFolder->selectedPath().trimmed().isEmpty()) + if (m_ui->groupAltWebUI->isChecked() && m_ui->textWebUIRootFolder->selectedPath().isEmpty()) { QMessageBox::warning(this, tr("Location Error"), tr("The alternative Web UI files location cannot be blank.")); return false; diff --git a/src/gui/optionsdialog.h b/src/gui/optionsdialog.h index 1a14ba8ae..c03287fa1 100644 --- a/src/gui/optionsdialog.h +++ b/src/gui/optionsdialog.h @@ -30,6 +30,7 @@ #include +#include "base/pathfwd.h" #include "base/settingvalue.h" class QCloseEvent; @@ -112,8 +113,8 @@ private slots: void on_removeWatchedFolderButton_clicked(); void on_registerDNSBtn_clicked(); void setLocale(const QString &localeStr); - void webUIHttpsCertChanged(const QString &path, ShowError showError); - void webUIHttpsKeyChanged(const QString &path, ShowError showError); + void webUIHttpsCertChanged(const Path &path, ShowError showError); + void webUIHttpsKeyChanged(const Path &path, ShowError showError); private: // Methods @@ -136,9 +137,8 @@ private: bool preAllocateAllFiles() const; bool useAdditionDialog() const; bool addTorrentsInPause() const; - QString getTorrentExportDir() const; - QString getFinishedTorrentExportDir() const; - QString askForExportDir(const QString ¤tExportPath); + Path getTorrentExportDir() const; + Path getFinishedTorrentExportDir() const; // Connection options int getPort() const; bool isUPnPEnabled() const; @@ -162,7 +162,7 @@ private: Net::ProxyType getProxyType() const; // IP Filter bool isIPFilteringEnabled() const; - QString getFilter() const; + Path getFilter() const; // Queueing system bool isQueueingSystemEnabled() const; int getMaxActiveDownloads() const; diff --git a/src/gui/previewselectdialog.cpp b/src/gui/previewselectdialog.cpp index a51e66e49..753d10576 100644 --- a/src/gui/previewselectdialog.cpp +++ b/src/gui/previewselectdialog.cpp @@ -93,12 +93,12 @@ PreviewSelectDialog::PreviewSelectDialog(QWidget *parent, const BitTorrent::Torr const QVector fp = torrent->filesProgress(); for (int i = 0; i < torrent->filesCount(); ++i) { - const QString fileName = Utils::Fs::fileName(torrent->filePath(i)); - if (Utils::Misc::isPreviewable(fileName)) + const Path filePath = torrent->filePath(i); + if (Utils::Misc::isPreviewable(filePath)) { int row = m_previewListModel->rowCount(); m_previewListModel->insertRow(row); - m_previewListModel->setData(m_previewListModel->index(row, NAME), fileName); + m_previewListModel->setData(m_previewListModel->index(row, NAME), filePath.filename()); m_previewListModel->setData(m_previewListModel->index(row, SIZE), torrent->fileSize(i)); m_previewListModel->setData(m_previewListModel->index(row, PROGRESS), fp[i]); m_previewListModel->setData(m_previewListModel->index(row, FILE_INDEX), i); @@ -133,14 +133,14 @@ void PreviewSelectDialog::previewButtonClicked() // Only one file should be selected const int fileIndex = selectedIndexes.at(0).data().toInt(); - const QString path = QDir(m_torrent->actualStorageLocation()).absoluteFilePath(m_torrent->actualFilePath(fileIndex)); + const Path path = m_torrent->actualStorageLocation() / m_torrent->actualFilePath(fileIndex); // File - if (!QFile::exists(path)) + if (!path.exists()) { const bool isSingleFile = (m_previewListModel->rowCount() == 1); QWidget *parent = isSingleFile ? this->parentWidget() : this; QMessageBox::critical(parent, tr("Preview impossible") - , tr("Sorry, we can't preview this file: \"%1\".").arg(Utils::Fs::toNativePath(path))); + , tr("Sorry, we can't preview this file: \"%1\".").arg(path.toString())); if (isSingleFile) reject(); return; diff --git a/src/gui/previewselectdialog.h b/src/gui/previewselectdialog.h index 874f5cc83..010cb25d5 100644 --- a/src/gui/previewselectdialog.h +++ b/src/gui/previewselectdialog.h @@ -30,6 +30,7 @@ #include +#include "base/path.h" #include "base/settingvalue.h" class QStandardItemModel; @@ -38,6 +39,7 @@ namespace BitTorrent { class Torrent; } + namespace Ui { class PreviewSelectDialog; @@ -64,7 +66,7 @@ public: ~PreviewSelectDialog(); signals: - void readyToPreviewFile(QString) const; + void readyToPreviewFile(const Path &filePath) const; private slots: void previewButtonClicked(); diff --git a/src/gui/properties/peerlistwidget.cpp b/src/gui/properties/peerlistwidget.cpp index eb15b6bab..ff1cbd128 100644 --- a/src/gui/properties/peerlistwidget.cpp +++ b/src/gui/properties/peerlistwidget.cpp @@ -470,9 +470,11 @@ void PeerListWidget::updatePeer(const BitTorrent::Torrent *torrent, const BitTor setModelData(row, PeerListColumns::TOT_UP, totalUp, peer.totalUpload(), intDataTextAlignment); setModelData(row, PeerListColumns::RELEVANCE, (Utils::String::fromDouble(peer.relevance() * 100, 1) + '%'), peer.relevance(), intDataTextAlignment); - const QStringList downloadingFiles {torrent->hasMetadata() - ? torrent->info().filesForPiece(peer.downloadingPieceIndex()) - : QStringList()}; + const PathList filePaths = torrent->info().filesForPiece(peer.downloadingPieceIndex()); + QStringList downloadingFiles; + downloadingFiles.reserve(filePaths.size()); + for (const Path &filePath : filePaths) + downloadingFiles.append(filePath.toString()); const QString downloadingFilesDisplayValue = downloadingFiles.join(';'); setModelData(row, PeerListColumns::DOWNLOADING_PIECE, downloadingFilesDisplayValue, downloadingFilesDisplayValue, {}, downloadingFiles.join(QLatin1Char('\n'))); diff --git a/src/gui/properties/piecesbar.cpp b/src/gui/properties/piecesbar.cpp index 8a624b00e..57e75178e 100644 --- a/src/gui/properties/piecesbar.cpp +++ b/src/gui/properties/piecesbar.cpp @@ -37,9 +37,10 @@ #include #include -#include "base/indexrange.h" #include "base/bittorrent/torrent.h" #include "base/bittorrent/torrentinfo.h" +#include "base/indexrange.h" +#include "base/path.h" #include "base/utils/misc.h" namespace @@ -53,9 +54,8 @@ namespace { public: PieceIndexToImagePos(const BitTorrent::TorrentInfo &torrentInfo, const QImage &image) - : m_bytesPerPixel - {((image.width() > 0) && (torrentInfo.totalSize() >= image.width())) - ? torrentInfo.totalSize() / image.width() : -1} + : m_bytesPerPixel {((image.width() > 0) && (torrentInfo.totalSize() >= image.width())) + ? torrentInfo.totalSize() / image.width() : -1} , m_torrentInfo {torrentInfo} { if ((m_bytesPerPixel > 0) && (m_bytesPerPixel < 10)) @@ -100,9 +100,9 @@ namespace m_stream << ""; } - void operator()(const QString &size, const QString &path) + void operator()(const QString &size, const Path &path) { - m_stream << R"()" << size << "" << path << ""; + m_stream << R"()" << size << "" << path.toString() << ""; } private: @@ -282,7 +282,7 @@ void PiecesBar::showToolTip(const QHelpEvent *e) for (int f : files) { - const QString filePath {torrentInfo.filePath(f)}; + const Path filePath = torrentInfo.filePath(f); renderer(Utils::Misc::friendlyUnit(torrentInfo.fileSize(f)), filePath); } stream << ""; diff --git a/src/gui/properties/propertieswidget.cpp b/src/gui/properties/propertieswidget.cpp index 09a867a0d..736c2c1f6 100644 --- a/src/gui/properties/propertieswidget.cpp +++ b/src/gui/properties/propertieswidget.cpp @@ -31,7 +31,6 @@ #include #include #include -#include #include #include #include @@ -45,6 +44,7 @@ #include "base/bittorrent/infohash.h" #include "base/bittorrent/session.h" #include "base/bittorrent/torrent.h" +#include "base/path.h" #include "base/preferences.h" #include "base/unicodestrings.h" #include "base/utils/fs.h" @@ -333,7 +333,7 @@ QTreeView *PropertiesWidget::getFilesList() const void PropertiesWidget::updateSavePath(BitTorrent::Torrent *const torrent) { if (torrent == m_torrent) - m_ui->labelSavePathVal->setText(Utils::Fs::toNativePath(m_torrent->savePath())); + m_ui->labelSavePathVal->setText(m_torrent->savePath().toString()); } void PropertiesWidget::loadTrackers(BitTorrent::Torrent *const torrent) @@ -593,25 +593,22 @@ void PropertiesWidget::loadUrlSeeds() } } -QString PropertiesWidget::getFullPath(const QModelIndex &index) const +Path PropertiesWidget::getFullPath(const QModelIndex &index) const { - const QDir saveDir {m_torrent->actualStorageLocation()}; - if (m_propListModel->itemType(index) == TorrentContentModelItem::FileType) { const int fileIdx = m_propListModel->getFileIndex(index); - const QString filename {m_torrent->actualFilePath(fileIdx)}; - const QString fullPath {Utils::Fs::expandPath(saveDir.absoluteFilePath(filename))}; + const Path fullPath = m_torrent->actualStorageLocation() / m_torrent->actualFilePath(fileIdx); return fullPath; } // folder type const QModelIndex nameIndex {index.sibling(index.row(), TorrentContentModelItem::COL_NAME)}; - QString folderPath {nameIndex.data().toString()}; + Path folderPath {nameIndex.data().toString()}; for (QModelIndex modelIdx = m_propListModel->parent(nameIndex); modelIdx.isValid(); modelIdx = modelIdx.parent()) - folderPath.prepend(modelIdx.data().toString() + '/'); + folderPath = Path(modelIdx.data().toString()) / folderPath; - const QString fullPath {Utils::Fs::expandPath(saveDir.absoluteFilePath(folderPath))}; + const Path fullPath = m_torrent->actualStorageLocation() / folderPath; return fullPath; } @@ -626,7 +623,7 @@ void PropertiesWidget::openItem(const QModelIndex &index) const void PropertiesWidget::openParentFolder(const QModelIndex &index) const { - const QString path = getFullPath(index); + const Path path = getFullPath(index); m_torrent->flushCache(); // Flush data #ifdef Q_OS_MACOS MacUtils::openFiles({path}); diff --git a/src/gui/properties/propertieswidget.h b/src/gui/properties/propertieswidget.h index 7c2d00dde..7f9a208b3 100644 --- a/src/gui/properties/propertieswidget.h +++ b/src/gui/properties/propertieswidget.h @@ -31,6 +31,8 @@ #include #include +#include "base/pathfwd.h" + class QPushButton; class QTreeView; @@ -108,7 +110,7 @@ private: QPushButton *getButtonFromIndex(int index); void applyPriorities(); void openParentFolder(const QModelIndex &index) const; - QString getFullPath(const QModelIndex &index) const; + Path getFullPath(const QModelIndex &index) const; Ui::PropertiesWidget *m_ui; BitTorrent::Torrent *m_torrent; diff --git a/src/gui/rss/automatedrssdownloader.cpp b/src/gui/rss/automatedrssdownloader.cpp index 917e4f4ac..7f7df276e 100644 --- a/src/gui/rss/automatedrssdownloader.cpp +++ b/src/gui/rss/automatedrssdownloader.cpp @@ -40,6 +40,7 @@ #include "base/bittorrent/session.h" #include "base/global.h" +#include "base/path.h" #include "base/preferences.h" #include "base/rss/rss_article.h" #include "base/rss/rss_autodownloader.h" @@ -47,7 +48,6 @@ #include "base/rss/rss_folder.h" #include "base/rss/rss_session.h" #include "base/utils/compare.h" -#include "base/utils/fs.h" #include "base/utils/io.h" #include "base/utils/string.h" #include "gui/autoexpandabledialog.h" @@ -261,7 +261,7 @@ void AutomatedRssDownloader::updateRuleDefinitionBox() else m_ui->lineEFilter->clear(); m_ui->checkBoxSaveDiffDir->setChecked(!m_currentRule.savePath().isEmpty()); - m_ui->lineSavePath->setSelectedPath(Utils::Fs::toNativePath(m_currentRule.savePath())); + m_ui->lineSavePath->setSelectedPath(m_currentRule.savePath()); m_ui->checkRegex->blockSignals(true); m_ui->checkRegex->setChecked(m_currentRule.useRegex()); m_ui->checkRegex->blockSignals(false); @@ -346,7 +346,7 @@ void AutomatedRssDownloader::updateEditedRule() m_currentRule.setMustContain(m_ui->lineContains->text()); m_currentRule.setMustNotContain(m_ui->lineNotContains->text()); m_currentRule.setEpisodeFilter(m_ui->lineEFilter->text()); - m_currentRule.setSavePath(m_ui->checkBoxSaveDiffDir->isChecked() ? m_ui->lineSavePath->selectedPath() : ""); + m_currentRule.setSavePath(m_ui->checkBoxSaveDiffDir->isChecked() ? m_ui->lineSavePath->selectedPath() : Path()); m_currentRule.setCategory(m_ui->comboCategory->currentText()); std::optional addPaused; if (m_ui->comboAddPaused->currentIndex() == 1) @@ -429,9 +429,10 @@ void AutomatedRssDownloader::on_exportBtn_clicked() } QString selectedFilter {m_formatFilterJSON}; - QString path = QFileDialog::getSaveFileName( + Path path {QFileDialog::getSaveFileName( this, tr("Export RSS rules"), QDir::homePath() - , QString::fromLatin1("%1;;%2").arg(m_formatFilterJSON, m_formatFilterLegacy), &selectedFilter); + , QString::fromLatin1("%1;;%2").arg(m_formatFilterJSON, m_formatFilterLegacy), &selectedFilter)}; + if (path.isEmpty()) return; const RSS::AutoDownloader::RulesFileFormat format @@ -443,12 +444,12 @@ void AutomatedRssDownloader::on_exportBtn_clicked() if (format == RSS::AutoDownloader::RulesFileFormat::JSON) { - if (!path.endsWith(EXT_JSON, Qt::CaseInsensitive)) + if (!path.hasExtension(EXT_JSON)) path += EXT_JSON; } else { - if (!path.endsWith(EXT_LEGACY, Qt::CaseInsensitive)) + if (!path.hasExtension(EXT_LEGACY)) path += EXT_LEGACY; } @@ -464,13 +465,13 @@ void AutomatedRssDownloader::on_exportBtn_clicked() void AutomatedRssDownloader::on_importBtn_clicked() { QString selectedFilter {m_formatFilterJSON}; - QString path = QFileDialog::getOpenFileName( - this, tr("Import RSS rules"), QDir::homePath() - , QString::fromLatin1("%1;;%2").arg(m_formatFilterJSON, m_formatFilterLegacy), &selectedFilter); - if (path.isEmpty() || !QFile::exists(path)) + const Path path {QFileDialog::getOpenFileName( + this, tr("Import RSS rules"), QDir::homePath() + , QString::fromLatin1("%1;;%2").arg(m_formatFilterJSON, m_formatFilterLegacy), &selectedFilter)}; + if (!path.exists()) return; - QFile file {path}; + QFile file {path.data()}; if (!file.open(QIODevice::ReadOnly)) { QMessageBox::critical( diff --git a/src/gui/rss/feedlistwidget.cpp b/src/gui/rss/feedlistwidget.cpp index 5ccb58bd9..2f1ee04e6 100644 --- a/src/gui/rss/feedlistwidget.cpp +++ b/src/gui/rss/feedlistwidget.cpp @@ -67,9 +67,9 @@ namespace } }; - QIcon loadIcon(const QString &path, const QString &fallbackId) + QIcon loadIcon(const Path &path, const QString &fallbackId) { - const QPixmap pixmap {path}; + const QPixmap pixmap {path.data()}; if (!pixmap.isNull()) return {pixmap}; diff --git a/src/gui/rss/htmlbrowser.cpp b/src/gui/rss/htmlbrowser.cpp index 89dbbacd4..4bbd71cfd 100644 --- a/src/gui/rss/htmlbrowser.cpp +++ b/src/gui/rss/htmlbrowser.cpp @@ -30,7 +30,6 @@ #include #include -#include #include #include #include @@ -38,6 +37,7 @@ #include #include +#include "base/path.h" #include "base/profile.h" HtmlBrowser::HtmlBrowser(QWidget *parent) @@ -45,7 +45,7 @@ HtmlBrowser::HtmlBrowser(QWidget *parent) { m_netManager = new QNetworkAccessManager(this); m_diskCache = new QNetworkDiskCache(this); - m_diskCache->setCacheDirectory(QDir::cleanPath(specialFolderLocation(SpecialFolder::Cache) + "/rss")); + m_diskCache->setCacheDirectory((specialFolderLocation(SpecialFolder::Cache) / Path("rss")).data()); m_diskCache->setMaximumCacheSize(50 * 1024 * 1024); qDebug() << "HtmlBrowser cache path:" << m_diskCache->cacheDirectory() << " max size:" << m_diskCache->maximumCacheSize() / 1024 / 1024 << "MB"; m_netManager->setCache(m_diskCache); diff --git a/src/gui/search/pluginselectdialog.cpp b/src/gui/search/pluginselectdialog.cpp index b7fa25cf3..64d5e6187 100644 --- a/src/gui/search/pluginselectdialog.cpp +++ b/src/gui/search/pluginselectdialog.cpp @@ -309,10 +309,10 @@ void PluginSelectDialog::addNewPlugin(const QString &pluginName) setRowColor(m_ui->pluginsTree->indexOfTopLevelItem(item), "red"); } // Handle icon - if (QFile::exists(plugin->iconPath)) + if (plugin->iconPath.exists()) { // Good, we already have the icon - item->setData(PLUGIN_NAME, Qt::DecorationRole, QIcon(plugin->iconPath)); + item->setData(PLUGIN_NAME, Qt::DecorationRole, QIcon(plugin->iconPath.data())); } else { @@ -406,10 +406,10 @@ void PluginSelectDialog::iconDownloadFinished(const Net::DownloadResult &result) return; } - const QString filePath = Utils::Fs::toUniformPath(result.filePath); + const Path filePath = result.filePath; // Icon downloaded - QIcon icon(filePath); + QIcon icon {filePath.data()}; // Detect a non-decodable icon QList sizes = icon.availableSizes(); bool invalid = (sizes.isEmpty() || icon.pixmap(sizes.first()).isNull()); @@ -421,21 +421,19 @@ void PluginSelectDialog::iconDownloadFinished(const Net::DownloadResult &result) PluginInfo *plugin = m_pluginManager->pluginInfo(id); if (!plugin) continue; - QString iconPath = QString("%1/%2.%3") - .arg(SearchPluginManager::pluginsLocation() - , id - , result.url.endsWith(".ico", Qt::CaseInsensitive) ? "ico" : "png"); - if (QFile::copy(filePath, iconPath)) + const QString ext = result.url.endsWith(QLatin1String(".ico"), Qt::CaseInsensitive) ? QLatin1String(".ico") : QLatin1String(".png"); + const Path iconPath = SearchPluginManager::pluginsLocation() / Path(id + ext); + if (Utils::Fs::copyFile(filePath, iconPath)) { // This 2nd check is necessary. Some favicons (eg from piratebay) // decode fine without an ext, but fail to do so when appending the ext // from the url. Probably a Qt bug. - QIcon iconWithExt(iconPath); + QIcon iconWithExt {iconPath.data()}; QList sizesExt = iconWithExt.availableSizes(); bool invalidExt = (sizesExt.isEmpty() || iconWithExt.pixmap(sizesExt.first()).isNull()); if (invalidExt) { - Utils::Fs::forceRemove(iconPath); + Utils::Fs::removeFile(iconPath); continue; } @@ -445,7 +443,7 @@ void PluginSelectDialog::iconDownloadFinished(const Net::DownloadResult &result) } } // Delete tmp file - Utils::Fs::forceRemove(filePath); + Utils::Fs::removeFile(filePath); } void PluginSelectDialog::checkForUpdatesFinished(const QHash &updateInfo) diff --git a/src/gui/torrentcategorydialog.cpp b/src/gui/torrentcategorydialog.cpp index 948791882..ef78db009 100644 --- a/src/gui/torrentcategorydialog.cpp +++ b/src/gui/torrentcategorydialog.cpp @@ -153,7 +153,7 @@ void TorrentCategoryDialog::setCategoryOptions(const BitTorrent::CategoryOptions if (categoryOptions.downloadPath) { m_ui->comboUseDownloadPath->setCurrentIndex(categoryOptions.downloadPath->enabled ? 1 : 2); - m_ui->comboDownloadPath->setSelectedPath(categoryOptions.downloadPath->enabled ? categoryOptions.downloadPath->path : QString()); + m_ui->comboDownloadPath->setSelectedPath(categoryOptions.downloadPath->enabled ? categoryOptions.downloadPath->path : Path()); } else { @@ -164,30 +164,29 @@ void TorrentCategoryDialog::setCategoryOptions(const BitTorrent::CategoryOptions void TorrentCategoryDialog::categoryNameChanged(const QString &categoryName) { - const QString categoryPath = Utils::Fs::toValidFileSystemName(categoryName, true); + const Path categoryPath = Utils::Fs::toValidPath(categoryName); const auto *btSession = BitTorrent::Session::instance(); - m_ui->comboSavePath->setPlaceholder(Utils::Fs::resolvePath(categoryPath, btSession->savePath())); + m_ui->comboSavePath->setPlaceholder(btSession->savePath() / categoryPath); const int index = m_ui->comboUseDownloadPath->currentIndex(); const bool useDownloadPath = (index == 1) || ((index == 0) && BitTorrent::Session::instance()->isDownloadPathEnabled()); if (useDownloadPath) - m_ui->comboDownloadPath->setPlaceholder(Utils::Fs::resolvePath(categoryPath, btSession->downloadPath())); + m_ui->comboDownloadPath->setPlaceholder(btSession->downloadPath() / categoryPath); m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!categoryName.isEmpty()); } void TorrentCategoryDialog::useDownloadPathChanged(const int index) { - if (const QString selectedPath = m_ui->comboDownloadPath->selectedPath(); !selectedPath.isEmpty()) + if (const Path selectedPath = m_ui->comboDownloadPath->selectedPath(); !selectedPath.isEmpty()) m_lastEnteredDownloadPath = selectedPath; m_ui->labelDownloadPath->setEnabled(index == 1); m_ui->comboDownloadPath->setEnabled(index == 1); - m_ui->comboDownloadPath->setSelectedPath((index == 1) ? m_lastEnteredDownloadPath : QString()); + m_ui->comboDownloadPath->setSelectedPath((index == 1) ? m_lastEnteredDownloadPath : Path()); const QString categoryName = m_ui->textCategoryName->text(); - const QString categoryPath = Utils::Fs::resolvePath(Utils::Fs::toValidFileSystemName(categoryName, true) - , BitTorrent::Session::instance()->downloadPath()); + const Path categoryPath = BitTorrent::Session::instance()->downloadPath() / Utils::Fs::toValidPath(categoryName); const bool useDownloadPath = (index == 1) || ((index == 0) && BitTorrent::Session::instance()->isDownloadPathEnabled()); - m_ui->comboDownloadPath->setPlaceholder(useDownloadPath ? categoryPath : QString()); + m_ui->comboDownloadPath->setPlaceholder(useDownloadPath ? categoryPath : Path()); } diff --git a/src/gui/torrentcategorydialog.h b/src/gui/torrentcategorydialog.h index 074f16f88..889226a2f 100644 --- a/src/gui/torrentcategorydialog.h +++ b/src/gui/torrentcategorydialog.h @@ -30,6 +30,8 @@ #include +#include "base/path.h" + namespace BitTorrent { struct CategoryOptions; @@ -64,5 +66,5 @@ private slots: private: Ui::TorrentCategoryDialog *m_ui; - QString m_lastEnteredDownloadPath; + Path m_lastEnteredDownloadPath; }; diff --git a/src/gui/torrentcontentmodel.cpp b/src/gui/torrentcontentmodel.cpp index 733f90587..3e14c7bec 100644 --- a/src/gui/torrentcontentmodel.cpp +++ b/src/gui/torrentcontentmodel.cpp @@ -53,6 +53,7 @@ #include "base/bittorrent/abstractfilestorage.h" #include "base/bittorrent/downloadpriority.h" #include "base/global.h" +#include "base/path.h" #include "base/utils/fs.h" #include "torrentcontentmodelfile.h" #include "torrentcontentmodelfolder.h" @@ -503,7 +504,7 @@ void TorrentContentModel::setupModelData(const BitTorrent::AbstractFileStorage & for (int i = 0; i < filesCount; ++i) { currentParent = m_rootItem; - const QString path = Utils::Fs::toUniformPath(info.filePath(i)); + const QString path = info.filePath(i).data(); // Iterate of parts of the path to create necessary folders QList pathFolders = QStringView(path).split(u'/', Qt::SkipEmptyParts); @@ -523,7 +524,7 @@ void TorrentContentModel::setupModelData(const BitTorrent::AbstractFileStorage & } // Actually create the file TorrentContentModelFile *fileItem = new TorrentContentModelFile( - Utils::Fs::fileName(info.filePath(i)), info.fileSize(i), currentParent, i); + info.filePath(i).filename(), info.fileSize(i), currentParent, i); currentParent->appendChild(fileItem); m_filesIndex.push_back(fileItem); } diff --git a/src/gui/torrentcontenttreeview.cpp b/src/gui/torrentcontenttreeview.cpp index ad2b7c4f8..96bbaa7ef 100644 --- a/src/gui/torrentcontenttreeview.cpp +++ b/src/gui/torrentcontenttreeview.cpp @@ -44,6 +44,7 @@ #include "base/bittorrent/torrentinfo.h" #include "base/exceptions.h" #include "base/global.h" +#include "base/path.h" #include "base/utils/fs.h" #include "autoexpandabledialog.h" #include "raisedmessagebox.h" @@ -52,12 +53,12 @@ namespace { - QString getFullPath(const QModelIndex &idx) + Path getFullPath(const QModelIndex &idx) { - QStringList paths; + Path path; for (QModelIndex i = idx; i.isValid(); i = i.parent()) - paths.prepend(i.data().toString()); - return paths.join(QLatin1Char {'/'}); + path = Path(i.data().toString()) / path; + return path; } } @@ -130,9 +131,9 @@ void TorrentContentTreeView::renameSelectedFile(BitTorrent::AbstractFileStorage if (newName == oldName) return; // Name did not change - const QString parentPath = getFullPath(modelIndex.parent()); - const QString oldPath {parentPath.isEmpty() ? oldName : parentPath + QLatin1Char {'/'} + oldName}; - const QString newPath {parentPath.isEmpty() ? newName : parentPath + QLatin1Char {'/'} + newName}; + const Path parentPath = getFullPath(modelIndex.parent()); + const Path oldPath = parentPath / Path(oldName); + const Path newPath = parentPath / Path(newName); try { diff --git a/src/gui/torrentcreatordialog.cpp b/src/gui/torrentcreatordialog.cpp index 452b0dcd7..491539808 100644 --- a/src/gui/torrentcreatordialog.cpp +++ b/src/gui/torrentcreatordialog.cpp @@ -44,7 +44,7 @@ #define SETTINGS_KEY(name) "TorrentCreator/" name -TorrentCreatorDialog::TorrentCreatorDialog(QWidget *parent, const QString &defaultPath) +TorrentCreatorDialog::TorrentCreatorDialog(QWidget *parent, const Path &defaultPath) : QDialog(parent) , m_ui(new Ui::TorrentCreatorDialog) , m_creatorThread(new BitTorrent::TorrentCreatorThread(this)) @@ -100,24 +100,24 @@ TorrentCreatorDialog::~TorrentCreatorDialog() delete m_ui; } -void TorrentCreatorDialog::updateInputPath(const QString &path) +void TorrentCreatorDialog::updateInputPath(const Path &path) { if (path.isEmpty()) return; - m_ui->textInputPath->setText(Utils::Fs::toNativePath(path)); + m_ui->textInputPath->setText(path.toString()); updateProgressBar(0); } void TorrentCreatorDialog::onAddFolderButtonClicked() { - QString oldPath = m_ui->textInputPath->text(); - QString path = QFileDialog::getExistingDirectory(this, tr("Select folder"), oldPath); + const QString oldPath = m_ui->textInputPath->text(); + const Path path {QFileDialog::getExistingDirectory(this, tr("Select folder"), oldPath)}; updateInputPath(path); } void TorrentCreatorDialog::onAddFileButtonClicked() { - QString oldPath = m_ui->textInputPath->text(); - QString path = QFileDialog::getOpenFileName(this, tr("Select file"), oldPath); + const QString oldPath = m_ui->textInputPath->text(); + const Path path {QFileDialog::getOpenFileName(this, tr("Select file"), oldPath)}; updateInputPath(path); } @@ -156,9 +156,11 @@ void TorrentCreatorDialog::dropEvent(QDropEvent *event) if (event->mimeData()->hasUrls()) { // only take the first one - QUrl firstItem = event->mimeData()->urls().first(); - QString path = (firstItem.scheme().compare("file", Qt::CaseInsensitive) == 0) - ? firstItem.toLocalFile() : firstItem.toString(); + const QUrl firstItem = event->mimeData()->urls().first(); + const Path path { + (firstItem.scheme().compare("file", Qt::CaseInsensitive) == 0) + ? firstItem.toLocalFile() : firstItem.toString() + }; updateInputPath(path); } } @@ -172,25 +174,23 @@ void TorrentCreatorDialog::dragEnterEvent(QDragEnterEvent *event) // Main function that create a .torrent file void TorrentCreatorDialog::onCreateButtonClicked() { - QString input = Utils::Fs::toUniformPath(m_ui->textInputPath->text()).trimmed(); + const auto inputPath = Utils::Fs::toCanonicalPath(Path(m_ui->textInputPath->text().trimmed())); // test if readable - const QFileInfo fi(input); - if (!fi.isReadable()) + if (!Utils::Fs::isReadable(inputPath)) { QMessageBox::critical(this, tr("Torrent creation failed"), tr("Reason: Path to file/folder is not readable.")); return; } - input = fi.canonicalFilePath(); // get save path - const QString savePath = m_storeLastSavePath.get(QDir::homePath()) + QLatin1Char('/') + fi.fileName() + QLatin1String(".torrent"); - QString destination = QFileDialog::getSaveFileName(this, tr("Select where to save the new torrent"), savePath, tr("Torrent Files (*.torrent)")); - if (destination.isEmpty()) + const Path savePath = m_storeLastSavePath.get(Utils::Fs::homePath() / Path(inputPath.filename() + QLatin1String(".torrent"))); + Path destPath {QFileDialog::getSaveFileName(this, tr("Select where to save the new torrent"), savePath.data(), tr("Torrent Files (*.torrent)"))}; + if (destPath.isEmpty()) return; - if (!destination.endsWith(C_TORRENT_FILE_EXTENSION, Qt::CaseInsensitive)) - destination += C_TORRENT_FILE_EXTENSION; - m_storeLastSavePath = Utils::Fs::branchPath(destination); + if (!destPath.hasExtension(C_TORRENT_FILE_EXTENSION)) + destPath += C_TORRENT_FILE_EXTENSION; + m_storeLastSavePath = destPath.parentPath(); // Disable dialog & set busy cursor setInteractionEnabled(false); @@ -208,7 +208,8 @@ void TorrentCreatorDialog::onCreateButtonClicked() , getPaddedFileSizeLimit() #endif , getPieceSize() - , input, destination + , inputPath + , destPath , m_ui->txtComment->toPlainText() , m_ui->lineEditSource->text() , trackers @@ -227,14 +228,14 @@ void TorrentCreatorDialog::handleCreationFailure(const QString &msg) setInteractionEnabled(true); } -void TorrentCreatorDialog::handleCreationSuccess(const QString &path, const QString &branchPath) +void TorrentCreatorDialog::handleCreationSuccess(const Path &path, const Path &branchPath) { // Remove busy cursor setCursor(QCursor(Qt::ArrowCursor)); if (m_ui->checkStartSeeding->isChecked()) { // Create save path temp data - const nonstd::expected result = BitTorrent::TorrentInfo::loadFromFile(Utils::Fs::toNativePath(path)); + const nonstd::expected result = BitTorrent::TorrentInfo::loadFromFile(path); if (!result) { QMessageBox::critical(this, tr("Torrent creation failed"), tr("Reason: Created torrent is invalid. It won't be added to download list.")); @@ -254,7 +255,7 @@ void TorrentCreatorDialog::handleCreationSuccess(const QString &path, const QStr BitTorrent::Session::instance()->addTorrent(result.value(), params); } QMessageBox::information(this, tr("Torrent creator") - , QString::fromLatin1("%1\n%2").arg(tr("Torrent created:"), Utils::Fs::toNativePath(path))); + , QString::fromLatin1("%1\n%2").arg(tr("Torrent created:"), path.toString())); setInteractionEnabled(true); } @@ -265,7 +266,7 @@ void TorrentCreatorDialog::updateProgressBar(int progress) void TorrentCreatorDialog::updatePiecesCount() { - const QString path = m_ui->textInputPath->text().trimmed(); + const Path path {m_ui->textInputPath->text().trimmed()}; #ifdef QBT_USES_LIBTORRENT2 const int count = BitTorrent::TorrentCreatorThread::calculateTotalPieces( path, getPieceSize(), getTorrentFormat()); @@ -301,7 +302,7 @@ void TorrentCreatorDialog::setInteractionEnabled(const bool enabled) const void TorrentCreatorDialog::saveSettings() { - m_storeLastAddPath = m_ui->textInputPath->text().trimmed(); + m_storeLastAddPath = Path(m_ui->textInputPath->text().trimmed()); m_storePieceSize = m_ui->comboPieceSize->currentIndex(); m_storePrivateTorrent = m_ui->checkPrivate->isChecked(); @@ -324,7 +325,7 @@ void TorrentCreatorDialog::saveSettings() void TorrentCreatorDialog::loadSettings() { - m_ui->textInputPath->setText(m_storeLastAddPath.get(QDir::homePath())); + m_ui->textInputPath->setText(m_storeLastAddPath.get(Utils::Fs::homePath()).toString()); m_ui->comboPieceSize->setCurrentIndex(m_storePieceSize); m_ui->checkPrivate->setChecked(m_storePrivateTorrent); diff --git a/src/gui/torrentcreatordialog.h b/src/gui/torrentcreatordialog.h index 0b08ff36b..9b6228f49 100644 --- a/src/gui/torrentcreatordialog.h +++ b/src/gui/torrentcreatordialog.h @@ -32,6 +32,7 @@ #include #include "base/bittorrent/torrentcreatorthread.h" +#include "base/path.h" #include "base/settingvalue.h" namespace Ui @@ -42,11 +43,12 @@ namespace Ui class TorrentCreatorDialog final : public QDialog { Q_OBJECT + Q_DISABLE_COPY_MOVE(TorrentCreatorDialog) public: - TorrentCreatorDialog(QWidget *parent = nullptr, const QString &defaultPath = {}); + TorrentCreatorDialog(QWidget *parent = nullptr, const Path &defaultPath = {}); ~TorrentCreatorDialog() override; - void updateInputPath(const QString &path); + void updateInputPath(const Path &path); private slots: void updateProgressBar(int progress); @@ -55,7 +57,7 @@ private slots: void onAddFileButtonClicked(); void onAddFolderButtonClicked(); void handleCreationFailure(const QString &msg); - void handleCreationSuccess(const QString &path, const QString &branchPath); + void handleCreationSuccess(const Path &path, const Path &branchPath); private: void dropEvent(QDropEvent *event) override; @@ -87,10 +89,10 @@ private: SettingValue m_storeOptimizeAlignment; SettingValue m_paddedFileSizeLimit; #endif - SettingValue m_storeLastAddPath; + SettingValue m_storeLastAddPath; SettingValue m_storeTrackerList; SettingValue m_storeWebSeedList; SettingValue m_storeComments; - SettingValue m_storeLastSavePath; + SettingValue m_storeLastSavePath; SettingValue m_storeSource; }; diff --git a/src/gui/torrentoptionsdialog.cpp b/src/gui/torrentoptionsdialog.cpp index 259539e5b..e0673d4a0 100644 --- a/src/gui/torrentoptionsdialog.cpp +++ b/src/gui/torrentoptionsdialog.cpp @@ -90,8 +90,8 @@ TorrentOptionsDialog::TorrentOptionsDialog(QWidget *parent, const QVectorisAutoTMMEnabled(); - const QString firstTorrentSavePath = torrents[0]->savePath(); - const QString firstTorrentDownloadPath = torrents[0]->downloadPath(); + const Path firstTorrentSavePath = torrents[0]->savePath(); + const Path firstTorrentDownloadPath = torrents[0]->downloadPath(); const QString firstTorrentCategory = torrents[0]->category(); const int firstTorrentUpLimit = std::max(0, torrents[0]->uploadLimit()); @@ -418,20 +418,20 @@ void TorrentOptionsDialog::accept() if (m_ui->checkAutoTMM->checkState() == Qt::Unchecked) { - const QString savePath = m_ui->savePath->selectedPath(); + const Path savePath = m_ui->savePath->selectedPath(); if (m_initialValues.savePath != savePath) - torrent->setSavePath(Utils::Fs::expandPathAbs(savePath)); + torrent->setSavePath(savePath); const Qt::CheckState useDownloadPathState = m_ui->checkUseDownloadPath->checkState(); if (useDownloadPathState == Qt::Checked) { - const QString downloadPath = m_ui->downloadPath->selectedPath(); + const Path downloadPath = m_ui->downloadPath->selectedPath(); if (m_initialValues.downloadPath != downloadPath) - torrent->setDownloadPath(Utils::Fs::expandPathAbs(downloadPath)); + torrent->setDownloadPath(downloadPath); } else if (useDownloadPathState == Qt::Unchecked) { - torrent->setDownloadPath(QString()); + torrent->setDownloadPath({}); } } @@ -513,14 +513,14 @@ void TorrentOptionsDialog::handleCategoryChanged(const int index) { if (!m_allSameCategory && (m_ui->comboCategory->currentIndex() == 0)) { - m_ui->savePath->setSelectedPath(QString()); + m_ui->savePath->setSelectedPath({}); } else { - const QString savePath = BitTorrent::Session::instance()->categorySavePath(m_ui->comboCategory->currentText()); - m_ui->savePath->setSelectedPath(Utils::Fs::toNativePath(savePath)); - const QString downloadPath = BitTorrent::Session::instance()->categoryDownloadPath(m_ui->comboCategory->currentText()); - m_ui->downloadPath->setSelectedPath(Utils::Fs::toNativePath(downloadPath)); + const Path savePath = BitTorrent::Session::instance()->categorySavePath(m_ui->comboCategory->currentText()); + m_ui->savePath->setSelectedPath(savePath); + const Path downloadPath = BitTorrent::Session::instance()->categoryDownloadPath(m_ui->comboCategory->currentText()); + m_ui->downloadPath->setSelectedPath(downloadPath); m_ui->checkUseDownloadPath->setChecked(!downloadPath.isEmpty()); } } @@ -541,8 +541,8 @@ void TorrentOptionsDialog::handleTMMChanged() if (m_ui->checkAutoTMM->checkState() == Qt::Unchecked) { m_ui->groupBoxSavePath->setEnabled(true); - m_ui->savePath->setSelectedPath(Utils::Fs::toNativePath(m_initialValues.savePath)); - m_ui->downloadPath->setSelectedPath(Utils::Fs::toNativePath(m_initialValues.downloadPath)); + m_ui->savePath->setSelectedPath(m_initialValues.savePath); + m_ui->downloadPath->setSelectedPath(m_initialValues.downloadPath); m_ui->checkUseDownloadPath->setCheckState(m_initialValues.useDownloadPath); } else @@ -552,23 +552,23 @@ void TorrentOptionsDialog::handleTMMChanged() { if (!m_allSameCategory && (m_ui->comboCategory->currentIndex() == 0)) { - m_ui->savePath->setSelectedPath(QString()); - m_ui->downloadPath->setSelectedPath(QString()); + m_ui->savePath->setSelectedPath({}); + m_ui->downloadPath->setSelectedPath({}); m_ui->checkUseDownloadPath->setCheckState(Qt::PartiallyChecked); } else { - const QString savePath = BitTorrent::Session::instance()->categorySavePath(m_ui->comboCategory->currentText()); - m_ui->savePath->setSelectedPath(Utils::Fs::toNativePath(savePath)); - const QString downloadPath = BitTorrent::Session::instance()->categoryDownloadPath(m_ui->comboCategory->currentText()); - m_ui->downloadPath->setSelectedPath(Utils::Fs::toNativePath(downloadPath)); + const Path savePath = BitTorrent::Session::instance()->categorySavePath(m_ui->comboCategory->currentText()); + m_ui->savePath->setSelectedPath(savePath); + const Path downloadPath = BitTorrent::Session::instance()->categoryDownloadPath(m_ui->comboCategory->currentText()); + m_ui->downloadPath->setSelectedPath(downloadPath); m_ui->checkUseDownloadPath->setChecked(!downloadPath.isEmpty()); } } else // partially checked { - m_ui->savePath->setSelectedPath(QString()); - m_ui->downloadPath->setSelectedPath(QString()); + m_ui->savePath->setSelectedPath({}); + m_ui->downloadPath->setSelectedPath({}); m_ui->checkUseDownloadPath->setCheckState(Qt::PartiallyChecked); } } diff --git a/src/gui/torrentoptionsdialog.h b/src/gui/torrentoptionsdialog.h index 82204e311..0291fe9e9 100644 --- a/src/gui/torrentoptionsdialog.h +++ b/src/gui/torrentoptionsdialog.h @@ -32,6 +32,7 @@ #include +#include "base/path.h" #include "base/settingvalue.h" class QAbstractButton; @@ -82,8 +83,8 @@ private: QAbstractButton *m_previousRadio = nullptr; struct { - QString savePath; - QString downloadPath; + Path savePath; + Path downloadPath; QString category; qreal ratio; int seedingTime; diff --git a/src/gui/transferlistfilterswidget.cpp b/src/gui/transferlistfilterswidget.cpp index 3d51ec170..f26f40dd4 100644 --- a/src/gui/transferlistfilterswidget.cpp +++ b/src/gui/transferlistfilterswidget.cpp @@ -322,8 +322,8 @@ TrackerFiltersList::TrackerFiltersList(QWidget *parent, TransferListWidget *tran TrackerFiltersList::~TrackerFiltersList() { - for (const QString &iconPath : asConst(m_iconPaths)) - Utils::Fs::forceRemove(iconPath); + for (const Path &iconPath : asConst(m_iconPaths)) + Utils::Fs::removeFile(iconPath); } void TrackerFiltersList::addItem(const QString &tracker, const BitTorrent::TorrentID &id) @@ -534,14 +534,14 @@ void TrackerFiltersList::handleFavicoDownloadFinished(const Net::DownloadResult if (!m_trackers.contains(host)) { - Utils::Fs::forceRemove(result.filePath); + Utils::Fs::removeFile(result.filePath); return; } QListWidgetItem *trackerItem = item(rowFromTracker(host)); if (!trackerItem) return; - QIcon icon(result.filePath); + const QIcon icon {result.filePath.data()}; //Detect a non-decodable icon QList sizes = icon.availableSizes(); bool invalid = (sizes.isEmpty() || icon.pixmap(sizes.first()).isNull()); @@ -549,11 +549,11 @@ void TrackerFiltersList::handleFavicoDownloadFinished(const Net::DownloadResult { if (result.url.endsWith(".ico", Qt::CaseInsensitive)) downloadFavicon(result.url.left(result.url.size() - 4) + ".png"); - Utils::Fs::forceRemove(result.filePath); + Utils::Fs::removeFile(result.filePath); } else { - trackerItem->setData(Qt::DecorationRole, QIcon(result.filePath)); + trackerItem->setData(Qt::DecorationRole, QIcon(result.filePath.data())); m_iconPaths.append(result.filePath); } } diff --git a/src/gui/transferlistfilterswidget.h b/src/gui/transferlistfilterswidget.h index 2fef31aad..21e468421 100644 --- a/src/gui/transferlistfilterswidget.h +++ b/src/gui/transferlistfilterswidget.h @@ -34,6 +34,7 @@ #include "base/bittorrent/infohash.h" #include "base/bittorrent/trackerentry.h" +#include "base/path.h" class QCheckBox; class QResizeEvent; @@ -133,7 +134,7 @@ private: QHash> m_trackers; // QHash> m_errors; // QHash> m_warnings; // - QStringList m_iconPaths; + PathList m_iconPaths; int m_totalTorrents; bool m_downloadTrackerFavicon; }; diff --git a/src/gui/transferlistmodel.cpp b/src/gui/transferlistmodel.cpp index abb336d81..4e6485d42 100644 --- a/src/gui/transferlistmodel.cpp +++ b/src/gui/transferlistmodel.cpp @@ -392,7 +392,7 @@ QString TransferListModel::displayValue(const BitTorrent::Torrent *torrent, cons case TR_TIME_ELAPSED: return timeElapsedString(torrent->activeTime(), torrent->finishedTime()); case TR_SAVE_PATH: - return Utils::Fs::toNativePath(torrent->savePath()); + return torrent->savePath().toString(); case TR_COMPLETED: return unitString(torrent->completedSize()); case TR_SEEN_COMPLETE_DATE: @@ -461,7 +461,7 @@ QVariant TransferListModel::internalValue(const BitTorrent::Torrent *torrent, co case TR_TIME_ELAPSED: return !alt ? torrent->activeTime() : torrent->finishedTime(); case TR_SAVE_PATH: - return Utils::Fs::toNativePath(torrent->savePath()); + return torrent->savePath().toString(); case TR_COMPLETED: return torrent->completedSize(); case TR_RATIO_LIMIT: diff --git a/src/gui/transferlistwidget.cpp b/src/gui/transferlistwidget.cpp index efc97ad97..193aab036 100644 --- a/src/gui/transferlistwidget.cpp +++ b/src/gui/transferlistwidget.cpp @@ -90,10 +90,9 @@ namespace if (!torrent->hasMetadata()) return false; - for (const QString &filePath : asConst(torrent->filePaths())) + for (const Path &filePath : asConst(torrent->filePaths())) { - const QString fileName = Utils::Fs::fileName(filePath); - if (Utils::Misc::isPreviewable(fileName)) + if (Utils::Misc::isPreviewable(filePath)) return true; } @@ -243,7 +242,7 @@ TransferListModel *TransferListWidget::getSourceModel() const return m_listModel; } -void TransferListWidget::previewFile(const QString &filePath) +void TransferListWidget::previewFile(const Path &filePath) { Utils::Gui::openPath(filePath); } @@ -336,9 +335,9 @@ void TransferListWidget::setSelectedTorrentsLocation() if (torrents.isEmpty()) return; - const QString oldLocation = torrents[0]->savePath(); + const Path oldLocation = torrents[0]->savePath(); - auto fileDialog = new QFileDialog(this, tr("Choose save path"), oldLocation); + auto fileDialog = new QFileDialog(this, tr("Choose save path"), oldLocation.data()); fileDialog->setAttribute(Qt::WA_DeleteOnClose); fileDialog->setFileMode(QFileDialog::Directory); fileDialog->setModal(true); @@ -349,8 +348,8 @@ void TransferListWidget::setSelectedTorrentsLocation() if (torrents.isEmpty()) return; - const QString newLocation = fileDialog->selectedFiles().constFirst(); - if (newLocation.isEmpty() || !QDir(newLocation).exists()) + const Path newLocation {fileDialog->selectedFiles().constFirst()}; + if (newLocation.exists()) return; // Actually move storage @@ -552,28 +551,28 @@ void TransferListWidget::hideQueuePosColumn(bool hide) void TransferListWidget::openSelectedTorrentsFolder() const { - QSet pathsList; + QSet paths; #ifdef Q_OS_MACOS // On macOS you expect both the files and folders to be opened in their parent // folders prehilighted for opening, so we use a custom method. for (BitTorrent::Torrent *const torrent : asConst(getSelectedTorrents())) { - const QString contentPath = QDir(torrent->actualStorageLocation()).absoluteFilePath(torrent->contentPath()); - pathsList.insert(contentPath); + const Path contentPath = torrent->actualStorageLocation() / torrent->contentPath(); + paths.insert(contentPath); } - MacUtils::openFiles(pathsList); + MacUtils::openFiles(PathList(paths.cbegin(), paths.cend())); #else for (BitTorrent::Torrent *const torrent : asConst(getSelectedTorrents())) { - const QString contentPath = torrent->contentPath(); - if (!pathsList.contains(contentPath)) + const Path contentPath = torrent->contentPath(); + if (!paths.contains(contentPath)) { if (torrent->filesCount() == 1) Utils::Gui::openFolderSelect(contentPath); else Utils::Gui::openPath(contentPath); } - pathsList.insert(contentPath); + paths.insert(contentPath); } #endif // Q_OS_MACOS } diff --git a/src/gui/transferlistwidget.h b/src/gui/transferlistwidget.h index 2bc6542ac..8ead6b5d5 100644 --- a/src/gui/transferlistwidget.h +++ b/src/gui/transferlistwidget.h @@ -34,6 +34,7 @@ #include #include "base/bittorrent/infohash.h" +#include "base/path.h" class MainWindow; class TransferListModel; @@ -97,7 +98,7 @@ public slots: void applyTagFilter(const QString &tag); void applyTrackerFilterAll(); void applyTrackerFilter(const QSet &torrentIDs); - void previewFile(const QString &filePath); + void previewFile(const Path &filePath); void renameSelectedTorrent(); signals: diff --git a/src/gui/uithememanager.cpp b/src/gui/uithememanager.cpp index bceef158d..295c92d97 100644 --- a/src/gui/uithememanager.cpp +++ b/src/gui/uithememanager.cpp @@ -38,42 +38,43 @@ #include #include "base/logger.h" +#include "base/path.h" #include "base/preferences.h" #include "base/utils/fs.h" namespace { - const QString CONFIG_FILE_NAME = QStringLiteral("config.json"); - const QString DEFAULT_ICONS_DIR = QStringLiteral(":icons/"); - const QString STYLESHEET_FILE_NAME = QStringLiteral("stylesheet.qss"); + const Path DEFAULT_ICONS_DIR {":icons"}; + const QString CONFIG_FILE_NAME {QStringLiteral("config.json")}; + const QString STYLESHEET_FILE_NAME {QStringLiteral("stylesheet.qss")}; // Directory used by stylesheet to reference internal resources // for example `icon: url(:/uitheme/file.svg)` will be expected to // point to a file `file.svg` in root directory of CONFIG_FILE_NAME - const QString STYLESHEET_RESOURCES_DIR = QStringLiteral(":/uitheme/"); + const QString STYLESHEET_RESOURCES_DIR {QStringLiteral(":/uitheme")}; - const QString THEME_ICONS_DIR = QStringLiteral("icons/"); + const Path THEME_ICONS_DIR {"icons"}; - QString findIcon(const QString &iconId, const QString &dir) + Path findIcon(const QString &iconId, const Path &dir) { - const QString pathSvg = dir + iconId + QLatin1String(".svg"); - if (QFile::exists(pathSvg)) + const Path pathSvg = dir / Path(iconId + QLatin1String(".svg")); + if (pathSvg.exists()) return pathSvg; - const QString pathPng = dir + iconId + QLatin1String(".png"); - if (QFile::exists(pathPng)) + const Path pathPng = dir / Path(iconId + QLatin1String(".png")); + if (pathPng.exists()) return pathPng; return {}; } - QByteArray readFile(const QString &fileName) + QByteArray readFile(const Path &filePath) { - QFile file {fileName}; + QFile file {filePath.data()}; if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { LogMsg(UIThemeManager::tr("UITheme - Failed to open \"%1\". Reason: %2") - .arg(QFileInfo(fileName).fileName(), file.errorString()) + .arg(filePath.filename(), file.errorString()) , Log::WARNING); return {}; } @@ -86,64 +87,62 @@ namespace public: QByteArray readStyleSheet() override { - return readFile(m_qrcThemeDir + STYLESHEET_FILE_NAME); + return readFile(m_qrcThemeDir / Path(STYLESHEET_FILE_NAME)); } QByteArray readConfig() override { - return readFile(m_qrcThemeDir + CONFIG_FILE_NAME); + return readFile(m_qrcThemeDir / Path(CONFIG_FILE_NAME)); } - QString iconPath(const QString &iconId) const override + Path iconPath(const QString &iconId) const override { return findIcon(iconId, m_qrcIconsDir); } private: - const QString m_qrcThemeDir {":/uitheme/"}; - const QString m_qrcIconsDir = m_qrcThemeDir + THEME_ICONS_DIR; + const Path m_qrcThemeDir {":/uitheme"}; + const Path m_qrcIconsDir = m_qrcThemeDir / THEME_ICONS_DIR; }; class FolderThemeSource final : public UIThemeSource { public: - explicit FolderThemeSource(const QDir &dir) - : m_folder {dir} - , m_iconsDir {m_folder.absolutePath() + '/' + THEME_ICONS_DIR} + explicit FolderThemeSource(const Path &folderPath) + : m_folder {folderPath} + , m_iconsDir {m_folder / THEME_ICONS_DIR} { } QByteArray readStyleSheet() override { - QByteArray styleSheetData = readFile(m_folder.absoluteFilePath(STYLESHEET_FILE_NAME)); - return styleSheetData.replace(STYLESHEET_RESOURCES_DIR.toUtf8(), (m_folder.absolutePath() + '/').toUtf8()); + QByteArray styleSheetData = readFile(m_folder / Path(STYLESHEET_FILE_NAME)); + return styleSheetData.replace(STYLESHEET_RESOURCES_DIR.toUtf8(), m_folder.data().toUtf8()); } QByteArray readConfig() override { - return readFile(m_folder.absoluteFilePath(CONFIG_FILE_NAME)); + return readFile(m_folder / Path(CONFIG_FILE_NAME)); } - QString iconPath(const QString &iconId) const override + Path iconPath(const QString &iconId) const override { return findIcon(iconId, m_iconsDir); } private: - const QDir m_folder; - const QString m_iconsDir; + const Path m_folder; + const Path m_iconsDir; }; - std::unique_ptr createUIThemeSource(const QString &themePath) + std::unique_ptr createUIThemeSource(const Path &themePath) { - const QFileInfo themeInfo {themePath}; + if (themePath.filename() == CONFIG_FILE_NAME) + return std::make_unique(themePath); - if (themeInfo.fileName() == CONFIG_FILE_NAME) - return std::make_unique(themeInfo.dir()); - - if ((themeInfo.suffix() == QLatin1String {"qbtheme"}) - && QResource::registerResource(themePath, QLatin1String {"/uitheme"})) + if ((themePath.extension() == QLatin1String(".qbtheme")) + && QResource::registerResource(themePath.data(), QLatin1String("/uitheme"))) { return std::make_unique(); } @@ -174,11 +173,11 @@ UIThemeManager::UIThemeManager() { if (m_useCustomTheme) { - const QString themePath = Preferences::instance()->customUIThemePath(); + const Path themePath = Preferences::instance()->customUIThemePath(); m_themeSource = createUIThemeSource(themePath); if (!m_themeSource) { - LogMsg(tr("Failed to load UI theme from file: \"%1\"").arg(themePath), Log::WARNING); + LogMsg(tr("Failed to load UI theme from file: \"%1\"").arg(themePath.toString()), Log::WARNING); } else { @@ -206,7 +205,7 @@ QIcon UIThemeManager::getIcon(const QString &iconId, const QString &fallback) co { QIcon icon = QIcon::fromTheme(iconId); if (icon.name() != iconId) - icon = QIcon::fromTheme(fallback, QIcon(getIconPathFromResources(iconId, fallback))); + icon = QIcon::fromTheme(fallback, QIcon(getIconPathFromResources(iconId, fallback).toString())); return icon; } #endif @@ -217,7 +216,7 @@ QIcon UIThemeManager::getIcon(const QString &iconId, const QString &fallback) co if (iter != m_iconCache.end()) return *iter; - const QIcon icon {getIconPathFromResources(iconId, fallback)}; + const QIcon icon {getIconPathFromResources(iconId, fallback).data()}; m_iconCache[iconId] = icon; return icon; } @@ -271,17 +270,17 @@ QIcon UIThemeManager::getSystrayIcon() const } #endif -QString UIThemeManager::getIconPath(const QString &iconId) const +Path UIThemeManager::getIconPath(const QString &iconId) const { #if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) if (m_useSystemTheme) { - QString path = Utils::Fs::tempPath() + iconId + QLatin1String(".png"); - if (!QFile::exists(path)) + Path path = Utils::Fs::tempPath() / Path(iconId + QLatin1String(".png")); + if (!path.exists()) { const QIcon icon = QIcon::fromTheme(iconId); if (!icon.isNull()) - icon.pixmap(32).save(path); + icon.pixmap(32).save(path.toString()); else path = getIconPathFromResources(iconId); } @@ -292,17 +291,17 @@ QString UIThemeManager::getIconPath(const QString &iconId) const return getIconPathFromResources(iconId, {}); } -QString UIThemeManager::getIconPathFromResources(const QString &iconId, const QString &fallback) const +Path UIThemeManager::getIconPathFromResources(const QString &iconId, const QString &fallback) const { if (m_useCustomTheme && m_themeSource) { - const QString customIcon = m_themeSource->iconPath(iconId); + const Path customIcon = m_themeSource->iconPath(iconId); if (!customIcon.isEmpty()) return customIcon; if (!fallback.isEmpty()) { - const QString fallbackIcon = m_themeSource->iconPath(fallback); + const Path fallbackIcon = m_themeSource->iconPath(fallback); if (!fallbackIcon.isEmpty()) return fallbackIcon; } diff --git a/src/gui/uithememanager.h b/src/gui/uithememanager.h index a50504748..41989783d 100644 --- a/src/gui/uithememanager.h +++ b/src/gui/uithememanager.h @@ -36,6 +36,8 @@ #include #include +#include "base/pathfwd.h" + class UIThemeSource { public: @@ -43,7 +45,7 @@ public: virtual QByteArray readStyleSheet() = 0; virtual QByteArray readConfig() = 0; - virtual QString iconPath(const QString &iconId) const = 0; + virtual Path iconPath(const QString &iconId) const = 0; }; class UIThemeManager : public QObject @@ -56,7 +58,7 @@ public: static void freeInstance(); static UIThemeManager *instance(); - QString getIconPath(const QString &iconId) const; + Path getIconPath(const QString &iconId) const; QIcon getIcon(const QString &iconId, const QString &fallback = {}) const; QIcon getFlagIcon(const QString &countryIsoCode) const; @@ -68,7 +70,7 @@ public: private: UIThemeManager(); // singleton class - QString getIconPathFromResources(const QString &iconId, const QString &fallback = {}) const; + Path getIconPathFromResources(const QString &iconId, const QString &fallback = {}) const; void loadColorsFromJSONConfig(); void applyPalette() const; void applyStyleSheet() const; diff --git a/src/gui/utils.cpp b/src/gui/utils.cpp index c29c165ee..979b80aeb 100644 --- a/src/gui/utils.cpp +++ b/src/gui/utils.cpp @@ -48,6 +48,7 @@ #include #include +#include "base/path.h" #include "base/utils/fs.h" #include "base/utils/version.h" @@ -72,23 +73,23 @@ QPixmap Utils::Gui::scaledPixmap(const QIcon &icon, const QWidget *widget, const return icon.pixmap(scaledHeight); } -QPixmap Utils::Gui::scaledPixmap(const QString &path, const QWidget *widget, const int height) +QPixmap Utils::Gui::scaledPixmap(const Path &path, const QWidget *widget, const int height) { - const QPixmap pixmap(path); + const QPixmap pixmap {path.data()}; const int scaledHeight = ((height > 0) ? height : pixmap.height()) * Utils::Gui::screenScalingFactor(widget); return pixmap.scaledToHeight(scaledHeight, Qt::SmoothTransformation); } -QPixmap Utils::Gui::scaledPixmapSvg(const QString &path, const QWidget *widget, const int baseHeight) +QPixmap Utils::Gui::scaledPixmapSvg(const Path &path, const QWidget *widget, const int baseHeight) { const int scaledHeight = baseHeight * Utils::Gui::screenScalingFactor(widget); - const QString normalizedKey = path + '@' + QString::number(scaledHeight); + const QString normalizedKey = path.data() + '@' + QString::number(scaledHeight); QPixmap pm; QPixmapCache cache; if (!cache.find(normalizedKey, &pm)) { - pm = QIcon(path).pixmap(scaledHeight); + pm = QIcon(path.data()).pixmap(scaledHeight); cache.insert(normalizedKey, pm); } return pm; @@ -143,32 +144,29 @@ QPoint Utils::Gui::screenCenter(const QWidget *w) } // Open the given path with an appropriate application -void Utils::Gui::openPath(const QString &absolutePath) +void Utils::Gui::openPath(const Path &path) { - const QString path = Utils::Fs::toUniformPath(absolutePath); // Hack to access samba shares with QDesktopServices::openUrl - if (path.startsWith("//")) - QDesktopServices::openUrl(Utils::Fs::toNativePath("file:" + path)); + if (path.data().startsWith("//")) + QDesktopServices::openUrl(QUrl(QString::fromLatin1("file:") + path.toString())); else - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); + QDesktopServices::openUrl(QUrl::fromLocalFile(path.data())); } // Open the parent directory of the given path with a file manager and select // (if possible) the item at the given path -void Utils::Gui::openFolderSelect(const QString &absolutePath) +void Utils::Gui::openFolderSelect(const Path &path) { - QString path {Utils::Fs::toUniformPath(absolutePath)}; - const QFileInfo pathInfo {path}; // If the item to select doesn't exist, try to open its parent - if (!pathInfo.exists(path)) + if (!path.exists()) { - openPath(path.left(path.lastIndexOf('/'))); + openPath(path.parentPath()); return; } #ifdef Q_OS_WIN HRESULT hresult = ::CoInitializeEx(nullptr, COINIT_MULTITHREADED); - PIDLIST_ABSOLUTE pidl = ::ILCreateFromPathW(reinterpret_cast(Utils::Fs::toNativePath(path).utf16())); + PIDLIST_ABSOLUTE pidl = ::ILCreateFromPathW(reinterpret_cast(path.toString().utf16())); if (pidl) { ::SHOpenFolderAndSelectItems(pidl, 0, nullptr, 0); @@ -183,38 +181,34 @@ void Utils::Gui::openFolderSelect(const QString &absolutePath) const QString output = proc.readLine().simplified(); if ((output == "dolphin.desktop") || (output == "org.kde.dolphin.desktop")) { - proc.startDetached("dolphin", {"--select", Utils::Fs::toNativePath(path)}); + proc.startDetached("dolphin", {"--select", path.toString()}); } else if ((output == "nautilus.desktop") || (output == "org.gnome.Nautilus.desktop") || (output == "nautilus-folder-handler.desktop")) - { - if (pathInfo.isDir()) - path = path.left(path.lastIndexOf('/')); + { proc.start("nautilus", {"--version"}); proc.waitForFinished(); const QString nautilusVerStr = QString(proc.readLine()).remove(QRegularExpression("[^0-9.]")); using NautilusVersion = Utils::Version; if (NautilusVersion::tryParse(nautilusVerStr, {1, 0, 0}) > NautilusVersion {3, 28}) - proc.startDetached("nautilus", {Utils::Fs::toNativePath(path)}); + proc.startDetached("nautilus", {(Fs::isDir(path) ? path.parentPath() : path).toString()}); else - proc.startDetached("nautilus", {"--no-desktop", Utils::Fs::toNativePath(path)}); + proc.startDetached("nautilus", {"--no-desktop", (Fs::isDir(path) ? path.parentPath() : path).toString()}); } else if (output == "nemo.desktop") { - if (pathInfo.isDir()) - path = path.left(path.lastIndexOf('/')); - proc.startDetached("nemo", {"--no-desktop", Utils::Fs::toNativePath(path)}); + proc.startDetached("nemo", {"--no-desktop", (Fs::isDir(path) ? path.parentPath() : path).toString()}); } else if ((output == "konqueror.desktop") || (output == "kfmclient_dir.desktop")) { - proc.startDetached("konqueror", {"--select", Utils::Fs::toNativePath(path)}); + proc.startDetached("konqueror", {"--select", path.toString()}); } else { // "caja" manager can't pinpoint the file, see: https://github.com/qbittorrent/qBittorrent/issues/5003 - openPath(path.left(path.lastIndexOf('/'))); + openPath(path.parentPath()); } #else - openPath(path.left(path.lastIndexOf('/'))); + openPath(path.parentPath()); #endif } diff --git a/src/gui/utils.h b/src/gui/utils.h index d7ec79785..472627817 100644 --- a/src/gui/utils.h +++ b/src/gui/utils.h @@ -30,34 +30,33 @@ #include +#include "base/pathfwd.h" + class QIcon; class QPixmap; class QPoint; class QWidget; -namespace Utils +namespace Utils::Gui { - namespace Gui + void resize(QWidget *widget, const QSize &newSize = {}); + qreal screenScalingFactor(const QWidget *widget); + + template + T scaledSize(const QWidget *widget, const T &size) { - void resize(QWidget *widget, const QSize &newSize = {}); - qreal screenScalingFactor(const QWidget *widget); - - template - T scaledSize(const QWidget *widget, const T &size) - { - return (size * screenScalingFactor(widget)); - } - - QPixmap scaledPixmap(const QIcon &icon, const QWidget *widget, int height); - QPixmap scaledPixmap(const QString &path, const QWidget *widget, int height = 0); - QPixmap scaledPixmapSvg(const QString &path, const QWidget *widget, int baseHeight); - QSize smallIconSize(const QWidget *widget = nullptr); - QSize mediumIconSize(const QWidget *widget = nullptr); - QSize largeIconSize(const QWidget *widget = nullptr); - - QPoint screenCenter(const QWidget *w); - - void openPath(const QString &absolutePath); - void openFolderSelect(const QString &absolutePath); + return (size * screenScalingFactor(widget)); } + + QPixmap scaledPixmap(const QIcon &icon, const QWidget *widget, int height); + QPixmap scaledPixmap(const Path &path, const QWidget *widget, int height = 0); + QPixmap scaledPixmapSvg(const Path &path, const QWidget *widget, int baseHeight); + QSize smallIconSize(const QWidget *widget = nullptr); + QSize mediumIconSize(const QWidget *widget = nullptr); + QSize largeIconSize(const QWidget *widget = nullptr); + + QPoint screenCenter(const QWidget *w); + + void openPath(const Path &path); + void openFolderSelect(const Path &path); } diff --git a/src/gui/watchedfolderoptionsdialog.cpp b/src/gui/watchedfolderoptionsdialog.cpp index 33612129b..6949104b6 100644 --- a/src/gui/watchedfolderoptionsdialog.cpp +++ b/src/gui/watchedfolderoptionsdialog.cpp @@ -138,13 +138,13 @@ void WatchedFolderOptionsDialog::onCategoryChanged(const int index) const auto *btSession = BitTorrent::Session::instance(); const QString categoryName = m_ui->categoryComboBox->currentText(); - const QString savePath = btSession->categorySavePath(categoryName); - m_ui->savePath->setSelectedPath(Utils::Fs::toNativePath(savePath)); + const Path savePath = btSession->categorySavePath(categoryName); + m_ui->savePath->setSelectedPath(savePath); - const QString finishedSavePath = btSession->categoryDownloadPath(categoryName); - m_ui->downloadPath->setSelectedPath(Utils::Fs::toNativePath(finishedSavePath)); + const Path downloadPath = btSession->categoryDownloadPath(categoryName); + m_ui->downloadPath->setSelectedPath(downloadPath); - m_ui->groupBoxDownloadPath->setChecked(!finishedSavePath.isEmpty()); + m_ui->groupBoxDownloadPath->setChecked(!downloadPath.isEmpty()); } } @@ -152,11 +152,11 @@ void WatchedFolderOptionsDialog::populateSavePaths() { const auto *btSession = BitTorrent::Session::instance(); - const QString defaultSavePath {btSession->savePath()}; + const Path defaultSavePath {btSession->savePath()}; m_ui->savePath->setSelectedPath(!m_savePath.isEmpty() ? m_savePath : defaultSavePath); - const QString defaultFinishedSavePath {btSession->downloadPath()}; - m_ui->downloadPath->setSelectedPath(!m_downloadPath.isEmpty() ? m_downloadPath : defaultFinishedSavePath); + const Path defaultDownloadPath {btSession->downloadPath()}; + m_ui->downloadPath->setSelectedPath(!m_downloadPath.isEmpty() ? m_downloadPath : defaultDownloadPath); m_ui->groupBoxDownloadPath->setChecked(m_useDownloadPath); } @@ -178,15 +178,15 @@ void WatchedFolderOptionsDialog::onTMMChanged(const int index) m_ui->savePath->blockSignals(true); m_savePath = m_ui->savePath->selectedPath(); - const QString savePath = btSession->categorySavePath(m_ui->categoryComboBox->currentText()); + const Path savePath = btSession->categorySavePath(m_ui->categoryComboBox->currentText()); m_ui->savePath->setSelectedPath(savePath); m_ui->downloadPath->blockSignals(true); m_downloadPath = m_ui->downloadPath->selectedPath(); - const QString finishedSavePath = btSession->categoryDownloadPath(m_ui->categoryComboBox->currentText()); - m_ui->downloadPath->setSelectedPath(finishedSavePath); + const Path downloadPath = btSession->categoryDownloadPath(m_ui->categoryComboBox->currentText()); + m_ui->downloadPath->setSelectedPath(downloadPath); m_useDownloadPath = m_ui->groupBoxDownloadPath->isChecked(); - m_ui->groupBoxDownloadPath->setChecked(!finishedSavePath.isEmpty()); + m_ui->groupBoxDownloadPath->setChecked(!downloadPath.isEmpty()); } } diff --git a/src/gui/watchedfolderoptionsdialog.h b/src/gui/watchedfolderoptionsdialog.h index 19af8df04..4dacc8d6b 100644 --- a/src/gui/watchedfolderoptionsdialog.h +++ b/src/gui/watchedfolderoptionsdialog.h @@ -30,6 +30,7 @@ #include +#include "base/path.h" #include "base/settingvalue.h" #include "base/torrentfileswatcher.h" @@ -57,8 +58,8 @@ private: void onCategoryChanged(int index); Ui::WatchedFolderOptionsDialog *m_ui; - QString m_savePath; - QString m_downloadPath; + Path m_savePath; + Path m_downloadPath; bool m_useDownloadPath = false; SettingValue m_storeDialogSize; }; diff --git a/src/gui/watchedfoldersmodel.cpp b/src/gui/watchedfoldersmodel.cpp index 0a3a5dd5f..ebd867f58 100644 --- a/src/gui/watchedfoldersmodel.cpp +++ b/src/gui/watchedfoldersmodel.cpp @@ -61,7 +61,7 @@ QVariant WatchedFoldersModel::data(const QModelIndex &index, const int role) con return {}; if (role == Qt::DisplayRole) - return Utils::Fs::toNativePath(m_watchedFolders.at(index.row())); + return m_watchedFolders.at(index.row()).toString(); return {}; } @@ -91,7 +91,7 @@ bool WatchedFoldersModel::removeRows(const int row, const int count, const QMode beginRemoveRows(parent, firstRow, lastRow); for (int i = firstRow; i <= lastRow; ++i) { - const QString folderPath = m_watchedFolders.takeAt(i); + const Path folderPath = m_watchedFolders.takeAt(i); m_watchedFoldersOptions.remove(folderPath); m_deletedFolders.insert(folderPath); } @@ -100,23 +100,28 @@ bool WatchedFoldersModel::removeRows(const int row, const int count, const QMode return true; } -void WatchedFoldersModel::addFolder(const QString &path, const TorrentFilesWatcher::WatchedFolderOptions &options) +void WatchedFoldersModel::addFolder(const Path &path, const TorrentFilesWatcher::WatchedFolderOptions &options) { - const QString cleanWatchPath = m_fsWatcher->makeCleanPath(path); - if (m_watchedFoldersOptions.contains(cleanWatchPath)) - throw RuntimeError(tr("Folder '%1' is already in watch list.").arg(path)); + if (path.isEmpty()) + throw InvalidArgument(tr("Watched folder path cannot be empty.")); - const QDir watchDir {cleanWatchPath}; + if (path.isRelative()) + throw InvalidArgument(tr("Watched folder path cannot be relative.")); + + if (m_watchedFoldersOptions.contains(path)) + throw RuntimeError(tr("Folder '%1' is already in watch list.").arg(path.toString())); + + const QDir watchDir {path.data()}; if (!watchDir.exists()) - throw RuntimeError(tr("Folder '%1' doesn't exist.").arg(path)); + throw RuntimeError(tr("Folder '%1' doesn't exist.").arg(path.toString())); if (!watchDir.isReadable()) - throw RuntimeError(tr("Folder '%1' isn't readable.").arg(path)); + throw RuntimeError(tr("Folder '%1' isn't readable.").arg(path.toString())); - m_deletedFolders.remove(cleanWatchPath); + m_deletedFolders.remove(path); beginInsertRows(QModelIndex(), rowCount(), rowCount()); - m_watchedFolders.append(cleanWatchPath); - m_watchedFoldersOptions[cleanWatchPath] = options; + m_watchedFolders.append(path); + m_watchedFoldersOptions[path] = options; endInsertRows(); } @@ -124,7 +129,7 @@ TorrentFilesWatcher::WatchedFolderOptions WatchedFoldersModel::folderOptions(con { Q_ASSERT((row >= 0) && (row < rowCount())); - const QString folderPath = m_watchedFolders.at(row); + const Path folderPath = m_watchedFolders.at(row); return m_watchedFoldersOptions[folderPath]; } @@ -132,24 +137,24 @@ void WatchedFoldersModel::setFolderOptions(const int row, const TorrentFilesWatc { Q_ASSERT((row >= 0) && (row < rowCount())); - const QString folderPath = m_watchedFolders.at(row); + const Path folderPath = m_watchedFolders.at(row); m_watchedFoldersOptions[folderPath] = options; } void WatchedFoldersModel::apply() { - const QSet deletedFolders {m_deletedFolders}; + const QSet deletedFolders {m_deletedFolders}; // We have to clear `m_deletedFolders` for optimization reason, otherwise // it will be cleared one element at a time in `onFolderRemoved()` handler m_deletedFolders.clear(); - for (const QString &path : deletedFolders) + for (const Path &path : deletedFolders) m_fsWatcher->removeWatchedFolder(path); - for (const QString &path : asConst(m_watchedFolders)) + for (const Path &path : asConst(m_watchedFolders)) m_fsWatcher->setWatchedFolder(path, m_watchedFoldersOptions.value(path)); } -void WatchedFoldersModel::onFolderSet(const QString &path, const TorrentFilesWatcher::WatchedFolderOptions &options) +void WatchedFoldersModel::onFolderSet(const Path &path, const TorrentFilesWatcher::WatchedFolderOptions &options) { if (!m_watchedFoldersOptions.contains(path)) { @@ -166,7 +171,7 @@ void WatchedFoldersModel::onFolderSet(const QString &path, const TorrentFilesWat } } -void WatchedFoldersModel::onFolderRemoved(const QString &path) +void WatchedFoldersModel::onFolderRemoved(const Path &path) { const int row = m_watchedFolders.indexOf(path); if (row >= 0) diff --git a/src/gui/watchedfoldersmodel.h b/src/gui/watchedfoldersmodel.h index dbca62135..368fbd8cf 100644 --- a/src/gui/watchedfoldersmodel.h +++ b/src/gui/watchedfoldersmodel.h @@ -34,6 +34,7 @@ #include #include +#include "base/path.h" #include "base/torrentfileswatcher.h" class WatchedFoldersModel final : public QAbstractListModel @@ -50,7 +51,7 @@ public: QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; - void addFolder(const QString &path, const TorrentFilesWatcher::WatchedFolderOptions &options); + void addFolder(const Path &path, const TorrentFilesWatcher::WatchedFolderOptions &options); TorrentFilesWatcher::WatchedFolderOptions folderOptions(int row) const; void setFolderOptions(int row, const TorrentFilesWatcher::WatchedFolderOptions &options); @@ -58,11 +59,11 @@ public: void apply(); private: - void onFolderSet(const QString &path, const TorrentFilesWatcher::WatchedFolderOptions &options); - void onFolderRemoved(const QString &path); + void onFolderSet(const Path &path, const TorrentFilesWatcher::WatchedFolderOptions &options); + void onFolderRemoved(const Path &path); TorrentFilesWatcher *m_fsWatcher; - QStringList m_watchedFolders; - QHash m_watchedFoldersOptions; - QSet m_deletedFolders; + PathList m_watchedFolders; + QHash m_watchedFoldersOptions; + QSet m_deletedFolders; }; diff --git a/src/webui/api/appcontroller.cpp b/src/webui/api/appcontroller.cpp index 726573af5..db1d999df 100644 --- a/src/webui/api/appcontroller.cpp +++ b/src/webui/api/appcontroller.cpp @@ -47,6 +47,7 @@ #include "base/global.h" #include "base/net/portforwarder.h" #include "base/net/proxyconfigurationmanager.h" +#include "base/path.h" #include "base/preferences.h" #include "base/rss/rss_autodownloader.h" #include "base/rss/rss_session.h" @@ -112,28 +113,28 @@ void AppController::preferencesAction() data["torrent_changed_tmm_enabled"] = !session->isDisableAutoTMMWhenCategoryChanged(); data["save_path_changed_tmm_enabled"] = !session->isDisableAutoTMMWhenDefaultSavePathChanged(); data["category_changed_tmm_enabled"] = !session->isDisableAutoTMMWhenCategorySavePathChanged(); - data["save_path"] = Utils::Fs::toNativePath(session->savePath()); + data["save_path"] = session->savePath().toString(); data["temp_path_enabled"] = session->isDownloadPathEnabled(); - data["temp_path"] = Utils::Fs::toNativePath(session->downloadPath()); + data["temp_path"] = session->downloadPath().toString(); data["use_category_paths_in_manual_mode"] = session->useCategoryPathsInManualMode(); - data["export_dir"] = Utils::Fs::toNativePath(session->torrentExportDirectory()); - data["export_dir_fin"] = Utils::Fs::toNativePath(session->finishedTorrentExportDirectory()); + data["export_dir"] = session->torrentExportDirectory().toString(); + data["export_dir_fin"] = session->finishedTorrentExportDirectory().toString(); // TODO: The following code is deprecated. Delete it once replaced by updated API method. // === BEGIN DEPRECATED CODE === // TorrentFilesWatcher *fsWatcher = TorrentFilesWatcher::instance(); - const QHash watchedFolders = fsWatcher->folders(); + const QHash watchedFolders = fsWatcher->folders(); QJsonObject nativeDirs; for (auto i = watchedFolders.cbegin(); i != watchedFolders.cend(); ++i) { - const QString watchedFolder = i.key(); + const Path watchedFolder = i.key(); const BitTorrent::AddTorrentParams params = i.value().addTorrentParams; if (params.savePath.isEmpty()) - nativeDirs.insert(Utils::Fs::toNativePath(watchedFolder), 1); + nativeDirs.insert(watchedFolder.toString(), 1); else if (params.savePath == watchedFolder) - nativeDirs.insert(Utils::Fs::toNativePath(watchedFolder), 0); + nativeDirs.insert(watchedFolder.toString(), 0); else - nativeDirs.insert(Utils::Fs::toNativePath(watchedFolder), Utils::Fs::toNativePath(params.savePath)); + nativeDirs.insert(watchedFolder.toString(), params.savePath.toString()); } data["scan_dirs"] = nativeDirs; // === END DEPRECATED CODE === // @@ -149,7 +150,7 @@ void AppController::preferencesAction() data["mail_notification_password"] = pref->getMailNotificationSMTPPassword(); // Run an external program on torrent completion data["autorun_enabled"] = pref->isAutoRunEnabled(); - data["autorun_program"] = Utils::Fs::toNativePath(pref->getAutoRunProgram()); + data["autorun_program"] = pref->getAutoRunProgram(); // Connection // Listening Port @@ -177,7 +178,7 @@ void AppController::preferencesAction() // IP Filtering data["ip_filter_enabled"] = session->isIPFilteringEnabled(); - data["ip_filter_path"] = Utils::Fs::toNativePath(session->IPFilterFile()); + data["ip_filter_path"] = session->IPFilterFile().toString(); data["ip_filter_trackers"] = session->isTrackerFilteringEnabled(); data["banned_IPs"] = session->bannedIPs().join('\n'); @@ -236,8 +237,8 @@ void AppController::preferencesAction() data["web_ui_port"] = pref->getWebUiPort(); data["web_ui_upnp"] = pref->useUPnPForWebUIPort(); data["use_https"] = pref->isWebUiHttpsEnabled(); - data["web_ui_https_cert_path"] = pref->getWebUIHttpsCertificatePath(); - data["web_ui_https_key_path"] = pref->getWebUIHttpsKeyPath(); + data["web_ui_https_cert_path"] = pref->getWebUIHttpsCertificatePath().toString(); + data["web_ui_https_key_path"] = pref->getWebUIHttpsKeyPath().toString(); // Authentication data["web_ui_username"] = pref->getWebUiUsername(); data["bypass_local_auth"] = !pref->isWebUiLocalAuthEnabled(); @@ -251,7 +252,7 @@ void AppController::preferencesAction() data["web_ui_session_timeout"] = pref->getWebUISessionTimeout(); // Use alternative Web UI data["alternative_webui_enabled"] = pref->isAltWebUiEnabled(); - data["alternative_webui_path"] = pref->getWebUiRootFolder(); + data["alternative_webui_path"] = pref->getWebUiRootFolder().toString(); // Security data["web_ui_clickjacking_protection_enabled"] = pref->isWebUiClickjackingProtectionEnabled(); data["web_ui_csrf_protection_enabled"] = pref->isWebUiCSRFProtectionEnabled(); @@ -400,31 +401,31 @@ void AppController::setPreferencesAction() if (hasKey("category_changed_tmm_enabled")) session->setDisableAutoTMMWhenCategorySavePathChanged(!it.value().toBool()); if (hasKey("save_path")) - session->setSavePath(it.value().toString()); + session->setSavePath(Path(it.value().toString())); if (hasKey("temp_path_enabled")) session->setDownloadPathEnabled(it.value().toBool()); if (hasKey("temp_path")) - session->setDownloadPath(it.value().toString()); + session->setDownloadPath(Path(it.value().toString())); if (hasKey("use_category_paths_in_manual_mode")) session->setUseCategoryPathsInManualMode(it.value().toBool()); if (hasKey("export_dir")) - session->setTorrentExportDirectory(it.value().toString()); + session->setTorrentExportDirectory(Path(it.value().toString())); if (hasKey("export_dir_fin")) - session->setFinishedTorrentExportDirectory(it.value().toString()); + session->setFinishedTorrentExportDirectory(Path(it.value().toString())); // TODO: The following code is deprecated. Delete it once replaced by updated API method. // === BEGIN DEPRECATED CODE === // if (hasKey("scan_dirs")) { - QStringList scanDirs; + PathList scanDirs; TorrentFilesWatcher *fsWatcher = TorrentFilesWatcher::instance(); - const QStringList oldScanDirs = fsWatcher->folders().keys(); + const PathList oldScanDirs = fsWatcher->folders().keys(); const QVariantHash nativeDirs = it.value().toHash(); for (auto i = nativeDirs.cbegin(); i != nativeDirs.cend(); ++i) { try { - const QString watchedFolder = TorrentFilesWatcher::makeCleanPath(i.key()); + const Path watchedFolder {i.key()}; TorrentFilesWatcher::WatchedFolderOptions options = fsWatcher->folders().value(watchedFolder); BitTorrent::AddTorrentParams ¶ms = options.addTorrentParams; @@ -440,7 +441,7 @@ void AppController::setPreferencesAction() } else { - const QString customSavePath = i.value().toString(); + const Path customSavePath {i.value().toString()}; params.savePath = customSavePath; params.useAutoTMM = false; } @@ -454,7 +455,7 @@ void AppController::setPreferencesAction() } // Update deleted folders - for (const QString &path : oldScanDirs) + for (const Path &path : oldScanDirs) { if (!scanDirs.contains(path)) fsWatcher->removeWatchedFolder(path); @@ -531,7 +532,7 @@ void AppController::setPreferencesAction() if (hasKey("ip_filter_enabled")) session->setIPFilteringEnabled(it.value().toBool()); if (hasKey("ip_filter_path")) - session->setIPFilterFile(it.value().toString()); + session->setIPFilterFile(Path(it.value().toString())); if (hasKey("ip_filter_trackers")) session->setTrackerFilteringEnabled(it.value().toBool()); if (hasKey("banned_IPs")) @@ -650,9 +651,9 @@ void AppController::setPreferencesAction() if (hasKey("use_https")) pref->setWebUiHttpsEnabled(it.value().toBool()); if (hasKey("web_ui_https_cert_path")) - pref->setWebUIHttpsCertificatePath(it.value().toString()); + pref->setWebUIHttpsCertificatePath(Path(it.value().toString())); if (hasKey("web_ui_https_key_path")) - pref->setWebUIHttpsKeyPath(it.value().toString()); + pref->setWebUIHttpsKeyPath(Path(it.value().toString())); // Authentication if (hasKey("web_ui_username")) pref->setWebUiUsername(it.value().toString()); @@ -677,7 +678,7 @@ void AppController::setPreferencesAction() if (hasKey("alternative_webui_enabled")) pref->setAltWebUiEnabled(it.value().toBool()); if (hasKey("alternative_webui_path")) - pref->setWebUiRootFolder(it.value().toString()); + pref->setWebUiRootFolder(Path(it.value().toString())); // Security if (hasKey("web_ui_clickjacking_protection_enabled")) pref->setWebUiClickjackingProtectionEnabled(it.value().toBool()); @@ -869,7 +870,7 @@ void AppController::setPreferencesAction() void AppController::defaultSavePathAction() { - setResult(BitTorrent::Session::instance()->savePath()); + setResult(BitTorrent::Session::instance()->savePath().toString()); } void AppController::networkInterfaceListAction() diff --git a/src/webui/api/serialize/serialize_torrent.cpp b/src/webui/api/serialize/serialize_torrent.cpp index 204f3d3a2..0a1a630b7 100644 --- a/src/webui/api/serialize/serialize_torrent.cpp +++ b/src/webui/api/serialize/serialize_torrent.cpp @@ -34,6 +34,7 @@ #include "base/bittorrent/infohash.h" #include "base/bittorrent/torrent.h" #include "base/bittorrent/trackerentry.h" +#include "base/path.h" #include "base/tagset.h" #include "base/utils/fs.h" @@ -130,9 +131,9 @@ QVariantMap serialize(const BitTorrent::Torrent &torrent) {KEY_TORRENT_TAGS, torrent.tags().join(QLatin1String(", "))}, {KEY_TORRENT_SUPER_SEEDING, torrent.superSeeding()}, {KEY_TORRENT_FORCE_START, torrent.isForced()}, - {KEY_TORRENT_SAVE_PATH, Utils::Fs::toNativePath(torrent.savePath())}, - {KEY_TORRENT_DOWNLOAD_PATH, Utils::Fs::toNativePath(torrent.downloadPath())}, - {KEY_TORRENT_CONTENT_PATH, Utils::Fs::toNativePath(torrent.contentPath())}, + {KEY_TORRENT_SAVE_PATH, torrent.savePath().toString()}, + {KEY_TORRENT_DOWNLOAD_PATH, torrent.downloadPath().toString()}, + {KEY_TORRENT_CONTENT_PATH, torrent.contentPath().toString()}, {KEY_TORRENT_ADDED_ON, torrent.addedTime().toSecsSinceEpoch()}, {KEY_TORRENT_COMPLETION_ON, torrent.completedTime().toSecsSinceEpoch()}, {KEY_TORRENT_TRACKER, torrent.currentTracker()}, diff --git a/src/webui/api/synccontroller.cpp b/src/webui/api/synccontroller.cpp index 9171c2b41..480d15f9a 100644 --- a/src/webui/api/synccontroller.cpp +++ b/src/webui/api/synccontroller.cpp @@ -579,7 +579,14 @@ void SyncController::torrentPeersAction() }; if (torrent->hasMetadata()) - peer.insert(KEY_PEER_FILES, torrent->info().filesForPiece(pi.downloadingPieceIndex()).join('\n')); + { + const PathList filePaths = torrent->info().filesForPiece(pi.downloadingPieceIndex()); + QStringList filesForPiece; + filesForPiece.reserve(filePaths.size()); + for (const Path &filePath : filePaths) + filesForPiece.append(filePath.toString()); + peer.insert(KEY_PEER_FILES, filesForPiece.join(QLatin1Char('\n'))); + } if (resolvePeerCountries) { diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index b9e772332..bc3df88ab 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -31,7 +31,6 @@ #include #include -#include #include #include #include @@ -431,8 +430,8 @@ void TorrentsController::propertiesAction() dataDict[KEY_PROP_COMPLETION_DATE] = -1; dataDict[KEY_PROP_CREATION_DATE] = -1; } - dataDict[KEY_PROP_SAVE_PATH] = Utils::Fs::toNativePath(torrent->savePath()); - dataDict[KEY_PROP_DOWNLOAD_PATH] = Utils::Fs::toNativePath(torrent->downloadPath()); + dataDict[KEY_PROP_SAVE_PATH] = torrent->savePath().toString(); + dataDict[KEY_PROP_DOWNLOAD_PATH] = torrent->downloadPath().toString(); dataDict[KEY_PROP_COMMENT] = torrent->comment(); setResult(dataDict); @@ -565,7 +564,7 @@ void TorrentsController::filesAction() {KEY_FILE_PRIORITY, static_cast(priorities[index])}, {KEY_FILE_SIZE, torrent->fileSize(index)}, {KEY_FILE_AVAILABILITY, fileAvailability[index]}, - {KEY_FILE_NAME, Utils::Fs::toUniformPath(torrent->filePath(index))} + {KEY_FILE_NAME, torrent->filePath(index).toString()} }; const BitTorrent::TorrentInfo::PieceRange idx = info.filePieces(index); @@ -682,8 +681,8 @@ void TorrentsController::addAction() addTorrentParams.firstLastPiecePriority = firstLastPiece; addTorrentParams.addPaused = addPaused; addTorrentParams.contentLayout = contentLayout; - addTorrentParams.savePath = savepath; - addTorrentParams.downloadPath = downloadPath; + addTorrentParams.savePath = Path(savepath); + addTorrentParams.downloadPath = Path(downloadPath); addTorrentParams.useDownloadPath = useDownloadPath; addTorrentParams.category = category; addTorrentParams.tags.insert(tags.cbegin(), tags.cend()); @@ -1081,25 +1080,25 @@ void TorrentsController::setLocationAction() requireParams({"hashes", "location"}); const QStringList hashes {params()["hashes"].split('|')}; - const QString newLocation {params()["location"].trimmed()}; + const Path newLocation {params()["location"].trimmed()}; if (newLocation.isEmpty()) throw APIError(APIErrorType::BadParams, tr("Save path cannot be empty")); // try to create the location if it does not exist - if (!QDir(newLocation).mkpath(".")) + if (!Utils::Fs::mkpath(newLocation)) throw APIError(APIErrorType::Conflict, tr("Cannot make save path")); // check permissions - if (!QFileInfo(newLocation).isWritable()) + if (!Utils::Fs::isWritable(newLocation)) throw APIError(APIErrorType::AccessDenied, tr("Cannot write to directory")); applyToTorrents(hashes, [newLocation](BitTorrent::Torrent *const torrent) { LogMsg(tr("WebUI Set location: moving \"%1\", from \"%2\" to \"%3\"") - .arg(torrent->name(), Utils::Fs::toNativePath(torrent->savePath()), Utils::Fs::toNativePath(newLocation))); + .arg(torrent->name(), torrent->savePath().toString(), newLocation.toString())); torrent->setAutoTMMEnabled(false); - torrent->setSavePath(Utils::Fs::expandPathAbs(newLocation)); + torrent->setSavePath(newLocation); }); } @@ -1108,17 +1107,17 @@ void TorrentsController::setSavePathAction() requireParams({"id", "path"}); const QStringList ids {params()["id"].split('|')}; - const QString newPath {params()["path"]}; + const Path newPath {params()["path"]}; if (newPath.isEmpty()) throw APIError(APIErrorType::BadParams, tr("Save path cannot be empty")); // try to create the directory if it does not exist - if (!QDir(newPath).mkpath(".")) + if (!Utils::Fs::mkpath(newPath)) throw APIError(APIErrorType::Conflict, tr("Cannot create target directory")); // check permissions - if (!QFileInfo(newPath).isWritable()) + if (!Utils::Fs::isWritable(newPath)) throw APIError(APIErrorType::AccessDenied, tr("Cannot write to directory")); applyToTorrents(ids, [&newPath](BitTorrent::Torrent *const torrent) @@ -1133,16 +1132,16 @@ void TorrentsController::setDownloadPathAction() requireParams({"id", "path"}); const QStringList ids {params()["id"].split('|')}; - const QString newPath {params()["path"]}; + const Path newPath {params()["path"]}; if (!newPath.isEmpty()) { // try to create the directory if it does not exist - if (!QDir(newPath).mkpath(".")) + if (!Utils::Fs::mkpath(newPath)) throw APIError(APIErrorType::Conflict, tr("Cannot create target directory")); // check permissions - if (!QFileInfo(newPath).isWritable()) + if (!Utils::Fs::isWritable(newPath)) throw APIError(APIErrorType::AccessDenied, tr("Cannot write to directory")); } @@ -1225,13 +1224,13 @@ void TorrentsController::createCategoryAction() if (!BitTorrent::Session::isValidCategoryName(category)) throw APIError(APIErrorType::Conflict, tr("Incorrect category name")); - const QString savePath = params()["savePath"]; + const Path savePath {params()["savePath"]}; const auto useDownloadPath = parseBool(params()["downloadPathEnabled"]); BitTorrent::CategoryOptions categoryOptions; categoryOptions.savePath = savePath; if (useDownloadPath.has_value()) { - const QString downloadPath = params()["downloadPath"]; + const Path downloadPath {params()["downloadPath"]}; categoryOptions.downloadPath = {useDownloadPath.value(), downloadPath}; } @@ -1247,13 +1246,13 @@ void TorrentsController::editCategoryAction() if (category.isEmpty()) throw APIError(APIErrorType::BadParams, tr("Category cannot be empty")); - const QString savePath = params()["savePath"]; + const Path savePath {params()["savePath"]}; const auto useDownloadPath = parseBool(params()["downloadPathEnabled"]); BitTorrent::CategoryOptions categoryOptions; categoryOptions.savePath = savePath; if (useDownloadPath.has_value()) { - const QString downloadPath = params()["downloadPath"]; + const Path downloadPath {params()["downloadPath"]}; categoryOptions.downloadPath = {useDownloadPath.value(), downloadPath}; } @@ -1367,8 +1366,8 @@ void TorrentsController::renameFileAction() if (!torrent) throw APIError(APIErrorType::NotFound); - const QString oldPath = params()["oldPath"]; - const QString newPath = params()["newPath"]; + const Path oldPath {params()["oldPath"]}; + const Path newPath {params()["newPath"]}; try { @@ -1389,8 +1388,8 @@ void TorrentsController::renameFolderAction() if (!torrent) throw APIError(APIErrorType::NotFound); - const QString oldPath = params()["oldPath"]; - const QString newPath = params()["newPath"]; + const Path oldPath {params()["oldPath"]}; + const Path newPath {params()["newPath"]}; try { diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index f471da42e..55e78ec44 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -151,8 +151,8 @@ void WebApplication::sendWebUIFile() { if (request().path.startsWith(PATH_PREFIX_ICONS)) { - const QString imageFilename {request().path.mid(PATH_PREFIX_ICONS.size())}; - sendFile(QLatin1String(":/icons/") + imageFilename); + const Path imageFilename {request().path.mid(PATH_PREFIX_ICONS.size())}; + sendFile(Path(":/icons") / imageFilename); return; } } @@ -164,20 +164,13 @@ void WebApplication::sendWebUIFile() : QLatin1String("/index.html")) }; - QString localPath - { - m_rootFolder - + (session() ? PRIVATE_FOLDER : PUBLIC_FOLDER) - + path - }; - - QFileInfo fileInfo {localPath}; - - if (!fileInfo.exists() && session()) + Path localPath = m_rootFolder + / Path(session() ? PRIVATE_FOLDER : PUBLIC_FOLDER) + / Path(path); + if (!localPath.exists() && session()) { // try to send public file if there is no private one - localPath = m_rootFolder + PUBLIC_FOLDER + path; - fileInfo.setFile(localPath); + localPath = m_rootFolder / Path(PUBLIC_FOLDER) / Path(path); } if (m_isAltUIUsed) @@ -191,7 +184,8 @@ void WebApplication::sendWebUIFile() } #endif - while (fileInfo.filePath() != m_rootFolder) + QFileInfo fileInfo {localPath.data()}; + while (Path(fileInfo.filePath()) != m_rootFolder) { if (fileInfo.isSymLink()) throw InternalServerErrorHTTPError(tr("Symlinks inside alternative UI folder are forbidden.")); @@ -320,8 +314,7 @@ void WebApplication::configure() const auto *pref = Preferences::instance(); const bool isAltUIUsed = pref->isAltWebUiEnabled(); - const QString rootFolder = Utils::Fs::expandPathAbs( - !isAltUIUsed ? WWW_FOLDER : pref->getWebUiRootFolder()); + const Path rootFolder = (!isAltUIUsed ? Path(WWW_FOLDER) : pref->getWebUiRootFolder()); if ((isAltUIUsed != m_isAltUIUsed) || (rootFolder != m_rootFolder)) { m_isAltUIUsed = isAltUIUsed; @@ -330,7 +323,7 @@ void WebApplication::configure() if (!m_isAltUIUsed) LogMsg(tr("Using built-in Web UI.")); else - LogMsg(tr("Using custom Web UI. Location: \"%1\".").arg(m_rootFolder)); + LogMsg(tr("Using custom Web UI. Location: \"%1\".").arg(m_rootFolder.toString())); } const QString newLocale = pref->getLocale(); @@ -339,7 +332,7 @@ void WebApplication::configure() m_currentLocale = newLocale; m_translatedFiles.clear(); - m_translationFileLoaded = m_translator.load(m_rootFolder + QLatin1String("/translations/webui_") + newLocale); + m_translationFileLoaded = m_translator.load((m_rootFolder / Path("translations/webui_") + newLocale).data()); if (m_translationFileLoaded) { LogMsg(tr("Web UI translation for selected locale (%1) has been successfully loaded.") @@ -437,9 +430,9 @@ void WebApplication::declarePublicAPI(const QString &apiPath) m_publicAPIs << apiPath; } -void WebApplication::sendFile(const QString &path) +void WebApplication::sendFile(const Path &path) { - const QDateTime lastModified {QFileInfo(path).lastModified()}; + const QDateTime lastModified = Utils::Fs::lastModified(path); // find translated file in cache const auto it = m_translatedFiles.constFind(path); @@ -450,16 +443,16 @@ void WebApplication::sendFile(const QString &path) return; } - QFile file {path}; + QFile file {path.data()}; if (!file.open(QIODevice::ReadOnly)) { - qDebug("File %s was not found!", qUtf8Printable(path)); + qDebug("File %s was not found!", qUtf8Printable(path.toString())); throw NotFoundHTTPError(); } if (file.size() > MAX_ALLOWED_FILESIZE) { - qWarning("%s: exceeded the maximum allowed file size!", qUtf8Printable(path)); + qWarning("%s: exceeded the maximum allowed file size!", qUtf8Printable(path.toString())); throw InternalServerErrorHTTPError(tr("Exceeded the maximum allowed file size (%1)!") .arg(Utils::Misc::friendlyUnit(MAX_ALLOWED_FILESIZE))); } @@ -467,8 +460,8 @@ void WebApplication::sendFile(const QString &path) QByteArray data {file.readAll()}; file.close(); - const QMimeType mimeType {QMimeDatabase().mimeTypeForFileNameAndData(path, data)}; - const bool isTranslatable {mimeType.inherits(QLatin1String("text/plain"))}; + const QMimeType mimeType = QMimeDatabase().mimeTypeForFileNameAndData(path.data(), data); + const bool isTranslatable = mimeType.inherits(QLatin1String("text/plain")); // Translate the file if (isTranslatable) diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 77c5f2c3d..05daca124 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -40,6 +40,7 @@ #include "base/http/irequesthandler.h" #include "base/http/responsebuilder.h" #include "base/http/types.h" +#include "base/path.h" #include "base/utils/net.h" #include "base/utils/version.h" @@ -100,7 +101,7 @@ private: void registerAPIController(const QString &scope, APIController *controller); void declarePublicAPI(const QString &apiPath); - void sendFile(const QString &path); + void sendFile(const Path &path); void sendWebUIFile(); void translateDocument(QString &data) const; @@ -131,7 +132,7 @@ private: QHash m_apiControllers; QSet m_publicAPIs; bool m_isAltUIUsed = false; - QString m_rootFolder; + Path m_rootFolder; struct TranslatedFile { @@ -139,7 +140,7 @@ private: QString mimeType; QDateTime lastModified; }; - QHash m_translatedFiles; + QHash m_translatedFiles; QString m_currentLocale; QTranslator m_translator; bool m_translationFileLoaded = false; diff --git a/src/webui/webui.cpp b/src/webui/webui.cpp index 3b86596e1..7813d997d 100644 --- a/src/webui/webui.cpp +++ b/src/webui/webui.cpp @@ -34,6 +34,7 @@ #include "base/logger.h" #include "base/net/dnsupdater.h" #include "base/net/portforwarder.h" +#include "base/path.h" #include "base/preferences.h" #include "base/utils/net.h" #include "webapplication.h" @@ -88,9 +89,9 @@ void WebUI::configure() if (pref->isWebUiHttpsEnabled()) { - const auto readData = [](const QString &path) -> QByteArray + const auto readData = [](const Path &path) -> QByteArray { - QFile file(path); + QFile file {path.data()}; if (!file.open(QIODevice::ReadOnly)) return {}; return file.read(Utils::Net::MAX_SSL_FILE_SIZE);