diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c3d2469bb04228eab654e5f65ca98df2513f69dd..88280fb7c0fbb6f0a9bf50e64dad91b1839bdd22 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,8 +9,12 @@ stages: tags: - kurisu only: - - main + - master - merge_requests + except: + variables: + - $CI_MERGE_REQUEST_TITLE =~ /^WIP:/ + - $CI_MERGE_REQUEST_TITLE =~ /^Draft:/ variables: SRC_DIR: $CI_PROJECT_DIR # Where the sources are WORK_DIR: "/home/gitlab-runner/CI_$CI_PIPELINE_ID" # Where we will work, so that it's not erased diff --git a/configure b/configure index 440c4c2844dad8c136d117db7a1a0e533f16b3ba..ca7d3351e34d7450c5fd52df31e9866bd8a453f9 100755 --- a/configure +++ b/configure @@ -1422,7 +1422,7 @@ Optional Features: optimize for fast installation [default=yes] --disable-libtool-lock avoid locking (might break parallel builds) --enable-debug Enable debug so you can use gdb - --enable-static-module Build modules statically in liblektor + --disable-static-module Build modules statically not in liblektor Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] @@ -13541,9 +13541,9 @@ fi # Check whether --enable-static-module was given. if test "${enable_static_module+set}" = set; then : - enableval=$enable_static_module; LKT_STATIC_MODULE=yes + enableval=$enable_static_module; LKT_STATIC_MODULE=no else - LKT_STATIC_MODULE=no + LKT_STATIC_MODULE=yes fi if test "x${LKT_STATIC_MODULE}" = "xyes"; then diff --git a/configure.ac b/configure.ac index 8f32808d1692d4c87b189b394bdb6276d3d82f64..cfa8891779f1e5f89dc1cc73b3dbcb567d90fdbb 100644 --- a/configure.ac +++ b/configure.ac @@ -88,10 +88,10 @@ if test "x${LKT_DEBUG_ENABLED}" = "xyes" ; then fi AC_ARG_ENABLE([static-module], - [AS_HELP_STRING([--enable-static-module], - [Build modules statically in liblektor])], - [LKT_STATIC_MODULE=yes], - [LKT_STATIC_MODULE=no]) + [AS_HELP_STRING([--disable-static-module], + [Build modules statically not in liblektor])], + [LKT_STATIC_MODULE=no], + [LKT_STATIC_MODULE=yes]) AM_CONDITIONAL([LKT_STATIC_MODULE], [test "x${LKT_STATIC_MODULE}" = "xyes"]) if test "x${LKT_STATIC_MODULE}" = "xyes" ; then AC_DEFINE([LKT_STATIC_MODULE], [], [Build with modules inside liblektor]) diff --git a/inc/lektor/common.h b/inc/lektor/common.h index 6699edeffb2658f0948d96c562b063981efaec15..c65f732a5fc441a5d90858350852c337ef4af2ad 100644 --- a/inc/lektor/common.h +++ b/inc/lektor/common.h @@ -180,5 +180,10 @@ long long_length(long integer); void *safe_malloc(size_t size); void *safe_zero_malloc(size_t size); int safe_snprintf(char *dest, const size_t max_len, const char *format, ...); +char *safe_strncpy(char *dest, const char *src, size_t n); + +/* Iterate throught a string, copy each element in the dest string. + The save ptr must contains a null integer. */ +int iter_string(const char *str, const char *sep, char *dest, const size_t dest_len, size_t *save); #endif /* __LKT_COMMON_H__ */ diff --git a/inc/lektor/config.def b/inc/lektor/config.def index 8c442b0174fc5749145561b10e4186218a738fca..e32c0f78000a0ce91c3712dfad0448c69758b937 100644 --- a/inc/lektor/config.def +++ b/inc/lektor/config.def @@ -13,6 +13,8 @@ value("kara_dir", "/home/kara") /* All the folder and sub folder must be on the same partition */ value("db_path", "/home/kara/kara.db") +/* The repo module, here to be able to sync the bakabase with kurisu or + any other server, if configured correctly */ section("repo") value("obfuscate", "1") value("module", "repo") @@ -23,6 +25,9 @@ value("json", "https://kurisu.iiens.net/api") value("id_json", "https://kurisu.iiens.net/api?id=%ld") value("id_kara", "https://kurisu.iiens.net/download.php?id=%ld") +/* The player module, so that people can 'sing' when using lektor. By + default it's sdl, but one can create any module, for audio support + only for example. */ section("player") value("module", "sdl2") value("autoclear", "true") @@ -33,3 +38,16 @@ value("def_repeat", "false") value("font_size", "20") value("font_name", "Hack Nerd Font") value("msg_duration", "4") + +/* To place hooks, when some things are done in lektor. The default value 'none' is here so that + nothing happens. Like any module, the value can point to a module or a function in the internal + register. To specify multiple modules, use a comma separated list, line this: + `kara_load = print_stdout, print_stderr` + For the moment, only functions in the server register are supported. + NOTE: All the functions will recieve a va_list as a unique argument. */ +section("hook") +value("kara_load", "none") /* Before a kara is loaded to be played, + the 'player' module needs to support it */ +value("queue_begin", "none") /* On play, when the player is not already playing or paused */ + +// vi:syntax=c diff --git a/inc/lektor/config.h b/inc/lektor/config.h index 4515f0e352df48e9bc35978cedd42993206534ce..051e1e617104f85c86da00f2e5dd222bc2b6ae3b 100644 --- a/inc/lektor/config.h +++ b/inc/lektor/config.h @@ -9,6 +9,7 @@ /* Forward definition of the lkt_state structure */ struct lkt_state; +/* The default configuration, stored as a string */ #define section(sct) "\n[" sct "]\n" #define value(key, val) key " = " val "\n" #define value_opt(key, val) value(key, val) @@ -44,4 +45,7 @@ void config_default_file(char *dest, size_t len); #define env_set(var, val) setenv(var, val, 1) #define env_get getenv +/* Handle everything for a hook */ +void config_handle_hook(volatile sqlite3 *db, const char *hook_name); + #endif /* __LKT_CONFIG_H__ */ diff --git a/inc/lektor/database.h b/inc/lektor/database.h index 1c709d90f14bad6ccd2c65add613ebd80a1b0a06..dc7f91c396eab8d9d23d7342999731c26f162540 100644 --- a/inc/lektor/database.h +++ b/inc/lektor/database.h @@ -144,8 +144,9 @@ bool database_validate_conf (volatile sqlite3 *db); bool database_config_set (volatile sqlite3 *db, const char *section, const char *key, const char *value); bool database_config_get_text(volatile sqlite3 *db, const char *section, const char *key, - char *value, - size_t len); + char *value, size_t len); +bool database_config_get_text_nospace(volatile sqlite3 *db, const char *section, + const char *key, char *value, size_t len); bool database_config_get_int (volatile sqlite3 *db, const char *section, const char *key, int *value); bool database_config_exists (volatile sqlite3 *db, const char *section, const char *key); diff --git a/inc/lektor/reg.h b/inc/lektor/reg.h index 9439439d1f63e3ad939160a70a5e6f60c8a0ee16..f1b4ec8ea92842ba4f3673a6235720c6974a59a1 100644 --- a/inc/lektor/reg.h +++ b/inc/lektor/reg.h @@ -46,6 +46,9 @@ struct lkt_module { REG_DECLARE(reg) \ static struct module_reg *as = reg; +/* The server register */ +#define REG_SERVER __reg__ + /* Call a function from a reg. * NOTE: * - Use the MOD_CALL macro to call a function that takes arguments @@ -66,5 +69,6 @@ int reg_import(const char *mod, struct module_reg **ret, void **handle); /* Set the __reg__ pointer. */ void reg_export(struct module_reg *reg); +void reg_global(struct module_reg **reg_ptr); #endif /* __LKT_REG_H__ */ diff --git a/src/base/commands.c b/src/base/commands.c index 5fc5b0ac8eedd10e6ee2078d0400252c00fc4d60..cb0808dd0b67d09af388c956ef0ac8244835875b 100644 --- a/src/base/commands.c +++ b/src/base/commands.c @@ -161,7 +161,12 @@ command_next(struct lkt_state *srv, char __attribute__((unused)) *args[LKT_MESSA char filepath[PATH_MAX]; if (!database_queue_next(srv->db, filepath)) return false; - return ! MOD_CALL(srv->window_mod, "load", filepath); + if (MOD_CALL(srv->window_mod, "load", filepath)) + return false; + else { + config_handle_hook(srv->db, "kara_load"); + return true; + } } bool @@ -180,7 +185,12 @@ command_previous(struct lkt_state *srv, char __attribute__((unused)) *args[LKT_M char filepath[PATH_MAX]; if (!database_queue_prev(srv->db, filepath)) return false; - return ! MOD_CALL(srv->window_mod, "load", filepath); + if (MOD_CALL(srv->window_mod, "load", filepath)) + return false; + else { + config_handle_hook(srv->db, "kara_load"); + return true; + } } static inline bool @@ -210,7 +220,14 @@ command_play(struct lkt_state *srv, char __attribute__((unused)) *args[LKT_MESSA database_queue_stop(srv->db); RETURN_IF(MOD_CALL(srv->window_mod, "new", &srv->queue, srv->db), "Can't create window", false); srv->mpd_idle_events |= MPD_IDLE_PLAYER; - return __play_that_file(srv, pos); + + /* Hooks */ + if (__play_that_file(srv, pos)) { + config_handle_hook(srv->db, "queue_begin"); + config_handle_hook(srv->db, "kara_load"); + return true; + } else + return false; } bool @@ -238,14 +255,21 @@ command_playid(struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX]) srv->mpd_idle_events |= MPD_IDLE_PLAYER; RETURN_IF(MOD_CALL(srv->window_mod, "new", &srv->queue, srv->db), "Can't create window", false); database_queue_seekid(srv->db, (int) id, &pos); - return __play_that_file(srv, pos); + + /* Hooks */ + if (__play_that_file(srv, pos)) { + config_handle_hook(srv->db, "queue_begin"); + config_handle_hook(srv->db, "kara_load"); + return true; + } else + return false; } bool command_stop(struct lkt_state *srv, char __attribute__((unused)) *args[LKT_MESSAGE_ARGS_MAX]) { - RETURN_UNLESS(database_queue_stop(srv->db), "DB error", false); + RETURN_UNLESS(database_queue_stop(srv->db), "DB error on stop", false); MOD_PROC(srv->window_mod, "close"); srv->mpd_idle_events |= MPD_IDLE_PLAYER; return true; @@ -283,7 +307,8 @@ inline bool command_clear(struct lkt_state *srv, char __attribute__((unused)) *args[LKT_MESSAGE_ARGS_MAX]) { srv->mpd_idle_events |= MPD_IDLE_PLAYER; - return database_queue_clear(srv->db); + return command_stop(srv, args) && + database_queue_clear(srv->db); } inline bool diff --git a/src/base/common.c b/src/base/common.c index 7d76ad345b8bcaff011d6388eb564475c1bf98ef..093e5ff3fe816a7b1bba9e9a04a02031d2fec86a 100644 --- a/src/base/common.c +++ b/src/base/common.c @@ -274,3 +274,27 @@ long_length(long integer) } return count; } + +char * +safe_strncpy(char *dest, const char *src, size_t n) +{ + char *ret = strncpy(dest, src, n); + dest[n - 1] = '\0'; + return ret; +} + +int +iter_string(const char *str_list, const char *sep, char *dest, const size_t dest_len, size_t *save) +{ + size_t len = strcspn(&str_list[*save], sep); + + if (len == 0) + return 1; + if (len >= dest_len) + return -1; + + strncpy(dest, &str_list[*save], len); + dest[len] = '\0'; + *save += len + 1; + return 0; +} diff --git a/src/base/config.c b/src/base/config.c index 4d5d7d50564dfdbe22a04befaa19cef117c30d8f..6ec549ae16bb368977914111c8c8d268922a5179 100644 --- a/src/base/config.c +++ b/src/base/config.c @@ -296,3 +296,28 @@ config_open(volatile sqlite3 *db) error: return ret; } + +void +config_handle_hook(volatile sqlite3 *db, const char *hook_name) +{ + char hook_handlers[LKT_LINE_MAX]; + if (!database_config_get_text_nospace(db, "hook", hook_name, hook_handlers, LKT_LINE_MAX)) { + LOG_ERROR("HOOK", "Failed to get the hook handlers list for hook '%s'", hook_name); + return; + } + + size_t save = 0; + char name[LKT_LINE_MAX]; + int code; + struct module_reg *server_reg; + reg_global(&server_reg); + + while (0 == (code = iter_string(hook_handlers, ",", name, LKT_LINE_MAX, &save))) { + if (STR_MATCH("none", name)) + continue; + reg_call(server_reg, name, 1, db); + } + + if (code == -1) + LOG_ERROR("HOOK", "Failed to parse the handle list for hook '%s'", hook_name); +} diff --git a/src/base/reg.c b/src/base/reg.c index 5a92130142a629d67b315b9cb96468e633cc9ea7..ec42e88851878a5971851aabf93d4c279c353742 100644 --- a/src/base/reg.c +++ b/src/base/reg.c @@ -20,6 +20,12 @@ reg_export(struct module_reg *reg) __reg__ = reg; } +void +reg_global(struct module_reg **reg_ptr) +{ + *reg_ptr = __reg__; +} + void * __reg_get(struct module_reg *reg, const char *name) { diff --git a/src/database/config.c b/src/database/config.c index ec8c5b139abcb0279ef7a3a4c3022f6b8e2d8af1..86fd2d605dc58eaec6adfd56db04057c101b9c0c 100644 --- a/src/database/config.c +++ b/src/database/config.c @@ -67,6 +67,32 @@ error: return ret; } +bool +database_config_get_text_nospace(volatile sqlite3 *db, const char *section, const char *key, + char *value, size_t len) +{ + static const char *SQL_STMT = + "SELECT REPLACE(value, ' ', '')" + " FROM config" + " WHERE section = ? AND key = ?;\n"; + sqlite3_stmt *stmt = 0; + bool ret = false; + char *row; + + SQLITE_PREPARE(db, stmt, SQL_STMT, error); + SQLITE_BIND_TEXT(db, stmt, 1, section, error); + SQLITE_BIND_TEXT(db, stmt, 2, key, error); + SQLITE_STEP_ROW(db, stmt, error); + + row = (char *) sqlite3_column_text(stmt, 0); + strncpy(value, row, len); + value[len - 1] = 0; + ret = true; +error: + sqlite3_finalize(stmt); + return ret; +} + bool database_config_exists(volatile sqlite3 *db, const char *section, const char *key) diff --git a/src/database/queue.c b/src/database/queue.c index 1573e56557e7023618ed3cfdbfc0129cfef5966b..ac5d1181731da4fc42923e8e85aceec6648b0c7d 100644 --- a/src/database/queue.c +++ b/src/database/queue.c @@ -19,11 +19,18 @@ SQLITE_DO_ROLLBACK(db); \ return false; \ } -sqlite_just_exec(database_queue_toggle_pause, "UPDATE queue_state SET paused = 1 - paused;") -sqlite_just_exec(database_queue_crop, "DELETE FROM queue WHERE queue.kara_id <> (SELECT current FROM queue_state LIMIT 1);") -sqlite_just_exec(database_queue_stop, "UPDATE queue_state SET current = NULL;") -sqlite_just_exec(database_queue_clear, "DELETE FROM queue;DELETE FROM sqlite_sequence WHERE name = 'queue';UPDATE queue_state SET current = NULL;") -sqlite_just_exec(database_config_queue_default, "UPDATE queue_state SET volume = 100, paused = 1, random = 0, repeat = 0, single = 0, consume = 0, current = NULL, duration = 0;") +sqlite_just_exec(database_queue_toggle_pause, "UPDATE queue_state SET paused = 1 - paused;") +sqlite_just_exec(database_queue_crop, "DELETE FROM queue WHERE queue.kara_id <>" + " (SELECT current FROM queue_state LIMIT 1);") +sqlite_just_exec(database_queue_stop, "UPDATE queue_state SET current = NULL;") +sqlite_just_exec(database_queue_clear, "DELETE FROM queue;" + "DELETE FROM queue_tmp;" + "DELETE FROM sqlite_sequence WHERE name = 'queue_tmp';" + "DELETE FROM sqlite_sequence WHERE name = 'queue';" + "UPDATE queue_state SET current = NULL;") +sqlite_just_exec(database_config_queue_default, "UPDATE queue_state SET volume = 100, paused = 1," + " random = 0, repeat = 0, single = 0, consume = 0," + " current = NULL, duration = 0;") #undef sqlite_just_exec // *INDENT-ON* diff --git a/src/module/module_repo.c b/src/module/module_repo.c index fc93c722f26819397560f8a2d409a2690e36d4d3..e11cc7101f547009fa5244e8ebb4a2bf982e7b5c 100644 --- a/src/module/module_repo.c +++ b/src/module/module_repo.c @@ -387,9 +387,16 @@ static void * __worker_rescan(void *__repo) { struct module_repo_internal *repo = __repo; + char kara_prefix[LKT_LINE_MAX]; lkt_queue_send(repo->queue, lkt_event_db_updating, LKT_DB_UPDATING_PROGRESS); + if (!database_config_get_text(repo->db, "database", "kara_dir", kara_prefix, LKT_LINE_MAX)) { + LOG_ERROR("REPO", "Failed to get kara prefix from config"); + pthread_exit(NULL); + } repo->updating = 1; - /* Use the database_update(db, prefix, forced) function here ! */ + database_update(repo->db, kara_prefix, 0); /* Don't check timestamp. + TODO: Sometimes we want to check them */ + repo->updating = 0; lkt_queue_send(repo->queue, lkt_event_db_updating, LKT_DB_UPDATING_FINISHED); pthread_exit(NULL); }