int ret;
char *msg;
- if (!arg) {
- ret = mood_load(NULL, &msg);
+ if (!arg) { /* load dummy mood */
+ ret = mood_load(NULL, NULL, &msg);
mode = PLAY_MODE_MOOD;
} else if (!strncmp(arg, "p/", 2)) {
- ret = playlist_load(arg + 2, &msg);
+ ret = playlist_load(arg + 2, NULL, &msg);
mode = PLAY_MODE_PLAYLIST;
} else if (!strncmp(arg, "m/", 2)) {
- ret = mood_load(arg + 2, &msg);
+ ret = mood_load(arg + 2, NULL, &msg);
mode = PLAY_MODE_MOOD;
} else {
ret = -ERRNO_TO_PARA_ERROR(EINVAL);
- msg = make_message("%s: parse error", arg);
+ msg = make_message("%s: parse error\n", arg);
}
if (pb)
para_printf(pb, "%s", msg);
PARA_INFO_LOG("afs_database dir %s\n", database_dir);
}
-static int make_database_dir(void)
-{
- int ret;
-
- get_database_dir();
- ret = para_mkdir(database_dir, 0777);
- if (ret >= 0 || ret == -ERRNO_TO_PARA_ERROR(EEXIST))
- return 1;
- return ret;
-}
-
static int open_afs_tables(void)
{
int i, ret;
}
ret = schedule(&s);
sched_shutdown(&s);
- mood_unload();
+ mood_unload(NULL);
+ playlist_unload(NULL);
out_close:
close_afs_tables();
out:
const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
const char *arg;
int ret;
+ struct para_buffer *pbout;
ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
assert(ret >= 0);
arg = lls_input(0, aca->lpr);
+ pbout = SERVER_CMD_OPT_GIVEN(SELECT, VERBOSE, aca->lpr)?
+ &aca->pbout : NULL;
score_clear();
if (current_play_mode == PLAY_MODE_MOOD)
- mood_unload();
+ mood_unload(NULL);
else
- playlist_unload();
+ playlist_unload(NULL);
- ret = activate_mood_or_playlist(arg, &aca->pbout);
+ ret = activate_mood_or_playlist(arg, 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);
+ ret2 = activate_mood_or_playlist(current_mop, 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);
+ activate_mood_or_playlist(NULL, pbout);
free_lpr:
lls_free_parse_result(aca->lpr, cmd);
return ret;
.size = sizeof(table_mask)};
unsigned num_inputs = lls_num_inputs(lpr);
- ret = make_database_dir();
+ get_database_dir();
+ ret = para_mkdir(database_dir);
if (ret < 0)
return ret;
if (num_inputs > 0) {
struct mp_context *parser_context;
/** To compute the score. */
struct afs_statistics stats;
+ /** NULL means to operate on the global score table. */
+ struct osl_table *score_table;
};
/*
if (!m)
return;
mp_shutdown(m->parser_context);
+ if (m->score_table)
+ score_close(m->score_table);
free(m->name);
free(m);
}
}
static int add_to_score_table(const struct osl_row *aft_row,
- const struct afs_statistics *stats)
+ struct mood_instance *m)
{
long score;
struct afs_info afsi;
if (ret < 0)
return ret;
- score = compute_score(&afsi, stats);
- return score_add(aft_row, score);
+ score = compute_score(&afsi, &m->stats);
+ return score_add(aft_row, score, m->score_table);
}
static int delete_from_statistics_and_score_table(const struct osl_row *aft_row)
ret = add_afs_statistics(aft_row, ¤t_mood->stats);
if (ret < 0)
return ret;
- return add_to_score_table(aft_row, ¤t_mood->stats);
+ return add_to_score_table(aft_row, current_mood);
}
/* update score */
ret = get_afsi_of_row(aft_row, &afsi);
unsigned n = m->stats.num;
int mean_days, sigma_days;
+ if (n == 0)
+ return make_message("no admissible files\n");
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"
+ "correction factor ratio: %.2lf\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)
+ int_sqrt(m->stats.num_played_qd / n),
+ 86400.0 * m->stats.last_played_correction /
+ m->stats.num_played_correction
);
}
-/** Free all resources of the current mood, if any. */
-void mood_unload(void)
+/**
+ * Free all resources of a mood instance.
+ *
+ * \param m As obtained by \ref mood_load(). If NULL, unload the current mood.
+ *
+ * It's OK to call this with m == NULL even if no current mood is loaded.
+ */
+void mood_unload(struct mood_instance *m)
{
+ if (m)
+ return destroy_mood(m);
destroy_mood(current_mood);
current_mood = NULL;
}
}
/**
- * Change the current mood.
+ * Populate a score table with admissible files for the given mood.
+ *
+ * This consults the mood table to initialize the mood parser with the mood
+ * expression stored in the blob object which corresponds to the given name. A
+ * score table is allocated and populated with references to those entries of
+ * the audio file table which evaluate as admissible with respect to the mood
+ * expression. For each audio file a score value is computed and stored along
+ * with the file reference.
*
* \param mood_name The name of the mood to load.
+ * \param result Opaque, refers to the mood parser and the score table.
* \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.
+ * If the mood name is NULL, the dummy mood is loaded. This mood regards every
+ * audio file as admissible.
+ *
+ * A NULL result pointer instructs the function to operate on the current mood.
+ * That is, on the mood instance which is used by the server to select the next
+ * audio file for streaming. In this mode of operation, the mood which was
+ * active before the call, if any, is unloaded on success.
+ *
+ * If result is not NULL, the current mood is unaffected and *result points to
+ * an initialized mood instance on success. The caller can pass this reference
+ * to \ref mood_loop() to iterate over the admissible files, and should call
+ * \ref mood_unload() to free the mood instance afterwards.
*
* If the message pointer is not NULL, a suitable message is returned there in
* all cases. The caller must free this string.
*
- * \return The number of admissible files on success, negative on errors. It is
+ * \return The number of admissible files on success, negative on errors. On
+ * errors, the current mood remains unaffected even if result is NULL. It is
* not considered an error if no files are admissible.
*
- * \sa struct \ref afs_info::last_played, \ref mp_eval_row().
+ * \sa \ref mp_eval_row().
*/
-int mood_load(const char *mood_name, char **msg)
+int mood_load(const char *mood_name, struct mood_instance **result, char **msg)
{
int i, ret;
struct admissible_array aa = {.size = 0};
}
clock_get_realtime(&rnow);
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;
- }
+ if (result)
+ score_open(&aa.m->score_table);
for (i = 0; i < aa.m->stats.num; i++) {
- ret = add_to_score_table(aa.array[i], &aa.m->stats);
+ ret = add_to_score_table(aa.array[i], aa.m);
if (ret < 0) {
if (msg)
*msg = make_message(
if (msg)
*msg = get_statistics(aa.m, rnow.tv_sec);
ret = aa.m->stats.num;
- mood_unload();
- current_mood = aa.m;
+ if (result)
+ *result = aa.m;
+ else {
+ mood_unload(NULL);
+ current_mood = aa.m;
+ }
ret = 1;
out:
free(aa.array);
return ret;
}
+/**
+ * Iterate over the admissible files of a mood instance.
+ *
+ * This wrapper around \ref score_loop() is the mood counterpart of \ref
+ * playlist_loop().
+ *
+ * \param m Determines the score table to iterate. Must not be NULL.
+ * \param func See \ref score_loop().
+ * \param data See \ref score_loop().
+ *
+ * \return See \ref score_loop(), \ref playlist_loop().
+ */
+int mood_loop(struct mood_instance *m, osl_rbtree_loop_func *func, void *data)
+{
+ return score_loop(func, m->score_table, data);
+}
+
/*
* 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.
+ * This function is called on events which render the current set of admissible
+ * files invalid, for example if an attribute is removed from the attribute
+ * table.
*/
static int reload_current_mood(void)
{
current_mood->name : "(dummy)");
if (current_mood->name)
mood_name = para_strdup(current_mood->name);
- mood_unload();
- ret = mood_load(mood_name, NULL);
+ mood_unload(NULL);
+ ret = mood_load(mood_name, NULL, NULL);
free(mood_name);
return ret;
}