webui: edit categories (rename + save path, incl. Uncategorized)

Add POST /api/categories/edit. Renames a category, updating its save
path and reassigning every torrent that referenced the old name; the
empty-named Uncategorized pseudo-category can have its save path set
but not renamed. Changes persist via the daemon taxonomy.

Closes ISSUES #2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ookami125 2026-06-23 00:29:16 -04:00
parent 4708db151d
commit f6c933fd1c
2 changed files with 50 additions and 1 deletions

View file

@ -1734,6 +1734,52 @@ static void api_categories(int fd, const char *body, size_t len, bool remove) {
json_decref(reply);
}
/* POST /api/categories/edit — rename a category and/or change its save path,
* reassigning every torrent that referenced the old name. The empty-named
* "Uncategorized" pseudo-category can have its savePath set but not renamed. */
static void api_category_edit(int fd, const char *body, size_t len) {
json_t *req = read_body_json(body, len);
const char *name = json_string_value(json_object_get(req, "name"));
const char *new_name = json_string_value(json_object_get(req, "newName"));
const char *save_path = json_string_value(json_object_get(req, "savePath"));
if (!name) name = "";
if (!save_path) save_path = "";
bool rename = new_name && *new_name && *name && strcmp(new_name, name) != 0;
const char *target = rename ? new_name : name;
pthread_mutex_lock(&g_webui.meta_lock);
int idx = find_category(name);
if (idx >= 0) {
json_t *cat = json_array_get(g_webui.categories, (size_t)idx);
json_object_set_new(cat, "name", json_string(target));
json_object_set_new(cat, "savePath", json_string(save_path));
} else if (*target || *save_path) {
/* The category didn't exist yet (e.g. setting Uncategorized's path, or
* editing a name that was only implied by assignments). An empty name
* is allowed here: it holds Uncategorized's default save path. */
json_array_append_new(g_webui.categories, json_pack(
"{s:s,s:s}", "name", target, "savePath", save_path));
}
if (rename) {
const char *key;
json_t *entry;
json_object_foreach(g_webui.assignments, key, entry)
if (strcmp(json_string_or(entry, "category", ""), name) == 0)
json_object_set_new(entry, "category", json_string(target));
}
pthread_mutex_unlock(&g_webui.meta_lock);
if (rename) webui_sync_all_labels();
webui_sync_taxonomy();
json_decref(req);
pthread_mutex_lock(&g_webui.meta_lock);
json_t *reply = json_deep_copy(g_webui.categories);
pthread_mutex_unlock(&g_webui.meta_lock);
http_json(fd, 200, reply);
json_decref(reply);
}
/* POST /api/tags and /api/tags/delete */
static void api_tags(int fd, const char *body, size_t len, bool remove) {
json_t *req = read_body_json(body, len);
@ -1877,6 +1923,9 @@ static void handle_api(int fd, const char *method, char *path,
} else if (strcmp(path, "/api/categories/delete") == 0 &&
strcmp(method, "POST") == 0) {
api_categories(fd, body, body_len, true);
} else if (strcmp(path, "/api/categories/edit") == 0 &&
strcmp(method, "POST") == 0) {
api_category_edit(fd, body, body_len);
} else if (strcmp(path, "/api/tags") == 0 &&
strcmp(method, "POST") == 0) {
api_tags(fd, body, body_len, false);