refactor: clean up config parsing with minor QoL tweaks

This commit is contained in:
frosty 2026-09-06 17:19:08 -04:00
commit f41fa9e4b3
No known key found for this signature in database
GPG key ID: 32DCAC76C4388DBF
3 changed files with 154 additions and 92 deletions

View file

@ -1,112 +1,189 @@
#include "Config.h"
#include <errno.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
typedef enum {
CONFIG_STRING,
CONFIG_INTEGER,
CONFIG_BOOLEAN,
} ConfigValueType;
typedef struct {
const char *section;
const char *key;
ConfigValueType type;
size_t offset;
size_t size;
} ConfigOption;
#define STRING_OPTION(section_name, key_name, field) \
{section_name, key_name, CONFIG_STRING, offsetof(Config, field), \
sizeof(((Config *)0)->field)}
#define INTEGER_OPTION(section_name, key_name, field) \
{section_name, key_name, CONFIG_INTEGER, offsetof(Config, field), 0}
#define BOOLEAN_OPTION(section_name, key_name, field) \
{section_name, key_name, CONFIG_BOOLEAN, offsetof(Config, field), 0}
static const ConfigOption CONFIG_OPTIONS[] = {
STRING_OPTION("server", "host", host),
INTEGER_OPTION("server", "port", port),
STRING_OPTION("server", "locale", default_locale),
STRING_OPTION("proxy", "proxy", proxy),
STRING_OPTION("proxy", "list_file", proxy_list_file),
INTEGER_OPTION("proxy", "max_retries", max_proxy_retries),
BOOLEAN_OPTION("proxy", "randomize_username", randomize_username),
BOOLEAN_OPTION("proxy", "randomize_password", randomize_password),
STRING_OPTION("cache", "dir", cache_dir),
INTEGER_OPTION("cache", "ttl_search", cache_ttl_search),
INTEGER_OPTION("cache", "ttl_infobox", cache_ttl_infobox),
INTEGER_OPTION("cache", "ttl_image", cache_ttl_image),
STRING_OPTION("engines", "engines", engines),
STRING_OPTION("engines", "yacy_instance", yacy_instance),
INTEGER_OPTION("rate_limit", "search_requests", rate_limit_search_requests),
INTEGER_OPTION("rate_limit", "search_interval", rate_limit_search_interval),
INTEGER_OPTION("rate_limit", "images_requests", rate_limit_images_requests),
INTEGER_OPTION("rate_limit", "images_interval", rate_limit_images_interval),
};
void config_set_defaults(Config *config) {
if (!config) {
return;
}
*config = (Config){
.host = DEFAULT_HOST,
.port = DEFAULT_PORT,
.default_locale = "en_gb",
.max_proxy_retries = DEFAULT_MAX_PROXY_RETRIES,
.cache_dir = DEFAULT_CACHE_DIR,
.cache_ttl_search = DEFAULT_CACHE_TTL_SEARCH,
.cache_ttl_infobox = DEFAULT_CACHE_TTL_INFOBOX,
.cache_ttl_image = DEFAULT_CACHE_TTL_IMAGE,
};
}
static char *trim(char *value) {
while (*value == ' ' || *value == '\t')
while (*value == ' ' || *value == '\t') {
value++;
}
char *end = value + strlen(value);
while (end > value && (end[-1] == ' ' || end[-1] == '\t'))
while (end > value && (end[-1] == ' ' || end[-1] == '\t')) {
end--;
}
*end = '\0';
return value;
}
static char *unquote(char *value) {
size_t length = strlen(value);
if (length >= 2 && (value[0] == '"' || value[0] == '\'') &&
value[length - 1] == value[0]) {
value[length - 1] = '\0';
return value + 1;
}
return value;
}
static int parse_integer(const char *value, int *result) {
char *end;
errno = 0;
long parsed = strtol(value, &end, 10);
if (value == end || *end != '\0' || errno == ERANGE || parsed < INT_MIN ||
parsed > INT_MAX) {
return -1;
}
*result = (int)parsed;
return 0;
}
static int parse_boolean(const char *value, int *result) {
if (strcmp(value, "1") == 0 || strcasecmp(value, "true") == 0 ||
strcasecmp(value, "yes") == 0 || strcasecmp(value, "on") == 0) {
*result = 1;
return 0;
}
if (strcmp(value, "0") == 0 || strcasecmp(value, "false") == 0 ||
strcasecmp(value, "no") == 0 || strcasecmp(value, "off") == 0) {
*result = 0;
return 0;
}
return -1;
}
static void set_option(Config *config, const char *section, const char *key,
const char *value) {
size_t option_count = sizeof(CONFIG_OPTIONS) / sizeof(CONFIG_OPTIONS[0]);
for (size_t i = 0; i < option_count; i++) {
const ConfigOption *option = &CONFIG_OPTIONS[i];
if (strcmp(section, option->section) != 0 ||
strcmp(key, option->key) != 0) {
continue;
}
void *destination = (char *)config + option->offset;
if (option->type == CONFIG_STRING) {
snprintf(destination, option->size, "%s", value);
} else if (option->type == CONFIG_INTEGER) {
parse_integer(value, destination);
} else {
parse_boolean(value, destination);
}
return;
}
}
int load_config(const char *filename, Config *config) {
if (!filename || !config) {
return -1;
}
FILE *file = fopen(filename, "r");
if (!file) {
return -1;
}
char line[512];
char line[1024];
char section[64] = "";
while (fgets(line, sizeof(line), file)) {
line[strcspn(line, "\r\n")] = 0;
line[strcspn(line, "\r\n")] = '\0';
char *content = trim(line);
if (line[0] == '\0' || line[0] == '#' || line[0] == ';') {
if (*content == '\0' || *content == '#' || *content == ';') {
continue;
}
if (line[0] == '[') {
char *end = strchr(line, ']');
if (*content == '[') {
char *end = strchr(content + 1, ']');
if (end) {
*end = '\0';
snprintf(section, sizeof(section), "%.*s", (int)(sizeof(section) - 1),
line + 1);
section[sizeof(section) - 1] = '\0';
snprintf(section, sizeof(section), "%s", trim(content + 1));
}
continue;
}
char *delimiter = strchr(line, '=');
if (delimiter) {
*delimiter = '\0';
char *key = trim(line);
char *value = trim(delimiter + 1);
char *value_end = value + strlen(value);
while (value_end > value &&
(value_end[-1] == '"' || value_end[-1] == '\''))
*--value_end = '\0';
while (*value == '"' || *value == '\'')
value++;
if (strcmp(section, "server") == 0) {
if (strcmp(key, "host") == 0) {
snprintf(config->host, sizeof(config->host), "%s", value);
} else if (strcmp(key, "port") == 0) {
config->port = atoi(value);
} else if (strcmp(key, "locale") == 0) {
snprintf(config->default_locale, sizeof(config->default_locale), "%s",
value);
}
} else if (strcmp(section, "proxy") == 0) {
if (strcmp(key, "proxy") == 0) {
snprintf(config->proxy, sizeof(config->proxy), "%s", value);
} else if (strcmp(key, "list_file") == 0) {
snprintf(config->proxy_list_file, sizeof(config->proxy_list_file),
"%s", value);
} else if (strcmp(key, "max_retries") == 0) {
config->max_proxy_retries = atoi(value);
} else if (strcmp(key, "randomize_username") == 0) {
config->randomize_username = atoi(value);
} else if (strcmp(key, "randomize_password") == 0) {
config->randomize_password = atoi(value);
}
} else if (strcmp(section, "cache") == 0) {
if (strcmp(key, "dir") == 0) {
snprintf(config->cache_dir, sizeof(config->cache_dir), "%s", value);
} else if (strcmp(key, "ttl_search") == 0) {
config->cache_ttl_search = atoi(value);
} else if (strcmp(key, "ttl_infobox") == 0) {
config->cache_ttl_infobox = atoi(value);
} else if (strcmp(key, "ttl_image") == 0) {
config->cache_ttl_image = atoi(value);
}
} else if (strcmp(section, "engines") == 0) {
if (strcmp(key, "engines") == 0) {
snprintf(config->engines, sizeof(config->engines), "%s", value);
} else if (strcmp(key, "yacy_instance") == 0) {
snprintf(config->yacy_instance, sizeof(config->yacy_instance), "%s",
value);
}
} else if (strcmp(section, "rate_limit") == 0) {
if (strcmp(key, "search_requests") == 0) {
config->rate_limit_search_requests = atoi(value);
} else if (strcmp(key, "search_interval") == 0) {
config->rate_limit_search_interval = atoi(value);
} else if (strcmp(key, "images_requests") == 0) {
config->rate_limit_images_requests = atoi(value);
} else if (strcmp(key, "images_interval") == 0) {
config->rate_limit_images_interval = atoi(value);
}
}
char *delimiter = strchr(content, '=');
if (!delimiter) {
continue;
}
*delimiter = '\0';
char *key = trim(content);
char *value = unquote(trim(delimiter + 1));
set_option(config, section, key, value);
}
int result = ferror(file) ? -1 : 0;
fclose(file);
return 0;
return result;
}

View file

@ -54,6 +54,7 @@ typedef struct {
int rate_limit_images_interval;
} Config;
void config_set_defaults(Config *config);
int load_config(const char *filename, Config *config);
#endif

View file

@ -65,24 +65,8 @@ int main() {
curl_global_init(CURL_GLOBAL_DEFAULT);
Config cfg = {.host = DEFAULT_HOST,
.port = DEFAULT_PORT,
.default_locale = "en_gb",
.proxy = "",
.proxy_list_file = "",
.max_proxy_retries = DEFAULT_MAX_PROXY_RETRIES,
.randomize_username = 0,
.randomize_password = 0,
.cache_dir = DEFAULT_CACHE_DIR,
.cache_ttl_search = DEFAULT_CACHE_TTL_SEARCH,
.cache_ttl_infobox = DEFAULT_CACHE_TTL_INFOBOX,
.cache_ttl_image = DEFAULT_CACHE_TTL_IMAGE,
.engines = "",
.yacy_instance = "",
.rate_limit_search_requests = 0,
.rate_limit_search_interval = 0,
.rate_limit_images_requests = 0,
.rate_limit_images_interval = 0};
Config cfg;
config_set_defaults(&cfg);
if (load_config("config.ini", &cfg) != 0) {
fprintf(stderr, "[WARN] Could not load config file, using defaults\n");