From d567bd004a55660df5f18da8d79a6fc88be76ed1 Mon Sep 17 00:00:00 2001 From: whatdoineed2do/Ray Date: Sat, 11 Jul 2020 14:10:11 +0100 Subject: [PATCH] [db,conf,json] new backup endpoint --- src/conffile.c | 1 + src/db.c | 61 +++++++++++++++++++++++++++++++++++++++++++++ src/db.h | 2 ++ src/httpd_jsonapi.c | 22 ++++++++++++++++ 4 files changed, 86 insertions(+) diff --git a/src/conffile.c b/src/conffile.c index 6cc6173e..7f535ed9 100644 --- a/src/conffile.c +++ b/src/conffile.c @@ -46,6 +46,7 @@ static cfg_opt_t sec_general[] = { CFG_STR("uid", "nobody", CFGF_NONE), CFG_STR("db_path", STATEDIR "/cache/" PACKAGE "/songs3.db", CFGF_NONE), + CFG_STR("db_backup_path", NULL, CFGF_NONE), CFG_STR("logfile", STATEDIR "/log/" PACKAGE ".log", CFGF_NONE), CFG_INT_CB("loglevel", E_LOG, CFGF_NONE, &cb_loglevel), CFG_STR("admin_password", NULL, CFGF_NONE), diff --git a/src/db.c b/src/db.c index 25ec6673..8635d010 100644 --- a/src/db.c +++ b/src/db.c @@ -6946,6 +6946,67 @@ db_statements_prepare(void) return 0; } +int +db_backup() +{ + int ret; + sqlite3 *backup_hdl; + sqlite3_backup *backup; + const char *backup_path; + + char resolved_bp[PATH_MAX]; + char resolved_dbp[PATH_MAX]; + + backup_path = cfg_getstr(cfg_getsec(cfg, "general"), "db_backup_path"); + if (!backup_path) + { + DPRINTF(E_LOG, L_DB, "Backup not enabled, 'db_backup_path' is unset\n"); + return -2; + } + + if (realpath(db_path, resolved_dbp) == NULL || realpath(backup_path, resolved_bp) == NULL) + { + DPRINTF(E_LOG, L_DB, "Failed to resolve real path of db/backup path: %s\n", strerror(errno)); + goto error; + } + + if (strcmp(resolved_bp, resolved_dbp) == 0) + { + DPRINTF(E_LOG, L_DB, "Backup path same as main db path, ignoring\n"); + return -2; + } + + DPRINTF(E_INFO, L_DB, "Backup starting...\n"); + + ret = sqlite3_open(backup_path, &backup_hdl); + if (ret != SQLITE_OK) + { + DPRINTF(E_WARN, L_DB, "Failed to create backup '%s': %s\n", backup_path, sqlite3_errmsg(backup_hdl)); + goto error; + } + + backup = sqlite3_backup_init(backup_hdl, "main", hdl, "main"); + if (!backup) + { + DPRINTF(E_WARN, L_DB, "Failed to initiate backup '%s': %s\n", backup_path, sqlite3_errmsg(backup_hdl)); + goto error; + } + + ret = sqlite3_backup_step(backup, -1); + sqlite3_backup_finish(backup); + sqlite3_close(backup_hdl); + + if (ret == SQLITE_DONE || ret == SQLITE_OK) + DPRINTF(E_INFO, L_DB, "Backup complete to '%s'\n", backup_path); + else + DPRINTF(E_WARN, L_DB, "Failed to complete backup '%s': %s (%d)\n", backup_path, sqlite3_errstr(ret), ret); + + return ret; + +error: + return -1; +} + int db_perthread_init(void) { diff --git a/src/db.h b/src/db.h index 22084fce..cfb8efff 100644 --- a/src/db.h +++ b/src/db.h @@ -938,6 +938,8 @@ db_watch_enum_end(struct watch_enum *we); int db_watch_enum_fetchwd(struct watch_enum *we, uint32_t *wd); +int +db_backup(); int db_perthread_init(void); diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 89a9d2e0..6b17fdf8 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -4213,6 +4213,24 @@ jsonapi_reply_search(struct httpd_request *hreq) return HTTP_OK; } +static int +jsonapi_reply_library_backup(struct httpd_request *hreq) +{ + int ret; + ret = db_backup(); + + if (ret < 0) + { + if (ret == -2) + return HTTP_SERVUNAVAIL; // not enabled by config + + return HTTP_INTERNAL; + } + + return HTTP_OK; +} + + static struct httpd_uri_map adm_handlers[] = { { EVHTTP_REQ_GET, "^/api/config$", jsonapi_reply_config }, @@ -4284,6 +4302,7 @@ static struct httpd_uri_map adm_handlers[] = { EVHTTP_REQ_GET, "^/api/library/count$", jsonapi_reply_library_count }, { EVHTTP_REQ_GET, "^/api/library/files$", jsonapi_reply_library_files }, { EVHTTP_REQ_POST, "^/api/library/add$", jsonapi_reply_library_add }, + { EVHTTP_REQ_PUT, "^/api/library/backup$", jsonapi_reply_library_backup }, { EVHTTP_REQ_GET, "^/api/search$", jsonapi_reply_search }, @@ -4344,6 +4363,9 @@ jsonapi_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) case HTTP_NOTFOUND: /* 404 Not Found */ httpd_send_error(req, status_code, "Not Found"); break; + case HTTP_SERVUNAVAIL: /* 503 */ + httpd_send_error(req, status_code, "Service Unavailable"); + break; case HTTP_INTERNAL: /* 500 Internal Server Error */ default: httpd_send_error(req, HTTP_INTERNAL, "Internal Server Error");