vnote/src/core/configmgr.cpp
tootal 911392deab
Feature Task System (#1947)
* add task system code

* enable ci build

* Revert "enable ci build"

This reverts commit 8c457a22e44e64c7d87804fc3c76ee778c1c3b6f.
2021-12-09 18:40:14 +08:00

567 lines
17 KiB
C++

#include "configmgr.h"
#include <QDir>
#include <QCoreApplication>
#include <QFileInfo>
#include <QDebug>
#include <QStandardPaths>
#include <QJsonDocument>
#include <QScopeGuard>
#include <QResource>
#include <QPixmap>
#include <QSplashScreen>
#include <QScopedPointer>
#include <QTemporaryDir>
#include <utils/pathutils.h>
#include <utils/fileutils.h>
#include "exception.h"
#include <utils/utils.h>
#include "mainconfig.h"
#include "coreconfig.h"
#include "sessionconfig.h"
using namespace vnotex;
#ifndef QT_NO_DEBUG
// #define VX_DEBUG_WEB
#endif
const QString ConfigMgr::c_orgName = "VNote";
const QString ConfigMgr::c_appName = "VNote";
const QString ConfigMgr::c_configFileName = "vnotex.json";
const QString ConfigMgr::c_sessionFileName = "session.json";
const QJsonObject &ConfigMgr::Settings::getJson() const
{
return m_jobj;
}
QSharedPointer<ConfigMgr::Settings> ConfigMgr::Settings::fromFile(const QString &p_jsonFilePath)
{
if (!QFileInfo::exists(p_jsonFilePath)) {
qWarning() << "return empty Settings from non-exist config file" << p_jsonFilePath;
return QSharedPointer<Settings>::create();
}
auto bytes = FileUtils::readFile(p_jsonFilePath);
return QSharedPointer<Settings>::create(QJsonDocument::fromJson(bytes).object());
}
void ConfigMgr::Settings::writeToFile(const QString &p_jsonFilePath) const
{
FileUtils::writeFile(p_jsonFilePath, QJsonDocument(this->m_jobj).toJson());
}
ConfigMgr::ConfigMgr(bool p_isUnitTest, QObject *p_parent)
: QObject(p_parent),
m_config(new MainConfig(this)),
m_sessionConfig(new SessionConfig(this))
{
if (p_isUnitTest) {
m_dirForUnitTest.reset(new QTemporaryDir());
if (!m_dirForUnitTest->isValid()) {
qWarning() << "failed to init ConfigMgr for UnitTest";
return;
}
m_appConfigFolderPath = m_dirForUnitTest->filePath("vnotex_files");
m_userConfigFolderPath = m_dirForUnitTest->filePath("user_files");
FileUtils::copyFile(getConfigFilePath(Source::Default), PathUtils::concatenateFilePath(m_appConfigFolderPath, c_configFileName));
} else {
locateConfigFolder();
bool needUpdate = checkAppConfig();
if (needUpdate) {
checkUserConfig();
}
}
m_config->init();
m_sessionConfig->init();
}
ConfigMgr::~ConfigMgr()
{
}
ConfigMgr &ConfigMgr::getInst(bool p_isUnitTest)
{
static ConfigMgr inst(p_isUnitTest);
return inst;
}
void ConfigMgr::initForUnitTest()
{
getInst(true);
}
void ConfigMgr::locateConfigFolder()
{
const auto appDirPath = getApplicationDirPath();
qInfo() << "app folder" << appDirPath;
// Check app config.
{
const QString configFolderName("vnotex_files");
QString folderPath(appDirPath + '/' + configFolderName);
if (QDir(folderPath).exists()) {
// Config folder in app/.
m_appConfigFolderPath = PathUtils::cleanPath(folderPath);
} else {
m_appConfigFolderPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
}
}
// Check user config.
{
const QString configFolderName("user_files");
QString folderPath(appDirPath + '/' + configFolderName);
if (QDir(folderPath).exists()) {
// Config folder in app/.
m_userConfigFolderPath = PathUtils::cleanPath(folderPath);
} else {
m_userConfigFolderPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
// Make sure it exists.
QDir dir(m_userConfigFolderPath);
dir.mkpath(m_userConfigFolderPath);
}
}
Q_ASSERT(m_appConfigFolderPath != m_userConfigFolderPath);
qInfo() << "app config folder" << m_appConfigFolderPath;
qInfo() << "user config folder" << m_userConfigFolderPath;
}
bool ConfigMgr::checkAppConfig()
{
bool needUpdate = false;
QDir appConfigDir(m_appConfigFolderPath);
if (!appConfigDir.exists()) {
needUpdate = true;
appConfigDir.mkpath(m_appConfigFolderPath);
} else {
if (!appConfigDir.exists(c_configFileName)) {
needUpdate = true;
} else {
// Check version of config file.
auto defaultSettings = getSettings(Source::Default);
auto appSettings = getSettings(Source::App);
auto defaultVersion = MainConfig::getVersion(defaultSettings->getJson());
auto appVersion = MainConfig::getVersion(appSettings->getJson());
if (defaultVersion != appVersion) {
qInfo() << "new version" << appVersion << defaultVersion;
needUpdate = true;
}
}
if (needUpdate) {
FileUtils::removeDir(m_appConfigFolderPath);
// Wait for the OS delete the folder.
Utils::sleepWait(1000);
appConfigDir.mkpath(m_appConfigFolderPath);
}
}
const auto mainConfigFilePath = appConfigDir.filePath(c_configFileName);
#ifndef VX_DEBUG_WEB
if (!needUpdate) {
return false;
}
#endif
qInfo() << "update app config files in" << m_appConfigFolderPath;
Q_ASSERT(appConfigDir.exists());
QPixmap pixmap(":/vnotex/data/core/logo/vnote.png");
QScopedPointer<QSplashScreen> splash(new QSplashScreen(pixmap));
splash->show();
// Load extra data.
splash->showMessage("Loading extra resource data");
const QString extraRcc(PathUtils::concatenateFilePath(QCoreApplication::applicationDirPath(),
QStringLiteral("vnote_extra.rcc")));
bool ret = QResource::registerResource(extraRcc);
if (!ret) {
Exception::throwOne(Exception::Type::FailToReadFile,
QString("failed to register resource file %1").arg(extraRcc));
}
auto cleanup = qScopeGuard([extraRcc]() {
QResource::unregisterResource(extraRcc);
});
const QString extraDataRoot(QStringLiteral(":/vnotex/data/extra"));
#ifdef VX_DEBUG_WEB
if (!needUpdate) {
// Always update main config file and web folder.
qDebug() << "forced to update main config file and web folder for debugging";
splash->showMessage("update main config file and web folder for debugging");
// Cancel the read-only permission of the main config file.
QFile::setPermissions(mainConfigFilePath, QFile::WriteUser);
FileUtils::removeFile(mainConfigFilePath);
FileUtils::removeDir(appConfigDir.filePath(QStringLiteral("web")));
// Wait for the OS delete the folder.
Utils::sleepWait(1000);
FileUtils::copyFile(getConfigFilePath(Source::Default), mainConfigFilePath);
FileUtils::copyDir(extraDataRoot + QStringLiteral("/web"),
appConfigDir.filePath(QStringLiteral("web")));
return false;
}
#else
Q_ASSERT(needUpdate);
#endif
// Copy themes.
qApp->processEvents();
splash->showMessage("Copying themes");
FileUtils::copyDir(extraDataRoot + QStringLiteral("/themes"),
appConfigDir.filePath(QStringLiteral("themes")));
// Copy tasks.
qApp->processEvents();
splash->showMessage("Copying tasks");
FileUtils::copyDir(extraDataRoot + QStringLiteral("/tasks"),
appConfigDir.filePath(QStringLiteral("tasks")));
// Copy docs.
qApp->processEvents();
splash->showMessage("Copying docs");
FileUtils::copyDir(extraDataRoot + QStringLiteral("/docs"),
appConfigDir.filePath(QStringLiteral("docs")));
// Copy syntax-highlighting.
qApp->processEvents();
splash->showMessage("Copying syntax-highlighting");
FileUtils::copyDir(extraDataRoot + QStringLiteral("/syntax-highlighting"),
appConfigDir.filePath(QStringLiteral("syntax-highlighting")));
// Copy web.
qApp->processEvents();
splash->showMessage("Copying web");
FileUtils::copyDir(extraDataRoot + QStringLiteral("/web"),
appConfigDir.filePath(QStringLiteral("web")));
// Copy dicts.
qApp->processEvents();
splash->showMessage("Copying dicts");
FileUtils::copyDir(extraDataRoot + QStringLiteral("/dicts"),
appConfigDir.filePath(QStringLiteral("dicts")));
// Main config file.
FileUtils::copyFile(getConfigFilePath(Source::Default), appConfigDir.filePath(c_configFileName));
return needUpdate;
}
void ConfigMgr::checkUserConfig()
{
// Mainly check if the user config and session config is read-only.
const auto userFile = getConfigFilePath(Source::User);
if (QFileInfo::exists(userFile)) {
if (!(QFile::permissions(userFile) & QFile::WriteUser)) {
qDebug() << "make user config file writable" << userFile;
QFile::setPermissions(userFile, QFile::WriteUser);
}
}
const auto sessionFile = getConfigFilePath(Source::Session);
if (QFileInfo::exists(sessionFile)) {
if (!(QFile::permissions(sessionFile) & QFile::WriteUser)) {
qDebug() << "make session config file writable" << sessionFile;
QFile::setPermissions(sessionFile, QFile::WriteUser);
}
}
}
QString ConfigMgr::getConfigFilePath(Source p_src) const
{
QString configPath;
switch (p_src) {
case Source::Default:
configPath = getDefaultConfigFilePath();
break;
case Source::App:
configPath = m_appConfigFolderPath + QLatin1Char('/') + c_configFileName;
break;
case Source::User:
{
configPath = m_userConfigFolderPath + QLatin1Char('/') + c_configFileName;
break;
}
case Source::Session:
{
configPath = m_userConfigFolderPath + QLatin1Char('/') + c_sessionFileName;
break;
}
default:
Q_ASSERT(false);
}
return configPath;
}
QString ConfigMgr::getDefaultConfigFilePath()
{
return QStringLiteral(":/vnotex/data/core/") + c_configFileName;
}
QSharedPointer<ConfigMgr::Settings> ConfigMgr::getSettings(Source p_src) const
{
return ConfigMgr::Settings::fromFile(getConfigFilePath(p_src));
}
void ConfigMgr::writeUserSettings(const QJsonObject &p_jobj)
{
Settings settings(p_jobj);
settings.writeToFile(getConfigFilePath(Source::User));
}
void ConfigMgr::writeSessionSettings(const QJsonObject &p_jobj)
{
Settings settings(p_jobj);
settings.writeToFile(getConfigFilePath(Source::Session));
}
MainConfig &ConfigMgr::getConfig()
{
return *m_config;
}
SessionConfig &ConfigMgr::getSessionConfig()
{
return *m_sessionConfig;
}
CoreConfig &ConfigMgr::getCoreConfig()
{
return m_config->getCoreConfig();
}
EditorConfig &ConfigMgr::getEditorConfig()
{
return m_config->getEditorConfig();
}
WidgetConfig &ConfigMgr::getWidgetConfig()
{
return m_config->getWidgetConfig();
}
QString ConfigMgr::getAppFolder() const
{
return m_appConfigFolderPath;
}
QString ConfigMgr::getUserFolder() const
{
return m_userConfigFolderPath;
}
QString ConfigMgr::getAppThemeFolder() const
{
return PathUtils::concatenateFilePath(m_appConfigFolderPath, QStringLiteral("themes"));
}
QString ConfigMgr::getUserThemeFolder() const
{
auto folderPath = PathUtils::concatenateFilePath(m_userConfigFolderPath, QStringLiteral("themes"));
QDir().mkpath(folderPath);
return folderPath;
}
QString ConfigMgr::getAppTaskFolder() const
{
return PathUtils::concatenateFilePath(m_appConfigFolderPath, QStringLiteral("tasks"));
}
QString ConfigMgr::getUserTaskFolder() const
{
auto folderPath = PathUtils::concatenateFilePath(m_userConfigFolderPath, QStringLiteral("tasks"));
QDir().mkpath(folderPath);
return folderPath;
}
QString ConfigMgr::getAppWebStylesFolder() const
{
return PathUtils::concatenateFilePath(m_appConfigFolderPath, QStringLiteral("web-styles"));
}
QString ConfigMgr::getUserWebStylesFolder() const
{
auto folderPath = PathUtils::concatenateFilePath(m_userConfigFolderPath, QStringLiteral("web-styles"));
QDir().mkpath(folderPath);
return folderPath;
}
QString ConfigMgr::getAppDocsFolder() const
{
return PathUtils::concatenateFilePath(m_appConfigFolderPath, QStringLiteral("docs"));
}
QString ConfigMgr::getUserDocsFolder() const
{
auto folderPath = PathUtils::concatenateFilePath(m_userConfigFolderPath, QStringLiteral("docs"));
QDir().mkpath(folderPath);
return folderPath;
}
QString ConfigMgr::getAppSyntaxHighlightingFolder() const
{
return PathUtils::concatenateFilePath(m_appConfigFolderPath,
QStringLiteral("syntax-highlighting"));
}
QString ConfigMgr::getUserSyntaxHighlightingFolder() const
{
auto folderPath = PathUtils::concatenateFilePath(m_userConfigFolderPath,
QStringLiteral("syntax-highlighting"));
QDir().mkpath(folderPath);
return folderPath;
}
QString ConfigMgr::getAppDictsFolder() const
{
return PathUtils::concatenateFilePath(m_appConfigFolderPath,
QStringLiteral("dicts"));
}
QString ConfigMgr::getUserDictsFolder() const
{
auto folderPath = PathUtils::concatenateFilePath(m_userConfigFolderPath,
QStringLiteral("dicts"));
QDir().mkpath(folderPath);
return folderPath;
}
QString ConfigMgr::getUserTemplateFolder() const
{
auto folderPath = PathUtils::concatenateFilePath(m_userConfigFolderPath, QStringLiteral("templates"));
QDir().mkpath(folderPath);
return folderPath;
}
QString ConfigMgr::getUserSnippetFolder() const
{
auto folderPath = PathUtils::concatenateFilePath(m_userConfigFolderPath, QStringLiteral("snippets"));
QDir().mkpath(folderPath);
return folderPath;
}
QString ConfigMgr::getUserMarkdownUserStyleFile() const
{
auto folderPath = PathUtils::concatenateFilePath(m_userConfigFolderPath, QStringLiteral("web/css"));
auto filePath = PathUtils::concatenateFilePath(folderPath, QStringLiteral("user.css"));
if (!QFileInfo::exists(filePath)) {
QDir().mkpath(folderPath);
FileUtils::writeFile(filePath, QByteArray());
}
return filePath;
}
QString ConfigMgr::getUserOrAppFile(const QString &p_filePath) const
{
QFileInfo fi(p_filePath);
if (fi.isAbsolute()) {
return p_filePath;
}
// Check user folder first.
QDir userConfigDir(m_userConfigFolderPath);
if (userConfigDir.exists(p_filePath)) {
return userConfigDir.absoluteFilePath(p_filePath);
}
// App folder.
QDir appConfigDir(m_appConfigFolderPath);
return appConfigDir.absoluteFilePath(p_filePath);
}
QString ConfigMgr::locateSessionConfigFilePathAtBootstrap()
{
// QApplication is not init yet, so org and app name are empty here.
auto folderPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
folderPath = PathUtils::concatenateFilePath(folderPath, c_orgName + "/" + c_appName);
QDir dir(folderPath);
if (dir.exists(c_sessionFileName)) {
qInfo() << "locateSessionConfigFilePathAtBootstrap" << folderPath;
return dir.filePath(c_sessionFileName);
}
return QString();
}
QString ConfigMgr::getLogFile() const
{
return PathUtils::concatenateFilePath(ConfigMgr::getInst().getUserFolder(), "vnotex.log");
}
QString ConfigMgr::getApplicationFilePath()
{
#if defined(Q_OS_LINUX)
// We could get the APPIMAGE env variable from the AppRun script.
auto appImageVar = QString::fromLocal8Bit(qgetenv("APPIMAGE"));
qInfo() << "APPIMAGE" << appImageVar;
if (!appImageVar.isEmpty()) {
return appImageVar;
}
#elif defined(Q_OS_MACOS)
auto exePath = QCoreApplication::applicationFilePath();
const QString exeName = c_appName.toLower() + ".app";
int idx = exePath.indexOf(exeName + QStringLiteral("/Contents/MacOS/"));
if (idx != -1) {
return exePath.left(idx + exeName.size());
}
#endif
return QCoreApplication::applicationFilePath();
}
QString ConfigMgr::getApplicationDirPath()
{
return PathUtils::parentDirPath(getApplicationFilePath());
}
QString ConfigMgr::getDocumentOrHomePath()
{
static QString docHomePath;
if (docHomePath.isEmpty()) {
QStringList folders = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation);
if (folders.isEmpty()) {
docHomePath = QDir::homePath();
} else {
docHomePath = folders[0];
}
}
return docHomePath;
}
QString ConfigMgr::getApplicationVersion()
{
static QString appVersion;
if (appVersion.isEmpty()) {
auto defaultSettings = ConfigMgr::Settings::fromFile(getDefaultConfigFilePath());
const auto &defaultObj = defaultSettings->getJson();
auto metaDataObj = defaultObj.value(QStringLiteral("metadata")).toObject();
appVersion = metaDataObj.value(QStringLiteral("version")).toString();
}
return appVersion;
}