summaryrefslogtreecommitdiff
path: root/clients/drafts.c
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-22 12:43:52 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-22 12:43:52 +0200
commit07f5b140922d13519ad7a0d1dad67a9b403be9f1 (patch)
treeb6dde2ed6adb90c6423a5e585b693b908637e5ee /clients/drafts.c
parent760cb25bd220718e37d8b95016f30cdf8ef6acdc (diff)
downloadbokf-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.c249
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);
+}