228 lines
7.3 KiB
C++
228 lines
7.3 KiB
C++
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <cmath>
|
|
#include <cfloat>
|
|
#include <sys/stat.h>
|
|
#include <string>
|
|
#include <memory>
|
|
#include <system_error>
|
|
|
|
#include "log.h"
|
|
#include "config.h"
|
|
|
|
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(), std::string("fopen(") + quote(path) + ')');
|
|
}
|
|
|
|
static void fclose_and_log(FILE* file) {
|
|
if (!fclose(file)) {
|
|
return;
|
|
}
|
|
std::system_error e = std::system_error(errno, std::generic_category(), "fclose()");
|
|
log("Failed to close a file", std::move(e));
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
static inline float to_float(const char* str) {
|
|
char* endptr;
|
|
|
|
while (isspace(str[0])) {
|
|
str++;
|
|
}
|
|
if (str[0] == '\0') {
|
|
throw std::invalid_argument("float string is empty");
|
|
}
|
|
|
|
errno = 0;
|
|
float res = strtof(str, &endptr);
|
|
if (res == HUGE_VALF && errno == ERANGE) {
|
|
throw std::overflow_error(std::string(str) + " is bigger than HUGE_VALF");
|
|
} else if (res == FLT_MIN && errno == ERANGE) {
|
|
throw std::underflow_error(std::string(str) + " is smaller than FLT_MIN");
|
|
} else if (endptr[0] != '\0') {
|
|
throw std::invalid_argument(std::string(str) + " has trailing text");
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static bool validate_font_size_or_log(float size) {
|
|
if (size <= 0.0f) {
|
|
log(std::string("Font size ") + std::to_string(size) + " is lower or equal to 0");
|
|
return false;
|
|
} else if (std::isnan(size)) {
|
|
log(std::string("Font size ") + std::to_string(size) + " is NaN");
|
|
return false;
|
|
} else if (std::isinf(size)) {
|
|
log(std::string("Font size ") + std::to_string(size) + " is infinity or negative infinity");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
std::string get_config_folder() {
|
|
const char* path;
|
|
|
|
path = getenv("XDG_CONFIG_HOME");
|
|
if (path) {
|
|
return std::string(path) + "/logmeow";
|
|
}
|
|
path = getenv("HOME");
|
|
if (path) {
|
|
return std::string(path) + "/.config/logmeow";
|
|
}
|
|
|
|
throw std::runtime_error("cannot find suitable config folder, please set XDG_CONFIG_HOME or HOME");
|
|
}
|
|
|
|
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 full_path = get_config_folder();
|
|
std::string path;
|
|
size_t pos = 0;
|
|
|
|
while (pos != std::string::npos) {
|
|
pos = full_path.find('/', pos);
|
|
if (pos != std::string::npos) {
|
|
pos++;
|
|
}
|
|
path = full_path.substr(0, pos);
|
|
if (!mkdir(path.c_str(), 0600)) {
|
|
continue;
|
|
}
|
|
if (errno == EEXIST) {
|
|
continue;
|
|
}
|
|
throw std::system_error(errno, std::generic_category(), std::string("mkdir(") + quote(path) + ')');
|
|
}
|
|
}
|
|
|
|
|
|
static inline void parse_config_line(Config& config, char* line) {
|
|
if (strncmp(line, "logcat_command=", 15 * sizeof(char)) == 0) {
|
|
config.logcat_command = &line[15];
|
|
} else if (strncmp(line, "normal_font_size=", 17 * sizeof(char)) == 0) {
|
|
float size = to_float(&line[17]);
|
|
if (validate_font_size_or_log(size)) {
|
|
config.normal_font_size = size;
|
|
}
|
|
} else if (strncmp(line, "monospace_font_size=", 20 * sizeof(char)) == 0) {
|
|
float size = to_float(&line[20]);
|
|
if (validate_font_size_or_log(size)) {
|
|
config.monospace_font_size = size;
|
|
}
|
|
} else if (line[0] != '\0') {
|
|
throw std::invalid_argument(std::string("unknown config line: ") + line);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
throw std::bad_alloc();
|
|
}
|
|
Config config;
|
|
|
|
while (true) {
|
|
errno = 0;
|
|
if (getline(&line, &line_capacity_size, file) < 0) {
|
|
int errsv = errno;
|
|
free(line);
|
|
if (errsv == ENOMEM) {
|
|
throw std::bad_alloc();
|
|
} else if (errsv != 0) {
|
|
throw std::system_error(errsv, std::generic_category(), "getline()");
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (line_capacity_size == 0 || line[0] == '\0' || line[0] == '#') {
|
|
continue;
|
|
}
|
|
if (char* newline = strchr(line, '\n')) {
|
|
*newline = '\0';
|
|
}
|
|
|
|
try {
|
|
parse_config_line(config, line);
|
|
} catch (const std::exception& e) {
|
|
free(line);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
if (!write("\nnormal_font_size=", file)) {
|
|
throw std::runtime_error("Failed to write normal font size key");
|
|
}
|
|
if (!write(std::to_string(config.normal_font_size), file)) {
|
|
throw std::runtime_error("Failed to write normal font size value");
|
|
}
|
|
if (!write("\nmonospace_font_size=", file)) {
|
|
throw std::runtime_error("Failed to write monospace font size key");
|
|
}
|
|
if (!write(std::to_string(config.monospace_font_size), file)) {
|
|
throw std::runtime_error("Failed to write monospace font size 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(),
|
|
std::string("rename(") + quote(tmp_config_file_path) + ", " + quote(config_file_path) + ')');
|
|
}
|