diff options
| author | Anders Betts <anders.betts@gmail.com> | 2026-09-22 12:43:52 +0200 |
|---|---|---|
| committer | Anders Betts <anders.betts@gmail.com> | 2026-09-22 12:43:52 +0200 |
| commit | 07f5b140922d13519ad7a0d1dad67a9b403be9f1 (patch) | |
| tree | b6dde2ed6adb90c6423a5e585b693b908637e5ee /clients/drafts.c | |
| parent | 760cb25bd220718e37d8b95016f30cdf8ef6acdc (diff) | |
| download | bokf-07f5b140922d13519ad7a0d1dad67a9b403be9f1.tar.gz bokf-07f5b140922d13519ad7a0d1dad67a9b403be9f1.zip | |
tui: kundutkast, <UTKAST>, explicit Spara och F2-åtgärder
Diffstat (limited to 'clients/drafts.c')
| -rw-r--r-- | clients/drafts.c | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/clients/drafts.c b/clients/drafts.c new file mode 100644 index 0000000..f71e85c --- /dev/null +++ b/clients/drafts.c @@ -0,0 +1,249 @@ +#include "drafts.h" + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> + +#include "util.h" +#include "yyjson.h" + +static void mkdirs_for(const char *path) +{ + char tmp[640]; + + if (snprintf(tmp, sizeof tmp, "%s", path) >= (int)sizeof tmp) + return; + for (char *p = tmp + 1; *p; p++) { + if (*p != '/') + continue; + *p = '\0'; + mkdir(tmp, 0700); + *p = '/'; + } +} + +static void cache_path(char *buf, size_t n) +{ + const char *cache = getenv("XDG_CACHE_HOME"); + + if (cache && *cache) { + snprintf(buf, n, "%s/bokf/drafts.json", cache); + return; + } + const char *home = getenv("HOME"); + snprintf(buf, n, "%s/.cache/bokf/drafts.json", + home && *home ? home : "."); +} + +static void rec_clear(struct draft *r) +{ + free(r->entity); + free(r->id); + free(r->fields); +} + +void drafts_free(struct drafts *d) +{ + if (!d) + return; + for (size_t i = 0; i < d->n; i++) + rec_clear(&d->v[i]); + free(d->v); + d->v = NULL; + d->n = d->cap = 0; +} + +static struct draft *rec_find(const struct drafts *d, int64_t org, + const char *entity, const char *id) +{ + for (size_t i = 0; i < d->n; i++) { + struct draft *r = &d->v[i]; + + if (r->org == org && strcmp(r->entity, entity) == 0 && + strcmp(r->id, id) == 0) + return r; + } + return NULL; +} + +static void rec_put(struct drafts *d, int64_t org, const char *entity, + const char *id, const char *fields) +{ + struct draft *r = rec_find(d, org, entity, id); + + if (!r) { + if (d->n == d->cap) { + d->cap = d->cap ? d->cap * 2 : 8; + d->v = xrealloc(d->v, d->cap * sizeof *d->v); + } + r = &d->v[d->n++]; + memset(r, 0, sizeof *r); + r->org = org; + r->entity = xstrdup(entity); + r->id = xstrdup(id); + r->fields = xstrdup(fields && *fields ? fields : "{}"); + return; + } + free(r->fields); + r->fields = xstrdup(fields && *fields ? fields : "{}"); +} + +static void drafts_write(const struct drafts *d) +{ + yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); + yyjson_mut_val *root = yyjson_mut_obj(doc); + + yyjson_mut_doc_set_root(doc, root); + yyjson_mut_obj_add_int(doc, root, "version", 1); + yyjson_mut_val *arr = yyjson_mut_arr(doc); + for (size_t i = 0; i < d->n; i++) { + const struct draft *r = &d->v[i]; + yyjson_mut_val *o = yyjson_mut_arr_add_obj(doc, arr); + yyjson_mut_obj_add_int(doc, o, "org", r->org); + yyjson_mut_obj_add_strcpy(doc, o, "entity", r->entity); + yyjson_mut_obj_add_strcpy(doc, o, "id", r->id); + yyjson_mut_val *f = yyjson_mut_rawcpy(doc, r->fields); + yyjson_mut_obj_add_val(doc, o, "fields", f ? f : yyjson_mut_obj(doc)); + } + yyjson_mut_obj_add_val(doc, root, "drafts", arr); + char *json = yyjson_mut_write(doc, YYJSON_WRITE_PRETTY, NULL); + yyjson_mut_doc_free(doc); + if (!json) + return; + + mkdirs_for(d->path); + char tmp[640]; + if (snprintf(tmp, sizeof tmp, "%s.tmp", d->path) >= (int)sizeof tmp) { + free(json); + return; + } + int fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd >= 0) { + FILE *f = fdopen(fd, "w"); + if (f) { + fputs(json, f); + fclose(f); + rename(tmp, d->path); + } else { + close(fd); + } + } + free(json); +} + +static void drafts_load(struct drafts *d) +{ + FILE *f = fopen(d->path, "rb"); + if (!f) + return; + struct buf b; + char tmp[8192]; + size_t k; + buf_init(&b); + while ((k = fread(tmp, 1, sizeof tmp, f)) > 0) + buf_append(&b, tmp, k); + fclose(f); + yyjson_doc *doc = yyjson_read((const char *)b.p, b.len, 0); + buf_free(&b); + if (!doc) + return; + yyjson_val *arr = yyjson_obj_get(yyjson_doc_get_root(doc), "drafts"); + if (yyjson_is_arr(arr)) { + size_t idx, max; + yyjson_val *it; + yyjson_arr_foreach(arr, idx, max, it) { + yyjson_val *org = yyjson_obj_get(it, "org"); + yyjson_val *entity = yyjson_obj_get(it, "entity"); + yyjson_val *id = yyjson_obj_get(it, "id"); + yyjson_val *fields = yyjson_obj_get(it, "fields"); + if (!yyjson_is_int(org) || !yyjson_is_str(entity) || + !yyjson_is_str(id) || !yyjson_is_obj(fields)) + continue; + char *ft = yyjson_val_write(fields, 0, NULL); + rec_put(d, yyjson_get_sint(org), yyjson_get_str(entity), + yyjson_get_str(id), ft ? ft : "{}"); + free(ft); + } + } + yyjson_doc_free(doc); +} + +void drafts_init_path(struct drafts *d, const char *path) +{ + memset(d, 0, sizeof *d); + snprintf(d->path, sizeof d->path, "%s", path); + drafts_load(d); +} + +void drafts_init(struct drafts *d) +{ + char path[600]; + cache_path(path, sizeof path); + drafts_init_path(d, path); +} + +const char *drafts_get(const struct drafts *d, int64_t org, + const char *entity, const char *id) +{ + const struct draft *r = rec_find(d, org, entity, id); + return r ? r->fields : NULL; +} + +int drafts_have(const struct drafts *d, int64_t org, const char *entity, + const char *id) +{ + return rec_find(d, org, entity, id) != NULL; +} + +void drafts_put(struct drafts *d, int64_t org, const char *entity, + const char *id, const char *fields) +{ + rec_put(d, org, entity, id, fields); + drafts_write(d); +} + +void drafts_del(struct drafts *d, int64_t org, const char *entity, + const char *id) +{ + struct draft *r = rec_find(d, org, entity, id); + + if (!r) + return; + size_t i = (size_t)(r - d->v); + rec_clear(r); + memmove(&d->v[i], &d->v[i + 1], (d->n - i - 1) * sizeof *d->v); + d->n--; + drafts_write(d); +} + +size_t drafts_count(const struct drafts *d, int64_t org, const char *entity) +{ + size_t n = 0; + + for (size_t i = 0; i < d->n; i++) + if (d->v[i].org == org && strcmp(d->v[i].entity, entity) == 0) + n++; + return n; +} + +void drafts_foreach(const struct drafts *d, int64_t org, const char *entity, + void (*cb)(void *ud, const char *id, const char *fields), + void *ud) +{ + for (size_t i = 0; i < d->n; i++) + if (d->v[i].org == org && strcmp(d->v[i].entity, entity) == 0) + cb(ud, d->v[i].id, d->v[i].fields); +} + +char *drafts_new_id(void) +{ + static unsigned seq; + char buf[64]; + + snprintf(buf, sizeof buf, "ny-%lld-%u", (long long)time(NULL), ++seq); + return xstrdup(buf); +} |
