Refactor config handling and related stuff
This commit is contained in:
parent
e7401a6bd1
commit
dfdb631b63
113
config.cpp
113
config.cpp
|
@ -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()");
|
||||
}
|
||||
|
|
8
config.h
8
config.h
|
@ -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);
|
||||
|
|
|
@ -3,36 +3,46 @@
|
|||
|
||||
#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)) {
|
||||
if (!ImGui::BeginPopupModal("Exit?", nullptr, ImGuiWindowFlags_NoResize)) {
|
||||
return;
|
||||
}
|
||||
ImGui::Text("Are you sure you want to exit?");
|
||||
|
||||
if (ImGui::Button("Yes", ImVec2(120, 0))) {
|
||||
|
@ -44,25 +54,25 @@ static inline void exit_modal_if_necessary(bool* run_event_loop) {
|
|||
}
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
42
main.cpp
42
main.cpp
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue