#include "drafts.h" #include #include #include #include #include #include #include #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); }