diff options
author | Dominick Allen <djallen@librehumanitas.org> | 2024-10-02 15:07:24 -0500 |
---|---|---|
committer | Dominick Allen <djallen@librehumanitas.org> | 2024-10-02 15:07:24 -0500 |
commit | 47e0ff88edd4660513f1d4f3d731008461532a13 (patch) | |
tree | 5b73ad0920101190e6e7cb558833e24f31ccdc4d /src/config.cpp | |
parent | 99c6c809b961f2eb3c8538bfa50de7f2f98587ea (diff) |
Get and save user keybinds.
Diffstat (limited to 'src/config.cpp')
-rw-r--r-- | src/config.cpp | 390 |
1 files changed, 238 insertions, 152 deletions
diff --git a/src/config.cpp b/src/config.cpp index 5ffb76e..ab7d3ca 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -3,13 +3,145 @@ #include "luacxx.hpp" #include <algorithm> -#include <fcntl.h> +#include <filesystem> +#include <fud_c_file.hpp> +#include <fud_status.hpp> +#include <fud_string.hpp> #include <vector> namespace getsuyomi { using fud::FudStatus; +constexpr auto PermNone = fud::PermissionType::None; +constexpr auto defaultPermissions = fud::Permissions(fud::PermReadWrite, PermNone, PermNone); + +std::optional<std::string> getEnvVar(const char* envVar) +{ + const QByteArray defaultArray{}; + if (envVar == nullptr) { + return std::nullopt; + } + QByteArray varArray = qgetenv(envVar); + if (varArray == defaultArray) { + return std::nullopt; + } + return varArray.toStdString(); +} + +void getEnvVar(const std::string& homeVar, const std::string& envVar, std::string& envValue, const char* backup) +{ + auto envValueOpt = getEnvVar(envVar.c_str()); + std::filesystem::path envValuePath{homeVar}; + if (envValueOpt == std::nullopt || envValueOpt->length() == 0) { + envValuePath.append(backup); + } else { + envValuePath = *envValueOpt; + } + envValuePath.append(AppName); + envValue = envValuePath; +} + +GetEnvResult getEnvironment() +{ + GetsuyomiConfig config{}; + + auto homeOpt = getEnvVar(HOME); + if (homeOpt == std::nullopt || homeOpt->length() == 0) { + qCritical("Error getting home"); + return GetEnvResult::error(FudStatus::Failure); + } + config.home = *homeOpt; + + /* If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used. */ + const std::string XdgDataHome{"XDG_DATA_HOME"}; + getEnvVar(config.home, XdgDataHome, config.dataHome, ".local/share"); + + /* If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used. */ + const std::string XdgConfigHome{"XDG_CONFIG_HOME"}; + getEnvVar(config.home, XdgConfigHome, config.configHome, ".config"); + + /* If $XDG_STATE_HOME is either not set or empty, a default equal to $HOME/.local/state should be used. */ + const std::string XdgStateHome{"XDG_STATE_HOME"}; + getEnvVar(config.home, XdgStateHome, config.stateHome, ".local/state"); + + return GetEnvResult::okay(config); +} + +FudStatus createXdgDirectory(const std::string& directoryName) +{ + constexpr mode_t xdgMode = 0700; + auto dirStatus = mkdir(directoryName.c_str(), xdgMode); + if (dirStatus == 0) { + return FudStatus::Success; + } + + if (errno != EEXIST) { + return FudStatus::Failure; + } + struct stat statBuffer {}; + dirStatus = stat(directoryName.c_str(), &statBuffer); + + if (dirStatus != 0) { + return FudStatus::Failure; + } + + if ((statBuffer.st_mode & S_IFMT) != S_IFDIR) { + return FudStatus::Failure; + } + + return FudStatus::Success; +} + +void setUserConfig(GetsuyomiConfig& config, const ShortcutMap& shortcutMap) +{ + auto binder = [&](ShortcutList& shortcuts, ActionType action) { + shortcuts = shortcutListFromSet(shortcutMap.at(action)); + }; + + binder(config.openFileShortcuts, ActionType::OpenFile); + binder(config.openDirectoryShortcuts, ActionType::OpenDirectory); + binder(config.quitShortcuts, ActionType::Quit); + + binder(config.settingsShortcuts, ActionType::Configure); + + binder(config.nextShortcuts, ActionType::Next); + binder(config.backShortcuts, ActionType::Back); + + binder(config.singlePageShortcuts, ActionType::SinglePage); + binder(config.dualPageShortcuts, ActionType::DualPage); + binder(config.mangaPageShortcuts, ActionType::MangaPage); +} + +GetConfigResult getUserConfig() +{ + auto configResult = getEnvironment(); + if (configResult.isError()) { + return GetConfigResult::error(configResult.getError()); + } + auto config = configResult.getOkay(); + + auto dirStatus = createXdgDirectory(config.dataHome); + if (dirStatus != FudStatus::Success) { + return GetConfigResult::error(dirStatus); + } + dirStatus = createXdgDirectory(config.configHome); + if (dirStatus != FudStatus::Success) { + return GetConfigResult::error(dirStatus); + } + dirStatus = createXdgDirectory(config.stateHome); + if (dirStatus != FudStatus::Success) { + return GetConfigResult::error(dirStatus); + } + + config.configFilename = std::filesystem::path(config.configHome).append("config.lua"); + auto shortcutMap = Shortcuts::fromUserConfig(config.configFilename); + + setUserConfig(config, shortcutMap); + + return GetConfigResult::okay(config); +} + ShortcutSet shortcutSetFromList(const ShortcutList& shortcutList) { ShortcutSet shortcutSet{}; @@ -214,14 +346,14 @@ Shortcuts Shortcuts::fromLuaConfig(const std::filesystem::path& configFileName) ShortcutMap Shortcuts::fromUserConfig(const std::filesystem::path& configFileName) { - constexpr mode_t configFileMode = 0600; - auto fdResult = open(configFileName.c_str(), O_CREAT, configFileMode); - if (fdResult == -1) { + fud::String filepath(configFileName.c_str()); + fud::CTextFile file{filepath, fud::CFileMode::ReadWriteAppend}; + auto status = file.create(defaultPermissions); + if (status != FudStatus::Success) { if (errno != EEXIST) { qCritical("Could not load or create user config file %s.", configFileName.c_str()); } } - close(fdResult); auto shortcuts = Shortcuts::fromLuaConfig(configFileName); @@ -254,181 +386,135 @@ ShortcutMap Shortcuts::fromUserConfig(const std::filesystem::path& configFileNam return shortcuts.shortcutMap(); } -ShortcutMap GetsuyomiConfig::shortcuts() const +FudStatus backupFile(const fud::String& filepath) { - ShortcutMap shortcuts{}; + fud::CTextFile file{filepath, fud::CFileMode::ReadWriteAppend}; + auto status = file.create(defaultPermissions); + if (status != FudStatus::Success) { + if (errno != EEXIST) { + qCritical("Could not load or create user config file %s.", filepath.c_str()); + } + } - shortcuts.insert({ActionType::OpenFile, shortcutSetFromList(openFileShortcuts)}); - shortcuts.insert({ActionType::OpenDirectory, shortcutSetFromList(openDirectoryShortcuts)}); - shortcuts.insert({ActionType::Quit, shortcutSetFromList(quitShortcuts)}); + size_t fileSize; + auto fileSizeStatus = file.size(); + if (fileSizeStatus.isOkay()) { + fileSize = fileSizeStatus.getOkay(); + } else { + qWarning("Could not get file size"); + return fileSizeStatus.getError(); + } - shortcuts.insert({ActionType::Configure, shortcutSetFromList(settingsShortcuts)}); + if (fileSize == 0) { + return FudStatus::Empty; + } - shortcuts.insert({ActionType::Next, shortcutSetFromList(nextShortcuts)}); - shortcuts.insert({ActionType::Back, shortcutSetFromList(backShortcuts)}); + std::vector<char> backupFileData(fileSize); + auto readStatus = file.read(backupFileData.data(), backupFileData.size(), fileSize); + if (readStatus.status != FudStatus::Success) { + qWarning("Could not read existing config"); + return readStatus.status; + } - shortcuts.insert({ActionType::SinglePage, shortcutSetFromList(singlePageShortcuts)}); - shortcuts.insert({ActionType::DualPage, shortcutSetFromList(dualPageShortcuts)}); - shortcuts.insert({ActionType::MangaPage, shortcutSetFromList(mangaPageShortcuts)}); + auto backupFileName{filepath.catenate(".bak")}; + fud::CTextFile backupFile{backupFileName, fud::CFileMode::ReadWriteTruncate}; + status = backupFile.create(defaultPermissions); + if (status != FudStatus::Success) { + qWarning("Could not create or open backup file %s", backupFileName.c_str()); + return status; + } - return shortcuts; -} + auto writeResult = backupFile.write(backupFileData.data(), backupFileData.size()); + if (writeResult.status != FudStatus::Success) { + qWarning("Could not write backupfile"); + } -ShortcutDisplay::ShortcutDisplay(QWidget* parent, QKeySequence shortcut) : QWidget{parent}, m_binding{shortcut} -{ - auto layout = new QHBoxLayout(); - layout->addWidget(new QLabel(shortcut.toString())); - auto* deleteButton = new QPushButton("Delete", this); - connect(deleteButton, &QPushButton::clicked, this, &ShortcutDisplay::removeOnClicked); - layout->addWidget(deleteButton); - setLayout(layout); + return writeResult.status; } -void ShortcutDisplay::removeOnClicked() +FudStatus Shortcuts::save(const std::filesystem::path& configFileName) const { - emit removeClicked(m_binding); -} + fud::String filepath(configFileName.c_str()); + static_cast<void>(backupFile(filepath)); -ShortcutCollector::ShortcutCollector(QWidget* parent, ActionType action, Shortcuts& shortcuts) : - QWidget(parent), m_action{action}, m_shortcuts{shortcuts}, m_bindings{} -{ - m_layout = new QVBoxLayout(); - auto headerLayout = new QHBoxLayout(); - auto* name = new QLabel(actionTypeToString(m_action)); - headerLayout->addWidget(name); - m_shortcutEditor = new QKeySequenceEdit(this); - m_shortcutEditor->setMaximumSequenceLength(1); - connect(m_shortcutEditor, &QKeySequenceEdit::editingFinished, this, &ShortcutCollector::checkBinding); - headerLayout->addWidget(m_shortcutEditor); - - m_acceptButton = new QPushButton("Accept", this); - m_acceptButton->setEnabled(false); - connect(m_acceptButton, &QPushButton::clicked, this, &ShortcutCollector::addBinding); - headerLayout->addWidget(m_acceptButton); - headerLayout->addStretch(); - - m_layout->addLayout(headerLayout); - - auto shortcutOptions = m_shortcuts.shortcuts(m_action); - if (shortcutOptions != std::nullopt) { - for (const auto& binding : *shortcutOptions) { - createBinding(binding); + fud::CTextFile file{filepath, fud::CFileMode::ReadWriteTruncate}; + auto status = file.create(defaultPermissions); + if (status != FudStatus::Success) { + if (errno != EEXIST) { + qCritical("Could not load or create user config file %s.", configFileName.c_str()); } - } else { - qWarning("No shortcuts found for %s", actionTypeToString(action)); } - setLayout(m_layout); -} - -void ShortcutCollector::createBinding(QKeySequence binding) -{ - auto displayItem = new ShortcutDisplay(this, binding); - m_bindings[binding] = displayItem; - m_layout->addWidget(displayItem); - connect(displayItem, &ShortcutDisplay::removeClicked, this, &ShortcutCollector::removeBinding); -} - -void ShortcutCollector::checkBinding() -{ - auto keySequence = m_shortcutEditor->keySequence(); - if (keySequence == QKeySequence::UnknownKey) { - m_acceptButton->setEnabled(false); - return; + fud::String output{}; + constexpr size_t defaultFileSize = 4096; + auto reserveStatus = output.reserve(defaultFileSize); + if (reserveStatus != FudStatus::Success) { + qCritical("Failed to reserve space for new file data."); + return reserveStatus; } - m_acceptButton->setEnabled(not m_shortcuts.contains(keySequence)); -} + for (auto [action, bindings] : m_actionToShortcuts) { + auto appendStatus = output.append(actionTypeToString(action)); + if (appendStatus == FudStatus::Success) { + appendStatus = output.append(" = {"); + } -void ShortcutCollector::addBinding() -{ - auto keySequence = m_shortcutEditor->keySequence(); - if (keySequence == QKeySequence::UnknownKey) { - qWarning("Invalid state - can't accept unknown key"); - return; - } else if (m_shortcuts.contains(keySequence)) { - qWarning("Shortcut %s already bound", qPrintable(keySequence.toString())); - return; - } - auto result = m_shortcuts.bind(m_action, keySequence); - if (result != FudStatus::Success) { - qCritical("Error binding %s to action %s", qPrintable(keySequence.toString()), actionTypeToString(m_action)); - return; - } - if (m_bindings.contains(keySequence)) { - qWarning("binding %s to action %s already exists", qPrintable(keySequence.toString()), actionTypeToString(m_action)); - m_layout->removeWidget(m_bindings[keySequence]); - delete m_bindings[keySequence]; - } - - createBinding(keySequence); - m_shortcutEditor->clear(); -} + for (auto binding : bindings) { + if (appendStatus == FudStatus::Success) { + appendStatus = output.append("\""); + } else { + break; + } + if (appendStatus == FudStatus::Success) { + appendStatus = output.append(binding.toString().toUtf8().data()); + } else { + break; + } + if (appendStatus == FudStatus::Success) { + appendStatus = output.append("\", "); + } else { + break; + } + } + if (appendStatus == FudStatus::Success) { + appendStatus = output.append("}\n"); + } else { + qWarning("Failed to append to file data %s.", fud::FudStatusToString(appendStatus)); + return appendStatus; + } -void ShortcutCollector::removeBinding(QKeySequence binding) -{ - auto result = m_shortcuts.remove(binding); - if (result == FudStatus::NotFound) { - qWarning("binding %s not found", qPrintable(binding.toString())); - } else if (result != FudStatus::Success) { - qWarning("error removing binding %s: %s", qPrintable(binding.toString()), FudStatusToString(result)); - } - auto bindingHandle = m_bindings.extract(binding); - if (bindingHandle) { - m_layout->removeWidget(bindingHandle.mapped()); - delete bindingHandle.mapped(); - } else { - qWarning("Could not remove widget!"); + if (!output.utf8Valid()) { + qCritical("Output string is invalid."); + return FudStatus::Failure; + } } - checkBinding(); -} - -Settings::Settings(QWidget* parent, Shortcuts&& shortcuts) : QDialog{parent}, m_shortcuts{std::move(shortcuts)} -{ - auto* layout = new QVBoxLayout(); - setWindowTitle("getsuyomi settings"); - if (!m_shortcuts.valid()) { - return; + auto writeStatus = file.write(output.data(), output.length()); + if (writeStatus.status != FudStatus::Success) { + qCritical("Could not save user config file %s.", configFileName.c_str()); } - auto containerLayout = new QHBoxLayout(); - auto* columnLayout = new QVBoxLayout(); - size_t counter{0}; - constexpr size_t maxEntriesPerColumn{4}; - - for (const auto& action : m_shortcuts.actions()) { - auto* collector = new ShortcutCollector(this, action, m_shortcuts); - columnLayout->addWidget(collector); - counter++; - if (counter % (maxEntriesPerColumn + 1) == 0) { - containerLayout->addLayout(columnLayout); - columnLayout = new QVBoxLayout(); - } - } + return writeStatus.status; +} - if (columnLayout->count() > 0) { - containerLayout->addLayout(columnLayout); - } +ShortcutMap GetsuyomiConfig::shortcuts() const +{ + ShortcutMap shortcuts{}; - layout->addLayout(containerLayout); + shortcuts.insert({ActionType::OpenFile, shortcutSetFromList(openFileShortcuts)}); + shortcuts.insert({ActionType::OpenDirectory, shortcutSetFromList(openDirectoryShortcuts)}); + shortcuts.insert({ActionType::Quit, shortcutSetFromList(quitShortcuts)}); - auto* dialogButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(dialogButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(dialogButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - layout->addWidget(dialogButtonBox); + shortcuts.insert({ActionType::Configure, shortcutSetFromList(settingsShortcuts)}); - setLayout(layout); -} + shortcuts.insert({ActionType::Next, shortcutSetFromList(nextShortcuts)}); + shortcuts.insert({ActionType::Back, shortcutSetFromList(backShortcuts)}); -bool Settings::valid() const -{ - return m_shortcuts.valid(); -} + shortcuts.insert({ActionType::SinglePage, shortcutSetFromList(singlePageShortcuts)}); + shortcuts.insert({ActionType::DualPage, shortcutSetFromList(dualPageShortcuts)}); + shortcuts.insert({ActionType::MangaPage, shortcutSetFromList(mangaPageShortcuts)}); -const Shortcuts& Settings::shortcuts() const -{ - return m_shortcuts; + return shortcuts; } } // namespace getsuyomi |