| 380 | #endif |
| 381 | |
| 382 | bool cbm_ui_config_save(const cbm_ui_config_t *cfg) { |
| 383 | if (!cfg || cfg->ui_port <= 0 || cfg->ui_port > 65535) { |
| 384 | cbm_log_error("ui.config.write_fail", "reason", "invalid_config"); |
| 385 | return false; |
| 386 | } |
| 387 | char path[CBM_SZ_1K]; |
| 388 | cbm_ui_config_path(path, (int)sizeof(path)); |
| 389 | |
| 390 | /* Ensure directory exists (recursive) */ |
| 391 | char dir[CBM_SZ_1K]; |
| 392 | bool directory_ready = config_parent_directory(path, dir, sizeof(dir)); |
| 393 | if (directory_ready && !cbm_is_dir(dir)) { |
| 394 | directory_ready = cbm_mkdir_p(dir, 0750) || cbm_is_dir(dir); |
| 395 | } |
| 396 | if (!directory_ready) { |
| 397 | cbm_log_error("ui.config.write_fail", "path", path, "reason", "create_directory"); |
| 398 | return false; |
| 399 | } |
| 400 | |
| 401 | yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); |
| 402 | yyjson_mut_val *root = doc ? yyjson_mut_obj(doc) : NULL; |
| 403 | bool serialized = doc && root; |
| 404 | if (serialized) { |
| 405 | yyjson_mut_doc_set_root(doc, root); |
| 406 | serialized = yyjson_mut_obj_add_bool(doc, root, "ui_enabled", cfg->ui_enabled) && |
| 407 | yyjson_mut_obj_add_int(doc, root, "ui_port", cfg->ui_port); |
| 408 | } |
| 409 | |
| 410 | size_t json_len = 0; |
| 411 | char *json = serialized ? yyjson_mut_write(doc, YYJSON_WRITE_PRETTY, &json_len) : NULL; |
| 412 | if (doc) { |
| 413 | yyjson_mut_doc_free(doc); |
| 414 | } |
| 415 | |
| 416 | if (!json) { |
| 417 | cbm_log_error("ui.config.write_fail", "reason", "serialize"); |
| 418 | return false; |
| 419 | } |
| 420 | |
| 421 | bool saved = config_write_atomic(path, json, json_len); |
| 422 | free(json); |
| 423 | if (!saved) { |
| 424 | cbm_log_error("ui.config.write_fail", "path", path, "reason", "atomic_publish"); |
| 425 | return false; |
| 426 | } |
| 427 | |
| 428 | cbm_log_debug("ui.config.saved", "path", path); |
| 429 | return true; |
| 430 | } |
no test coverage detected