DVX_GUI/core/dvxPrefs.c

450 lines
10 KiB
C

// dvxPrefs.c -- INI-based preferences system (read/write)
//
// Handle-based: each PrefsHandleT holds its own entry array and file
// path. Multiple INI files can be open simultaneously.
#include "dvxPrefs.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "dvxMem.h"
#include "thirdparty/stb_ds_wrap.h"
// ============================================================
// Internal types
// ============================================================
typedef struct {
char *section;
char *key;
char *value;
} PrefsEntryT;
// Comment lines are stored to preserve them on save. A comment has
// key=NULL and value=the full line text (including the ; prefix).
// Section headers have key=NULL and value=NULL.
struct PrefsHandleT {
PrefsEntryT *entries; // stb_ds dynamic array
char *filePath; // path used by prefsSave
};
// ============================================================
// Helpers
// ============================================================
static char *dupStr(const char *s) {
if (!s) {
return NULL;
}
size_t len = strlen(s);
char *d = (char *)malloc(len + 1);
if (d) {
memcpy(d, s, len + 1);
}
return d;
}
static void freeEntry(PrefsEntryT *e) {
free(e->section);
free(e->key);
free(e->value);
e->section = NULL;
e->key = NULL;
e->value = NULL;
}
static int strcmpci(const char *a, const char *b) {
for (;;) {
int d = tolower((unsigned char)*a) - tolower((unsigned char)*b);
if (d != 0 || !*a) {
return d;
}
a++;
b++;
}
}
static int32_t findEntry(PrefsHandleT *h, const char *section, const char *key) {
for (int32_t i = 0; i < arrlen(h->entries); i++) {
PrefsEntryT *e = &h->entries[i];
if (e->key && e->section &&
strcmpci(e->section, section) == 0 &&
strcmpci(e->key, key) == 0) {
return i;
}
}
return -1;
}
static int32_t findSection(PrefsHandleT *h, const char *section) {
for (int32_t i = 0; i < arrlen(h->entries); i++) {
PrefsEntryT *e = &h->entries[i];
if (!e->key && !e->value && e->section &&
strcmpci(e->section, section) == 0) {
return i;
}
}
return -1;
}
static char *trimInPlace(char *buf) {
while (*buf == ' ' || *buf == '\t') {
buf++;
}
char *end = buf + strlen(buf) - 1;
while (end >= buf && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) {
*end-- = '\0';
}
return buf;
}
// ============================================================
// prefsClose
// ============================================================
void prefsClose(PrefsHandleT *h) {
if (!h) {
return;
}
for (int32_t i = 0; i < arrlen(h->entries); i++) {
freeEntry(&h->entries[i]);
}
arrfree(h->entries);
free(h->filePath);
free(h);
}
// ============================================================
// prefsCreate
// ============================================================
PrefsHandleT *prefsCreate(void) {
PrefsHandleT *h = (PrefsHandleT *)calloc(1, sizeof(PrefsHandleT));
return h;
}
// ============================================================
// prefsGetBool
// ============================================================
bool prefsGetBool(PrefsHandleT *h, const char *section, const char *key, bool defaultVal) {
const char *val = prefsGetString(h, section, key, NULL);
if (!val) {
return defaultVal;
}
char c = (char)tolower((unsigned char)val[0]);
if (c == 't' || c == 'y' || c == '1') {
return true;
}
if (c == 'f' || c == 'n' || c == '0') {
return false;
}
return defaultVal;
}
// ============================================================
// prefsGetInt
// ============================================================
int32_t prefsGetInt(PrefsHandleT *h, const char *section, const char *key, int32_t defaultVal) {
const char *val = prefsGetString(h, section, key, NULL);
if (!val) {
return defaultVal;
}
char *end = NULL;
long n = strtol(val, &end, 10);
if (end == val) {
return defaultVal;
}
return (int32_t)n;
}
// ============================================================
// prefsGetString
// ============================================================
const char *prefsGetString(PrefsHandleT *h, const char *section, const char *key, const char *defaultVal) {
if (!h) {
return defaultVal;
}
int32_t idx = findEntry(h, section, key);
if (idx < 0) {
return defaultVal;
}
return h->entries[idx].value;
}
// ============================================================
// prefsLoad
// ============================================================
PrefsHandleT *prefsLoad(const char *filename) {
PrefsHandleT *h = prefsCreate();
if (!h) {
return NULL;
}
h->filePath = dupStr(filename);
FILE *fp = fopen(filename, "rb");
if (!fp) {
return h;
}
char line[512];
char *currentSection = dupStr("");
while (fgets(line, sizeof(line), fp)) {
char *end = line + strlen(line) - 1;
while (end >= line && (*end == '\r' || *end == '\n' || *end == ' ' || *end == '\t')) {
*end-- = '\0';
}
char *p = line;
while (*p == ' ' || *p == '\t') {
p++;
}
if (*p == '\0') {
PrefsEntryT e = {0};
e.section = dupStr(currentSection);
e.value = dupStr("");
arrput(h->entries, e);
continue;
}
if (*p == ';' || *p == '#') {
PrefsEntryT e = {0};
e.section = dupStr(currentSection);
e.value = dupStr(line);
arrput(h->entries, e);
continue;
}
if (*p == '[') {
char *close = strchr(p, ']');
if (close) {
*close = '\0';
free(currentSection);
currentSection = dupStr(trimInPlace(p + 1));
PrefsEntryT e = {0};
e.section = dupStr(currentSection);
arrput(h->entries, e);
}
continue;
}
char *eq = strchr(p, '=');
if (eq) {
*eq = '\0';
PrefsEntryT e = {0};
e.section = dupStr(currentSection);
e.key = dupStr(trimInPlace(p));
e.value = dupStr(trimInPlace(eq + 1));
arrput(h->entries, e);
}
}
free(currentSection);
fclose(fp);
return h;
}
// ============================================================
// prefsRemove
// ============================================================
void prefsRemove(PrefsHandleT *h, const char *section, const char *key) {
if (!h) {
return;
}
int32_t idx = findEntry(h, section, key);
if (idx >= 0) {
freeEntry(&h->entries[idx]);
arrdel(h->entries, idx);
}
}
// ============================================================
// prefsSave
// ============================================================
bool prefsSave(PrefsHandleT *h) {
if (!h || !h->filePath) {
return false;
}
return prefsSaveAs(h, h->filePath);
}
// ============================================================
// prefsSaveAs
// ============================================================
bool prefsSaveAs(PrefsHandleT *h, const char *filename) {
if (!h) {
return false;
}
FILE *fp = fopen(filename, "wb");
if (!fp) {
return false;
}
for (int32_t i = 0; i < arrlen(h->entries); i++) {
PrefsEntryT *e = &h->entries[i];
if (!e->key && e->value) {
fprintf(fp, "%s\r\n", e->value);
continue;
}
if (!e->key && !e->value) {
fprintf(fp, "[%s]\r\n", e->section);
continue;
}
if (e->key && e->value) {
fprintf(fp, "%s = %s\r\n", e->key, e->value);
}
}
fclose(fp);
return true;
}
// ============================================================
// prefsSetBool
// ============================================================
void prefsSetBool(PrefsHandleT *h, const char *section, const char *key, bool value) {
prefsSetString(h, section, key, value ? "true" : "false");
}
// ============================================================
// prefsSetInt
// ============================================================
void prefsSetInt(PrefsHandleT *h, const char *section, const char *key, int32_t value) {
char buf[32];
snprintf(buf, sizeof(buf), "%ld", (long)value);
prefsSetString(h, section, key, buf);
}
// ============================================================
// prefsSetString
// ============================================================
void prefsSetString(PrefsHandleT *h, const char *section, const char *key, const char *value) {
if (!h) {
return;
}
int32_t idx = findEntry(h, section, key);
if (idx >= 0) {
free(h->entries[idx].value);
h->entries[idx].value = dupStr(value);
return;
}
int32_t secIdx = findSection(h, section);
if (secIdx < 0) {
if (arrlen(h->entries) > 0) {
PrefsEntryT blank = {0};
blank.section = dupStr(section);
blank.value = dupStr("");
arrput(h->entries, blank);
}
PrefsEntryT secEntry = {0};
secEntry.section = dupStr(section);
arrput(h->entries, secEntry);
secIdx = arrlen(h->entries) - 1;
}
int32_t insertAt = secIdx + 1;
while (insertAt < arrlen(h->entries)) {
PrefsEntryT *e = &h->entries[insertAt];
if (!e->key && !e->value && e->section &&
strcmpci(e->section, section) != 0) {
break;
}
if (e->section && strcmpci(e->section, section) != 0) {
break;
}
insertAt++;
}
PrefsEntryT newEntry = {0};
newEntry.section = dupStr(section);
newEntry.key = dupStr(key);
newEntry.value = dupStr(value);
arrins(h->entries, insertAt, newEntry);
}