X-Git-Url: http://git.tuebingen.mpg.de/?a=blobdiff_plain;ds=sidebyside;f=mood.c;h=1e15ef0e081480381fcbc4fdad2179848f429c1f;hb=HEAD;hp=66024db6add623716b428edd0263847705bc80e9;hpb=ebb53a6a2d300d33832ffa51aa8e1100c4d700e6;p=paraslash.git diff --git a/mood.c b/mood.c index 66024db6..1e15ef0e 100644 --- a/mood.c +++ b/mood.c @@ -48,27 +48,29 @@ struct afs_statistics { }; /** - * Stores an instance of an open mood (parser and statistics). + * Stores an instance of a loaded mood (parser and statistics). * - * A structure of this type is allocated and initialized at mood open time. + * A structure of this type is allocated and initialized when a mood is loaded. */ -struct mood { +struct mood_instance { /** NULL means that this is the "dummy" mood. */ char *name; /** Bison's abstract syntax tree, used to determine admissibility. */ 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 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. @@ -127,25 +129,29 @@ __a_const static uint64_t int_sqrt(uint64_t x) return res; } -static void destroy_mood(struct mood *m) +static void destroy_mood(struct mood_instance *m) { if (!m) return; mp_shutdown(m->parser_context); + if (m->score_table) + score_close(m->score_table); free(m->name); 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 init_mood_parser(const char *mood_name, struct mood **m, char **err) +static int init_mood_parser(const char *mood_name, struct mood_instance **m, + char **err) { struct osl_object mood_def; int ret; @@ -162,7 +168,7 @@ static int init_mood_parser(const char *mood_name, struct mood **m, char **err) return ret; } *m = alloc_new_mood(mood_name); - PARA_INFO_LOG("opening mood %s\n", mood_name); + 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) @@ -172,14 +178,14 @@ static int init_mood_parser(const char *mood_name, struct mood **m, char **err) 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 */ @@ -188,9 +194,9 @@ static int check_mood(struct osl_row *mood_row, void *data) 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 */ @@ -202,7 +208,7 @@ out: /** * 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. @@ -210,8 +216,7 @@ out: 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)); } /* @@ -251,21 +256,23 @@ int mood_check_callback(struct afs_callback_arg *aca) * overflows and rounding errors we store the common divisor of the * correction factors separately. */ -static int64_t normalized_value(int64_t x, int64_t n, int64_t sum, int64_t qd) -{ - if (!n || !qd) - return 0; - return 100 * (n * x - sum) / (int64_t)int_sqrt(n) / (int64_t)int_sqrt(qd); -} - static long compute_score(struct afs_info *afsi, const struct afs_statistics *stats) { - long score = -normalized_value(afsi->num_played, stats->num, - stats->num_played_sum, stats->num_played_qd); - score -= normalized_value(afsi->last_played, stats->num, - stats->last_played_sum, stats->last_played_qd); - return score / 2; + int64_t mean_n, mean_l,score_n, score_l; + + 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) + * stats->num_played_correction + / stats->normalization_divisor; + score_l = -((int64_t)afsi->last_played - mean_l) + * stats->last_played_correction + / stats->normalization_divisor; + return (score_n + score_l) / 2; } static int add_afs_statistics(const struct osl_row *row, @@ -336,7 +343,7 @@ static int del_afs_statistics(const struct osl_row *row) } /* - * 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 @@ -346,7 +353,7 @@ static int del_afs_statistics(const struct osl_row *row) */ 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. */ @@ -434,7 +441,7 @@ static void update_afs_statistics(struct afs_info *old_afsi, } 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; @@ -442,8 +449,8 @@ static int add_to_score_table(const struct osl_row *aft_row, 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) @@ -501,7 +508,7 @@ static int mood_update_audio_file(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); @@ -521,37 +528,42 @@ static int mood_update_audio_file(const struct osl_row *aft_row, } /* sse: seconds since epoch. */ -static void log_statistics(struct afs_statistics *stats, int64_t sse) +static char *get_statistics(struct mood_instance *m, int64_t sse) { - unsigned n = stats->num; + unsigned n = m->stats.num; int mean_days, sigma_days; - if (!n) { - PARA_WARNING_LOG("no admissible files\n"); - return; - } - PARA_NOTICE_LOG("%u admissible files\n", stats->num); - mean_days = (sse - stats->last_played_sum / n) / 3600 / 24; - sigma_days = int_sqrt(stats->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", - stats->num_played_sum / n, - int_sqrt(stats->num_played_qd / n)); - PARA_NOTICE_LOG("num_played correction factor: %" PRId64 "\n", - stats->num_played_correction); - PARA_NOTICE_LOG("last_played correction factor: %" PRId64 "\n", - stats->last_played_correction); - PARA_NOTICE_LOG("normalization divisor: %" PRId64 "\n", - stats->normalization_divisor); + 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), + 86400.0 * m->stats.last_played_correction / + m->stats.num_played_correction + ); } /** - * Close the current mood. + * Free all resources of a mood instance. * - * Frees all resources of the current mood. + * \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 close_current_mood(void) +void mood_unload(struct mood_instance *m) { + if (m) + return destroy_mood(m); destroy_mood(current_mood); current_mood = NULL; } @@ -573,24 +585,42 @@ static void compute_correction_factors(int64_t sse, struct afs_statistics *s) } /** - * Change the current mood. + * Populate a score table with admissible files for the given mood. * - * \param mood_name The name of the mood to open. - * \param errmsg Error description is returned here. + * 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. * - * 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. + * \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. * - * 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 the mood name is NULL, the dummy mood is loaded. This mood regards every + * audio file as admissible. * - * If there is already an open mood, it will be closed first. + * 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. * - * \return Positive on success, negative on errors. + * 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. * - * \sa struct \ref afs_info::last_played, \ref mp_eval_row(). + * \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 \ref mp_eval_row(). */ -int mood_switch(const char *mood_name, char **errmsg) +int mood_load(const char *mood_name, struct mood_instance **result, char **msg) { int i, ret; struct admissible_array aa = {.size = 0}; @@ -601,7 +631,7 @@ int mood_switch(const char *mood_name, char **errmsg) struct timeval rnow; if (mood_name) { - ret = init_mood_parser(mood_name, &aa.m, errmsg); + ret = init_mood_parser(mood_name, &aa.m, msg); if (ret < 0) return ret; } else /* load dummy mood */ @@ -609,40 +639,64 @@ int mood_switch(const char *mood_name, char **errmsg) 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, &aa.m->stats); - log_statistics(&aa.m->stats, rnow.tv_sec); + 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 (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; } } /* success */ - close_current_mood(); - current_mood = aa.m; - PARA_NOTICE_LOG("loaded mood %s\n", mood_name? mood_name : "(dummy)"); + if (msg) + *msg = get_statistics(aa.m, rnow.tv_sec); ret = aa.m->stats.num; + if (result) + *result = aa.m; + else { + mood_unload(NULL); + current_mood = aa.m; + } + ret = 1; out: free(aa.array); - if (ret < 0) + if (ret <= 0) /* error, or no admissible files */ destroy_mood(aa.m); 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); +} + /* - * 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. + * 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) { @@ -650,15 +704,13 @@ static int reload_current_mood(void) char *mood_name = NULL; assert(current_mood); - ret = clear_score_table(); - if (ret < 0) - return ret; + 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 = mood_switch(mood_name, NULL); + mood_unload(NULL); + ret = mood_load(mood_name, NULL, NULL); free(mood_name); return ret; }