refactor: clean up config parsing with minor QoL tweaks
This commit is contained in:
parent
acdff2d3a2
commit
f41fa9e4b3
3 changed files with 154 additions and 92 deletions
225
src/Config.c
225
src/Config.c
|
|
@ -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) {
|
||||
char *delimiter = strchr(content, '=');
|
||||
if (!delimiter) {
|
||||
continue;
|
||||
}
|
||||
|
||||
*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 *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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
20
src/Main.c
20
src/Main.c
|
|
@ -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");
|
||||
|
|
|
|||
Loading…
Reference in a new issue