// 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 #include #include #include #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); }