0.7.2 (to be announced) "optical friction"
------------------------------------------
+- A major cleanup of the audio file selector.
+- The client no longer prints error messages from afs commands to
+ stdout but to stderr.
- The sleep subcommand of para_mixer gained two options to control
the startup mood and the time period before fade-out starts. A bunch
of further improvements for this subcommand went in as well.
#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. */
*/
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 = 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
*/
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;
}
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;
}
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);
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));
struct afs_info *old_afsi;
};
-/** Function pointers for table handling. */
-struct afs_table {
- /** Initializes the other pointers in this struct. */
- void (*init)(struct afs_table *t);
- /** The name of this table. */
- const char *name;
- /** Gets called on startup and on \p SIGHUP. */
+/** Methods for table startup/shutdown and event handling. */
+struct afs_table_operations {
+ /** Gets called on startup and on SIGHUP. */
int (*open)(const char *base_dir);
- /** Gets called on shutdown and on \p SIGHUP. */
+ /** Gets called on shutdown and on SIGHUP. */
void (*close)(void);
- /** Called by the \a init afs command. */
+ /** Called from the init command. */
int (*create)(const char *);
- /** Handles afs events. */
+ /** Handle events generated by other tables. See enum \ref afs_events. */
int (*event_handler)(enum afs_events event, struct para_buffer *pb,
void *data);
};
int send_lls_callback_request(afs_callback *f,
const struct lls_command * const cmd,
struct lls_parse_result *lpr, void *private_result_data);
+__printf_2_3 void afs_error(const struct afs_callback_arg *aca,
+ const char *fmt,...);
int string_compare(const struct osl_object *obj1, const struct osl_object *obj2);
int for_each_matching_row(struct pattern_match_data *pmd);
/* score */
-void score_init(struct afs_table *t);
-int admissible_file_loop(void *data, osl_rbtree_loop_func *func);
+extern const struct afs_table_operations score_ops;
+int score_loop(osl_rbtree_loop_func *func, void *data);
int score_get_best(struct osl_row **aft_row, long *score);
int get_score_and_aft_row(struct osl_row *score_row, long *score, struct osl_row **aft_row);
int score_add(const struct osl_row *row, long score);
int score_update(const struct osl_row *aft_row, long new_score);
-int get_num_admissible_files(unsigned *num);
int score_delete(const struct osl_row *aft_row);
-int clear_score_table(void);
-int row_belongs_to_score_table(const struct osl_row *aft_row, unsigned *rank);
+void score_clear(void);
+bool row_belongs_to_score_table(const struct osl_row *aft_row);
/* attribute */
-void attribute_init(struct afs_table *t);
+extern const struct afs_table_operations attr_ops;
void get_attribute_bitmap(const uint64_t *atts, char *buf); /* needed by com_ls() */
int get_attribute_bitnum_by_name(const char *att_name, unsigned char *bitnum);
int get_attribute_text(uint64_t *atts, const char *delim, char **text);
int attribute_check_callback(struct afs_callback_arg *aca);
/* aft */
-void aft_init(struct afs_table *t);
+extern const struct afs_table_operations aft_ops;
int aft_get_row_of_path(const char *path, struct osl_row **row);
int aft_check_attributes(uint64_t att_mask, struct para_buffer *pb);
int open_and_update_audio_file(int *fd);
int aft_check_callback(struct afs_callback_arg *aca);
void free_status_items(void);
+/* mood */
+int mood_load(const char *mood_name, char **msg);
+void mood_unload(void);
+int mood_check_callback(struct afs_callback_arg *aca);
+
/* playlist */
-int playlist_open(const char *name);
-void playlist_close(void);
+int playlist_load(const char *name, char **msg);
+void playlist_unload(void);
int playlist_check_callback(struct afs_callback_arg *aca);
/** evaluates to 1 if x < y, to -1 if x > y and to 0 if x == y */
/** Define exported functions and a table pointer for an osl blob table. */
#define DECLARE_BLOB_SYMBOLS(table_name, cmd_prefix) \
- void table_name ## _init(struct afs_table *t); \
int cmd_prefix ## _get_name_by_id(uint32_t id, char **name); \
int cmd_prefix ## _get_def_by_id(uint32_t id, struct osl_object *def); \
- int cmd_prefix ## _get_def_by_name(char *name, struct osl_object *def); \
+ int cmd_prefix ## _get_def_by_name(const char *name, struct osl_object *def); \
int cmd_prefix ## _get_name_and_def_by_row(const struct osl_row *row, \
char **name, struct osl_object *def); \
int table_name ##_event_handler(enum afs_events event, \
struct para_buffer *pb, void *data); \
- extern struct osl_table *table_name ## _table;
+ extern struct osl_table *table_name ## _table; \
+ extern const struct afs_table_operations table_name ## _ops;
/** \cond blob_symbols */
DECLARE_BLOB_SYMBOLS(lyrics, lyr);
aca->pbout.flags = (opts->mode == LS_MODE_PARSER)? PBF_SIZE_PREFIX : 0;
if (admissible_only(opts))
- ret = admissible_file_loop(opts, prepare_ls_row);
+ ret = score_loop(prepare_ls_row, opts);
else
ret = osl(osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts,
prepare_ls_row));
ret = afs_event(AUDIO_FILE_ADD, &aca->pbout, aft_row);
out:
if (ret < 0)
- para_printf(&aca->pbout, "could not add %s\n", path);
+ afs_error(aca, "could not add %s\n", path);
lls_free_parse_result(aca->lpr, cmd);
return ret;
}
ret = get_afsi_object_of_row(row, &obj);
if (ret < 0) {
- para_printf(&aca->pbout, "cannot touch %s\n", name);
+ afs_error(aca, "cannot touch %s\n", name);
return ret;
}
ret = load_afsi(&old_afsi, &obj);
if (ret < 0) {
- para_printf(&aca->pbout, "cannot touch %s\n", name);
+ afs_error(aca, "cannot touch %s\n", name);
return ret;
}
new_afsi = old_afsi;
uint32_t id = lls_uint32_val(0, r_i);
ret = img_get_name_by_id(id, NULL);
if (ret < 0) {
- para_printf(&aca->pbout, "invalid image ID: %u\n", id);
+ afs_error(aca, "invalid image ID: %u\n", id);
return ret;
}
}
uint32_t id = lls_uint32_val(0, r_y);
ret = lyr_get_name_by_id(id, NULL);
if (ret < 0) {
- para_printf(&aca->pbout, "invalid lyrics ID: %u\n", id);
+ afs_error(aca, "invalid lyrics ID: %u\n", id);
return ret;
}
}
return ret;
ret = osl(osl_del_row(audio_file_table, row));
if (ret < 0)
- para_printf(&aca->pbout, "cannot remove %s\n", name);
+ afs_error(aca, "cannot remove %s\n", name);
return ret;
}
ret = get_attribute_bitnum_by_name(p, &bitnum);
free(p);
if (ret < 0) {
- para_printf(&aca->pbout, "invalid argument: %s\n", arg);
+ afs_error(aca, "invalid argument: %s\n", arg);
goto out;
}
if (c == '+')
}
}
-/**
- * Initialize the audio file table.
- *
- * \param t Pointer to the structure to be initialized.
- */
-void aft_init(struct afs_table *t)
-{
- t->open = aft_open;
- t->close = aft_close;
- t->create = aft_create;
- t->event_handler = aft_event_handler;
-}
+/** The audio file table contains information about known audio files. */
+const struct afs_table_operations aft_ops = {
+ .open = aft_open,
+ .close = aft_close,
+ .create = aft_create,
+ .event_handler = aft_event_handler,
+};
}
ret = osl(osl_get_object(table, row, ATTCOL_BITNUM, &bitnum_obj));
if (ret < 0) {
- para_printf(&aca->pbout, "%s: %s\n", name, para_strerror(-ret));
+ afs_error(aca, "%s: %s\n", name, para_strerror(-ret));
return ret;
}
para_printf(&aca->pbout, "%u\t%s\n", *(unsigned char*)bitnum_obj.data,
}
EXPORT_SERVER_CMD_HANDLER(lsatt);
-struct addatt_event_data {
- const char *name;
- unsigned char bitnum;
-};
-
static int com_addatt_callback(struct afs_callback_arg *aca)
{
const struct lls_command *cmd = SERVER_CMD_CMD_PTR(ADDATT);
struct osl_object objs[NUM_ATT_COLUMNS];
struct osl_row *row;
unsigned char bitnum;
- struct addatt_event_data aed;
len = strlen(name);
if (len == 0 || name[len - 1] == '-' || name[len - 1] == '+') {
- para_printf(&aca->pbout,
- "invalid attribute name: %s\n", name);
+ afs_error(aca, "invalid attribute name: %s\n", name);
continue;
}
ret = get_attribute_bitnum_by_name(name, &bitnum);
ret = osl(osl_add_row(attribute_table, objs));
if (ret < 0)
goto out;
- aed.name = name;
- aed.bitnum = bitnum;
- ret = afs_event(ATTRIBUTE_ADD, &aca->pbout, &aed);
+ ret = afs_event(ATTRIBUTE_ADD, &aca->pbout, NULL);
if (ret < 0)
goto out;
greatest_att_bitnum = PARA_MAX(greatest_att_bitnum, (int)bitnum);
}
out:
if (ret < 0)
- para_printf(&aca->pbout, "error while adding %s\n",
+ afs_error(aca, "error while adding %s\n",
lls_input(i, aca->lpr));
lls_free_parse_result(aca->lpr, cmd);
return ret;
ret = osl(osl_update_object(attribute_table, row, ATTCOL_NAME, &obj));
out:
if (ret < 0)
- para_printf(&aca->pbout, "cannot rename %s to %s\n", old, new);
+ afs_error(aca, "cannot rename %s to %s\n", old, new);
else
ret = afs_event(ATTRIBUTE_RENAME, &aca->pbout, NULL);
lls_free_parse_result(aca->lpr, cmd);
ret = get_attribute_bitnum_by_name(name, &red.bitnum);
if (ret < 0) {
- para_printf(&aca->pbout, "cannot remove %s\n", name);
+ afs_error(aca, "cannot remove %s\n", name);
return ret;
}
para_printf(&aca->pbout, "removing attribute %s\n", name);
ret = osl(osl_del_row(table, row));
if (ret < 0) {
- para_printf(&aca->pbout, "cannot remove %s\n", name);
+ afs_error(aca, "cannot remove %s\n", name);
return ret;
}
return afs_event(ATTRIBUTE_REMOVE, &aca->pbout, &red);
return osl(osl_create_table(&attribute_table_desc));
}
-/**
- * Initialize the attribute table structure.
- *
- * \param t The table structure to initialize.
- */
-void attribute_init(struct afs_table *t)
-{
- t->open = attribute_open;
- t->close = attribute_close;
- t->create = attribute_create;
-}
+/** The attribute table stores name/bitnum pairs. */
+const struct afs_table_operations attr_ops = { /* no event handler */
+ .open = attribute_open,
+ .close = attribute_close,
+ .create = attribute_create,
+};
}
ret = osl(osl_get_object(table, row, BLOBCOL_ID, &obj));
if (ret < 0) {
- para_printf(&aca->pbout, "cannot list %s\n", name);
+ afs_error(aca, "cannot list %s\n", name);
return ret;
}
id = read_u32(obj.data);
int ret = osl(osl_del_row(table, row));
if (ret < 0) {
- para_printf(&aca->pbout, "cannot remove %s\n", name);
+ afs_error(aca, "cannot remove %s\n", name);
return ret;
}
return 1;
ret = afs_event(BLOB_ADD, NULL, table);
out:
if (ret < 0)
- para_printf(&aca->pbout, "cannot add %s\n", name);
+ afs_error(aca, "cannot add %s\n", name);
else
para_printf(&aca->pbout, "added %s as id %u\n", name, id);
return ret;
ret = osl(osl_get_row(table, BLOBCOL_NAME, &obj, &row));
if (ret < 0) {
- para_printf(&aca->pbout, "cannot find source blob %s\n", src);
+ afs_error(aca, "cannot find source blob %s\n", src);
goto out;
}
obj.data = (char *)dest;
obj.size = strlen(dest) + 1;
ret = osl(osl_update_object(table, row, BLOBCOL_NAME, &obj));
if (ret < 0) {
- para_printf(&aca->pbout, "cannot rename blob %s to %s\n",
- src, dest);
+ afs_error(aca, "cannot rename blob %s to %s\n", src, dest);
goto out;
}
ret = afs_event(BLOB_RENAME, NULL, table);
return blob_get_name_by_id(table_name ## _table, id, name); \
}
-static int blob_get_def_by_name(struct osl_table *table, char *name,
+static int blob_get_def_by_name(struct osl_table *table, const char *name,
struct osl_object *def)
{
struct osl_row *row;
- struct osl_object obj = {.data = name, .size = strlen(name) + 1};
+ struct osl_object obj = {.data = (void *)name, .size = strlen(name) + 1};
int ret;
def->data = NULL;
/** Define the \p get_def_by_id function for this blob type. */
#define DEFINE_GET_DEF_BY_NAME(table_name, cmd_prefix) \
- int cmd_prefix ## _get_def_by_name(char *name, struct osl_object *def) \
+ int cmd_prefix ## _get_def_by_name(const char *name, struct osl_object *def) \
{ \
return blob_get_def_by_name(table_name ## _table, name, def); \
}
&table_name ## _table_desc, dir); \
}
-
-/** Define the \p init function for this blob type. */
-#define DEFINE_BLOB_INIT(table_name) \
- void table_name ## _init(struct afs_table *t) \
- { \
- t->open = table_name ## _open; \
- t->close = table_name ## _close; \
- t->create = table_name ## _create;\
- t->event_handler = table_name ##_event_handler; \
- table_name ## _table = NULL; \
- }
-
+/** Blob tables map integers to blobs. */
+#define DEFINE_BLOB_AFS_TABLE_OPS(table_name) \
+ const struct afs_table_operations table_name ## _ops = { \
+ .open = table_name ## _open, \
+ .close = table_name ## _close, \
+ .create = table_name ## _create, \
+ .event_handler = table_name ##_event_handler, \
+ };
/** Define all functions for this blob type. */
#define DEFINE_BLOB_FUNCTIONS(table_name, short_name, c_short_name) \
DEFINE_BLOB_OPEN(table_name) \
DEFINE_BLOB_CLOSE(table_name) \
DEFINE_BLOB_CREATE(table_name) \
- DEFINE_BLOB_INIT(table_name) \
+ DEFINE_BLOB_AFS_TABLE_OPS(table_name) \
DEFINE_BLOB_COMMAND(ls, LS, table_name, short_name, c_short_name) \
DEFINE_BLOB_COMMAND(cat, CAT, table_name, short_name, c_short_name) \
DEFINE_BLOB_COMMAND(add, ADD, table_name, short_name, c_short_name) \
PARA_ERROR(NO_AUDIO_FILE, "no audio file"), \
PARA_ERROR(NOFD, "did not receive open fd from afs"), \
PARA_ERROR(NO_MATCH, "no matches"), \
- PARA_ERROR(NO_MOOD, "no mood available"), \
PARA_ERROR(NO_MORE_SLOTS, "no more empty slots"), \
PARA_ERROR(NOT_PLAYING, "not playing"), \
PARA_ERROR(NO_VALID_FILES, "no valid file found in playlist"), \
PARA_ERROR(OPUS_SET_GAIN, "opus: could not set gain"), \
PARA_ERROR(PATH_FOUND, ""), /* not really an error */ \
PARA_ERROR(PLAYLIST_EMPTY, "attempted to load empty playlist"), \
- PARA_ERROR(PLAYLIST_LOADED, ""), /* not really an error */ \
PARA_ERROR(PREBUFFER_SUCCESS, "prebuffering complete"), \
PARA_ERROR(PRIVATE_KEY, "can not read private key"), \
PARA_ERROR(QUEUE, "packet queue overrun"), \
#include "afh.h"
#include "afs.h"
#include "list.h"
-#include "mood.h"
/*
* Mood parser API. It's overkill to have an own header file for
/** Number of admissible files */
unsigned num;
};
-static struct afs_statistics statistics = {.normalization_divisor = 1};
-struct mood {
- /** The name of this mood. */
+/**
+ * Stores an instance of a loaded mood (parser and statistics).
+ *
+ * A structure of this type is allocated and initialized when a mood is loaded.
+ */
+struct mood_instance {
+ /** NULL means that this is the "dummy" mood. */
char *name;
- /** Info for the bison parser. */
+ /** Bison's abstract syntax tree, used to determine admissibility. */
struct mp_context *parser_context;
+ /** To compute the score. */
+ struct afs_statistics stats;
};
/*
- * If current_mood is NULL then no mood is currently open. If
- * current_mood->name is NULL, the dummy mood is currently open.
+ * If current_mood is NULL then no mood is currently loaded. If
+ * current_mood->name is NULL, the current mood is the dummy mood.
+ *
+ * The statistics are adjusted dynamically through this pointer as files are
+ * added, removed or played.
*/
-static struct mood *current_mood;
+static struct mood_instance *current_mood;
/*
* Find the position of the most-significant set bit.
return res;
}
-/* returns 1 if row admissible, 0 if not, negative on errors */
-static int row_is_admissible(const struct osl_row *aft_row, struct mood *m)
-{
- if (!m)
- return -E_NO_MOOD;
- return mp_eval_row(aft_row, m->parser_context);
-}
-
-static void destroy_mood(struct mood *m)
+static void destroy_mood(struct mood_instance *m)
{
if (!m)
return;
free(m);
}
-static struct mood *alloc_new_mood(const char *name)
+static struct mood_instance *alloc_new_mood(const char *name)
{
- struct mood *m = zalloc(sizeof(struct mood));
+ struct mood_instance *m = zalloc(sizeof(*m));
+
if (name)
m->name = para_strdup(name);
+ m->stats.normalization_divisor = 1;
return m;
}
-static int load_mood(const struct osl_row *mood_row, struct mood **m,
- char **errmsg)
+static int init_mood_parser(const char *mood_name, struct mood_instance **m,
+ char **err)
{
- char *mood_name;
struct osl_object mood_def;
int ret;
- ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
+ if (!*mood_name) {
+ if (err)
+ *err = make_message("empty mood name\n");
+ return -ERRNO_TO_PARA_ERROR(EINVAL);
+ }
+ ret = mood_get_def_by_name(mood_name, &mood_def);
if (ret < 0) {
- if (errmsg)
- *errmsg = make_message(
- "could not read mood definition");
+ if (err)
+ *err = make_message("could not read mood definition\n");
return ret;
}
- assert(*mood_name);
*m = alloc_new_mood(mood_name);
- PARA_INFO_LOG("opening mood %s\n", mood_name);
- ret = mp_init(mood_def.data, mood_def.size, &(*m)->parser_context, errmsg);
+ PARA_INFO_LOG("loading mood %s\n", mood_name);
+ ret = mp_init(mood_def.data, mood_def.size, &(*m)->parser_context, err);
osl_close_disk_object(&mood_def);
if (ret < 0)
destroy_mood(*m);
static int check_mood(struct osl_row *mood_row, void *data)
{
- struct para_buffer *pb = data;
+ struct afs_callback_arg *aca = data;
char *mood_name, *errmsg;
struct osl_object mood_def;
- struct mood *m;
+ struct mood_instance *m;
int ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
if (ret < 0) {
- para_printf(pb, "cannot read mood\n");
+ afs_error(aca, "cannot read mood\n");
return ret;
}
if (!*mood_name) /* ignore dummy row */
ret = mp_init(mood_def.data, mood_def.size, &m->parser_context,
&errmsg);
if (ret < 0) {
- para_printf(pb, "%s: %s\n", mood_name, errmsg);
+ afs_error(aca, "%s: %s\n%s\n", mood_name, errmsg,
+ para_strerror(-ret));
free(errmsg);
- para_printf(pb, "%s\n", para_strerror(-ret));
} else
destroy_mood(m);
ret = 1; /* don't fail the loop on invalid mood definitions */
/**
* Check all moods for syntax errors.
*
- * \param aca Only ->pbout is used for diagnostics.
+ * \param aca Output goes to ->pbout, errors to ->fd on the error band.
*
* \return Negative on fatal errors. Inconsistent mood definitions are not
* considered an error.
int mood_check_callback(struct afs_callback_arg *aca)
{
para_printf(&aca->pbout, "checking moods...\n");
- return osl(osl_rbtree_loop(moods_table, BLOBCOL_ID, &aca->pbout,
- check_mood));
+ return osl(osl_rbtree_loop(moods_table, BLOBCOL_ID, aca, check_mood));
}
/*
* overflows and rounding errors we store the common divisor of the
* correction factors separately.
*/
-static long compute_score(struct afs_info *afsi)
+static long compute_score(struct afs_info *afsi,
+ const struct afs_statistics *stats)
{
int64_t mean_n, mean_l,score_n, score_l;
- assert(statistics.normalization_divisor > 0);
- assert(statistics.num > 0);
- mean_n = statistics.num_played_sum / statistics.num;
- mean_l = statistics.last_played_sum / statistics.num;
+ assert(stats->normalization_divisor > 0);
+ assert(stats->num > 0);
+ mean_n = stats->num_played_sum / stats->num;
+ mean_l = stats->last_played_sum / stats->num;
score_n = -((int64_t)afsi->num_played - mean_n)
- * statistics.num_played_correction
- / statistics.normalization_divisor;
+ * stats->num_played_correction
+ / stats->normalization_divisor;
score_l = -((int64_t)afsi->last_played - mean_l)
- * statistics.last_played_correction
- / statistics.normalization_divisor;
+ * stats->last_played_correction
+ / stats->normalization_divisor;
return (score_n + score_l) / 2;
}
-static int add_afs_statistics(const struct osl_row *row)
+static int add_afs_statistics(const struct osl_row *row,
+ struct afs_statistics *stats)
{
uint64_t n, x, s, q;
struct afs_info afsi;
ret = get_afsi_of_row(row, &afsi);
if (ret < 0)
return ret;
- n = statistics.num;
+ n = stats->num;
x = afsi.last_played;
- s = statistics.last_played_sum;
+ s = stats->last_played_sum;
if (n > 0) {
q = (x > s / n)? x - s / n : s / n - x;
- statistics.last_played_qd += q * q * n / (n + 1);
+ stats->last_played_qd += q * q * n / (n + 1);
}
- statistics.last_played_sum += x;
+ stats->last_played_sum += x;
x = afsi.num_played;
- s = statistics.num_played_sum;
+ s = stats->num_played_sum;
if (n > 0) {
q = (x > s / n)? x - s / n : s / n - x;
- statistics.num_played_qd += q * q * n / (n + 1);
+ stats->num_played_qd += q * q * n / (n + 1);
}
- statistics.num_played_sum += x;
- statistics.num++;
+ stats->num_played_sum += x;
+ stats->num++;
return 1;
}
static int del_afs_statistics(const struct osl_row *row)
{
+ struct afs_statistics *stats = ¤t_mood->stats;
uint64_t n, s, q, a, new_s;
struct afs_info afsi;
int ret;
ret = get_afsi_of_row(row, &afsi);
if (ret < 0)
return ret;
- n = statistics.num;
+ n = stats->num;
assert(n);
if (n == 1) {
- memset(&statistics, 0, sizeof(statistics));
- statistics.normalization_divisor = 1;
+ memset(stats, 0, sizeof(*stats));
+ stats->normalization_divisor = 1;
return 1;
}
- s = statistics.last_played_sum;
- q = statistics.last_played_qd;
+ s = stats->last_played_sum;
+ q = stats->last_played_qd;
a = afsi.last_played;
new_s = s - a;
- statistics.last_played_sum = new_s;
- statistics.last_played_qd = q + s * s / n - a * a
+ stats->last_played_sum = new_s;
+ stats->last_played_qd = q + s * s / n - a * a
- new_s * new_s / (n - 1);
- s = statistics.num_played_sum;
- q = statistics.num_played_qd;
+ s = stats->num_played_sum;
+ q = stats->num_played_qd;
a = afsi.num_played;
new_s = s - a;
- statistics.num_played_sum = new_s;
- statistics.num_played_qd = q + s * s / n - a * a
+ stats->num_played_sum = new_s;
+ stats->num_played_qd = q + s * s / n - a * a
- new_s * new_s / (n - 1);
- statistics.num--;
+ stats->num--;
return 1;
}
/*
- * At mood open time we determine the set of admissible files for the given
+ * At mood load time we determine the set of admissible files for the given
* mood where each file is identified by a pointer to a row of the audio file
* table. In the first pass the pointers are added to a temporary array and
* statistics are computed. When all admissible files have been processed in
*/
struct admissible_array {
/** Files are admissible wrt. this mood. */
- struct mood *m;
+ struct mood_instance *m;
/** The size of the array */
unsigned size;
/** Pointer to the array of admissible files. */
static int add_if_admissible(struct osl_row *aft_row, void *data)
{
struct admissible_array *aa = data;
- int ret;
+ struct afs_statistics *stats = &aa->m->stats;
- ret = row_is_admissible(aft_row, aa->m);
- if (ret <= 0)
- return ret;
- if (statistics.num >= aa->size) {
+ if (!mp_eval_row(aft_row, aa->m->parser_context))
+ return 0;
+ if (stats->num >= aa->size) {
aa->size *= 2;
aa->size += 100;
aa->array = arr_realloc(aa->array, aa->size,
sizeof(struct osl_row *));
}
- aa->array[statistics.num] = aft_row;
- return add_afs_statistics(aft_row);
+ aa->array[stats->num] = aft_row;
+ return add_afs_statistics(aft_row, stats);
}
/**
return old_qd + delta * (sigma - 2 * old_sum / n - delta / n);
}
-static int update_afs_statistics(struct afs_info *old_afsi,
+static void update_afs_statistics(struct afs_info *old_afsi,
struct afs_info *new_afsi)
{
- unsigned n;
- int ret = get_num_admissible_files(&n);
-
- if (ret < 0)
- return ret;
- assert(n);
-
- statistics.last_played_qd = update_quadratic_deviation(n,
- statistics.last_played_qd, old_afsi->last_played,
- new_afsi->last_played, statistics.last_played_sum);
- statistics.last_played_sum += new_afsi->last_played - old_afsi->last_played;
-
- statistics.num_played_qd = update_quadratic_deviation(n,
- statistics.num_played_qd, old_afsi->num_played,
- new_afsi->num_played, statistics.num_played_sum);
- statistics.num_played_sum += new_afsi->num_played - old_afsi->num_played;
- return 1;
+ struct afs_statistics *stats = ¤t_mood->stats;
+
+ assert(stats->num > 0);
+ stats->last_played_qd = update_quadratic_deviation(stats->num,
+ stats->last_played_qd, old_afsi->last_played,
+ new_afsi->last_played, stats->last_played_sum);
+ stats->last_played_sum += new_afsi->last_played - old_afsi->last_played;
+
+ stats->num_played_qd = update_quadratic_deviation(stats->num,
+ stats->num_played_qd, old_afsi->num_played,
+ new_afsi->num_played, stats->num_played_sum);
+ stats->num_played_sum += new_afsi->num_played - old_afsi->num_played;
}
-static int add_to_score_table(const struct osl_row *aft_row)
+static int add_to_score_table(const struct osl_row *aft_row,
+ const struct afs_statistics *stats)
{
long score;
struct afs_info afsi;
if (ret < 0)
return ret;
- score = compute_score(&afsi);
+ score = compute_score(&afsi, stats);
return score_add(aft_row, score);
}
}
/**
- * Delete one entry from the statistics and from the score table.
+ * Delete an audio file from the score table and update mood statistics.
*
- * \param aft_row The audio file which is no longer admissible.
+ * \param aft_row Identifies the row to delete.
*
- * \return Positive on success, negative on errors.
+ * \return Standard.
*
* \sa \ref score_delete().
*/
static int mood_delete_audio_file(const struct osl_row *aft_row)
{
- int ret;
-
- ret = row_belongs_to_score_table(aft_row, NULL);
- if (ret < 0)
- return ret;
- if (!ret) /* not admissible, nothing to do */
- return 1;
+ if (!row_belongs_to_score_table(aft_row))
+ return 0;
return delete_from_statistics_and_score_table(aft_row);
}
struct afs_info *old_afsi)
{
long score, percent;
- int ret, is_admissible, was_admissible = 0;
+ int ret;
+ bool is_admissible, was_admissible;
struct afs_info afsi;
- unsigned rank;
if (!current_mood)
return 1; /* nothing to do */
- ret = row_belongs_to_score_table(aft_row, &rank);
- if (ret < 0)
- return ret;
- was_admissible = ret;
- ret = row_is_admissible(aft_row, current_mood);
- if (ret < 0)
- return ret;
- is_admissible = (ret > 0);
+ was_admissible = row_belongs_to_score_table(aft_row);
+ is_admissible = mp_eval_row(aft_row, current_mood->parser_context);
if (!was_admissible && !is_admissible)
return 1;
if (was_admissible && !is_admissible)
return delete_from_statistics_and_score_table(aft_row);
if (!was_admissible && is_admissible) {
- ret = add_afs_statistics(aft_row);
+ ret = add_afs_statistics(aft_row, ¤t_mood->stats);
if (ret < 0)
return ret;
- return add_to_score_table(aft_row);
+ return add_to_score_table(aft_row, ¤t_mood->stats);
}
/* update score */
ret = get_afsi_of_row(aft_row, &afsi);
if (ret < 0)
return ret;
- if (old_afsi) {
- ret = update_afs_statistics(old_afsi, &afsi);
- if (ret < 0)
- return ret;
- }
- score = compute_score(&afsi);
+ if (old_afsi)
+ update_afs_statistics(old_afsi, &afsi);
+ score = compute_score(&afsi, ¤t_mood->stats);
PARA_DEBUG_LOG("score: %li\n", score);
percent = (score + 100) / 3;
if (percent > 100)
percent = 100;
else if (percent < 0)
percent = 0;
- PARA_DEBUG_LOG("moving from rank %u to %li%%\n", rank, percent);
+ PARA_DEBUG_LOG("moving to %li%%\n", percent);
return score_update(aft_row, percent);
}
/* sse: seconds since epoch. */
-static void log_statistics(int64_t sse)
+static char *get_statistics(struct mood_instance *m, int64_t sse)
{
- unsigned n = statistics.num;
+ unsigned n = m->stats.num;
int mean_days, sigma_days;
- assert(current_mood);
- PARA_NOTICE_LOG("loaded mood %s\n", current_mood->name?
- current_mood->name : "(dummy)");
- if (!n) {
- PARA_WARNING_LOG("no admissible files\n");
- return;
- }
- PARA_NOTICE_LOG("%u admissible files\n", statistics.num);
- mean_days = (sse - statistics.last_played_sum / n) / 3600 / 24;
- sigma_days = int_sqrt(statistics.last_played_qd / n) / 3600 / 24;
- PARA_NOTICE_LOG("last_played mean/sigma: %d/%d days\n", mean_days, sigma_days);
- PARA_NOTICE_LOG("num_played mean/sigma: %" PRId64 "/%" PRIu64 "\n",
- statistics.num_played_sum / n,
- int_sqrt(statistics.num_played_qd / n));
- PARA_NOTICE_LOG("num_played correction factor: %" PRId64 "\n",
- statistics.num_played_correction);
- PARA_NOTICE_LOG("last_played correction factor: %" PRId64 "\n",
- statistics.last_played_correction);
- PARA_NOTICE_LOG("normalization divisor: %" PRId64 "\n",
- statistics.normalization_divisor);
+ mean_days = (sse - m->stats.last_played_sum / n) / 3600 / 24;
+ sigma_days = int_sqrt(m->stats.last_played_qd / n) / 3600 / 24;
+ return make_message(
+ "loaded mood %s (%u files)\n"
+ "last_played mean/sigma: %d/%d days\n"
+ "num_played mean/sigma: %" PRId64 "/%" PRIu64 "\n"
+ ,
+ m->name? m->name : "(dummy)",
+ n,
+ mean_days, sigma_days,
+ m->stats.num_played_sum / n,
+ int_sqrt(m->stats.num_played_qd / n)
+ );
}
-/**
- * Close the current mood.
- *
- * Frees all resources of the current mood.
- */
-void close_current_mood(void)
+/** Free all resources of the current mood, if any. */
+void mood_unload(void)
{
destroy_mood(current_mood);
current_mood = NULL;
- memset(&statistics, 0, sizeof(statistics));
- statistics.normalization_divisor = 1;
}
-static void compute_correction_factors(int64_t sse)
+static void compute_correction_factors(int64_t sse, struct afs_statistics *s)
{
- struct afs_statistics *s = &statistics;
-
if (s->num > 0) {
s->normalization_divisor = int_sqrt(s->last_played_qd)
* int_sqrt(s->num_played_qd) / s->num / 100;
/**
* Change the current mood.
*
- * \param mood_name The name of the mood to open.
- * \param errmsg Error description is returned here.
+ * \param mood_name The name of the mood to load.
+ * \param msg Error message or mood info is returned here.
*
* If \a mood_name is \a NULL, load the dummy mood that accepts every audio file
* and uses a scoring method based only on the \a last_played information.
*
- * The errmsg pointer may be NULL, in which case no error message will be
- * returned. If a non-NULL pointer is given, the caller must free *errmsg.
- *
- * If there is already an open mood, it will be closed first.
+ * If the message pointer is not NULL, a suitable message is returned there in
+ * all cases. The caller must free this string.
*
- * \return Positive on success, negative on errors.
+ * \return The number of admissible files on success, negative on errors. It is
+ * not considered an error if no files are admissible.
*
* \sa struct \ref afs_info::last_played, \ref mp_eval_row().
*/
-int change_current_mood(const char *mood_name, char **errmsg)
+int mood_load(const char *mood_name, char **msg)
{
int i, ret;
- struct admissible_array aa = {
- .size = 0,
- .array = NULL
- };
+ struct admissible_array aa = {.size = 0};
/*
* We can not use the "now" pointer from sched.c here because we are
* called before schedule(), which initializes "now".
struct timeval rnow;
if (mood_name) {
- struct mood *m;
- struct osl_row *row;
- struct osl_object obj;
-
- if (!*mood_name) {
- *errmsg = make_message("empty mood name");
- return -ERRNO_TO_PARA_ERROR(EINVAL);
- }
- obj.data = (char *)mood_name;
- obj.size = strlen(mood_name) + 1;
- ret = osl(osl_get_row(moods_table, BLOBCOL_NAME, &obj, &row));
- if (ret < 0) {
- if (errmsg)
- *errmsg = make_message("no such mood: %s",
- mood_name);
- return ret;
- }
- ret = load_mood(row, &m, errmsg);
+ ret = init_mood_parser(mood_name, &aa.m, msg);
if (ret < 0)
return ret;
- close_current_mood();
- current_mood = m;
- } else { /* load dummy mood */
- close_current_mood();
- current_mood = alloc_new_mood(NULL);
- }
- aa.m = current_mood;
+ } else /* load dummy mood */
+ aa.m = alloc_new_mood(NULL);
PARA_NOTICE_LOG("computing statistics of admissible files\n");
ret = audio_file_loop(&aa, add_if_admissible);
if (ret < 0) {
- if (errmsg)
- *errmsg = make_message("audio file loop failed");
+ if (msg) /* false if we are called via the event handler */
+ *msg = make_message("audio file loop failed\n");
goto out;
}
clock_get_realtime(&rnow);
- compute_correction_factors(rnow.tv_sec);
- log_statistics(rnow.tv_sec);
- for (i = 0; i < statistics.num; i++) {
- ret = add_to_score_table(aa.array[i]);
+ compute_correction_factors(rnow.tv_sec, &aa.m->stats);
+ if (aa.m->stats.num == 0) {
+ if (msg)
+ *msg = make_message("no admissible files\n");
+ ret = 0;
+ goto out;
+ }
+ for (i = 0; i < aa.m->stats.num; i++) {
+ ret = add_to_score_table(aa.array[i], &aa.m->stats);
if (ret < 0) {
- if (errmsg)
- *errmsg = make_message(
- "could not add row to score table");
+ if (msg)
+ *msg = make_message(
+ "could not add row to score table\n");
goto out;
}
}
- ret = statistics.num;
+ /* success */
+ if (msg)
+ *msg = get_statistics(aa.m, rnow.tv_sec);
+ ret = aa.m->stats.num;
+ mood_unload();
+ current_mood = aa.m;
out:
free(aa.array);
if (ret < 0)
- close_current_mood();
+ destroy_mood(aa.m);
return ret;
}
/*
- * Close and re-open the current mood.
+ * Empty the score table and start over.
*
* This function is called on events which render the current list of
* admissible files useless, for example if an attribute is removed from the
* attribute table.
- *
- * If no mood is currently open, the function returns success.
*/
static int reload_current_mood(void)
{
int ret;
char *mood_name = NULL;
- ret = clear_score_table();
- if (ret < 0)
- return ret;
- if (!current_mood)
- return 1;
+ assert(current_mood);
+ score_clear();
PARA_NOTICE_LOG("reloading %s\n", current_mood->name?
current_mood->name : "(dummy)");
if (current_mood->name)
mood_name = para_strdup(current_mood->name);
- close_current_mood();
- ret = change_current_mood(mood_name, NULL);
+ mood_unload();
+ ret = mood_load(mood_name, NULL);
free(mood_name);
return ret;
}
+++ /dev/null
-/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
-
-/** \file mood.h Public functions of mood.c. */
-
-int change_current_mood(const char *mood_name, char **errmsg);
-void close_current_mood(void);
-int mood_check_callback(struct afs_callback_arg *aca);
* function returns true (without looking at the audio file metadata) to
* indicate that the given audio file should be considered admissible.
*
- * \sa \ref change_current_mood(), \ref mp_eval_ast().
+ * \sa \ref mood_load(), \ref mp_eval_ast().
*/
bool mp_eval_row(const struct osl_row *aft_row, struct mp_context *ctx)
{
/** \file playlist.c Functions for loading and saving playlists. */
/** Structure used for adding entries to a playlist. */
-struct playlist_info {
+struct playlist_instance {
/** The name of the playlist. */
char *name;
/** The number of entries currently in the playlist. */
unsigned length;
};
-static struct playlist_info current_playlist;
+static struct playlist_instance current_playlist;
/**
* Re-insert an audio file into the tree of admissible files.
static int add_playlist_entry(char *path, void *data)
{
- struct playlist_info *playlist = data;
+ struct playlist_instance *playlist = data;
struct osl_row *aft_row;
int ret = aft_get_row_of_path(path, &aft_row);
return 1;
}
-/* returns -E_PLAYLIST_LOADED on _success_ to terminate the loop */
-static int load_playlist(struct osl_row *row, void *data)
-{
- struct playlist_info *playlist = data;
- struct osl_object playlist_def;
- char *playlist_name;
- int ret;
-
- ret = pl_get_name_and_def_by_row(row, &playlist_name, &playlist_def);
- if (ret < 0)
- goto err;
- playlist->length = 0;
- ret = for_each_line(FELF_READ_ONLY, playlist_def.data,
- playlist_def.size, add_playlist_entry, playlist);
- osl_close_disk_object(&playlist_def);
- if (ret < 0)
- goto err;
- ret = -E_PLAYLIST_EMPTY;
- if (!playlist->length)
- goto err;
- playlist->name = para_strdup(playlist_name);
- PARA_NOTICE_LOG("loaded playlist %s (%u files)\n", playlist->name,
- playlist->length);
- return -E_PLAYLIST_LOADED;
-err:
- if (ret != -E_DUMMY_ROW)
- PARA_NOTICE_LOG("unable to load playlist (%s)\n",
- para_strerror(-ret));
- return 1;
-}
-
static int check_playlist_path(char *path, void *data)
{
- struct para_buffer *pb = data;
+ struct afs_callback_arg *aca = data;
struct osl_row *aft_row;
int ret = aft_get_row_of_path(path, &aft_row);
if (ret < 0)
- para_printf(pb, "%s: %s\n", path, para_strerror(-ret));
+ afs_error(aca, "%s: %s\n", path, para_strerror(-ret));
return 1; /* do not fail the loop on bad paths */
}
static int check_playlist(struct osl_row *row, void *data)
{
- struct para_buffer *pb = data;
+ struct afs_callback_arg *aca = data;
+ struct para_buffer *pb = &aca->pbout;
struct osl_object playlist_def;
char *playlist_name;
int ret = pl_get_name_and_def_by_row(row, &playlist_name, &playlist_def);
if (ret < 0) { /* log error, but continue */
- para_printf(pb, "failed to get playlist data: %s\n",
+ afs_error(aca, "failed to get playlist data: %s\n",
para_strerror(-ret));
return 1;
}
if (*playlist_name) { /* skip dummy row */
para_printf(pb, "checking playlist %s...\n", playlist_name);
for_each_line(FELF_READ_ONLY, playlist_def.data,
- playlist_def.size, check_playlist_path, pb);
+ playlist_def.size, check_playlist_path, aca);
}
osl_close_disk_object(&playlist_def);
return 1;
int playlist_check_callback(struct afs_callback_arg *aca)
{
para_printf(&aca->pbout, "checking playlists...\n");
- return osl(osl_rbtree_loop(playlists_table, BLOBCOL_ID, &aca->pbout,
+ return osl(osl_rbtree_loop(playlists_table, BLOBCOL_ID, aca,
check_playlist));
}
-/**
- * Close the current playlist.
- *
- * \sa \ref playlist_open().
- */
-void playlist_close(void)
+/** Free all resources of the current playlist, if any. */
+void playlist_unload(void)
{
if (!current_playlist.name)
return;
free(current_playlist.name);
current_playlist.name = NULL;
+ current_playlist.length = 0;
}
/**
- * Open the given playlist.
+ * Populate the score table from the paths of a playlist database object.
*
- * \param name The name of the playlist to open.
+ * This loads the blob object which corresponds to the given name from the
+ * playlist table. Each line of the blob is regarded as a path which is looked
+ * up in the audio file table. If the path lookup succeeds, a reference to the
+ * corresponding row of the audio file table is added to the score table.
*
- * Files which are listed in the playlist, but not contained in the database
- * are ignored. This is not considered an error.
+ * \param name The name of the playlist to load.
+ * \param msg Error message or playlist info is returned here.
*
- * \return Standard.
+ * \return The length of the loaded playlist on success, negative error code
+ * else. Files which are listed in the playlist, but are not contained in the
+ * database are ignored. This is not considered an error.
*/
-int playlist_open(const char *name)
+int playlist_load(const char *name, char **msg)
{
- struct osl_object obj;
int ret;
- struct osl_row *row;
+ struct playlist_instance *playlist = ¤t_playlist;
+ struct osl_object playlist_def;
- obj.data = (char *)name;
- obj.size = strlen(obj.data);
- ret = osl(osl_get_row(playlists_table, BLOBCOL_NAME, &obj, &row));
+ ret = pl_get_def_by_name(name, &playlist_def);
if (ret < 0) {
- PARA_NOTICE_LOG("failed to load playlist %s\n", name);
+ *msg = make_message("could not read playlist %s\n", name);
return ret;
}
- playlist_close();
- ret = load_playlist(row, ¤t_playlist);
- return (ret == -E_PLAYLIST_LOADED)? current_playlist.length : ret;
+ playlist_unload();
+ ret = for_each_line(FELF_READ_ONLY, playlist_def.data,
+ playlist_def.size, add_playlist_entry, playlist);
+ osl_close_disk_object(&playlist_def);
+ if (ret < 0)
+ goto err;
+ ret = -E_PLAYLIST_EMPTY;
+ if (!playlist->length)
+ goto err;
+ playlist->name = para_strdup(name);
+ *msg = make_message("loaded playlist %s (%u files)\n", playlist->name,
+ playlist->length);
+ /* success */
+ return current_playlist.length;
+err:
+ PARA_NOTICE_LOG("unable to load playlist %s\n", name);
+ *msg = make_message("unable to load playlist %s\n", name);
+ return ret;
}
static int search_path(char *path, void *data)
static int handle_audio_file_event(enum afs_events event, void *data)
{
- int ret, was_admissible = 0, is_admissible;
+ int ret;
+ bool was_admissible = false, is_admissible;
struct osl_object playlist_def;
char *new_path;
const struct osl_row *row = data;
- if (event == AUDIO_FILE_RENAME) {
- ret = row_belongs_to_score_table(row, NULL);
- if (ret < 0)
- return ret;
- was_admissible = ret;
- }
+ if (event == AUDIO_FILE_RENAME)
+ was_admissible = row_belongs_to_score_table(row);
ret = get_audio_file_path_of_row(row, &new_path);
if (ret < 0)
return ret;
int playlists_event_handler(enum afs_events event,
__a_unused struct para_buffer *pb, void *data)
{
- int ret;
struct afsi_change_event_data *aced = data;
if (!current_playlist.name)
case AUDIO_FILE_ADD:
return handle_audio_file_event(event, data);
case AUDIO_FILE_REMOVE:
- ret = row_belongs_to_score_table(data, NULL);
- if (ret < 0)
- return ret;
- if (!ret)
+ if (!row_belongs_to_score_table(data))
return 1;
current_playlist.length--;
return score_delete(data);
.column_descriptions = score_cols
};
-/**
- * Compute the number of files in score table.
- *
- * \param num Result is returned here.
- *
- * \return Positive on success, negative on errors.
- */
-int get_num_admissible_files(unsigned *num)
-{
- return osl(osl_get_num_rows(score_table, num));
-}
-
/* On errors (negative return value) the content of score is undefined. */
static int get_score_of_row(void *score_row, long *score)
{
return ret;
}
-static int get_nth_score(unsigned n, long *score)
-{
- struct osl_row *row;
- int ret = osl(osl_get_nth_row(score_table, SCORECOL_SCORE, n, &row));
-
- if (ret < 0)
- return ret;
- return get_score_of_row(row, score);
-}
-
/**
* Replace a row of the score table.
*
*/
int score_update(const struct osl_row *aft_row, long percent)
{
- struct osl_row *row;
+ struct osl_row *row, *rrow; /* score row, reference row */
long new_score;
unsigned n, new_pos;
struct osl_object obj = {.data = (struct osl_row *)aft_row,
return 1;
if (ret < 0)
return ret;
- ret = get_num_admissible_files(&n);
+ ret = osl(osl_get_num_rows(score_table, &n));
if (ret < 0)
return ret;
new_pos = 1 + (n - 1) * percent / 100;
- ret = get_nth_score(new_pos, &new_score);
+ ret = osl(osl_get_nth_row(score_table, SCORECOL_SCORE, new_pos, &rrow));
+ if (ret < 0)
+ return ret;
+ ret = get_score_of_row(rrow, &new_score);
if (ret < 0)
return ret;
new_score--;
}
/**
- * Loop over all files in the score table.
- *
- * \param data A pointer to arbitrary data.
- * \param func Function to be called for each admissible file.
+ * Call the given function for each row of the score table.
*
- * \return The return value of the underlying call to osl_rbtree_loop().
+ * \param func Callback, called once per row.
+ * \param data Passed verbatim to the callback.
*
- * This is used for the ls command. The \a data parameter is passed as the
- * second argument to \a func.
+ * \return The return value of the underlying call to osl_rbtree_loop(). The
+ * loop terminates early if the callback returns negative.
*/
-int admissible_file_loop(void *data, osl_rbtree_loop_func *func)
+int score_loop(osl_rbtree_loop_func *func, void *data)
{
return osl(osl_rbtree_loop(score_table, SCORECOL_SCORE, data, func));
}
* Find out whether an audio file is contained in the score table.
*
* \param aft_row The row of the audio file table.
- * \param rank Result pointer
*
- * \return Positive, if \a aft_row belongs to the audio file table,
- * zero if not, negative on errors. If \a aft_row was found, and \a rank
- * is not \p NULL, the rank of \a aft_row is returned in \a rank.
+ * \return If the lookup operation fails for any other reason than "not found",
+ * the function aborts the current process (afs), since this is considered a
+ * fatal error that should never happen.
*/
-int row_belongs_to_score_table(const struct osl_row *aft_row, unsigned *rank)
+bool row_belongs_to_score_table(const struct osl_row *aft_row)
{
struct osl_row *score_row;
int ret = get_score_row_from_aft_row(aft_row, &score_row);
if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND))
- return 0;
- if (ret < 0)
- return ret;
- if (!rank)
- return 1;
- ret = osl(osl_get_rank(score_table, score_row, SCORECOL_SCORE, rank));
- if (ret < 0)
- return ret;
- return 1;
+ return false;
+ assert(ret >= 0);
+ return true;
}
static void score_close(void)
static int score_open(__a_unused const char *dir)
{
- score_table_desc.dir = NULL; /* this table has only volatile columns */
- return osl(osl_open_table(&score_table_desc, &score_table));
+ assert(osl_open_table(&score_table_desc, &score_table) >= 0);
+ return 1;
}
/**
* Remove all entries from the score table, but keep the table open.
- *
- * \return Standard.
*/
-int clear_score_table(void)
+void score_clear(void)
{
score_close();
- return score_open(NULL);
+ score_open(NULL);
}
-static int score_event_handler(__a_unused enum afs_events event,
- __a_unused struct para_buffer *pb, __a_unused void *data)
-{
- return 1;
-}
-
-/**
- * Initialize the scoring subsystem.
- *
- * \param t The members of \a t are filled in by the function.
- */
-void score_init(struct afs_table *t)
-{
- t->name = score_table_desc.name;
- t->open = score_open;
- t->close = score_close;
- t->event_handler = score_event_handler;
-}
+/** The score table stores (aft row, score) pairs in memory. */
+const struct afs_table_operations score_ops = {
+ .open = score_open,
+ .close = score_close,
+};