#include "sideband.h"
#include "command.h"
-static struct afs_table afs_tables[] = {
- {.init = aft_init, .name = "audio_files"},
- {.init = attribute_init, .name = "attributes"},
- {.init = score_init, .name = "scores"},
- {.init = moods_init, .name = "moods"},
- {.init = lyrics_init, .name = "lyrics"},
- {.init = images_init, .name = "images"},
- {.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 {
*/
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;
};
if (ret < 0)
goto out;
cq = query_shm;
- cq->handler = f;
+ cq->cb = f;
cq->query_size = query_shm_size - sizeof(*cq);
if (query)
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 = mood_switch(NULL, errmsg);
- if (ret < 0) {
- if (num_admissible)
- *num_admissible = 0;
- return ret;
- }
} else {
- if (!strncmp(arg, "p/", 2)) {
- ret = playlist_open(arg + 2, errmsg);
- mode = PLAY_MODE_PLAYLIST;
- } else if (!strncmp(arg, "m/", 2)) {
- ret = mood_switch(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
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);
}
}
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;
}
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);
if (ret >= 0)
return ret;
while (i)
- afs_tables[--i].close();
+ afs_tables[--i].ops->close();
return ret;
}
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;
.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 */
__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;
}
ret = schedule(&s);
sched_shutdown(&s);
- close_current_mood();
+ mood_unload();
out_close:
close_afs_tables();
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;
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",
}
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;
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;
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));