Refactor config handling and related stuff

This commit is contained in:
blankie 2023-01-04 23:40:35 +07:00
parent e7401a6bd1
commit dfdb631b63
Signed by: blankie
GPG Key ID: CC15FC822C7F61F5
5 changed files with 167 additions and 66 deletions

View File

@ -1,27 +1,77 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/stat.h>
#include <string>
#include <fstream>
#include <memory>
#include <system_error>
#include "config.h"
std::string get_config_path() {
static FILE* fopen_or_raise(const char* path, const char* mode, bool ignore_enoent) {
FILE* file = fopen(path, mode);
if (file || (ignore_enoent && errno == ENOENT)) {
return file;
}
throw std::system_error(errno, std::generic_category(), "fopen()");
}
static void fclose_and_log(FILE* file) {
if (!fclose(file)) {
return;
}
// TODO log() when logging exists
}
static bool write(const std::string& ptr, FILE* file) {
return fwrite(ptr.data(), sizeof(char), ptr.size(), file) == ptr.size() * sizeof(char);
}
static bool write(const char* ptr, FILE* file) {
return fwrite(ptr, 1, strlen(ptr), file) == strlen(ptr);
}
std::string get_config_folder() {
const char* path;
path = getenv("XDG_CONFIG_HOME");
if (path) {
return std::string(path) + "/logmeow/config";
return std::string(path) + "/logmeow";
}
path = getenv("HOME");
if (path) {
return std::string(path) + "/.config/logmeow/config";
return std::string(path) + "/.config/logmeow";
}
throw std::runtime_error("cannot find suitable config folder, please set XDG_CONFIG_HOME or HOME");
}
Config parse_config(FILE* file) {
std::string get_config_file_path() {
return get_config_folder() + "/config";
}
// tries to create a directory with an empty string if it's just "/" but who cares
void create_config_folders_if_necessary() {
std::string path = get_config_folder();
size_t pos = 0;
while (pos != std::string::npos) {
pos = path.find('/', pos);
if (pos != std::string::npos) {
pos++;
}
if (!mkdir(path.substr(0, pos).c_str(), 0600)) {
continue;
}
if (errno == EEXIST) {
continue;
}
throw std::system_error(errno, std::generic_category(), "mkdir()");
}
}
static inline Config load_config(FILE* file) {
size_t line_capacity_size = 128 * sizeof(char);
char* line = static_cast<char*>(malloc(line_capacity_size));
if (line == nullptr) {
@ -37,7 +87,7 @@ Config parse_config(FILE* file) {
if (errsv == ENOMEM) {
throw std::bad_alloc();
} else if (errsv != 0) {
throw std::system_error(errsv, std::generic_category(), "getline()");
throw std::system_error(errsv, std::generic_category(), "reading config line");
} else {
break;
}
@ -50,8 +100,8 @@ Config parse_config(FILE* file) {
}
if (strncmp(line, "logcat_command=", 15 * sizeof(char)) == 0) {
config.logcat_command = &line[16];
} else {
config.logcat_command = &line[15];
} else if (line[0] != '\0') {
std::invalid_argument e = std::invalid_argument(std::string("unknown config line: ") + line);
free(line);
throw e;
@ -61,11 +111,44 @@ Config parse_config(FILE* file) {
return config;
}
void write_config(std::ofstream& file, const Config& config) {
file << "# This is an auto-generated file, comments made will be lost\n"
<< "# This is a poor man's config file \"format\", there are only three legal lines:\n"
<< "# # a comment, only available at the start of a line\n"
<< "# (an empty line, no whitespace)\n"
<< "# key=value pairs, no spaces around the delimiter, and no unknown keys\n\n"
<< "logcat_command=" << config.logcat_command;
Config load_config() {
std::string config_file_path = get_config_file_path();
std::unique_ptr<FILE, decltype(&fclose_and_log)> config_file(fopen_or_raise(config_file_path.c_str(), "r", true), fclose_and_log);
if (!config_file) {
return Config();
}
return load_config(config_file.get());
}
static inline void write_config(FILE* file, const Config& config) {
if (!write("# This is an auto-generated file, modifications will be lost\n"
"# This is a poor man's config file \"format\", there are only three legal lines:\n"
"# # a comment, only available at the start of a line\n"
"# (an empty line, no whitespace)\n"
"# key=value pairs, no spaces around the delimiter, and no unknown keys\n\n", file)) {
throw std::runtime_error("failed to write info comment");
}
if (!write("logcat_command=", file)) {
throw std::runtime_error("failed to write logcat command key");
}
if (!write(config.logcat_command, file)) {
throw std::runtime_error("failed to write logcat command value");
}
}
void write_config(const Config& config) {
std::string config_file_path = get_config_file_path();
std::string tmp_config_file_path = config_file_path + ".tmp";
std::unique_ptr<FILE, decltype(&fclose_and_log)> config_file(fopen_or_raise(tmp_config_file_path.c_str(), "w", false), fclose_and_log);
write_config(config_file.get(), config);
config_file.reset();
if (!rename(tmp_config_file_path.c_str(), config_file_path.c_str())) {
return;
}
throw std::system_error(errno, std::generic_category(), "rename()");
}

View File

@ -1,12 +1,12 @@
#pragma once
#include <string>
#include <fstream>
struct Config {
std::string logcat_command;
};
std::string get_config_path();
Config parse_config(FILE* file);
void write_config(std::ofstream& file, const Config& config);
std::string get_config_folder();
void create_config_folders_if_necessary();
Config load_config();
void write_config(const Config& config);

View File

@ -3,66 +3,76 @@
#include "config.h"
static inline void settings_window(Config& inactive_config, bool* show_settings_window) {
static inline void write_config_and_update_structures(const Config& config) {
try {
write_config(config);
} catch (const std::exception& e) {
// TODO log() when logging exists
throw;
}
}
static inline void settings_window(Config& config, float* config_write_timer, bool* show_settings_window) {
if (!ImGui::Begin("Settings", show_settings_window)) {
ImGui::End();
return;
}
ImGui::Text("Modifying logcat command will take effect after closing this window and will restart logcat");
ImGui::InputTextWithHint("Logcat command", "adb logcat -Dv 'threadtime UTC epoch usec'", &inactive_config.logcat_command);
// TODO actually have process control
ImGui::Text("Logcat command is only modifiable when logcat is stopped");
if (ImGui::InputTextWithHint("Logcat command", "adb logcat -Dv 'threadtime UTC epoch usec'", &config.logcat_command)) {
*config_write_timer = *config_write_timer > 0.0f ? *config_write_timer : 5.0f;
}
ImGui::End();
}
static inline void main_window(Config& active_config, Config& inactive_config, bool* show_demo_window,
bool* show_settings_window, bool* save_settings_when_settings_window_closed, bool* exit_requested_rev) {
static inline void main_window(Config& active_config, bool* show_demo_window,
bool* show_settings_window, bool* exit_requested_rev) {
if (!ImGui::Begin("LogMeow", exit_requested_rev)) {
ImGui::End();
return;
}
ImGui::Checkbox("Show Dear ImGui Demo Window", show_demo_window);
if (ImGui::Button("Settings")) {
if (!*show_settings_window) {
inactive_config = active_config;
}
*show_settings_window = true;
*save_settings_when_settings_window_closed = true;
}
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
ImGui::End();
}
static inline void exit_modal_if_necessary(bool* run_event_loop) {
if (ImGui::BeginPopupModal("Exit?", nullptr, ImGuiWindowFlags_NoResize)) {
ImGui::Text("Are you sure you want to exit?");
if (ImGui::Button("Yes", ImVec2(120, 0))) {
*run_event_loop = false;
}
ImGui::SameLine();
if (ImGui::Button("No", ImVec2(120, 0))) {
ImGui::CloseCurrentPopup();
}
ImGui::SetItemDefaultFocus();
ImGui::EndPopup();
if (!ImGui::BeginPopupModal("Exit?", nullptr, ImGuiWindowFlags_NoResize)) {
return;
}
ImGui::Text("Are you sure you want to exit?");
if (ImGui::Button("Yes", ImVec2(120, 0))) {
*run_event_loop = false;
}
ImGui::SameLine();
if (ImGui::Button("No", ImVec2(120, 0))) {
ImGui::CloseCurrentPopup();
}
ImGui::SetItemDefaultFocus();
ImGui::EndPopup();
}
void event_loop(Config& active_config, bool* run_event_loop) {
void event_loop(Config& config, float* config_write_timer, bool* run_event_loop) {
static bool show_demo_window = false;
static bool show_settings_window = false;
static bool save_settings_when_settings_window_closed = false;
static bool exit_requested_rev = true;
static Config inactive_config;
if (show_demo_window) {
ImGui::ShowDemoWindow(&show_demo_window);
}
if (show_settings_window) {
settings_window(inactive_config, &show_settings_window);
} else if (save_settings_when_settings_window_closed) {
active_config = inactive_config;
save_settings_when_settings_window_closed = false;
settings_window(config, config_write_timer, &show_settings_window);
}
if (*config_write_timer > 0.0f) {
*config_write_timer -= ImGui::GetIO().DeltaTime;
if (*config_write_timer <= 0.0f) {
write_config_and_update_structures(config);
}
}
if (!exit_requested_rev) {
@ -70,5 +80,5 @@ void event_loop(Config& active_config, bool* run_event_loop) {
exit_requested_rev = true;
}
exit_modal_if_necessary(run_event_loop);
main_window(active_config, inactive_config, &show_demo_window, &show_settings_window, &save_settings_when_settings_window_closed, &exit_requested_rev);
main_window(config, &show_demo_window, &show_settings_window, &exit_requested_rev);
}

View File

@ -2,4 +2,4 @@
#include "config.h"
void event_loop(Config& config, bool* run_event_loop);
void event_loop(Config& config, float* config_write_timer, bool* run_event_loop);

View File

@ -17,23 +17,18 @@
int main(int, char**)
{
Config config;
FILE* config_file = nullptr;
try {
config_file = fopen(get_config_path().c_str(), "r");
if (config_file) {
config = parse_config(config_file);
} else if (errno != ENOENT) {
perror("Failed to open config file");
return 1;
}
config = load_config();
} catch (const std::exception& e) {
fprintf(stderr, "Failed to parse config: %s\n", e.what());
fprintf(stderr, "Failed to load config: %s\n", e.what());
return 1;
}
if (config_file) {
if (fclose(config_file)) {
perror("Failed to close config file");
}
try {
create_config_folders_if_necessary();
} catch (const std::exception& e) {
fprintf(stderr, "Failed to create config folder: %s\n", e.what());
return 1;
}
// Setup SDL
@ -82,7 +77,10 @@ int main(int, char**)
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
ImGuiIO& io = ImGui::GetIO();
// needs to be a variable to keep the c-style string around
std::string ini_file_path = get_config_folder() + "/imgui.ini";
io.IniFilename = ini_file_path.c_str();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
//io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
@ -112,8 +110,9 @@ int main(int, char**)
// Main loop
bool run_event_loop = true;
while (run_event_loop)
{
float config_write_timer = 0.0f;
while (run_event_loop) {
// Poll and handle events (inputs, window resize, etc.)
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
@ -134,7 +133,7 @@ int main(int, char**)
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
event_loop(config, &run_event_loop);
event_loop(config, &config_write_timer, &run_event_loop);
// Rendering
ImGui::Render();
@ -154,5 +153,14 @@ int main(int, char**)
SDL_DestroyWindow(window);
SDL_Quit();
if (config_write_timer > 0.0f) {
try {
write_config(config);
} catch (const std::exception& e) {
fprintf(stderr, "Failed to write config: %s\n", e.what());
return 1;
}
}
return 0;
}