diff --git a/CMakeLists.txt b/CMakeLists.txt index e642fc209..08f44bba7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -319,6 +319,8 @@ set(${PROJECT_NAME}_SOURCES src/persistence/ifriendsettings.h src/persistence/offlinemsgengine.cpp src/persistence/offlinemsgengine.h + src/persistence/paths.cpp + src/persistence/paths.h src/persistence/profile.cpp src/persistence/profile.h src/persistence/profilelocker.cpp diff --git a/src/persistence/paths.cpp b/src/persistence/paths.cpp new file mode 100644 index 000000000..0eb12d0b3 --- /dev/null +++ b/src/persistence/paths.cpp @@ -0,0 +1,234 @@ +#include "paths.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +const QLatin1Literal globalSettingsFile{"qtox.ini"}; +const QLatin1Literal profileFolder{"profiles"}; +const QLatin1Literal themeFolder{"themes"}; +const QLatin1Literal avatarsFolder{"avatars"}; +const QLatin1Literal transfersFolder{"transfers"}; +const QLatin1Literal screenshotsFolder{"screenshots"}; + +// NOTE(sudden6): currently unused, but reflects the TCS at 2018-11 +#ifdef Q_OS_WIN +const QLatin1Literal TCSToxFileFolder{"%APPDATA%/tox/"}; +#elif defined(Q_OS_OSX) +const QLatin1Literal TCSToxFileFolder{"~/Library/Application Support/Tox"}; +#else +const QLatin1Literal TCSToxFileFolder{"~/.config/tox/"}; +#endif +} // namespace + +/** + * @class Profile + * @brief Handles all qTox internal paths + * + * The qTox internal file layout starts at ``. This directory is platform + * specific and depends on if qTox runs in portable mode. + * + * Example file layout for non-portable mode: + * @code + * /themes/ + * /profiles/ + * /... + * @endcode + * + * Example file layout for portable mode: + * @code + * /themes/ + * /profiles/ + * /qtox.ini + * @endcode + * + * All qTox or Tox specific directories should be looked up through this module. + */ + +/** + * @brief Paths::makePaths Factory method for the Paths object + * @param mode + * @return Pointer to Paths object on success, nullptr else + */ +Paths* Paths::makePaths(Portable mode) +{ + bool portable = false; + const QString basePortable = qApp->applicationDirPath(); + const QString baseNonPortable = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + const QString portableSettingsPath = basePortable % QDir::separator() % globalSettingsFile; + + switch (mode) { + case Portable::Portable: + qDebug() << "Forcing portable"; + portable = true; + break; + case Portable::NonPortable: + qDebug() << "Forcing non-portable"; + portable = false; + break; + case Portable::Auto: + // auto detect + if (QFile{portableSettingsPath}.exists()) { + qDebug() << "Automatic portable"; + portable = true; + } else { + qDebug() << "Automatic non-portable"; + portable = false; + } + break; + } + + QString basePath = portable ? basePortable : baseNonPortable; + + if (basePath.isEmpty()) { + qCritical() << "Couldn't find writeable path"; + return nullptr; + } + + return new Paths(basePath, portable); +} + +Paths::Paths(const QString& basePath, bool portable) + : basePath{basePath} + , portable{portable} +{} + +/** + * @brief Check if qTox is running in portable mode. + * @return True if running in portable mode, false else. + */ +bool Paths::isPortable() const +{ + return portable; +} + +/** + * @brief Returns the path to the global settings file "qtox.ini" + * @return The path to the folder. + */ +QString Paths::getGlobalSettingsPath() const +{ + QString path; + + if (portable) { + path = basePath; + } else { + path = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); + if (path.isEmpty()) { + qDebug() << "Can't find writable location for settings file"; + return {}; + } + } + + // we assume a writeable path for portable mode + + return path % QDir::separator() % globalSettingsFile; +} + +/** + * @brief Get the folder where profile specific information is stored, e.g. .ini + * @return The path to the folder. + */ +QString Paths::getProfilesDir() const +{ + return basePath % QDir::separator() % profileFolder % QDir::separator(); +} + +/** + * @brief Get the folder where the .tox file is stored + * @note Expect a change here, since TCS will probably be updated. + * @return The path to the folder on success, empty string else. + */ +QString Paths::getToxSaveDir() const +{ + if (isPortable()) { + return basePath % QDir::separator() % profileFolder % QDir::separator(); + } + + // GenericDataLocation would be a better solution, but we keep this code for backward + // compatibility + +// workaround for https://bugreports.qt-project.org/browse/QTBUG-38845 +#ifdef Q_OS_WIN + // TODO(sudden6): this doesn't really follow the Tox Client Standard and probably + // breaks when %APPDATA% is changed + return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + + QDir::separator() + "AppData" + QDir::separator() + "Roaming" + + QDir::separator() + "tox") + + QDir::separator(); +#elif defined(Q_OS_OSX) + return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + + QDir::separator() + "Library" + QDir::separator() + + "Application Support" + QDir::separator() + "Tox") + + QDir::separator(); +#else + // TODO(sudden6): This does not respect the XDG_* environment variables and also + // stores user data in a config location + return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + + QDir::separator() + "tox") + + QDir::separator(); +#endif +} + +/** + * @brief Get the folder where avatar files are stored + * @note Expect a change here, since TCS will probably be updated. + * @return The path to the folder on success, empty string else. + */ +QString Paths::getAvatarsDir() const +{ + // follow the layout in + // https://tox.gitbooks.io/tox-client-standard/content/data_storage/export_format.html + QString path = getToxSaveDir(); + if (path.isEmpty()) { + qDebug() << "Can't find location for avatars directory"; + return {}; + } + + return path % QDir::separator() % avatarsFolder % QDir::separator(); +} + +/** + * @brief Get the folder where screenshots are stored + * @return The path to the folder. + */ +QString Paths::getScreenshotsDir() const +{ + return basePath % QDir::separator() % screenshotsFolder % QDir::separator(); +} + +/** + * @brief Get the folder where file transfer data is stored + * @return The path to the folder. + */ +QString Paths::getTransfersDir() const +{ + return basePath % QDir::separator() % transfersFolder % QDir::separator(); +} + +/** + * @brief Get a prioritized list with directories that contain themes. + * @return A list of directories sorted from most important to least important. + * @note Users of this function should use the theme from the folder that appears first in the list. + */ +QStringList Paths::getThemeDirs() const +{ + QStringList themeFolders{}; + + if (!isPortable()) { + themeFolders += QStandardPaths::locate(QStandardPaths::AppDataLocation, themeFolder, + QStandardPaths::LocateDirectory); + } + + // look for themes beside the qTox binary with lowest priority + const QString curPath = qApp->applicationDirPath(); + themeFolders += curPath % QDir::separator() % themeFolder % QDir::separator(); + + return themeFolders; +} diff --git a/src/persistence/paths.h b/src/persistence/paths.h new file mode 100644 index 000000000..d666dcb4b --- /dev/null +++ b/src/persistence/paths.h @@ -0,0 +1,35 @@ +#ifndef PATHS_H +#define PATHS_H + +#include +#include + +class Paths +{ +public: + enum class Portable { + Auto, /** Auto detect if portable or non-portable */ + Portable, /** Force portable mode */ + NonPortable /** Force non-portable mode */ + }; + + static Paths* makePaths(Portable mode = Portable::Auto); + + bool isPortable() const; + QString getGlobalSettingsPath() const; + QString getProfilesDir() const; + QString getToxSaveDir() const; + QString getAvatarsDir() const; + QString getTransfersDir() const; + QStringList getThemeDirs() const; + QString getScreenshotsDir() const; + +private: + Paths(const QString &basePath, bool portable); + +private: + QString basePath{}; + bool portable = false; +}; + +#endif // PATHS_H