webui: store category/tag taxonomy in the webui DB

Move the category + tag lists out of the daemon (labels.json + the
get/set_label_taxonomy RPCs) and into the webui's own SQLite DB
(categories, tags tables). The daemon no longer persists webui taxonomy;
per-torrent labels still flow through set_labels for Lua.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ookami125 2026-06-23 22:05:22 -04:00
parent 1b79fe8079
commit 9474e6f31a
4 changed files with 160 additions and 146 deletions

View file

@ -114,13 +114,6 @@ struct daemon_state {
json_t *script_settings_schema; /* array of {key,label,type,default} */
json_t *script_settings; /* object: key -> value string (user-set)*/
char settings_file[PATH_MAX]; /* <state_dir>/script_settings.json */
/* Label taxonomy: the web UI's full category + tag lists (including ones
* created but not yet assigned). Web-layer schema; the daemon just persists
* it so they survive restarts. */
pthread_mutex_t taxonomy_lock;
json_t *label_categories; /* array of {name, savePath} */
json_t *label_tags; /* array of tag name strings */
char taxonomy_file[PATH_MAX]; /* <state_dir>/labels.json */
char data_dir[PATH_MAX]; /* the resolved state dir (for blobs) */
pthread_mutex_t blob_lock; /* guards webui_<key>.json blob files */
pthread_mutex_t torrent_lock;
@ -1320,89 +1313,6 @@ static json_t *rpc_set_script_settings(void *opaque, const json_t *params,
return script_status_json(state);
}
/* --- label taxonomy (web-layer category + tag lists, persisted here) ------- */
static void persist_taxonomy(daemon_state *state) {
if (!state->persist_enabled) return;
pthread_mutex_lock(&state->taxonomy_lock);
json_t *cats = state->label_categories
? json_deep_copy(state->label_categories) : json_array();
json_t *tags = state->label_tags
? json_deep_copy(state->label_tags) : json_array();
pthread_mutex_unlock(&state->taxonomy_lock);
json_t *doc = json_object();
if (!doc) { json_decref(cats); json_decref(tags); return; }
json_object_set_new(doc, "categories", cats ? cats : json_array());
json_object_set_new(doc, "tags", tags ? tags : json_array());
char tmp[PATH_MAX + 8];
snprintf(tmp, sizeof tmp, "%s.tmp", state->taxonomy_file);
if (json_dump_file(doc, tmp, JSON_INDENT(2)) != 0 ||
rename(tmp, state->taxonomy_file) != 0) {
NAUT_WARN("persist: write %s failed", state->taxonomy_file);
unlink(tmp);
}
json_decref(doc);
}
static void load_taxonomy(daemon_state *state) {
if (!state->persist_enabled) return;
json_error_t jerr;
json_t *doc = json_load_file(state->taxonomy_file, 0, &jerr);
if (!doc) return;
json_t *cats = json_object_get(doc, "categories");
json_t *tags = json_object_get(doc, "tags");
pthread_mutex_lock(&state->taxonomy_lock);
if (json_is_array(cats)) {
json_decref(state->label_categories);
state->label_categories = json_deep_copy(cats);
}
if (json_is_array(tags)) {
json_decref(state->label_tags);
state->label_tags = json_deep_copy(tags);
}
pthread_mutex_unlock(&state->taxonomy_lock);
json_decref(doc);
}
static json_t *rpc_get_label_taxonomy(void *opaque, const json_t *params,
naut_err *error) {
(void)params;
daemon_state *state = opaque;
pthread_mutex_lock(&state->taxonomy_lock);
json_t *cats = state->label_categories
? json_deep_copy(state->label_categories) : json_array();
json_t *tags = state->label_tags
? json_deep_copy(state->label_tags) : json_array();
pthread_mutex_unlock(&state->taxonomy_lock);
json_t *out = json_object();
if (!out) { json_decref(cats); json_decref(tags); *error = NAUT_ERR_NOMEM; return NULL; }
json_object_set_new(out, "categories", cats ? cats : json_array());
json_object_set_new(out, "tags", tags ? tags : json_array());
*error = NAUT_OK;
return out;
}
static json_t *rpc_set_label_taxonomy(void *opaque, const json_t *params,
naut_err *error) {
daemon_state *state = opaque;
if (!json_is_object(params)) { *error = NAUT_ERR_INVAL; return NULL; }
json_t *cats = json_object_get(params, "categories");
json_t *tags = json_object_get(params, "tags");
pthread_mutex_lock(&state->taxonomy_lock);
if (json_is_array(cats)) {
json_decref(state->label_categories);
state->label_categories = json_deep_copy(cats);
}
if (json_is_array(tags)) {
json_decref(state->label_tags);
state->label_tags = json_deep_copy(tags);
}
pthread_mutex_unlock(&state->taxonomy_lock);
persist_taxonomy(state);
*error = NAUT_OK;
return json_object();
}
/* --- generic web-UI blob store (RSS feeds, indexer config, ...) ------------ *
* The web layer owns these schemas; the daemon only persists them, one JSON
* document per key, under <state_dir>/webui_<key>.json. Keys are sanitized to a
@ -2393,8 +2303,6 @@ static bool register_commands(daemon_state *state) {
naut_rpc_register(state->rpc, "script_status", rpc_script_status, state) == NAUT_OK &&
naut_rpc_register(state->rpc, "update_script", rpc_update_script, state) == NAUT_OK &&
naut_rpc_register(state->rpc, "set_script_settings", rpc_set_script_settings, state) == NAUT_OK &&
naut_rpc_register(state->rpc, "get_label_taxonomy", rpc_get_label_taxonomy, state) == NAUT_OK &&
naut_rpc_register(state->rpc, "set_label_taxonomy", rpc_set_label_taxonomy, state) == NAUT_OK &&
naut_rpc_register(state->rpc, "get_webui_blob", rpc_get_webui_blob, state) == NAUT_OK &&
naut_rpc_register(state->rpc, "set_webui_blob", rpc_set_webui_blob, state) == NAUT_OK &&
naut_rpc_register(state->rpc, "unload_script", rpc_unload_script, state) == NAUT_OK &&
@ -2685,9 +2593,6 @@ static bool resolve_state_dir(daemon_state *state, const char *override) {
"%s/script_settings.json", dir) >=
sizeof state->settings_file)
return false;
if ((size_t)snprintf(state->taxonomy_file, sizeof state->taxonomy_file,
"%s/labels.json", dir) >= sizeof state->taxonomy_file)
return false;
if ((size_t)snprintf(state->data_dir, sizeof state->data_dir, "%s", dir) >=
sizeof state->data_dir)
return false;
@ -2796,8 +2701,6 @@ int main(int argc, char **argv) {
pthread_mutex_init(&state.script_lock, NULL);
pthread_mutex_init(&state.settings_lock, NULL);
load_script_settings(&state); /* user-set values; schema comes from the script */
pthread_mutex_init(&state.taxonomy_lock, NULL);
load_taxonomy(&state); /* persisted category + tag lists for the web UI */
pthread_mutex_init(&state.blob_lock, NULL);
state.events = naut_event_bus_create();
state.rpc = naut_rpc_registry_create();
@ -2881,9 +2784,6 @@ int main(int argc, char **argv) {
json_decref(state.script_settings);
json_decref(state.script_settings_schema);
pthread_mutex_destroy(&state.settings_lock);
json_decref(state.label_categories);
json_decref(state.label_tags);
pthread_mutex_destroy(&state.taxonomy_lock);
pthread_mutex_destroy(&state.blob_lock);
return 0;
}