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 <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <sys/stat.h>
#include <string> #include <string>
#include <fstream> #include <memory>
#include <system_error>
#include "config.h" #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; const char* path;
path = getenv("XDG_CONFIG_HOME"); path = getenv("XDG_CONFIG_HOME");
if (path) { if (path) {
return std::string(path) + "/logmeow/config"; return std::string(path) + "/logmeow";
} }
path = getenv("HOME"); path = getenv("HOME");
if (path) { 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"); 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); size_t line_capacity_size = 128 * sizeof(char);
char* line = static_cast<char*>(malloc(line_capacity_size)); char* line = static_cast<char*>(malloc(line_capacity_size));
if (line == nullptr) { if (line == nullptr) {
@ -37,7 +87,7 @@ Config parse_config(FILE* file) {
if (errsv == ENOMEM) { if (errsv == ENOMEM) {
throw std::bad_alloc(); throw std::bad_alloc();
} else if (errsv != 0) { } 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 { } else {
break; break;
} }
@ -50,8 +100,8 @@ Config parse_config(FILE* file) {
} }
if (strncmp(line, "logcat_command=", 15 * sizeof(char)) == 0) { if (strncmp(line, "logcat_command=", 15 * sizeof(char)) == 0) {
config.logcat_command = &line[16]; config.logcat_command = &line[15];
} else { } else if (line[0] != '\0') {
std::invalid_argument e = std::invalid_argument(std::string("unknown config line: ") + line); std::invalid_argument e = std::invalid_argument(std::string("unknown config line: ") + line);
free(line); free(line);
throw e; throw e;
@ -61,11 +111,44 @@ Config parse_config(FILE* file) {
return config; return config;
} }
void write_config(std::ofstream& file, const Config& config) { Config load_config() {
file << "# This is an auto-generated file, comments made will be lost\n" std::string config_file_path = get_config_file_path();
<< "# This is a poor man's config file \"format\", there are only three legal lines:\n" std::unique_ptr<FILE, decltype(&fclose_and_log)> config_file(fopen_or_raise(config_file_path.c_str(), "r", true), fclose_and_log);
<< "# # a comment, only available at the start of a line\n"
<< "# (an empty line, no whitespace)\n" if (!config_file) {
<< "# key=value pairs, no spaces around the delimiter, and no unknown keys\n\n" return Config();
<< "logcat_command=" << config.logcat_command; }
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 #pragma once
#include <string> #include <string>
#include <fstream>
struct Config { struct Config {
std::string logcat_command; std::string logcat_command;
}; };
std::string get_config_path(); std::string get_config_folder();
Config parse_config(FILE* file); void create_config_folders_if_necessary();
void write_config(std::ofstream& file, const Config& config); Config load_config();
void write_config(const Config& config);

View File

@ -3,66 +3,76 @@
#include "config.h" #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)) { if (!ImGui::Begin("Settings", show_settings_window)) {
ImGui::End(); ImGui::End();
return; return;
} }
ImGui::Text("Modifying logcat command will take effect after closing this window and will restart logcat"); // TODO actually have process control
ImGui::InputTextWithHint("Logcat command", "adb logcat -Dv 'threadtime UTC epoch usec'", &inactive_config.logcat_command); 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(); ImGui::End();
} }
static inline void main_window(Config& active_config, Config& inactive_config, bool* show_demo_window, static inline void main_window(Config& active_config, bool* show_demo_window,
bool* show_settings_window, bool* save_settings_when_settings_window_closed, bool* exit_requested_rev) { bool* show_settings_window, bool* exit_requested_rev) {
if (!ImGui::Begin("LogMeow", exit_requested_rev)) { if (!ImGui::Begin("LogMeow", exit_requested_rev)) {
ImGui::End(); ImGui::End();
return; return;
} }
ImGui::Checkbox("Show Dear ImGui Demo Window", show_demo_window); ImGui::Checkbox("Show Dear ImGui Demo Window", show_demo_window);
if (ImGui::Button("Settings")) { if (ImGui::Button("Settings")) {
if (!*show_settings_window) {
inactive_config = active_config;
}
*show_settings_window = true; *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::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
ImGui::End(); ImGui::End();
} }
static inline void exit_modal_if_necessary(bool* run_event_loop) { static inline void exit_modal_if_necessary(bool* run_event_loop) {
if (ImGui::BeginPopupModal("Exit?", nullptr, ImGuiWindowFlags_NoResize)) { if (!ImGui::BeginPopupModal("Exit?", nullptr, ImGuiWindowFlags_NoResize)) {
ImGui::Text("Are you sure you want to exit?"); return;
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();
} }
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_demo_window = false;
static bool show_settings_window = false; static bool show_settings_window = false;
static bool save_settings_when_settings_window_closed = false;
static bool exit_requested_rev = true; static bool exit_requested_rev = true;
static Config inactive_config;
if (show_demo_window) { if (show_demo_window) {
ImGui::ShowDemoWindow(&show_demo_window); ImGui::ShowDemoWindow(&show_demo_window);
} }
if (show_settings_window) { if (show_settings_window) {
settings_window(inactive_config, &show_settings_window); settings_window(config, config_write_timer, &show_settings_window);
} else if (save_settings_when_settings_window_closed) { }
active_config = inactive_config; if (*config_write_timer > 0.0f) {
save_settings_when_settings_window_closed = false; *config_write_timer -= ImGui::GetIO().DeltaTime;
if (*config_write_timer <= 0.0f) {
write_config_and_update_structures(config);
}
} }
if (!exit_requested_rev) { if (!exit_requested_rev) {
@ -70,5 +80,5 @@ void event_loop(Config& active_config, bool* run_event_loop) {
exit_requested_rev = true; exit_requested_rev = true;
} }
exit_modal_if_necessary(run_event_loop); 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" #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**) int main(int, char**)
{ {
Config config; Config config;
FILE* config_file = nullptr;
try { try {
config_file = fopen(get_config_path().c_str(), "r"); config = load_config();
if (config_file) {
config = parse_config(config_file);
} else if (errno != ENOENT) {
perror("Failed to open config file");
return 1;
}
} catch (const std::exception& e) { } 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; return 1;
} }
if (config_file) {
if (fclose(config_file)) { try {
perror("Failed to close config file"); 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 // Setup SDL
@ -82,7 +77,10 @@ int main(int, char**)
// Setup Dear ImGui context // Setup Dear ImGui context
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
ImGui::CreateContext(); 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_NavEnableKeyboard; // Enable Keyboard Controls
//io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
@ -112,8 +110,9 @@ int main(int, char**)
// Main loop // Main loop
bool run_event_loop = true; 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.) // 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. // 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. // - 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_ImplSDL2_NewFrame();
ImGui::NewFrame(); ImGui::NewFrame();
event_loop(config, &run_event_loop); event_loop(config, &config_write_timer, &run_event_loop);
// Rendering // Rendering
ImGui::Render(); ImGui::Render();
@ -154,5 +153,14 @@ int main(int, char**)
SDL_DestroyWindow(window); SDL_DestroyWindow(window);
SDL_Quit(); 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; return 0;
} }