diff --git a/doc/lkt.1 b/doc/lkt.1 index fd8e1648ea56b3786598cd1dad0fa640639280fc..bf0a7db2f1ff47a6d097074ec744e204bee05b50 100644 --- a/doc/lkt.1 +++ b/doc/lkt.1 @@ -71,6 +71,12 @@ Deletes karas from a playlist with a valid \fIquery\fP .TP \fBplt add\fP <plt-name> <query> Adds karas to a playlist with a valid \fIquery\fP +.TP +\fBplt list\fP <plt-name> +List the content of the playlist named <plt-name> +.TP +\fBplt list\fP +List all the available playlists .PP \fIQUEUE-COMMANDS\fP @@ -125,6 +131,12 @@ Search and prints the kara that correspond to the query in the database Search, prints and add to an existing playlist the karas that match the query .TP +\fBsearch plt\fP <plt-name> +List the content of the playlist named <plt-name> +.TP +\fBsearch plt\fP +List all the available playlists +.TP \fBsearch count\fP <query> Search and prints the number of karas that match the query .TP diff --git a/inc/lektor/commands.h b/inc/lektor/commands.h index 34201ae7f36b9bc65a53827a0e52e73b532cdb1d..9e3424a40660e2f87f57b19ea7b1c50b21d24ae9 100644 --- a/inc/lektor/commands.h +++ b/inc/lektor/commands.h @@ -47,6 +47,8 @@ bool command_plt_rename(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX]) bool command_plt_export(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX]); bool command_plt_import(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX]); bool command_plt_add (struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX]); +bool command_plt_list (struct lkt_state *srv, size_t c, long cont); +bool command_plt_ctx (struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX], long cont); /* The help */ bool command_help(struct lkt_state *srv, size_t c); diff --git a/inc/lektor/database.h b/inc/lektor/database.h index 3c46b47351981ce1ebb791bad495a58f1d1e9dc1..daae958341105a4897375ec7a9ba47e4cd8b809a 100644 --- a/inc/lektor/database.h +++ b/inc/lektor/database.h @@ -90,6 +90,7 @@ struct lkt_search { lkt_search_playlist, lkt_search_queue, lkt_search_sticker, + lkt_search_listplaylist, } type; void (*call)(void); /* Called during the iter phase, casted */ @@ -112,17 +113,19 @@ struct lkt_search { struct lkt_uri ka_uri; /* Search by uri */ }; -typedef bool (*lkt_search_init_add_func)(volatile sqlite3 *, struct lkt_uri *, int); -typedef bool (*lkt_search_database_func)(struct lkt_state *srv, size_t c, int id, int id_len, const char *row); -typedef bool (*lkt_search_queue_func) (struct lkt_state *srv, size_t c, int id, int id_len, const char *row); -typedef bool (*lkt_search_sticker_func) (struct lkt_state *srv, size_t c, const char *sticker, const char *type, int uri, int value); - -bool database_search_database_init(volatile sqlite3 *db, struct lkt_search *ret); -bool database_search_queue_init (volatile sqlite3 *db, struct lkt_search *ret); -bool database_search_sticker_init (volatile sqlite3 *db, struct lkt_search *ret); -bool database_search_playlist_init(volatile sqlite3 *db, struct lkt_search *ret); +typedef bool (*lkt_search_init_add_func) (volatile sqlite3 *, struct lkt_uri *, int); +typedef bool (*lkt_search_database_func) (struct lkt_state *srv, size_t c, int id, int id_len, const char *row); +typedef bool (*lkt_search_queue_func) (struct lkt_state *srv, size_t c, int id, int id_len, const char *row); +typedef bool (*lkt_search_sticker_func) (struct lkt_state *srv, size_t c, const char *sticker, const char *type, int uri, int value); +typedef bool (*lkt_search_listplaylist_func)(struct lkt_state *srv, size_t c, const char *name); + +bool database_search_database_init (volatile sqlite3 *db, struct lkt_search *ret); +bool database_search_queue_init (volatile sqlite3 *db, struct lkt_search *ret); +bool database_search_sticker_init (volatile sqlite3 *db, struct lkt_search *ret); +bool database_search_playlist_init (volatile sqlite3 *db, struct lkt_search *ret); +bool database_search_listplaylist_init(volatile sqlite3 *db, struct lkt_search *ret); bool database_search_iter(struct lkt_search *item); -bool database_kara_by_id(volatile sqlite3 *db, int id, struct kara_metadata *kara, char filepath[PATH_MAX]); +bool database_kara_by_id (volatile sqlite3 *db, int id, struct kara_metadata *kara, char filepath[PATH_MAX]); /* Next and prev operation on the queue. */ bool database_queue_next(volatile sqlite3 *db, char filepath[PATH_MAX]); diff --git a/inc/lektor/uri.h b/inc/lektor/uri.h index f18e476272de2bdb261a678c198f09dbf0c0f3f5..ca506f69d3813fcc050a0785dc71970b94515da8 100644 --- a/inc/lektor/uri.h +++ b/inc/lektor/uri.h @@ -5,6 +5,7 @@ #include <stdbool.h> enum lkt_uri_type { + uri_null, uri_id, uri_playlist, uri_type, diff --git a/src/commands.c b/src/commands.c index 54c7e5b8f45db415cda28b1f05cbbdeb72f50eb3..413386561ae09d5a38a08f2a2b45da81b10b0126 100644 --- a/src/commands.c +++ b/src/commands.c @@ -433,6 +433,16 @@ lkt_callback_send_row_v2(struct lkt_state *srv, size_t c, int id, int id_len, co return true; } +bool +lkt_callback_send_list_plts(struct lkt_state *srv, size_t c, const char *plt_name) +{ + struct lkt_message *out = lkt_message_new(); + /* If the playlist is named OK or ACK... */ + out->data_len = safe_snprintf(out->data, LKT_MESSAGE_MAX, "name: %s\n", plt_name); + lkt_state_send(srv, c, out); + return true; +} + static bool command_findid(struct lkt_state *srv, size_t c, char *id_str) { @@ -470,6 +480,20 @@ command_findid(struct lkt_state *srv, size_t c, char *id_str) return true; } +static inline bool +__iter_search(struct lkt_search *search) +{ + int count; + for (count = 0; database_search_iter(search); ++count) + continue; + + if (count) + lkt_set_continuation(search->srv, search->c, search->continuation + count); + else + LOG_WARN("COMMAND", "%s", "Nothing found"); + return true; +} + bool command_find(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX], long continuation, bool(*init)(volatile sqlite3 *, struct lkt_search *)) @@ -502,15 +526,38 @@ command_find(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX], } RETURN_UNLESS(init(srv->db, &search), "Failed to init search", false); - for (count = 0; database_search_iter(&search); ++count) - continue; + return __iter_search(&search); +} - if (count) - lkt_set_continuation(srv, c, continuation + count); - else - LOG_WARN("COMMAND", "%s", "Nothing found"); - lkt_uri_free(&search.ka_uri); - return true; +bool +command_plt_list(struct lkt_state *srv, size_t c, long cont) +{ + struct lkt_search search = { + .srv = srv, + .c = c, + .continuation = cont, + .msg_count = lkt_remaining_msg(srv, c) - 3, /* Slots for OK/ACK and continuation */ + .call = (void(*)(void)) lkt_callback_send_list_plts, + }; + + RETURN_UNLESS(database_search_listplaylist_init(srv->db, &search), "Failed to init search", false); + return __iter_search(&search); +} + +bool command_plt_ctx(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX], long cont) +{ + struct lkt_search search = { + .srv = srv, + .c = c, + .continuation = cont, + .msg_count = lkt_remaining_msg(srv, c) - 3, + .call = (void(*)(void)) lkt_callback_send_row_v2, + .plt_name = args[0], + .ka_uri = { .type = uri_null }, + }; + + RETURN_UNLESS(database_search_playlist_init(srv->db, &search), "Failed to init search", false); + return __iter_search(&search); } bool diff --git a/src/database/find.c b/src/database/find.c index 4ec182a564105e5251696e5fdae7945616e1cd8d..960a6295e59d2baca98ae7aabefa58a16aa59053 100644 --- a/src/database/find.c +++ b/src/database/find.c @@ -48,7 +48,8 @@ error: bool database_search_playlist_init(volatile sqlite3 *db, struct lkt_search *ret) { - RETURN_UNLESS(ret, "Exit because return pointer is NULL", false); + /* Sql queries definitions */ + char SQL_STMT[LKT_MAX_SQLITE_STATEMENT]; static const char *SQL_TEMPLATE = "WITH content AS (" " SELECT kara.id AS id, string, LENGTH(CAST(kara.id AS TEXT)) AS len" @@ -56,12 +57,28 @@ database_search_playlist_init(volatile sqlite3 *db, struct lkt_search *ret) " JOIN playlist ON playlist.id = playlist_id" " AND playlist.name COLLATE nocase = ?" " JOIN kara ON kara.id = kara_id" - " AND %s LIKE ?)" - "SELECT id, string, (SELECT MAX(len) FROM content)" + " AND %s LIKE ?) " + "SELECT id, string, (SELECT MAX(len) FROM content) " "FROM content LIMIT %d OFFSET %d;"; - char SQL_STMT[LKT_MAX_SQLITE_STATEMENT]; - ret->type = lkt_search_playlist; + static const char *SQL = + "WITH content AS (" + " SELECT kara.id AS id, string, LENGTH(CAST(kara.id AS TEXT)) AS len" + " FROM kara_playlist" + " JOIN playlist ON playlist.id = playlist_id" + " AND playlist.name COLLATE NOCASE = ?" + " JOIN kara ON kara.id = kara_id) " + "SELECT id, string, (SELECT MAX(len) FROM content) " + "FROM content LIMIT %d OFFSET %d;"; + + /* Common part */ + RETURN_UNLESS(ret, "Exit because return pointer is NULL", false); + ret->type = lkt_search_playlist; + ret->db = db; + if (ret->ka_uri.type == uri_null) + goto uri_is_null; + /* Filter with an uri */ + LOG_INFO("DB", "List plt '%s' with non null uri", ret->plt_name); safe_snprintf(SQL_STMT, LKT_MAX_SQLITE_STATEMENT, SQL_TEMPLATE, ret->ka_uri.column_name, ret->msg_count, ret->continuation); SQLITE_PREPARE(db, ret->stmt, SQL_STMT, error); @@ -73,7 +90,33 @@ database_search_playlist_init(volatile sqlite3 *db, struct lkt_search *ret) SQLITE_BIND_INT(db, ret->stmt, 2, (int) ret->ka_uri.id, error); LOG_DEBUG("DB", "%s", "Uri was a size_t"); } - ret->db = db; + return true; + + /* Don't filter */ +uri_is_null: + LOG_INFO("DB", "List plt '%s' with null uri", ret->plt_name); + safe_snprintf(SQL_STMT, LKT_MAX_SQLITE_STATEMENT, SQL, ret->msg_count, ret->continuation); + SQLITE_PREPARE(db, ret->stmt, SQL_STMT, error); + SQLITE_BIND_TEXT(db, ret->stmt, 1, ret->plt_name, error); + return true; + + /* Error */ +error: + sqlite3_finalize(ret->stmt); + return false; +} + +bool +database_search_listplaylist_init(volatile sqlite3 *db, struct lkt_search *ret) +{ + static const char *SQL = "SELECT name FROM playlist LIMIT %d OFFSET %d;"; + char SQL_STMT[LKT_MAX_SQLITE_STATEMENT]; + safe_snprintf(SQL_STMT, LKT_MAX_SQLITE_STATEMENT, SQL, ret->msg_count, ret->continuation); + ret->type = lkt_search_listplaylist; + ret->db = db; + SQLITE_PREPARE(db, ret->stmt, SQL_STMT, error); + LOG_INFO("DB", "List playlists with LIMIT %d and OFFSET %d", + ret->msg_count, (int) ret->continuation); return true; error: sqlite3_finalize(ret->stmt); @@ -194,6 +237,10 @@ database_search_iter(struct lkt_search *item) type = sqlite3_column_chars(item->stmt, 3); /* Type */ return ((lkt_search_sticker_func) item->call) (item->srv, item->c, sql_row, type, id, code); + case lkt_search_listplaylist: + sql_row = sqlite3_column_chars(item->stmt, 0); /* Name */ + return ((lkt_search_listplaylist_func) item->call) + (item->srv, item->c, sql_row); default: LOG_WARN("DB", "Search type %d is not implemented", item->type); goto end; diff --git a/src/database/open.c b/src/database/open.c index f82bbc7bef7ab111aaf349ae4400f4b6ae04a151..8a2701acdf4f1ffaf657375f6e89821986dd4f21 100644 --- a/src/database/open.c +++ b/src/database/open.c @@ -12,6 +12,12 @@ extern unsigned char ___src_database_disk_sql[]; extern unsigned char ___src_database_memory_sql[]; +static const char *__pragmas = + "PRAGMA case_sensitive_like = false;\n" + "PRAGMA encoding = 'UTF-8';\n" + "PRAGMA synchronous = 0;\n" + "PRAGMA journal_mode = off;\n"; + #define INVALID_CHARS_DBPATH ":?!'\"" #define HEAP_LIMIT_SOFT 100 * 1024 * 1024 #define HEAP_LIMIT_HARD 150 * 1024 * 1024 @@ -91,6 +97,7 @@ is_sql_str_invalid(const char *str) bool database_new(volatile sqlite3 **db) { + *db = NULL; static int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE | SQLITE_OPEN_NOFOLLOW | @@ -101,10 +108,15 @@ database_new(volatile sqlite3 **db) SQLITE_OK != sqlite3_open_v2(":memory:", (sqlite3 **) db, flags, NULL)) return false; SQLITE_EXEC(*db, (const char *) ___src_database_memory_sql, err_not_init); + SQLITE_EXEC(*db, __pragmas, err_pragma); return true; err_not_init: *db = NULL; return false; +err_pragma: + sqlite3_close((sqlite3 *) *db); + *db = NULL; + return false; } static inline bool diff --git a/src/main/lkt.c b/src/main/lkt.c index ecf48701a9824fcfc2c2b9a4cfdaacb1b24217d9..ce715f6108672d1ff340f2539a6aa2595c8ff3c0 100644 --- a/src/main/lkt.c +++ b/src/main/lkt.c @@ -130,12 +130,12 @@ write_socket(FILE *sock, const char *format, ...) buff[LKT_MESSAGE_MAX - 1] = 0; if (size < 0) - fail("Connexion error") + fail("Connexion error"); - if (fwrite(buff, sizeof(char), size, sock) < (unsigned int) size) - fail("Connexion error") + if (fwrite(buff, sizeof(char), size, sock) < (unsigned int) size) + fail("Connexion error"); - va_end(ap); + va_end(ap); } noreturn static inline void @@ -752,8 +752,11 @@ plt_delete__(struct lkt_cmd_args *args) { FILE *sock = lkt_connect(); char buff[2]; - fail_if(args->argc < 1, "Invalid argument"); - send_cmd_with_uri(sock, "playlistdelete", args->argc, args->argv); + char cmd[128]; + fail_if(args->argc < 3, "Invalid argument"); + snprintf(cmd, 128 - 1, "playlistdelete %s", args->argv[0]); + cmd[127] = '\0'; + send_cmd_with_uri(sock, cmd, args->argc - 1, &args->argv[1]); exit_with_status(sock, buff); } @@ -816,6 +819,52 @@ stickers_delete__(struct lkt_cmd_args *args) /* Search functions. */ +noreturn void +__continuation_calls(const char *cmd) +{ + char buff[LKT_MESSAGE_MAX]; + int continuation = 0; + FILE *sock = NULL; +redo: + sock = lkt_connect(); + + write_socket(sock, "%d %s\n", continuation, cmd); + for (;;) { + memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); + read_socket(sock, buff, LKT_MESSAGE_MAX - 1); + + if (STR_NMATCH(buff, "continue:", strlen("continue:"))) { + continuation = atoi(lkt_skip_key(buff)); + fclose(sock); + goto redo; + } + + if (STR_NMATCH(buff, "OK", 2)) + exit(EXIT_SUCCESS); + else if (STR_NMATCH(buff, "ACK", 3)) + exit(EXIT_FAILURE); + + fprintf(stdout, "%s", buff); + } +} + +noreturn void +list_plt__(struct lkt_cmd_args *args) +{ + fail_if(args->argc != 0, "Invalid argument number"); + __continuation_calls("listplaylists"); +} + +noreturn void +list_plt_content__(struct lkt_cmd_args *args) +{ + fail_if(args->argc != 1, "Invalid argument number"); + char buff[LKT_MESSAGE_MAX]; + safe_snprintf(buff, LKT_MESSAGE_MAX, "listplaylist %s", args->argv[0]); + __continuation_calls(buff); + exit(EXIT_FAILURE); +} + noreturn void search_with_cmd__(struct lkt_cmd_args *args, const char *cmd) { @@ -875,6 +924,11 @@ search_get__(struct lkt_cmd_args *args) noreturn void search_plt__(struct lkt_cmd_args *args) { + if (args->argc == 0) + list_plt__(args); + if (args->argc == 1) + list_plt_content__(args); + fail_if(args->argc < 3, "Invalid number of arguments"); fail_if(!lkt_valid_type(args->argv[1]), "Invalid type for query"); char buff[LKT_MESSAGE_MAX]; @@ -883,7 +937,7 @@ search_plt__(struct lkt_cmd_args *args) redo: sock = lkt_connect(); - write_socket(sock, "%d listplaylist %s %s://", continuation, + write_socket(sock, "%d listplaylistinfo %s %s://", continuation, args->argv[0], args->argv[1]); for (i = 2; i < args->argc - 1; ++i) write_socket(sock, "%s ", args->argv[i]); @@ -936,6 +990,7 @@ static struct lkt_cmd_opt options_plt[] = { { .name = "delete", .call = plt_delete__ }, { .name = "destroy", .call = plt_destroy__ }, { .name = "create", .call = plt_create__ }, + { .name = "list", .call = search_plt__ }, LKT_OPT_NULL, }; diff --git a/src/net/listen.c b/src/net/listen.c index 49b3e9771f4cae99fd15023df65a12ed9f7cad10..3916f4ccc4210c3cb4c486a6c105f3d70f9f3ad8 100644 --- a/src/net/listen.c +++ b/src/net/listen.c @@ -244,8 +244,12 @@ handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd) err = ! command_plt_import(srv, cmd.args); else if (STR_MATCH(cmd.name, "__dump")) err = ! command_dump(srv, cmd.args); - else if (STR_MATCH(cmd.name, "listplaylist")) + else if (STR_MATCH(cmd.name, "listplaylistinfo")) err = ! command_find(srv, c, cmd.args, cmd.cont, database_search_playlist_init); + else if (STR_MATCH(cmd.name, "listplaylists")) + err = ! command_plt_list(srv, c, cmd.cont); + else if (STR_MATCH(cmd.name, "listplaylist")) + err = ! command_plt_ctx(srv, c, cmd.args, cmd.cont); else if (STR_MATCH(cmd.name, "random")) err = !command_set_playback_option(srv, c, lkt_playback_option_random, cmd.args);