summaryrefslogtreecommitdiff
path: root/src/config.cpp
diff options
context:
space:
mode:
authorDominick Allen <djallen@librehumanitas.org>2024-10-02 15:07:24 -0500
committerDominick Allen <djallen@librehumanitas.org>2024-10-02 15:07:24 -0500
commit47e0ff88edd4660513f1d4f3d731008461532a13 (patch)
tree5b73ad0920101190e6e7cb558833e24f31ccdc4d /src/config.cpp
parent99c6c809b961f2eb3c8538bfa50de7f2f98587ea (diff)
Get and save user keybinds.
Diffstat (limited to 'src/config.cpp')
-rw-r--r--src/config.cpp390
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