diff --git a/inc/lektor/commands.h b/inc/lektor/commands.h index 7cc980f9952c6fd67e43540115cf0375b342b3d2..a923d8069f47e76b695bddaa7525b588b30fa448 100644 --- a/inc/lektor/commands.h +++ b/inc/lektor/commands.h @@ -89,4 +89,11 @@ bool command_set_playback_option(struct lkt_state *srv, enum lkt_playback_option opt, char *args[LKT_MESSAGE_ARGS_MAX]); - +/* Authentificate users */ +bool command_password(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS_MAX]); +bool command_user_add(sqlite3 *db, char *argv[LKT_MESSAGE_ARGS_MAX]); + +/* Program management control */ +bool command_restart(struct lkt_state *srv, size_t c); +bool command_kill(struct lkt_state *srv, size_t c); +bool command_rescan(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS_MAX]); diff --git a/inc/lektor/database.h b/inc/lektor/database.h index 8bf8a71ce041834370990f84a13770e260c1ffa8..a65a28a94a3ce7c483ba2a9b192ff8fe707491b7 100644 --- a/inc/lektor/database.h +++ b/inc/lektor/database.h @@ -47,7 +47,8 @@ bool database_sync_mpv_state(sqlite3 *db, mpv_handle **mpv); /* Update the database. */ bool database_update(sqlite3 *db, const char *kara_dir); -bool database_update_add(sqlite3 *db, const char *kara_path, struct kara_metadata *mdt, uint64_t id); +bool database_update_add(sqlite3 *db, const char *kara_path, struct kara_metadata *mdt, uint64_t id, + bool avail); /* Control the content of the queue. */ bool database_queue_add_id(sqlite3 *db, int id, int priority); @@ -121,3 +122,7 @@ bool database_plt_export(sqlite3 *db, const char *name, const char *file); bool database_plt_import(sqlite3 *db, const char *name, const char *file); bool database_plt_add_uri(sqlite3 *db, const char *name, struct lkt_uri_t *uri); + +/* User control, yeah, MPD authentification sucks. */ +bool database_user_authentificate(sqlite3 *db, const char *password); +bool database_user_add(sqlite3 *db, const char *username, const char *password); diff --git a/inc/lektor/mkv.h b/inc/lektor/mkv.h index 0528029e813832d8492270c676a51c250b47258f..dbc23e5079b9f95f84fab6d7c3f91bc197905fd7 100644 --- a/inc/lektor/mkv.h +++ b/inc/lektor/mkv.h @@ -37,10 +37,18 @@ struct kara_metadata { int song_number; }; +enum kara_action { + kara_action_none = 0, /* Do nothing */ + kara_action_add = (1 << 1), /* Add to database */ + kara_action_delete = (1 << 2), /* Delete from database */ + kara_action_unavail = (1 << 3), /* Mark kara as unavailable */ +}; + struct kara { - struct kara_metadata mdt; - char filename[PATH_MAX]; - size_t id; + enum kara_action action; /* What to do with this kara. */ + size_t id; /* Should never be NULL. NEVER!! */ + struct kara_metadata mdt; /* The metadata of the kara. */ + char filename[PATH_MAX]; /* The path to the matroska file. */ }; /* Reads the .mkv file at `filename` and stores its metadata in `dst`. diff --git a/inc/lektor/net.h b/inc/lektor/net.h index ff624a750c07d75f6a40da25dc379e45c7ad42f4..c1ce1b2031b62544440422b06de4b6a47790d3b4 100644 --- a/inc/lektor/net.h +++ b/inc/lektor/net.h @@ -72,5 +72,8 @@ void lkt_state_send(struct lkt_state *srv, size_t c, struct lkt_message *msg); * Return a pointer to the client's mask */ long long int *lkt_client_get_mask(struct lkt_state *srv, size_t c); +/* Register a client as authentificated. */ +bool lkt_client_auth(struct lkt_state *srv, size_t c, bool set_auth); + /* The server's listen function. */ int lkt_listen(lkt_config_t conf); diff --git a/inc/lektor/repo.h b/inc/lektor/repo.h index 4c7d4e330eb9fca157b31d7a84e96c4ccdf53f79..829011b95bdcf5d4d46f7b3a03a347eddbbf023b 100644 --- a/inc/lektor/repo.h +++ b/inc/lektor/repo.h @@ -14,6 +14,7 @@ struct lkt_repo { const char *name; const char *base_url; const char *kara_dir; + const char *get_all_json; const char *get_id_json; const char *get_id_file; const uint64_t version; @@ -28,6 +29,8 @@ int repo_join_thread(void); /* Get metadata of a kara. */ int repo_get_id(struct lkt_repo *const repo, const uint64_t id, struct kara_metadata *mdt); +int repo_get_alljson_sync(struct lkt_repo *const repo, struct json_object **json); +int repo_get_allid_async(void); /* Download a kara. */ int repo_download_id_sync(struct lkt_repo *const repo, sqlite3 *db, const uint64_t id, diff --git a/init.sql b/init.sql index b2ab788bc478403b5bb0878eaa184328e857450f..73fe4ef72962ca6da48cbb540e3e52f329761fc9 100644 --- a/init.sql +++ b/init.sql @@ -18,9 +18,11 @@ CREATE TABLE IF NOT EXISTS kara , is_new INTEGER NOT NULL , author_name TEXT , author_year INTEGER CHECK(author_year > 0) + , available INTEGER CHECK(available = 0 OR available = 1) DEFAULT 1 NOT NULL , string TEXT GENERATED ALWAYS AS ( song_type || ' - ' || language || ' / ' || source_name || ' - ' || category || - song_number || ' - ' || song_name || ' [ ' || author_name || ' ]' + song_number || ' - ' || song_name || ' [ ' || author_name || ' ]' || + CASE WHEN available = 0 THEN '(U)' ELSE '' END ) STORED ); @@ -77,6 +79,20 @@ CREATE TABLE IF NOT EXISTS queue ); +-- The user table +-- Used for the [password {passwd}] MPD command. The documentation can be found +-- here: https://www.musicpd.org/doc/html/protocol.html#connection-settings. +-- The password command is used to restrict access to specific commands. + +CREATE TABLE IF NOT EXISTS users + ( username TEXT NOT NULL UNIQUE + , password TEXT NOT NULL UNIQUE + , PRIMARY KEY (username, password) + ) WITHOUT ROWID; + +INSERT INTO users (username, password) VALUES ('sakura', 'hashire'); + + -- Some useful values: -- last_update is the timestamp of the last time the table of kara has been -- updated. This is so lektor doesn't have to read all kara in the filesystem, diff --git a/meson.build b/meson.build index 8d05c839462ec612e1affeb55141cb743a128f48..5a881e22ab03d0a8cc817d85fe770aa70ff49c1b 100644 --- a/meson.build +++ b/meson.build @@ -7,7 +7,8 @@ project( 'lektor' , 'warning_level=3' , 'werror=true' , 'strip=true' - , 'buildtype=debugoptimized' + , 'debug=true' + , 'buildtype=debug' , 'b_sanitize=undefined' ] ) @@ -34,6 +35,7 @@ core_sources = [ 'src/mkv/bufferfd.c' , 'src/database/find.c' , 'src/database/config.c' , 'src/database/playlist.c' + , 'src/database/user.c' , 'src/mkv/mkv.c' , 'src/net/command.c' , 'src/net/listen.c' @@ -59,11 +61,11 @@ core_deps = [ dependency('sqlite3', version : '>= 3.31.0') , dependency('threads', required : true) ] -lib = library( meson.project_name() - , files(core_sources) - , include_directories : includes - , dependencies : [ core_deps, libdl ] - ) +lib = static_library( meson.project_name() + , files(core_sources) + , include_directories : includes + , dependencies : [ core_deps, libdl ] + ) bin_deps = core_deps + [ declare_dependency( link_with: lib, include_directories: includes) ] diff --git a/src/commands.c b/src/commands.c index d1b487039c8bbcb5fded3150a8cbb7362b1a8ccd..b0db806537bf457c157122d91a7502f7404e98a2 100644 --- a/src/commands.c +++ b/src/commands.c @@ -4,6 +4,7 @@ #include <lektor/database.h> #include <lektor/net.h> #include <lektor/uri.h> +#include <lektor/repo.h> #include <errno.h> #include <linux/limits.h> @@ -13,6 +14,71 @@ #include <string.h> #include <strings.h> #include <limits.h> +#include <poll.h> +#include <unistd.h> + +#define SELF_EXECUTABLE_LINUX "/proc/self/exe" +#define SELF_EXECUTABLE_FREEBSD "/proc/curproc/file" +#define SELF_EXECUTABLE_SOLARIS "/proc/self/path/a.out" + +inline bool +command_restart(struct lkt_state *srv, size_t c) +{ + char exe[PATH_MAX]; + char *const argv[] = { exe, NULL }; + + if (!lkt_client_auth(srv, c, false)) { + fprintf(stderr, " ! command_restart: Failed to restart, user not authentificated %lu\n", c); + return false; + } + + if (readlink(SELF_EXECUTABLE_LINUX, exe, PATH_MAX - 1) > 0) { + fprintf(stderr, " * Restart lektord\n"); + close(srv->fds[0].fd); + execv(exe, argv); + } + + if (readlink(SELF_EXECUTABLE_FREEBSD, exe, PATH_MAX - 1) > 0) { + fprintf(stderr, " * Restart lektord\n"); + close(srv->fds[0].fd); + execv(exe, argv); + } + + if (readlink(SELF_EXECUTABLE_SOLARIS, exe, PATH_MAX - 1) > 0) { + fprintf(stderr, " * Restart lektord\n"); + close(srv->fds[0].fd); + execv(exe, argv); + } + + fprintf(stderr, " ! command_restart: Failed to exec lektor or OS not supported\n"); + abort(); +} + +bool +command_rescan(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS_MAX]) +{ + (void) argv; + + if (!lkt_client_auth(srv, c, false)) { + fprintf(stderr, " ! command_rescan: Failed, user %lu not authentificated\n", c); + return false; + } + + return ! repo_get_allid_async(); +} + +inline bool +command_kill(struct lkt_state *srv, size_t c) +{ + if (!lkt_client_auth(srv, c, false)) { + fprintf(stderr, " ! command_restart: Failed to restart, user not authentificated %lu\n", c); + return false; + } + + fprintf(stderr, " * Stopping lektord\n"); + close(srv->fds[0].fd); + exit(EXIT_SUCCESS); +} bool command_currentsong(struct lkt_state *srv, size_t c) @@ -946,3 +1012,38 @@ is_absolute: return database_queue_list_abs(srv->db, from, to, &callback_args, lkt_callback_send_row_v1); } + +bool +command_password(struct lkt_state *srv, size_t c, char *argv[LKT_MESSAGE_ARGS_MAX]) +{ + if (argv[0] == NULL || argv[1] != NULL) { + fprintf(stderr, " ! command_password: Invalid arguments, need only one argument\n"); + return false; + } + + if (database_user_authentificate(srv->db, argv[0])) { + fprintf(stderr, " . command_password: Authentificate user successfully for client %lu\n", c); + lkt_client_auth(srv, c, true); + return true; + } else { + fprintf(stderr, " . command_password: Failed to authentificate client %lu\n", c); + return false; + } +} + +bool +command_user_add(sqlite3 *db, char *argv[LKT_MESSAGE_ARGS_MAX]) +{ + if (argv[0] == NULL || argv[1] == NULL || argv[2] != NULL) { + fprintf(stderr, " ! command_user_add: Invalid arguments, need only two arguments\n"); + return false; + } + + if (database_user_add(db, argv[0], argv[1])) { + fprintf(stderr, " . command_user_add: Successfully added user %s\n", argv[0]); + return true; + } + + return false; + fprintf(stderr, " . command_user_add: Failed to add user %s\n", argv[0]); +} diff --git a/src/database/open.c b/src/database/open.c index 006499eef466e93ae191fde24a25d70802c750a7..cdc78a1298e64af7d07f918818f460596a327dc3 100644 --- a/src/database/open.c +++ b/src/database/open.c @@ -34,17 +34,17 @@ static const char *const SQL_MEM_SCHEM = #define HEAP_LIMIT_SOFT 100 * 1024 * 1024 #define HEAP_LIMIT_HARD 150 * 1024 * 1024 -static inline int -is_dbpath_invalid(const char *dbpath) +int +is_sql_str_invalid(const char *str) { - size_t len = strlen(dbpath); - return strcspn(dbpath, INVALID_CHARS_DBPATH) != len; + size_t len = strlen(str); + return strcspn(str, INVALID_CHARS_DBPATH) != len; } bool database_open(sqlite3 **db, const char *dbpath) { - if (is_dbpath_invalid(dbpath)) { + if (is_sql_str_invalid(dbpath)) { fprintf(stderr, " ! database_open: The database path %s is invalid\n", dbpath); return false; } diff --git a/src/database/queue.c b/src/database/queue.c index 6a2bbf2d3cc7bd0241ac5e258a0d83651ab29251..eb1d450949d3f9c51406d7517a97484937297d57 100644 --- a/src/database/queue.c +++ b/src/database/queue.c @@ -6,6 +6,9 @@ #define max(a, b) ((a) < (b) ? (b) : (a)) +/* Find in in database/open.c */ +extern int is_sql_str_invalid(const char *); + bool database_queue_state(sqlite3 *db, struct lkt_queue_state *res) { @@ -97,12 +100,17 @@ error: static bool queue_add_with_col_like_str(sqlite3 *db, const char *col, const char *val, int priority) { + if (is_sql_str_invalid(col)) { + fprintf(stderr, " ! queue_add_with_col_like_str: Column name %s is invalid\n", col); + return false; + } + char SQL[1024]; static const char *SQL_STMT = "INSERT INTO queue (kara_id, priority)" " SELECT id, ?" " FROM kara" - " WHERE %s LIKE ?" /* XXX: Here be carefull, may be subject to sql injections. */ + " WHERE %s LIKE ? AND available = 1" " ORDER BY RANDOM();"; bool status = false; sqlite3_stmt *stmt = NULL; @@ -137,6 +145,11 @@ error: static bool queue_insert_with_col_like_str(sqlite3 *db, const char *col, const char *val, int pos) { + if (is_sql_str_invalid(col)) { + fprintf(stderr, " ! queue_insert_with_col_like_str: Column name %s is invalid\n", col); + return false; + } + char SQL[4096]; static const char *SQL_STMT = "BEGIN TRANSACTION;" @@ -153,7 +166,7 @@ queue_insert_with_col_like_str(sqlite3 *db, const char *col, const char *val, in "INSERT INTO queue (kara_id, priority)" "SELECT id, priority" " FROM kara" - " WHERE %s LIKE ?" /* XXX: Here be carefull, may be subject to sql injections. */ + " WHERE %s LIKE ? AND available = 1" " ORDER BY RANDOM();" "INSERT INTO queue (kara_id, priority)" " SELECT kara_id_tmp, priority" @@ -211,7 +224,7 @@ database_queue_add_plt(sqlite3 *db, const char *plt_name, int priority) "INSERT INTO queue (kara_id, priority) " "SELECT kara.id, ?" " FROM kara" - " JOIN kara_playlist ON kara_id = kara.id" + " JOIN kara_playlist ON kara_id = kara.id AND kara.available = 1" " JOIN playlist ON playlist_id = playlist.id AND playlist.name = ?" " ORDER BY RANDOM();"; bool status = false; @@ -838,7 +851,9 @@ database_queue_list_from(sqlite3 *db, unsigned int count, void *args, const char *SQL_TEMPLATE = "SELECT kara.id, string" " FROM queue_" - " JOIN queue_state ON queue_.position >= queue_state.current" + " JOIN queue_state ON queue_.position >= CASE" + " WHEN queue_state.current IS NULL THEN 1" + " ELSE queue_state.current END" " JOIN kara ON kara_id = kara.id" " ORDER BY position" " LIMIT %d;"; diff --git a/src/database/update.c b/src/database/update.c index bae310eaae2c59db7fb1cf15816213eb0799c5c4..6320bb5a421745e775aed42fdb55072cc0fd4ce0 100644 --- a/src/database/update.c +++ b/src/database/update.c @@ -80,7 +80,7 @@ error: } bool -database_update_add(sqlite3 *db, const char *kara_path, struct kara_metadata *mdt, uint64_t id) +database_update_add(sqlite3 *db, const char *kara_path, struct kara_metadata *mdt, uint64_t id, bool avail) { if (db == NULL || kara_path == NULL || mdt == NULL) { fprintf(stderr, " ! database_add_kara: Invalid arguments\n"); @@ -89,9 +89,9 @@ database_update_add(sqlite3 *db, const char *kara_path, struct kara_metadata *md } static const char *SQL_STMT = - "INSERT INTO " - "kara (song_name, source_name, category, song_type, language, file_path, is_new, author_name, author_year, song_number, id)" - "SELECT ?, ?, ?, ?, ?, ?, ?, ?, strftime('%s','now'), ?, ?"; + "INSERT OR REPLACE INTO " + "kara (song_name, source_name, category, song_type, language, file_path, is_new, author_name, author_year, song_number, id, available)" + "SELECT ?, ?, ?, ?, ?, ?, ?, ?, strftime('%s','now'), ?, ?, ?"; sqlite3_stmt *stmt = NULL; int code = SQLITE_OK; bool ret = false; @@ -119,10 +119,11 @@ database_update_add(sqlite3 *db, const char *kara_path, struct kara_metadata *md (sqlite3_bind_text(stmt, 4, mdt->song_type, -1, 0) != SQLITE_OK) || (sqlite3_bind_text(stmt, 5, mdt->language, -1, 0) != SQLITE_OK) || (sqlite3_bind_text(stmt, 6, kara_path, -1, 0) != SQLITE_OK) || - (sqlite3_bind_int(stmt, 7, 0) != SQLITE_OK) || /* No new kara added (TODO). */ + (sqlite3_bind_int(stmt, 7, 0) != SQLITE_OK) /* TODO */ || /* No new kara added (TODO). */ (sqlite3_bind_text(stmt, 8, mdt->author_name, -1, 0) != SQLITE_OK) || (sqlite3_bind_int(stmt, 9, mdt->song_number) != SQLITE_OK) || - (sqlite3_bind_int(stmt, 10, id) != SQLITE_OK) + (sqlite3_bind_int(stmt, 10, id) != SQLITE_OK) || + (sqlite3_bind_int(stmt, 11, avail) != SQLITE_OK) ) { fprintf(stderr, " ! database_update_add: failed to bind argument to stmt statement for kara %s: %s\n", kara_path, sqlite3_errmsg(db)); diff --git a/src/database/user.c b/src/database/user.c new file mode 100644 index 0000000000000000000000000000000000000000..9ac954db857128ab989b9d4ecc10f99880afe2cf --- /dev/null +++ b/src/database/user.c @@ -0,0 +1,71 @@ +#define _POSIX_C_SOURCE 200809L + +#include <lektor/database.h> +#include <stdio.h> + +bool +database_user_authentificate(sqlite3 *db, const char *password) +{ + static const char *SQL_STMT = "SELECT username FROM users WHERE password = ?"; + sqlite3_stmt *stmt = 0; + bool ret = false; + + if (sqlite3_prepare_v2(db, SQL_STMT, -1, &stmt, 0) != SQLITE_OK) { + fprintf(stderr, " ! database_user_authentificate: Failed to prepare statement: %s\n", + sqlite3_errmsg(db)); + goto error; + } + + if (sqlite3_bind_text(stmt, 1, password, -1, 0) != SQLITE_OK) { + fprintf(stderr, " ! database_user_authentificate: Failed to bind password: %s\n", + sqlite3_errmsg(db)); + goto error; + } + + if (sqlite3_step(stmt) != SQLITE_ROW) { + fprintf(stderr, " ! database_user_authentificate: No user found\n"); + goto error; + } + + fprintf(stderr, " * User authentification was successfull for user %s\n", + sqlite3_column_text(stmt, 0)); + + ret = true; +error: + sqlite3_finalize(stmt); + return ret; +} + +bool +database_user_add(sqlite3 *db, const char *username, const char *password) +{ + static const char *SQL_STMT = "INSERT INTO users (username, password) VALUES (?, ?)"; + sqlite3_stmt *stmt = 0; + bool ret = false; + + if (sqlite3_prepare_v2(db, SQL_STMT, -1, &stmt, 0) != SQLITE_OK) { + fprintf(stderr, " ! database_user_add: Failed to prepare statement: %s\n", + sqlite3_errmsg(db)); + goto error; + } + + if (sqlite3_bind_text(stmt, 1, username, -1, 0) != SQLITE_OK && + sqlite3_bind_text(stmt, 2, password, -1, 0) != SQLITE_OK) { + fprintf(stderr, " ! database_user_add: Failed to bind : %s\n", + sqlite3_errmsg(db)); + goto error; + } + + if (sqlite3_step(stmt) != SQLITE_OK) { + fprintf(stderr, " ! database_user_add: Failed to add user %s: %s\n", + username, sqlite3_errmsg(db)); + goto error; + } + + fprintf(stderr, " * User %s added successfully\n", username); + + ret = true; +error: + sqlite3_finalize(stmt); + return ret; +} diff --git a/src/net/listen.c b/src/net/listen.c index a315ae840cea1b263aab00bd73d8afc0aecc7fd1..7c7dc5d59b4c8ee7c00e75d3542b188b8854d2a3 100644 --- a/src/net/listen.c +++ b/src/net/listen.c @@ -51,6 +51,8 @@ struct lkt_client { long long int mpd_idle_watch; bool request_close; + + bool authentificated; }; static bool @@ -134,6 +136,23 @@ handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd) switch (*lkt_client_get_mask(srv, c)) { case MPD_IDLE_NONE: + /* Commands that require authentification. */ + if (lkt_client_auth(srv, c, false)) { + if (!strcmp(cmd.name, "__adduser")) { + err = !command_user_add(srv->db, cmd.args); + break; + } else if (!strcmp(cmd.name, "__restart")) { + err = !command_restart(srv, c); + break; + } else if (!strcmp(cmd.name, "kill")) { + err = !command_kill(srv, c); + break; + } else if (!strcmp(cmd.name, "rescan")) { + err = !command_rescan(srv, c, cmd.args); + break; + } + } + /* Commands that are available if not in idle mode */ if (!strcmp(cmd.name, "currentsong")) err = !command_currentsong(srv, c); @@ -168,7 +187,7 @@ handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd) else if (!strcmp(cmd.name, "help")) err = !command_help(srv, c); - else if (!strcmp(cmd.name, "db-update")) + else if (!strcmp(cmd.name, "__dbupdate")) err = !command_update(srv, &srv->mpd_idle_events); else if (!strcmp(cmd.name, "add")) @@ -204,6 +223,9 @@ handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd) else if (!strcmp(cmd.name, "consume")) err = !command_set_playback_option(srv, c, lkt_playback_option_consume, cmd.args); + else if (!strcmp(cmd.name, "password")) + err = !command_password(srv, c, cmd.args); + else if (!strcmp(cmd.name, "idle")) { err = !command_idle(srv, c, &cmd); goto end_no_send_status; @@ -625,6 +647,12 @@ lkt_client_get_mask(struct lkt_state *srv, size_t c) return &(srv->clients[c - 1].mpd_idle_watch); } +inline bool +lkt_client_auth(struct lkt_state *srv, size_t c, bool set) +{ + return srv->clients[c - 1].authentificated |= set; +} + static inline void handle_repo_hevents(struct lkt_state *srv) { @@ -634,14 +662,38 @@ handle_repo_hevents(struct lkt_state *srv) if (repo_get_kara_async(&kara)) goto get_out; - if (!database_update_add(srv->db, kara->filename, &kara->mdt, kara->id)) { - fprintf(stderr, " ! handle_repo_hevents: Failed to add downloaded kara with id %lu and path %s\n", + switch (kara->action) { + /* Add the downloaded kara to the database. */ + case kara_action_add: + if (!database_update_add(srv->db, kara->filename, &kara->mdt, kara->id, true)) { + fprintf(stderr, " ! handle_repo_hevents: Failed to add downloaded kara with id %lu and path %s\n", + kara->id, kara->filename); + goto get_out; + } + + fprintf(stderr, " * Added kara %lu with path %s to database\n", kara->id, kara->filename); - goto get_out; + + break; + + /* Add the mdt of the kara to the database. Mark it unavailable. */ + case kara_action_unavail: + if (!database_update_add(srv->db, kara->filename, &kara->mdt, kara->id, false)) { + fprintf(stderr, " ! handle_repo_hevents: Failed to add kara with id %lu with flag unavailable\n", + kara->id); + goto get_out; + } + + fprintf(stderr, " * Added kara %lu to database and set it to unavailable\n", + kara->id); + + break; + + case kara_action_none: + default: + break; } - fprintf(stderr, " * Added kara %lu with path %s to database\n", - kara->id, kara->filename); free(kara); kara = NULL; } diff --git a/src/repo/async.c b/src/repo/async.c index 3f444548bf69d33f317526f41d5ae5982a427e35..36bd2e7949b6645ec8fb8eb200c6d942e100ef4e 100644 --- a/src/repo/async.c +++ b/src/repo/async.c @@ -12,26 +12,115 @@ #define LKT_DEFAULT_LIST_SIZE 10 static struct lkt_thread repo_thread; + +static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; static volatile int init = 0; static volatile int stop = 0; +static volatile int all_json = 0; int repo_join_thread(void) { + int ret = 1; + + if (pthread_mutex_lock(&mtx)) { + fprintf(stderr, " ! repo_join_thread: Failed to lock mutex\n"); + return 3; + } + if (!init) { fprintf(stderr, " ! repo_join_thread: repo thread is not launched, can't join\n"); - return 2; + goto error; } stop = 1; if (pthread_join(repo_thread.th, NULL)) { fprintf(stderr, " ! repo_join_thread: failed to join repo thread\n"); - return 1; + goto error; } fprintf(stderr, " . repo_join_thread: repo thread joined\n"); - return 0; + + ret = 0; +error: + if (pthread_mutex_unlock(&mtx)) { + fprintf(stderr, " ! repo_join_thread: Failed to unlock mutex\n"); + ret = 3; + } + return ret; +} + +/* Find it in the repo/curl.c file. */ +extern int +safe_json_get_string(struct json_object *jobj, const char *key, char *content, const size_t len); +extern int +safe_json_get_int32(struct json_object *json, const char *key, int32_t *ret); + +static inline void +__handle_got_json(struct lkt_thread *self, struct lkt_repo *repo, struct json_object *json) +{ + size_t i, len = json_object_array_length(json); + struct json_object *kara_json; + int32_t integer; + struct kara *kara; + int err; + + if (len <= 0 || NULL == json_object_get_array(json)) { + fprintf(stderr, "__handle_got_json: Got json is invalid or the array is empty\n"); + return; + } + + for (i = 0; i < len; ++i) { + kara_json = json_object_array_get_idx(json, i); + kara = calloc(1, sizeof(struct kara)); + err = 0; + + /* Get the id of the kara. */ + if (safe_json_get_int32(kara_json, "id", &integer)) + goto err; + + /* Craft a fake filepath here, it will be used later. */ + size_t kara_dir_len = strlen(repo->kara_dir); + memcpy(kara->filename, repo->kara_dir, sizeof(char) * (kara_dir_len + 1)); + if (kara->filename[kara_dir_len - 1] != '/') { + strncat(kara->filename, "/", PATH_MAX - 1); + kara->filename[++kara_dir_len] = 0; + } + integer = snprintf(kara->filename + kara_dir_len, PATH_MAX - kara_dir_len, "%d", integer); + kara->filename[PATH_MAX] = 0; + fprintf(stderr, " . __handle_got_json: Crafted filename is '%s'\n", kara->filename); + + if (!kara) { + fprintf(stderr, " ! __handle_got_json: Out of memory\n"); + return; + } + + /* Get the fields from the json. */ + err |= safe_json_get_string(kara_json, "song_name", kara->mdt.song_name, LEKTOR_TAG_MAX); + err |= safe_json_get_string(kara_json, "source_name", kara->mdt.source_name, LEKTOR_TAG_MAX); + err |= safe_json_get_string(kara_json, "category", kara->mdt.category, LEKTOR_TAG_MAX); + err |= safe_json_get_string(kara_json, "language", kara->mdt.language, LEKTOR_TAG_MAX); + err |= safe_json_get_string(kara_json, "author_name", kara->mdt.author_name, LEKTOR_TAG_MAX); + err |= safe_json_get_string(kara_json, "song_type", kara->mdt.song_type, LEKTOR_TAG_MAX); + + if (err) + goto err; + + /* Get the song number. */ + if (safe_json_get_int32(kara_json, "song_number", &kara->mdt.song_number)) + goto err; + + /* Append. */ + if (lkt_th_append_output(self, kara)) { + fprintf(stderr, " . __handle_got_json: Could not append downloaded kara mdt\n"); + goto err; + } + + continue; +err: + free(kara); + } } static void * @@ -41,14 +130,29 @@ __repo_thread_function(struct lkt_thread_arg *arg) struct lkt_repo *repo = arg->args; struct lkt_thread *self = arg->self; struct kara *kara; + struct json_object *json = NULL; char path[PATH_MAX]; free(arg); fprintf(stderr, " . __repo_thread_function: Starting the repo thread\n"); for (;;) { - if (stop) + if (pthread_mutex_lock(&mtx)) { + fprintf(stderr, " ! __repo_thread_function: Failed to lock mutex\n"); + goto end_loop; + } + + if (all_json) { + repo_get_alljson_sync(repo, &json); + __handle_got_json(self, repo, json); + json_object_put(json); + } + + if (stop) { + if (pthread_mutex_unlock(&mtx)) + fprintf(stderr, " ! __repo_thread_function: Failed to unlock mutex\n"); break; + } head = 0; @@ -77,6 +181,7 @@ __repo_thread_function(struct lkt_thread_arg *arg) } /* Copy data to the structure that we will pass to the main thread. */ + kara->action = kara_action_add; kara->id = head; memcpy(kara->filename, path, (strlen(path) + 1) * sizeof(char)); @@ -161,3 +266,21 @@ err: *downloaded = NULL; return 1; } + +inline int +repo_get_allid_async(void) +{ + if (pthread_mutex_lock(&mtx)) { + fprintf(stderr, " ! repo_get_allid_async: Failed to lock mutex\n"); + return 3; + } + + all_json = 1; + + if (pthread_mutex_unlock(&mtx)) { + fprintf(stderr, " ! repo_get_allid_async: Failed to unlock mutex\n"); + return 3; + } + + return 0; +} diff --git a/src/repo/curl.c b/src/repo/curl.c index 6469ae825030f96ce3dac6c6f0b099f1b346d789..277c489234cbffbe58c12222e25af3b028fb96d6 100644 --- a/src/repo/curl.c +++ b/src/repo/curl.c @@ -89,6 +89,7 @@ repo_new(struct lkt_repo *const repo_, const char *name_, const char *url_) .base_url = url_, .get_id_json = GET_ID_JSON, .get_id_file = GET_ID_FILE, + .get_all_json = DEFAULT_URL "/api_karas.php", .kara_dir = "/home/kara/", // TODO .version = 1, }; @@ -109,7 +110,7 @@ repo_free(struct lkt_repo *const repo) curl_global_cleanup(); } -static inline int +int safe_json_get_string(struct json_object *jobj, const char *key, char *content, const size_t len) { int ret = 1; @@ -136,10 +137,76 @@ err: return ret; } +int +safe_json_get_int32(struct json_object *json, const char *key, int32_t *ret) +{ + struct json_object *field; + + if (!json_object_object_get_ex(json, key, &field)) { + fprintf(stderr, " . safe_json_get_int32: No object with key '%s' in json, error\n", key); + goto err; + } + + errno = 0; + *ret = json_object_get_int(field); + + if (errno == EINVAL) { + fprintf(stderr, " . __handle_got_json: Invalid integer for field with key 'song_number'\n"); + goto err; + } + + if (*ret == INT32_MAX || *ret == INT32_MIN) { + fprintf(stderr, " . __handle_got_json: Out of bound integer for field with key 'song_number'\n"); + goto err; + } + + return 0; +err: + return 1; +} + +int +repo_get_alljson_sync(struct lkt_repo *const repo, struct json_object **json) +{ + if (!json) { + fprintf(stderr, " ! repo_get_alljson_sync: Invalid argument\n"); + return 1; + } + + CURL *curl_handle; + CURLcode res; + int ret = 1; + + struct memory file = { + .mem = NULL, + .size = 0. + }; + + curl_handle = curl_easy_init(); + curl_easy_setopt(curl_handle, CURLOPT_URL, repo->get_all_json); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_mem__); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &file); + curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); + res = curl_easy_perform(curl_handle); + + if (res != CURLE_OK) { + fprintf(stderr, " ! repo_get_alljson_sync: curl_easy_perform failed: %s\n", + curl_easy_strerror(res)); + free(file.mem); + goto err; + } + + *json = json_tokener_parse(file.mem); + + ret = 0; +err: + curl_easy_cleanup(curl_handle); + return ret; +} + int repo_get_id(struct lkt_repo *const repo, const uint64_t id, struct kara_metadata *mdt) { - struct memory file; CURL *curl_handle; CURLcode res; struct json_object *jobj, *field; @@ -155,8 +222,10 @@ repo_get_id(struct lkt_repo *const repo, const uint64_t id, struct kara_metadata return ENOMEM; } - file.mem = NULL; - file.size = 0; + struct memory file = { + .mem = NULL, + .size = 0, + }; memset(url, 0, URL_MAX_LEN * sizeof(char)); snprintf(url, URL_MAX_LEN - 1, repo->get_id_json, id); @@ -299,7 +368,7 @@ repo_download_id_sync(struct lkt_repo *const repo, sqlite3 *db, const uint64_t i goto no_db_update; } - if (! database_update_add(db, kara_path, mdt_ret ? mdt_ret : &mdt, id)) { + if (! database_update_add(db, kara_path, mdt_ret ? mdt_ret : &mdt, id, true)) { fprintf(stderr, " ! repo_download_id_sync: Failed to add kara to database\n"); goto err; }