X-Git-Url: http://git.tuebingen.mpg.de/?a=blobdiff_plain;f=afs.c;h=b15f83852e6919c4f0867065d13776c211416087;hb=6bbc7c29dd69ed34ef90dac72951abbfd7cc1877;hp=febe13b3e7c561cf2082ab9200c6578806ab8026;hpb=b142089267ef501e438c3dc77ecf19bead3d4e58;p=paraslash.git diff --git a/afs.c b/afs.c index febe13b3..b15f8385 100644 --- a/afs.c +++ b/afs.c @@ -24,51 +24,36 @@ #include "afs.h" #include "net.h" #include "server.h" +#include "daemon.h" #include "ipc.h" #include "list.h" #include "sched.h" #include "fd.h" #include "signal.h" -#include "mood.h" #include "sideband.h" #include "command.h" -/** The osl tables used by afs. \sa \ref blob.c. */ -enum afs_table_num { - /** Contains audio file information. See \ref aft.c. */ - TBLNUM_AUDIO_FILES, - /** The table for the paraslash attributes. See \ref attribute.c. */ - TBLNUM_ATTRIBUTES, - /* - * Moods and playlists organize the current set of admissible files in - * an osl table which contains only volatile columns. Each row consists - * of a pointer to an audio file and the score value of this file. - */ - TBLNUM_SCORES, - /** - * A standard blob table containing the mood definitions. For details - * see \ref mood.c. - */ - TBLNUM_MOODS, - /** A blob table containing lyrics on a per-song basis. */ - TBLNUM_LYRICS, - /** Another blob table for images (for example album cover art). */ - TBLNUM_IMAGES, - /** Yet another blob table for storing standard playlists. */ - TBLNUM_PLAYLIST, - /** How many tables are in use? */ - NUM_AFS_TABLES -}; - -static struct afs_table afs_tables[NUM_AFS_TABLES] = { - [TBLNUM_AUDIO_FILES] = {.init = aft_init, .name = "audio_files"}, - [TBLNUM_ATTRIBUTES] = {.init = attribute_init, .name = "attributes"}, - [TBLNUM_SCORES] = {.init = score_init, .name = "scores"}, - [TBLNUM_MOODS] = {.init = moods_init, .name = "moods"}, - [TBLNUM_LYRICS] = {.init = lyrics_init, .name = "lyrics"}, - [TBLNUM_IMAGES] = {.init = images_init, .name = "images"}, - [TBLNUM_PLAYLIST] = {.init = playlists_init, .name = "playlists"}, +/** + * The array of tables of the audio file selector. + * + * We organize them in an array to be able to loop over all tables. + */ +static const struct afs_table { + /** The name is no table operation, so define it here. */ + const char * const name; + /** The only way to invoke the ops is via this pointer. */ + const struct afs_table_operations *ops; +} afs_tables[] = { + {.name = "audio_files", .ops = &aft_ops}, + {.name = "attributes", .ops = &attr_ops}, + {.name = "scores", .ops = &score_ops}, + {.name = "moods", .ops = &moods_ops}, + {.name = "lyrics", .ops = &lyrics_ops}, + {.name = "images", .ops = &images_ops}, + {.name = "playlists", .ops = &playlists_ops}, }; +/** Used to loop over the afs tables. */ +#define NUM_AFS_TABLES ARRAY_SIZE(afs_tables) struct command_task { /** The file descriptor for the local socket. */ @@ -115,7 +100,7 @@ extern uint32_t afs_socket_cookie; */ struct callback_query { /** The function to be called. */ - afs_callback *handler; + afs_callback *cb; /** The number of bytes of the query */ size_t query_size; }; @@ -206,7 +191,7 @@ int send_callback_request(afs_callback *f, struct osl_object *query, if (ret < 0) goto out; cq = query_shm; - cq->handler = f; + cq->cb = f; cq->query_size = query_shm_size - sizeof(*cq); if (query) @@ -446,40 +431,30 @@ no_admissible_files: return write_all(server_socket, buf, 8); } -static int activate_mood_or_playlist(const char *arg, int *num_admissible, - char **errmsg) +static int activate_mood_or_playlist(const char *arg, struct para_buffer *pb) { enum play_mode mode; int ret; + char *msg; if (!arg) { + ret = mood_load(NULL, &msg); + mode = PLAY_MODE_MOOD; + } else if (!strncmp(arg, "p/", 2)) { + ret = playlist_load(arg + 2, &msg); + mode = PLAY_MODE_PLAYLIST; + } else if (!strncmp(arg, "m/", 2)) { + ret = mood_load(arg + 2, &msg); mode = PLAY_MODE_MOOD; - ret = change_current_mood(NULL, errmsg); - if (ret < 0) { - if (num_admissible) - *num_admissible = 0; - return ret; - } } else { - if (!strncmp(arg, "p/", 2)) { - ret = playlist_open(arg + 2); - if (ret < 0 && errmsg) - *errmsg = make_message( "could not open %s", - arg); - mode = PLAY_MODE_PLAYLIST; - } else if (!strncmp(arg, "m/", 2)) { - ret = change_current_mood(arg + 2, errmsg); - mode = PLAY_MODE_MOOD; - } else { - if (errmsg) - *errmsg = make_message("%s: parse error", arg); - return -ERRNO_TO_PARA_ERROR(EINVAL); - } - if (ret < 0) - return ret; + ret = -ERRNO_TO_PARA_ERROR(EINVAL); + msg = make_message("%s: parse error", arg); } - if (num_admissible) - *num_admissible = ret; + if (pb) + para_printf(pb, "%s", msg); + free(msg); + if (ret < 0) + return ret; current_play_mode = mode; /* * We get called with arg == current_mop from the signal dispatcher @@ -489,22 +464,15 @@ static int activate_mood_or_playlist(const char *arg, int *num_admissible, */ if (arg != current_mop) { free(current_mop); - if (arg) { - current_mop = para_strdup(arg); - mutex_lock(mmd_mutex); - strncpy(mmd->afs_mode_string, arg, - sizeof(mmd->afs_mode_string)); - mmd->afs_mode_string[sizeof(mmd->afs_mode_string) - 1] = '\0'; - mmd->events++; - mutex_unlock(mmd_mutex); - } else { - mutex_lock(mmd_mutex); - strcpy(mmd->afs_mode_string, "dummy"); - mmd->events++; - mutex_unlock(mmd_mutex); - current_mop = NULL; - } + current_mop = arg? para_strdup(arg) : NULL; } + /* Notify the server about the mood/playlist change. */ + mutex_lock(mmd_mutex); + strncpy(mmd->afs_mode_string, arg? arg: "dummy", + sizeof(mmd->afs_mode_string)); + mmd->afs_mode_string[sizeof(mmd->afs_mode_string) - 1] = '\0'; + mmd->events++; + mutex_unlock(mmd_mutex); return 1; } @@ -557,77 +525,14 @@ static void flush_and_free_pb(struct para_buffer *pb) free(pb->buf); } -static int com_select_callback(struct afs_callback_arg *aca) -{ - const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT); - const char *arg; - int num_admissible, ret; - char *errmsg; - - ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr); - assert(ret >= 0); - arg = lls_input(0, aca->lpr); - ret = clear_score_table(); - if (ret < 0) { - para_printf(&aca->pbout, "could not clear score table\n"); - goto free_lpr; - } - if (current_play_mode == PLAY_MODE_MOOD) - close_current_mood(); - else - playlist_close(); - ret = activate_mood_or_playlist(arg, &num_admissible, &errmsg); - if (ret >= 0) - goto out; - /* ignore subsequent errors (but log them) */ - para_printf(&aca->pbout, "%s\n", errmsg); - free(errmsg); - para_printf(&aca->pbout, "could not activate %s\n", arg); - if (current_mop && strcmp(current_mop, arg) != 0) { - int ret2; - para_printf(&aca->pbout, "switching back to %s\n", current_mop); - ret2 = activate_mood_or_playlist(current_mop, &num_admissible, - &errmsg); - if (ret2 >= 0) - goto out; - para_printf(&aca->pbout, "%s\n", errmsg); - free(errmsg); - para_printf(&aca->pbout, "could not reactivate %s: %s\n", - current_mop, para_strerror(-ret2)); - } - para_printf(&aca->pbout, "activating dummy mood\n"); - activate_mood_or_playlist(NULL, &num_admissible, NULL); -out: - para_printf(&aca->pbout, "activated %s (%d admissible file%s)\n", - current_mop? current_mop : "dummy mood", num_admissible, - num_admissible == 1? "" : "s"); -free_lpr: - lls_free_parse_result(aca->lpr, cmd); - return ret; -} - -static int com_select(struct command_context *cc, struct lls_parse_result *lpr) -{ - const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT); - char *errctx; - int ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx)); - - if (ret < 0) { - send_errctx(cc, errctx); - return ret; - } - return send_lls_callback_request(com_select_callback, cmd, lpr, cc); -} -EXPORT_SERVER_CMD_HANDLER(select); - static void init_admissible_files(const char *arg) { - int ret = activate_mood_or_playlist(arg, NULL, NULL); + int ret = activate_mood_or_playlist(arg, NULL); if (ret < 0) { PARA_WARNING_LOG("could not activate %s: %s\n", arg, para_strerror(-ret)); if (arg) - activate_mood_or_playlist(NULL, NULL, NULL); + activate_mood_or_playlist(NULL, NULL); } } @@ -655,7 +560,7 @@ static void close_afs_tables(void) int i; PARA_NOTICE_LOG("closing afs tables\n"); for (i = 0; i < NUM_AFS_TABLES; i++) - afs_tables[i].close(); + afs_tables[i].ops->close(); free(database_dir); database_dir = NULL; } @@ -691,10 +596,10 @@ static int open_afs_tables(void) int i, ret; get_database_dir(); - PARA_NOTICE_LOG("opening %d osl tables in %s\n", NUM_AFS_TABLES, + PARA_NOTICE_LOG("opening %zu osl tables in %s\n", NUM_AFS_TABLES, database_dir); for (i = 0; i < NUM_AFS_TABLES; i++) { - ret = afs_tables[i].open(database_dir); + ret = afs_tables[i].ops->open(database_dir); if (ret >= 0) continue; PARA_ERROR_LOG("could not open %s\n", afs_tables[i].name); @@ -703,7 +608,7 @@ static int open_afs_tables(void) if (ret >= 0) return ret; while (i) - afs_tables[--i].close(); + afs_tables[--i].ops->close(); return ret; } @@ -823,6 +728,43 @@ err: return ret; } +/** + * Format and send an error message to the command handler. + * + * To pass an error message from the callback of an afs command to the client, + * this function should be called. It formats the message into a buffer which + * is passed as a shared memory area to the command handler from where it + * propagates to the client. + * + * The message will be tagged with the ERROR_LOG sideband designator so that + * the client writes it to its stderr stream rather than to stdout as with + * aca->pbout. In analogy to the default Unix semantics of stderr, the message + * is sent without buffering. + * + * If sending the error message fails, an error is logged on the server side, + * but no other action is taken. + * + * \param aca Used to obtain the fd to send the shmid to. + * \param fmt Usual format string. + */ +__printf_2_3 void afs_error(const struct afs_callback_arg *aca, + const char *fmt,...) +{ + va_list argp; + char *msg; + unsigned n; + int ret; + + va_start(argp, fmt); + n = xvasprintf(&msg, fmt, argp); + va_end(argp); + ret = pass_buffer_as_shm(aca->fd, SBD_ERROR_LOG, msg, n + 1); + if (ret < 0) + PARA_ERROR_LOG("Could not send %s: %s\n", msg, + para_strerror(-ret)); + free(msg); +} + static int call_callback(int fd, int query_shmid) { void *query_shm; @@ -842,7 +784,7 @@ static int call_callback(int fd, int query_shmid) .fd = fd, .band = SBD_OUTPUT }; - ret = cq->handler(&aca); + ret = cq->cb(&aca); ret2 = shm_detach(query_shm); if (ret2 < 0) { if (ret < 0) /* ignore (but log) detach error */ @@ -959,7 +901,7 @@ static int command_post_monitor(struct sched *s, void *context) close(fd); return 0; } - client = para_malloc(sizeof(*client)); + client = alloc(sizeof(*client)); client->fd = fd; client->connect_time = *now; para_list_add(&client->node, &afs_client_list); @@ -979,6 +921,14 @@ static void register_command_task(struct sched *s) }, s); } +static int afs_poll(struct pollfd *fds, nfds_t nfds, int timeout) +{ + mutex_lock(mmd_mutex); + daemon_set_loglevel(mmd->loglevel); + mutex_unlock(mmd_mutex); + return xpoll(fds, nfds, timeout); +} + /** * Initialize the audio file selector process. * @@ -987,12 +937,10 @@ static void register_command_task(struct sched *s) __noreturn void afs_init(int socket_fd) { static struct sched s; - int i, ret; + int ret; register_signal_task(&s); init_list_head(&afs_client_list); - for (i = 0; i < NUM_AFS_TABLES; i++) - afs_tables[i].init(&afs_tables[i]); ret = open_afs_tables(); if (ret < 0) goto out; @@ -1004,6 +952,7 @@ __noreturn void afs_init(int socket_fd) init_admissible_files(OPT_STRING_VAL(AFS_INITIAL_MODE)); register_command_task(&s); s.default_timeout = 1000; + s.poll_function = afs_poll; ret = write(socket_fd, "\0", 1); if (ret != 1) { if (ret == 0) @@ -1013,7 +962,7 @@ __noreturn void afs_init(int socket_fd) } ret = schedule(&s); sched_shutdown(&s); - close_current_mood(); + mood_unload(); out_close: close_afs_tables(); out: @@ -1026,6 +975,53 @@ out: exit(EXIT_FAILURE); } +static int com_select_callback(struct afs_callback_arg *aca) +{ + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT); + const char *arg; + int ret; + + ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr); + assert(ret >= 0); + arg = lls_input(0, aca->lpr); + score_clear(); + if (current_play_mode == PLAY_MODE_MOOD) + mood_unload(); + else + playlist_unload(); + ret = activate_mood_or_playlist(arg, &aca->pbout); + if (ret >= 0) + goto free_lpr; + /* ignore subsequent errors (but log them) */ + if (current_mop && strcmp(current_mop, arg) != 0) { + int ret2; + afs_error(aca, "switching back to %s\n", current_mop); + ret2 = activate_mood_or_playlist(current_mop, &aca->pbout); + if (ret2 >= 0) + goto free_lpr; + afs_error(aca, "could not reactivate %s: %s\n", current_mop, + para_strerror(-ret2)); + } + activate_mood_or_playlist(NULL, &aca->pbout); +free_lpr: + lls_free_parse_result(aca->lpr, cmd); + return ret; +} + +static int com_select(struct command_context *cc, struct lls_parse_result *lpr) +{ + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT); + char *errctx; + int ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx)); + + if (ret < 0) { + send_errctx(cc, errctx); + return ret; + } + return send_lls_callback_request(com_select_callback, cmd, lpr, cc); +} +EXPORT_SERVER_CMD_HANDLER(select); + static int com_init_callback(struct afs_callback_arg *aca) { uint32_t table_mask = *(uint32_t *)aca->query.data; @@ -1034,16 +1030,15 @@ static int com_init_callback(struct afs_callback_arg *aca) close_afs_tables(); get_database_dir(); for (i = 0; i < NUM_AFS_TABLES; i++) { - struct afs_table *t = &afs_tables[i]; + const struct afs_table *t = afs_tables + i; if (!(table_mask & (1 << i))) continue; - if (!t->create) + if (!t->ops->create) continue; - ret = t->create(database_dir); + ret = t->ops->create(database_dir); if (ret < 0) { - para_printf(&aca->pbout, "cannot create table %s\n", - t->name); + afs_error(aca, "cannot create table %s\n", t->name); goto out; } para_printf(&aca->pbout, "successfully created %s table\n", @@ -1051,7 +1046,7 @@ static int com_init_callback(struct afs_callback_arg *aca) } ret = open_afs_tables(); if (ret < 0) - para_printf(&aca->pbout, "cannot open afs tables: %s\n", + afs_error(aca, "cannot open afs tables: %s\n", para_strerror(-ret)); out: return ret; @@ -1072,7 +1067,7 @@ static int com_init(struct command_context *cc, struct lls_parse_result *lpr) table_mask = 0; for (i = 0; i < num_inputs; i++) { for (j = 0; j < NUM_AFS_TABLES; j++) { - struct afs_table *t = &afs_tables[j]; + const struct afs_table *t = afs_tables + j; if (strcmp(lls_input(i, lpr), t->name)) continue; @@ -1146,10 +1141,10 @@ __must_check int afs_event(enum afs_events event, struct para_buffer *pb, int i, ret; for (i = 0; i < NUM_AFS_TABLES; i++) { - struct afs_table *t = &afs_tables[i]; - if (!t->event_handler) + const struct afs_table *t = afs_tables + i; + if (!t->ops->event_handler) continue; - ret = t->event_handler(event, pb, data); + ret = t->ops->event_handler(event, pb, data); if (ret < 0) { PARA_CRIT_LOG("table %s, event %u: %s\n", t->name, event, para_strerror(-ret));