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