diff --git a/inc/lektor/commands.h b/inc/lektor/commands.h index 42708c858d5d6e44fe45ca1388edf53f9af04d4d..7cc63b06322b7af24744adae05506ff149e140ca 100644 --- a/inc/lektor/commands.h +++ b/inc/lektor/commands.h @@ -17,6 +17,7 @@ /* Querying lektor's status */ bool command_currentsong(struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX]); bool command_status (struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX]); +bool command_stats (struct lkt_state *srv, size_t c, char *args[LKT_MESSAGE_ARGS_MAX]); /* Controlling playback */ bool command_next (struct lkt_state *srv, char *args[LKT_MESSAGE_ARGS_MAX]); diff --git a/inc/lektor/database.h b/inc/lektor/database.h index cef0a11c416826fe65ab6f7c66d946177f1df9ad..e980b680ac3dd7b0026b09492f4f3609826663f6 100644 --- a/inc/lektor/database.h +++ b/inc/lektor/database.h @@ -192,7 +192,9 @@ bool database_sticker_delete (volatile sqlite3 *db, const char *name); bool database_sticker_delete_specify(volatile sqlite3 *sb, const char *type, int uri, const char *name); bool database_sticker_set (volatile sqlite3 *db, const char *type, const char *name, - int uri, - int value); + int uri, int value); + +/* Get some stats from the database */ +bool database_stats(volatile sqlite3 *db, int *authors, int *sources, int *karas); #endif /* __LKT_DATABASE_H__ */ diff --git a/inc/lektor/net.h b/inc/lektor/net.h index 4ec9c4f89fe62677cf233083548846d8714c7af2..1bfb4c336682b204b12c550ccc4e9ba62aedf1b2 100644 --- a/inc/lektor/net.h +++ b/inc/lektor/net.h @@ -60,6 +60,8 @@ struct lkt_state { struct lkt_module window_mod; struct lkt_module repo_mod; + + time_t start_date; }; /* Send a message to the connected client. */ diff --git a/src/base/commands.c b/src/base/commands.c index 65db132a891a3a924f74915d26fca1bdeaf91ee9..98c2ca2374204fe5512fee4603d0ccc1b17a6fd8 100644 --- a/src/base/commands.c +++ b/src/base/commands.c @@ -23,7 +23,8 @@ command_restart(struct lkt_state *srv, size_t c, char __attribute__((unused)) *__argv[LKT_MESSAGE_ARGS_MAX]) { const char *const argv[] = { executable_name, NULL }; - struct lkt_queue_state sta = {0}; + struct lkt_queue_state sta; + memset(&sta, 0, sizeof(struct lkt_queue_state)); RETURN_UNLESS(lkt_client_auth(srv, c, false), "Failed to authentificate user", false); if (!executable_name) { LOG_ERROR("GENERAL", "Can't restart if the executable path was not found at start-up"); @@ -116,6 +117,37 @@ command_currentsong(struct lkt_state *srv, size_t c, return true; } +bool +command_stats(struct lkt_state *srv, size_t c, + char __attribute__((unused)) *args[LKT_MESSAGE_ARGS_MAX]) +{ + struct lkt_message *out = lkt_message_new(); + int is_updating = 0, artists = 0, albums = 0, songs = 0; + long ts_update = 0; + + MOD_CALL(srv->repo_mod, "is_updating", &is_updating); + database_get_update(srv->db, &ts_update, NULL, NULL); + database_stats(srv->db, &artists, &albums, &songs); + + /* TODO: Missing fields: + * db_playtime: total time of what can be played with the database (can be + * problematic, needs some work to be done) + * playtime: time already played by lektord (need some modifications of + * the sdl2 module and any player module) */ + out->data_len = safe_snprintf(out->data, LKT_MESSAGE_MAX, + "__is_updating: %d\n" /* Custom field here */ + "db_update: %ld\n" + "uptime: %ld\n" + "artists: %d\n" /* Number of authors */ + "albums: %d\n" /* Number of source names */ + "songs: %d\n", /* Number of songs */ + is_updating, (uint64_t) ts_update, + (uint64_t) (time(NULL) - srv->start_date), + artists, albums, songs); + lkt_state_send(srv, c, out); + return true; +} + bool command_status(struct lkt_state *srv, size_t c, char __attribute__((unused)) *args[LKT_MESSAGE_ARGS_MAX]) @@ -123,13 +155,15 @@ command_status(struct lkt_state *srv, size_t c, struct lkt_message *out; struct lkt_queue_state queue_state; int elapsed, duration, songid = 0; + long update_job; const char *play_state; RETURN_UNLESS(srv, "Invalid argument", false); RETURN_UNLESS(database_queue_state(srv->db, &queue_state), "Can't get playback status", false); database_queue_current_kara(srv->db, NULL, &songid); - MOD_CALL(srv->window_mod, "get_elapsed", &elapsed); + MOD_CALL(srv->window_mod, "get_elapsed", &elapsed); MOD_CALL(srv->window_mod, "get_duration", &duration); + database_get_update(srv->db, NULL, &update_job, NULL); out = lkt_message_new(); play_state = queue_state.current <= 0 @@ -146,11 +180,12 @@ command_status(struct lkt_state *srv, size_t c, "playlistlength: %d\n" "elapsed: %d\n" "duration: %d\n" + "updating_db: %d" "songid: %d\n", queue_state.volume, queue_state.repeat, queue_state.random, queue_state.single, queue_state.consume, play_state, queue_state.current < 0 ? -1 : queue_state.current - 1, - queue_state.length, elapsed, duration, songid); + queue_state.length, elapsed, duration, update_job, songid); lkt_state_send(srv, c, out); return true; } @@ -210,6 +245,7 @@ command_play(struct lkt_state *srv, char __attribute__((unused)) *args[LKT_MESSA { char *endptr, err; long pos = 1; + struct lkt_queue_state queue_state; /* Argument handle. */ if (args[0]) { @@ -217,6 +253,13 @@ command_play(struct lkt_state *srv, char __attribute__((unused)) *args[LKT_MESSA RETURN_IF(err, "STRTOL failed", false); } + /* If position is after the queue, don't do anything */ + RETURN_UNLESS(database_queue_state(srv->db, &queue_state), "Can't get playback status", false); + if (queue_state.length <= pos) { + LOG_INFO("COMMAND", "Don't perform play command, queue is empty or position is after queue length"); + return false; + } + /* Do the actual job here. */ database_queue_stop(srv->db); RETURN_IF(MOD_CALL(srv->window_mod, "new", &srv->queue, srv->db), "Can't create window", false); diff --git a/src/database/update.c b/src/database/update.c index 535174aef55cedaef95fb9fb2572d0e0938353f6..92b82dabc1ea6abd7407c32b1ca7feaa00959f33 100644 --- a/src/database/update.c +++ b/src/database/update.c @@ -321,3 +321,27 @@ sqlite_just_exec(database_stamp, "UPDATE misc SET last_update = strftime('%s','n sqlite_just_exec(database_updated, "UPDATE misc SET last_end_update = strftime('%s','now'), update_job = update_job + 1;") #undef sqlite_just_exec + +bool +database_stats(volatile sqlite3 *db, int *authors, int *sources, int *karas) +{ + static const char *SQL = + "SELECT" + " (SELECT COUNT(DISTINCT author_name) FROM kara)," + " (SELECT COUNT(DISTINCT source_name) FROM kara)," + " (SELECT COUNT(id) FROM kara);"; + sqlite3_stmt *stmt = 0; + + SQLITE_PREPARE(db, stmt, SQL, error); + SQLITE_STEP_ROW(db, stmt, error); + + *authors = sqlite3_column_int(stmt, 0); + *sources = sqlite3_column_int(stmt, 1); + *karas = sqlite3_column_int(stmt, 2); + + sqlite3_finalize(stmt); + return true; +error: + sqlite3_finalize(stmt); + return false; +} diff --git a/src/main/lkt.c b/src/main/lkt.c index 079a34e1a1f200fc9f8ffd114fe46e15ea8d7581..ade01eadd79185d9367f69b4c35b7467a26e1155 100644 --- a/src/main/lkt.c +++ b/src/main/lkt.c @@ -488,61 +488,91 @@ status__(struct cmd_args *args) fail("Invalid argument, the status command takes no arguments"); static const char *const status_str__ = "status\n"; - int ret = EXIT_FAILURE, it = 0; + static const char *const stats_str__ = "stats\n"; char buff[LKT_MESSAGE_MAX]; char flags[24]; - bool play = false, stopped = true; - int time_pos = 0, time_duration = 0, song_index = 0, plt_len = 0; + bool play = false, stopped = true, is_updating = false; + int time_pos = 0, time_duration = 0, song_index = 0, plt_len = 0, ret = EXIT_FAILURE, it = 0; + size_t len; + time_t update_ts = 0; memset(flags, 0, 24 * sizeof(char)); FILE *sock = lkt_connect(); - /* Get lektor's status. */ +#define assign_flag(str, f) if (STR_NMATCH(buff, str, len) && (atoi(lkt_skip_key(buff)) == 1)) { flags[it++] = f; continue; } +#define assign_int(str, var) if (STR_NMATCH(buff, str, len)) { var = (strtoll(lkt_skip_key(buff), NULL, 0)); continue; } +#define check_end() \ + if (STR_NMATCH(buff, "OK", 2)) break; \ + else if (STR_NMATCH(buff, "ACK", 3)) goto error; + /* Get lektor's status */ write_socket(sock, status_str__); - -#define assign_flag(str, f) if (STR_NMATCH(buff, str, len) && (atoi(lkt_skip_key(buff)) == 1)) { flags[it++] = f; continue; } -#define assign_int(str, var) if (STR_NMATCH(buff, str, len)) { var = (atoi(lkt_skip_key(buff))); continue; } for (;;) { - memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); - read_socket(sock, buff, LKT_MESSAGE_MAX - 1); - size_t len = strcspn(buff, LKT_KEY_VALUE_SEP); + len = read_socket(sock, buff, LKT_MESSAGE_MAX - 1); + buff[len] = '\0'; + len = strcspn(buff, LKT_KEY_VALUE_SEP); if (STR_NMATCH(buff, "state", len)) { char *it = lkt_skip_key(buff); - play = STR_NMATCH(it, "play", 4); - stopped = STR_NMATCH(it, "stop", 4); + play = STR_NMATCH(it, "play", 4); + stopped = STR_NMATCH(it, "stop", 4); continue; } - assign_flag("random", 'r') - assign_flag("repeat", 'l') - assign_flag("single", 's') + assign_flag("random", 'r') + assign_flag("repeat", 'l') + assign_flag("single", 's') assign_flag("consume", 'c') - assign_int("elapsed", time_pos) - assign_int("duration", time_duration) - assign_int("song", song_index) + assign_int("elapsed", time_pos) + assign_int("duration", time_duration) + assign_int("song", song_index) assign_int("playlistlength", plt_len) - /* At this point every key has been parsed. */ - if (STR_NMATCH(buff, "OK", 2)) - goto print; - else if (STR_NMATCH(buff, "ACK", 3)) - goto error; + check_end() } -#undef assign_flag -#undef assign_int -print: + /* Get lektor's stats */ + write_socket(sock, stats_str__); + for (;;) { + len = read_socket(sock, buff, LKT_MESSAGE_MAX - 1); + buff[len] = '\0'; + len = strcspn(buff, LKT_KEY_VALUE_SEP); + + assign_int("__is_updating", is_updating) + assign_int("db_update", update_ts) + + check_end() + } + + /* End of communication */ fclose(sock); - printf("Lektor: %s\nQueue: #%d/%d\nPlayback: %d/%ds\nFlags: %s\n", +#undef assign_flag +#undef assign_int +#undef check_end + + struct tm *p_tm = localtime(&update_ts); + len = strftime(buff, LKT_MESSAGE_MAX - 1, "%F %H:%M:%S", p_tm); + buff[len] = '\0'; + + int pla_m = time_pos / 60; + char pla_s = time_pos % 60; + int dur_m = time_duration / 60; + char dur_s = time_duration % 60; + + printf("Lektor: %s\n" + "Queue: #%d/%d\n" + "Playback: %d:%d/%d:%d\n" + "Flags: %s\n" + "%s: %s\n", play ? "play" : "stopped", - song_index + 1, plt_len, time_pos, time_duration, - flags[0] ? flags : "(none)"); + song_index + 1, plt_len, + pla_m, pla_s, dur_m, dur_s, + flags[0] ? flags : "(none)", + is_updating ? "Updating" : "Last update", buff); /* If there is a kara loaded in mpv, get this kara. */ if (!stopped) { diff --git a/src/module/module_repo.c b/src/module/module_repo.c index e163357ee15744fa0945c68a74421c075c61d933..726d5705902fde9efa12ad6faae95933d3e1497b 100644 --- a/src/module/module_repo.c +++ b/src/module/module_repo.c @@ -657,12 +657,29 @@ mod_rescan(va_list *va) return 0; } +static int +mod_is_updating(va_list *va) +{ + va_list copy; + struct module_repo_internal **repo; + int *ret; + va_copy(copy, *va); + repo = (struct module_repo_internal **) va_arg(copy, void **); + ret = va_arg(copy, int *); + if (ret == NULL) + return 1; + *ret = (*repo)->updating; + va_end(copy); + return 0; +} + REG_BEGIN(repo_reg) -REG_ADD_NAMED("new", mod_new) -REG_ADD_NAMED("free", mod_free) -REG_ADD_NAMED("close", mod_close) -REG_ADD_NAMED("update", mod_update) -REG_ADD_NAMED("rescan", mod_rescan) +REG_ADD_NAMED("new", mod_new) +REG_ADD_NAMED("free", mod_free) +REG_ADD_NAMED("close", mod_close) +REG_ADD_NAMED("update", mod_update) +REG_ADD_NAMED("rescan", mod_rescan) +REG_ADD_NAMED("is_updating", mod_is_updating) REG_END() #if ! defined (LKT_STATIC_MODULE) REG_EXPORT(repo_reg) diff --git a/src/net/listen.c b/src/net/listen.c index a1f1653d79bf314a012edfb45f886fc39610f03c..0a227239af1257c790507f9183500eeb51a39323 100644 --- a/src/net/listen.c +++ b/src/net/listen.c @@ -166,38 +166,40 @@ handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd) /* Commands that are available if not in idle mode */ else if (STR_MATCH(cmd.name, "currentsong")) - err = !command_currentsong(srv, c, NULL); + err = ! command_currentsong(srv, c, NULL); else if (STR_MATCH(cmd.name, "status")) - err = !command_status(srv, c, NULL); + err = ! command_status(srv, c, NULL); + else if (STR_MATCH(cmd.name, "stats")) + err = ! command_stats(srv, c, NULL); else if (STR_MATCH(cmd.name, "close")) - err = !lkt_close_client(srv, c); + err = ! lkt_close_client(srv, c); else if (STR_MATCH(cmd.name, "ping")) err = 0; else if (STR_MATCH(cmd.name, "next")) - err = !command_next(srv, NULL); + err = ! command_next(srv, NULL); else if (STR_MATCH(cmd.name, "pause")) - err = !command_pause(srv, NULL); + err = ! command_pause(srv, NULL); else if (STR_MATCH(cmd.name, "previous")) - err = !command_previous(srv, NULL); + err = ! command_previous(srv, NULL); else if (STR_MATCH(cmd.name, "play")) err = ! command_play(srv, cmd.args); else if (STR_MATCH(cmd.name, "playid")) err = ! command_playid(srv, cmd.args); else if (STR_MATCH(cmd.name, "stop")) - err = !command_stop(srv, NULL); + err = ! command_stop(srv, NULL); else if (STR_MATCH(cmd.name, "clear")) - err = !command_clear(srv, NULL); + err = ! command_clear(srv, NULL); else if (STR_MATCH(cmd.name, "crop")) - err = !command_crop(srv, NULL); + err = ! command_crop(srv, NULL); else if (STR_MATCH(cmd.name, "moveid")) - err = !command_move(srv, cmd.args); + err = ! command_move(srv, cmd.args); else if (STR_MATCH(cmd.name, "shuffle")) - err = !command_shuffle(srv, NULL); + err = ! command_shuffle(srv, NULL); else if (STR_MATCH(cmd.name, "playlist") || STR_MATCH(cmd.name, "playlistinfo")) - err = !command_queue_list(srv, c, cmd.args); + err = ! command_queue_list(srv, c, cmd.args); else if (STR_MATCH(cmd.name, "playlistfind") || STR_MATCH(cmd.name, "playlistsearch")) err = ! command_find(srv, c, cmd.args, cmd.cont, database_search_queue_init); @@ -215,7 +217,7 @@ handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd) } else if (STR_MATCH(cmd.name, "help")) - err = !command_help(srv, c, NULL); + err = ! command_help(srv, c, NULL); else if (STR_MATCH(cmd.name, "__insert")) err = ! command_add(srv, cmd.args, 5); @@ -253,21 +255,21 @@ handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd) 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); + err = ! command_set_playback_option(srv, c, lkt_playback_option_random, cmd.args); else if (STR_MATCH(cmd.name, "repeat")) - err = !command_set_playback_option(srv, c, lkt_playback_option_repeat, cmd.args); + err = ! command_set_playback_option(srv, c, lkt_playback_option_repeat, cmd.args); else if (STR_MATCH(cmd.name, "setvol")) - err = !command_set_playback_option(srv, c, lkt_playback_option_volume, cmd.args); + err = ! command_set_playback_option(srv, c, lkt_playback_option_volume, cmd.args); else if (STR_MATCH(cmd.name, "single")) - err = !command_set_playback_option(srv, c, lkt_playback_option_single, cmd.args); + err = ! command_set_playback_option(srv, c, lkt_playback_option_single, cmd.args); else if (STR_MATCH(cmd.name, "consume")) - err = !command_set_playback_option(srv, c, lkt_playback_option_consume, cmd.args); + err = ! command_set_playback_option(srv, c, lkt_playback_option_consume, cmd.args); else if (STR_MATCH(cmd.name, "password")) - err = !command_password(srv, c, cmd.args); + err = ! command_password(srv, c, cmd.args); else if (STR_MATCH(cmd.name, "idle")) { - err = !command_idle(srv, c, &cmd); + err = ! command_idle(srv, c, &cmd); goto end_no_send_status; } else if (STR_MATCH(cmd.name, "search") || STR_MATCH(cmd.name, "find")) err = ! command_find(srv, c, cmd.args, cmd.cont, database_search_database_init); @@ -279,10 +281,10 @@ handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd) default: /* commands available only in idle mode */ if (STR_MATCH(cmd.name, "idle")) { - err = !command_idle(srv, c, &cmd); + err = ! command_idle(srv, c, &cmd); goto end_no_send_status; } else if (STR_MATCH(cmd.name, "noidle")) - err = !command_noidle(srv, c); + err = ! command_noidle(srv, c); else err = 2; @@ -798,7 +800,13 @@ lkt_listen(struct lkt_state *srv) return; srv->fds[0].events = POLLIN; - srv->fds_len = 1; + srv->fds_len = 1; + srv->start_date = time(NULL); + + if (srv->start_date == (time_t) -1) { + LOG_ERROR("FATAL", "Overflow or error on call to time function"); + exit(EXIT_FAILURE); + } /* Listen */ for (;;) {