X-Git-Url: http://git.tuebingen.mpg.de/?a=blobdiff_plain;f=mood.c;h=a63d4d2af5d10d7b64c319d915e00b9b7ea62e89;hb=0e5dedaa7f494adcd2970c301343fc04933fae72;hp=9f1d7745a3589565045b919af94ad02b3f73602d;hpb=37f4717467d2235dba5fe403737eba66efb1dde4;p=paraslash.git diff --git a/mood.c b/mood.c index 9f1d7745..a63d4d2a 100644 --- a/mood.c +++ b/mood.c @@ -1,19 +1,29 @@ -/* - * Copyright (C) 2007-2009 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ +/* Copyright (C) 2007 Andre Noll , see file COPYING. */ /** \file mood.c Paraslash's mood handling functions. */ -#include +#include +#include +#include + #include "para.h" #include "error.h" #include "string.h" #include "afh.h" #include "afs.h" #include "list.h" -#include "ipc.h" +#include "mm.h" +#include "mood.h" + +/* + * Mood parser API. It's overkill to have an own header file for + * these declarations as they are only needed in this .c file. + */ +struct mp_context; +int mp_init(const char *definition, int nbytes, struct mp_context **result, + char **errmsg); +bool mp_eval_row(const struct osl_row *aft_row, struct mp_context *ctx); +void mp_shutdown(struct mp_context *ctx); /** * Contains statistical data of the currently admissible audio files. @@ -25,74 +35,14 @@ struct afs_statistics { int64_t num_played_sum; /** Sum of last played times over all admissible files. */ int64_t last_played_sum; - /** Quadratic deviation of num played time. */ + /** Quadratic deviation of num played count. */ int64_t num_played_qd; /** Quadratic deviation of last played time. */ int64_t last_played_qd; /** Number of admissible files */ unsigned num; }; -struct afs_statistics statistics; - -/** - * Assign scores according to a mood_method. - * - * Each mood_method has its own mood_score_function. The first three parameters - * passed to that function are informations about the audio file whose score is - * to be computed. The data argument depends on the mood method this function - * is used for. It usually is the argument given at the end of a mood line. - * - * Mood score functions must return values between -100 and +100 inclusively. - * Boolean score functions should always return either -100 or +100. - * - * \sa struct mood_method, mood_parser. - */ -typedef int mood_score_function(const char *path, const struct afs_info *afsi, - const struct afh_info *afhi, const void *data); - -/** - * Pre-process a mood line. - * - * The mood_parser of a mood_method is called once at mood open time for each - * line of the current mood definition that contains the mood_method's name as - * a keyword. The line is passed to the mood_parser as the first argument. The - * mood_parser must determine whether the line is syntactically correct and - * return a positive value if so and a negative value otherwise. - * - * Some mood parsers pre-process the data given in the mood line to compute a - * structure which depends of the particular mood_method and which is used - * later in the mood_score_function of the mood_method. The mood_parser may - * store a pointer to its structure via the second argument. - * - * \sa mood_open(), mood_cleanup_function, mood_score_function. - */ -typedef int mood_parser(const char *, void **); - -/** - * Deallocate resources which were allocated by the mood_parser. - * - * This optional function of a mood_method is used to free any resources - * allocated in mood_open() by the mood_parser. The argument passed is a - * pointer to the mood_method specific data structure that was returned by the - * mood_parser. - * - * \sa mood_parser. - */ -typedef void mood_cleanup_function(void *); - -/** - * Used for scoring and to determine whether a file is admissible. - */ -struct mood_method { - /** The name of the method. */ - const char *name; - /** Pointer to the mood parser. */ - mood_parser *parser; - /** Pointer to the score function */ - mood_score_function *score_function; - /** Optional cleanup function. */ - mood_cleanup_function *cleanup; -}; +static struct afs_statistics statistics; /** * Each line of the current mood corresponds to a mood_item. @@ -112,15 +62,13 @@ struct mood_item { struct list_head mood_item_node; }; -/** - * Created from the mood definition by mood_open(). +/* + * Created from the mood definition by \ref change_current_mood(). * * When a mood is opened, each line of its definition is investigated, and a - * corresponding mood item is produced. Each mood line starts with \p accept, - * \p deny, or \p score which determines the type of the mood line. For each - * such type a linked list is maintained whose entries are the mood items. - * - * \sa mood_item, mood_open(). + * corresponding mood item is produced. Each mood line starts with accept, + * deny, or score which determines the type of the mood line. For each such + * type a linked list is maintained whose entries are the mood items. */ struct mood { /** The name of this mood. */ @@ -131,149 +79,91 @@ struct mood { struct list_head deny_list; /** The list of mood items of type \p score. */ struct list_head score_list; + /* Only used for version 2 moods. */ + struct mp_context *parser_context; }; +/* + * If current_mood is NULL then no mood is currently open. If + * current_mood->name is NULL, the dummy mood is currently open. + */ static struct mood *current_mood; -/** - * Rough approximation to sqrt. - * - * \param x Integer of which to calculate the sqrt. +/* + * Find the position of the most-significant set bit. * - * \return An integer res with res * res <= x. + * Copied and slightly adapted from the linux source tree, version 4.9.39 + * (2017-07). */ -static uint64_t int_sqrt(uint64_t x) +__a_const static uint32_t fls64(uint64_t v) { - uint64_t op, res, one = 1; - op = x; - res = 0; + int n = 63; + const uint64_t ones = ~(uint64_t)0U; - one = one << 62; - while (one > op) - one >>= 2; + if ((v & (ones << 32)) == 0) { + n -= 32; + v <<= 32; + } + if ((v & (ones << (64 - 16))) == 0) { + n -= 16; + v <<= 16; + } + if ((v & (ones << (64 - 8))) == 0) { + n -= 8; + v <<= 8; + } + if ((v & (ones << (64 - 4))) == 0) { + n -= 4; + v <<= 4; + } + if ((v & (ones << (64 - 2))) == 0) { + n -= 2; + v <<= 2; + } + if ((v & (ones << (64 - 1))) == 0) + n -= 1; + return n; +} + +/* + * Compute the integer square root floor(sqrt(x)). + * + * Taken 2007 from the linux source tree. + */ +__a_const static uint64_t int_sqrt(uint64_t x) +{ + uint64_t op = x, res = 0, one = 1; + one = one << (fls64(x) & ~one); while (one != 0) { if (op >= res + one) { op = op - (res + one); - res = res + 2 * one; + res = res + 2 * one; } res /= 2; one /= 4; } -// PARA_NOTICE_LOG("sqrt(%llu) = %llu\n", x, res); return res; } -static int mm_no_attributes_set_parser(const char *arg, __a_unused void **ignored) -{ - if (arg && *arg) - PARA_WARNING_LOG("ignored junk at eol: %s\n", arg); - return 1; -} - -static int mm_no_attributes_set_score_function(__a_unused const char *path, - const struct afs_info *afsi, - __a_unused const struct afh_info *afhi, - __a_unused const void *data) -{ - if (!afsi->attributes) - return 100; - return -100; -} - -static int mm_played_rarely_score_function(__a_unused const char *path, - const struct afs_info *afsi, - __a_unused const struct afh_info *afhi, - __a_unused const void *data) -{ - unsigned num; - int ret = get_num_admissible_files(&num); - - if (ret < 0) - return 0; - if (statistics.num_played_sum - num * afsi->num_played - > int_sqrt(statistics.num_played_qd * num)) - return 100; - return -100; -} - -static int mm_played_rarely_parser(const char *arg, __a_unused void **ignored) -{ - if (arg && *arg) - PARA_WARNING_LOG("ignored junk at eol: %s\n", arg); - return 1; -} - -static int mm_path_matches_score_function(const char *path, - __a_unused const struct afs_info *afsi, - __a_unused const struct afh_info *afhi, - const void *data) -{ - if (fnmatch(data, path, 0)) - return -100; - return 100; -} - -static int mm_path_matches_parser(const char *arg, void **data) -{ - *data = para_strdup(arg); - return 1; -} - -static void mm_path_matches_cleanup(void *data) -{ - free(data); -} - -static int mm_is_set_parser(const char *arg, void **bitnum) -{ - unsigned char *c = para_malloc(1); - int ret = get_attribute_bitnum_by_name(arg, c); - - if (ret >= 0) - *bitnum = c; - else - free(c); - return ret; -} - -static int mm_is_set_score_function(__a_unused const char *path, - __a_unused const struct afs_info *afsi, - __a_unused const struct afh_info *afhi, - const void *data) -{ - const unsigned char *bn = data; - if (afsi->attributes & (1ULL << *bn)) - return 100; - return -100; -} - -/* returns 1 if row matches score item, 0 if not, negative on errors */ -static int get_item_score(const struct osl_row *row, struct mood_item *item, - long *score, long *score_arg_sum) +/* + * Returns true if row matches, false if it does not match. In any case score + * and score_arg_sum are set/increased accordingly. + */ +static bool get_item_score(struct mood_item *item, const struct afs_info *afsi, + const struct afh_info *afhi, const char *path, long *score, + long *score_arg_sum) { - struct afs_info afsi; - struct afh_info afhi; - char *path; - int ret, match = 1; + int ret; + bool match = true; *score_arg_sum += item->random_score? 100 : PARA_ABS(item->score_arg); ret = 100; if (item->method) { - ret = get_afsi_of_row(row, &afsi); - if (ret< 0) - return ret; - ret = get_afhi_of_row(row, &afhi); - if (ret< 0) - return ret; - free(afhi.info_string); /* don't need the tag info */ - ret = get_audio_file_path_of_row(row, &path); - if (ret< 0) - return ret; - ret = item->method->score_function(path, &afsi, &afhi, + ret = item->method->score_function(path, afsi, afhi, item->parser_data); if ((ret < 0 && !item->logical_not) || (ret >= 0 && item->logical_not)) - match = 0; /* no match */ + match = false; } if (item->random_score) *score = PARA_ABS(ret) * para_random(100); @@ -283,68 +173,64 @@ static int get_item_score(const struct osl_row *row, struct mood_item *item, } /* returns 1 if row admissible, 0 if not, negative on errors */ -static int compute_mood_score(const struct osl_row *aft_row, struct mood *m, - long *result) +static int row_is_admissible(const struct osl_row *aft_row, struct mood *m, + long *scorep) { struct mood_item *item; - int ret, match = 0; + int ret; + bool match; long score_arg_sum = 0, score = 0, item_score; + struct afs_info afsi; + struct afh_info afhi; + char *path; if (!m) return -E_NO_MOOD; + if (m->parser_context) { + *scorep = 0; + return mp_eval_row(aft_row, m->parser_context); + } + ret = get_afsi_of_row(aft_row, &afsi); + if (ret < 0) + return ret; + ret = get_afhi_of_row(aft_row, &afhi); + if (ret < 0) + return ret; + ret = get_audio_file_path_of_row(aft_row, &path); + if (ret < 0) + return ret; /* reject audio file if it matches any entry in the deny list */ list_for_each_entry(item, &m->deny_list, mood_item_node) { - ret = get_item_score(aft_row, item, &item_score, + match = get_item_score(item, &afsi, &afhi, path, &item_score, &score_arg_sum); - if (ret < 0) - return ret; - if (ret > 0) /* not admissible */ + if (match) /* not admissible */ return 0; score += item_score; } + match = false; list_for_each_entry(item, &m->accept_list, mood_item_node) { - ret = get_item_score(aft_row, item, &item_score, + ret = get_item_score(item, &afsi, &afhi, path, &item_score, &score_arg_sum); - if (ret < 0) - return ret; if (ret == 0) continue; - match = 1; + match = true; score += item_score; } /* reject if there is no matching entry in the accept list */ if (!match && !list_empty(&m->accept_list)) return 0; list_for_each_entry(item, &m->score_list, mood_item_node) { - ret = get_item_score(aft_row, item, &item_score, + match = get_item_score(item, &afsi, &afhi, path, &item_score, &score_arg_sum); - if (ret < 0) - return ret; - score += item_score; + if (match) + score += item_score; } if (score_arg_sum) score /= score_arg_sum; - *result = score; + *scorep = score; return 1; } -#define DEFINE_MOOD_METHOD(_name) \ -.parser = mm_ ## _name ## _parser, \ -.score_function = mm_ ## _name ## _score_function, \ -.name = #_name - -#define DEFINE_MOOD_METHOD_WITH_CLEANUP(_name) \ - DEFINE_MOOD_METHOD(_name), \ - .cleanup = mm_ ## _name ## _cleanup - -static const struct mood_method mood_methods[] = { - {DEFINE_MOOD_METHOD(no_attributes_set)}, - {DEFINE_MOOD_METHOD(played_rarely)}, - {DEFINE_MOOD_METHOD(is_set)}, - {DEFINE_MOOD_METHOD_WITH_CLEANUP(path_matches)}, - {.parser = NULL} -}; - static void cleanup_list_entry(struct mood_item *item) { if (item->method && item->method->cleanup) @@ -368,13 +254,15 @@ static void destroy_mood(struct mood *m) list_for_each_entry_safe(item, tmp, &m->score_list, mood_item_node) cleanup_list_entry(item); free(m->name); + mp_shutdown(m->parser_context); free(m); } static struct mood *alloc_new_mood(const char *name) { struct mood *m = para_calloc(sizeof(struct mood)); - m->name = para_strdup(name); + if (name) + m->name = para_strdup(name); INIT_LIST_HEAD(&m->accept_list); INIT_LIST_HEAD(&m->deny_list); INIT_LIST_HEAD(&m->score_list); @@ -407,22 +295,21 @@ struct mood_line_parser_data { * is either an integer or "random" which assigns a random score to * all matching files */ - static int parse_mood_line(char *mood_line, void *data) { struct mood_line_parser_data *mlpd = data; char **argv; - char *delim = " \t"; unsigned num_words; char **w; int i, ret; enum mood_line_type mlt = ML_INVALID; struct mood_item *mi = NULL; - char *buf = para_strdup(mood_line); mlpd->line_num++; - num_words = split_args(buf, &argv, delim); - ret = 1; + ret = create_argv(mood_line, " \t", &argv); + if (ret < 0) + return ret; + num_words = ret; if (!num_words) /* empty line */ goto out; w = argv; @@ -495,8 +382,8 @@ check_for_if: ret = -E_MOOD_SYNTAX; if (!mood_methods[i].parser) goto out; - w++; - ret = mood_methods[i].parser(*w, &mi->parser_data); + ret = mood_methods[i].parser(num_words - 1 - (w - argv), w, + &mi->parser_data); if (ret < 0) goto out; mi->method = &mood_methods[i]; @@ -513,40 +400,49 @@ success: (mlt == ML_DENY? "deny" : "score"), mi->method); ret = 1; out: - free(argv); - free(buf); - if (ret >= 0) - return ret; - if (mi) { + free_argv(argv); + if (mi && (ret < 0 || !mlpd->m)) { /* mi was not added to any list */ free(mi->parser_data); free(mi); } return ret; } -static int load_mood(const struct osl_row *mood_row, struct mood **m) +static int load_mood(const struct osl_row *mood_row, struct mood **m, + char **errmsg) { char *mood_name; struct osl_object mood_def; struct mood_line_parser_data mlpd = {.line_num = 0}; - int ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def); + int ret; - if (ret < 0) + *m = NULL; + ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def); + if (ret < 0) { + if (errmsg) + *errmsg = make_message( + "could not read mood definition"); return ret; - if (!*mood_name) - return -E_DUMMY_ROW; + } + assert(*mood_name); mlpd.m = alloc_new_mood(mood_name); - ret = for_each_line_ro(mood_def.data, mood_def.size, + ret = for_each_line(FELF_READ_ONLY, mood_def.data, mood_def.size, parse_mood_line, &mlpd); - osl_close_disk_object(&mood_def); if (ret < 0) { - PARA_ERROR_LOG("unable to load mood %s: %s\n", mlpd.m->name, - para_strerror(-ret)); - destroy_mood(mlpd.m); - return ret; + PARA_INFO_LOG("opening version 2 mood %s\n", mlpd.m->name); + ret = mp_init(mood_def.data, mood_def.size, &mlpd.m->parser_context, + errmsg); + if (ret < 0) + destroy_mood(mlpd.m); + } else { + PARA_WARNING_LOG("loaded version 1 mood %s\n", mlpd.m->name); + PARA_WARNING_LOG("please convert to version 2\n"); + ret = 1; } - *m = mlpd.m; - return 1; + osl_close_disk_object(&mood_def); + if (ret >= 0) + *m = mlpd.m; + return ret; } static int check_mood(struct osl_row *mood_row, void *data) @@ -559,20 +455,30 @@ static int check_mood(struct osl_row *mood_row, void *data) int ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def); if (ret < 0) { - para_printf(pb, "failed to get mood definition: %s\n", - para_strerror(-ret)); + para_printf(pb, "cannot read mood\n"); return ret; } if (!*mood_name) /* ignore dummy row */ goto out; - ret = para_printf(pb, "checking mood %s...\n", mood_name); - if (ret < 0) - goto out; - ret = for_each_line_ro(mood_def.data, mood_def.size, + ret = for_each_line(FELF_READ_ONLY, mood_def.data, mood_def.size, parse_mood_line, &mlpd); - if (ret < 0) - para_printf(pb, "%s line %u: %s\n", mood_name, mlpd.line_num, - para_strerror(-ret)); + if (ret < 0) { + char *errmsg; + struct mood *m = alloc_new_mood("check"); + 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); + free(errmsg); + para_printf(pb, "%s\n", para_strerror(-ret)); + } else + destroy_mood(m); + } else { + para_printf(pb, "%s: v1 mood, please convert to v2\n", + mood_name); + + } + ret = 1; /* don't fail the loop on invalid mood definitions */ out: osl_close_disk_object(&mood_def); return ret; @@ -581,77 +487,37 @@ out: /** * Check all moods for syntax errors. * - * \param fd The afs socket. - * \param query Unused. + * \param aca Only ->pbout is used for diagnostics. + * + * \return Negative on fatal errors. Inconsistent mood definitions are not + * considered an error. */ -void mood_check_callback(int fd, __a_unused const struct osl_object *query) +int mood_check_callback(struct afs_callback_arg *aca) { - struct para_buffer pb = { - .max_size = SHMMAX, - .private_data = &fd, - .max_size_handler = pass_buffer_as_shm - }; - - int ret = para_printf(&pb, "checking moods...\n"); - if (ret < 0) - return; - osl_rbtree_loop(moods_table, BLOBCOL_ID, &pb, - check_mood); - if (pb.offset) - pass_buffer_as_shm(pb.buf, pb.offset, &fd); - free(pb.buf); + para_printf(&aca->pbout, "checking moods...\n"); + return osl(osl_rbtree_loop(moods_table, BLOBCOL_ID, &aca->pbout, + check_mood)); } -#if 0 -static unsigned int_log2(uint64_t x) -{ - unsigned res = 0; - - while (x) { - x /= 2; - res++; - } - return res; -} -#endif - 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 * qd); + return 100 * (n * x - sum) / (int64_t)int_sqrt(n) / (int64_t)int_sqrt(qd); } -static long compute_num_played_score(struct afs_info *afsi) +static long compute_score(struct afs_info *afsi, long mood_score) { - return -normalized_value(afsi->num_played, statistics.num, + mood_score -= normalized_value(afsi->num_played, statistics.num, statistics.num_played_sum, statistics.num_played_qd); -} - -static long compute_last_played_score(struct afs_info *afsi) -{ - return -normalized_value(afsi->last_played, statistics.num, + mood_score -= normalized_value(afsi->last_played, statistics.num, statistics.last_played_sum, statistics.last_played_qd); -} - -static long compute_dynamic_score(const struct osl_row *aft_row) -{ - struct afs_info afsi; - int64_t score, nscore = 0, lscore = 0; - int ret; - - ret = get_afsi_of_row(aft_row, &afsi); - if (ret < 0) - return -100; - nscore = compute_num_played_score(&afsi); - lscore = compute_last_played_score(&afsi); - score = nscore + lscore; - return score; + return mood_score / 3; } static int add_afs_statistics(const struct osl_row *row) { - uint64_t n, x, s; + uint64_t n, x, s, q; struct afs_info afsi; int ret; @@ -661,14 +527,18 @@ static int add_afs_statistics(const struct osl_row *row) n = statistics.num; x = afsi.last_played; s = statistics.last_played_sum; - if (n > 0) - statistics.last_played_qd += (x - s / n) * (x - s / n) * n / (n + 1); + if (n > 0) { + q = (x > s / n)? x - s / n : s / n - x; + statistics.last_played_qd += q * q * n / (n + 1); + } statistics.last_played_sum += x; x = afsi.num_played; s = statistics.num_played_sum; - if (n > 0) - statistics.num_played_qd += (x - s / n) * (x - s / n) * n / (n + 1); + if (n > 0) { + q = (x > s / n)? x - s / n : s / n - x; + statistics.num_played_qd += q * q * n / (n + 1); + } statistics.num_played_sum += x; statistics.num++; return 1; @@ -709,21 +579,17 @@ static int del_afs_statistics(const struct osl_row *row) return 1; } -/** - * Structure used during mood_open(). - * - * At mood open time, we look at each file in the audio file table in order to - * determine whether it is admissible. If a file happens to be admissible, its - * mood score is computed by calling each relevant mood_score_function. Next, - * we update the afs_statistics and add a struct admissible_file_info to a - * temporary array. +/* + * At mood open time we determine the set of admissible files for the given + * mood. The mood score of each admissible file is computed by adding up all + * mood item scores. Next, we update the afs statistics and append a struct + * admissible_file_info to a temporary array. * - * If all files have been processed that way, the final score of each + * When all files have been processed in this way, the final score of each * admissible file is computed by adding the dynamic score (which depends on - * the afs_statistics) to the mood score. Finally, all audio files in the - * array are added to the score table and the admissible array is freed. - * - * \sa mood_method, admissible_array. + * the afs_statistics and the current time) to the mood score. Finally, all + * audio files in the temporary array are added to the score table and the + * array is freed. */ struct admissible_file_info { @@ -757,7 +623,7 @@ static int add_if_admissible(struct osl_row *aft_row, void *data) int ret; long score = 0; - ret = compute_mood_score(aft_row, aa->m, &score); + ret = row_is_admissible(aft_row, aa->m, &score); if (ret <= 0) return ret; if (statistics.num >= aa->size) { @@ -795,7 +661,8 @@ static int add_if_admissible(struct osl_row *aft_row, void *data) * the last number a_n was replaced by b) may be computed in O(1) time in terms * of n, q, a_n, b, and S as * - * q' = q + d * s - (2 * S + d) * d / n, + * q' = q + d * s - (2 * S + d) * d / n + * = q + d * (s - 2 * S / n - d /n), * * where d = b - a_n, and s = b + a_n. * @@ -812,10 +679,11 @@ _static_inline_ int64_t update_quadratic_deviation(int64_t n, int64_t old_qd, { int64_t delta = new_val - old_val; int64_t sigma = new_val + old_val; - return old_qd + delta * sigma - (2 * old_sum + delta) * delta / n; + return old_qd + delta * (sigma - 2 * old_sum / n - delta / n); } -static int update_afs_statistics(struct afs_info *old_afsi, struct afs_info *new_afsi) +static int update_afs_statistics(struct afs_info *old_afsi, + struct afs_info *new_afsi) { unsigned n; int ret = get_num_admissible_files(&n); @@ -838,7 +706,13 @@ static int update_afs_statistics(struct afs_info *old_afsi, struct afs_info *new static int add_to_score_table(const struct osl_row *aft_row, long mood_score) { - long score = (compute_dynamic_score(aft_row) + mood_score) / 3; + long score; + struct afs_info afsi; + int ret = get_afsi_of_row(aft_row, &afsi); + + if (ret < 0) + return ret; + score = compute_score(&afsi, mood_score); return score_add(aft_row, score); } @@ -857,7 +731,7 @@ static int delete_from_statistics_and_score_table(const struct osl_row *aft_row) * * \return Positive on success, negative on errors. * - * \sa score_delete(). + * \sa \ref score_delete(). */ static int mood_delete_audio_file(const struct osl_row *aft_row) { @@ -896,7 +770,7 @@ static int mood_update_audio_file(const struct osl_row *aft_row, if (ret < 0) return ret; was_admissible = ret; - ret = compute_mood_score(aft_row, current_mood, &score); + ret = row_is_admissible(aft_row, current_mood, &score); if (ret < 0) return ret; is_admissible = (ret > 0); @@ -919,40 +793,48 @@ static int mood_update_audio_file(const struct osl_row *aft_row, if (ret < 0) return ret; } - score += compute_num_played_score(&afsi); - score += compute_last_played_score(&afsi); - score /= 3; + score = compute_score(&afsi, score); 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 %lu%%\n", rank, percent); + PARA_DEBUG_LOG("moving from rank %u to %li%%\n", rank, percent); return score_update(aft_row, percent); } static void log_statistics(void) { unsigned n = statistics.num; + int mean_days, sigma_days; + /* + * We can not use the "now" pointer from sched.c here because we are + * called before schedule(), which initializes "now". + */ + struct timeval rnow; + assert(current_mood); + PARA_NOTICE_LOG("loaded mood %s\n", current_mood->name? + current_mood->name : "(dummy)"); if (!n) { - PARA_NOTICE_LOG("no admissible files\n"); + PARA_WARNING_LOG("no admissible files\n"); return; } - PARA_INFO_LOG("last_played mean: %lli, last_played sigma: %llu\n", - (long long int)(statistics.last_played_sum / n), - (long long unsigned)int_sqrt(statistics.last_played_qd / n)); - PARA_INFO_LOG("num_played mean: %lli, num_played sigma: %llu\n", - (long long int)statistics.num_played_sum / n, + PARA_NOTICE_LOG("%u admissible files\n", statistics.num); + clock_get_realtime(&rnow); + mean_days = (rnow.tv_sec - 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: %llu/%llu\n", + (long long unsigned)statistics.num_played_sum / n, (long long unsigned)int_sqrt(statistics.num_played_qd / n)); } /** * Close the current mood. * - * Free all resources of the current mood which were allocated during - * mood_open(). + * Frees all resources of the current mood. */ void close_current_mood(void) { @@ -961,24 +843,26 @@ void close_current_mood(void) memset(&statistics, 0, sizeof(statistics)); } - /** * Change the current mood. * * \param mood_name The name of the mood to open. + * \param errmsg Error description 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. * * \return Positive on success, negative on errors. Loading the dummy mood * always succeeds. * - * \sa struct admissible_file_info, struct admissible_array, struct - * afs_info::last_played, mood_close(). + * \sa struct \ref afs_info::last_played, \ref mp_eval_row(). */ -int change_current_mood(char *mood_name) +int change_current_mood(const char *mood_name, char **errmsg) { int i, ret; struct admissible_array aa = { @@ -990,60 +874,67 @@ int change_current_mood(char *mood_name) struct mood *m; struct osl_row *row; struct osl_object obj = { - .data = mood_name, + .data = (char *)mood_name, .size = strlen(mood_name) + 1 }; - ret = osl_get_row(moods_table, BLOBCOL_NAME, &obj, &row); + ret = osl(osl_get_row(moods_table, BLOBCOL_NAME, &obj, &row)); if (ret < 0) { - PARA_NOTICE_LOG("no such mood: %s\n", mood_name); + if (errmsg) + *errmsg = make_message("no such mood: %s", + mood_name); return ret; } - ret = load_mood(row, &m); + ret = load_mood(row, &m, errmsg); if (ret < 0) return ret; close_current_mood(); current_mood = m; - } else { + } else { /* load dummy mood */ close_current_mood(); - current_mood = alloc_new_mood("dummy"); + current_mood = alloc_new_mood(NULL); } aa.m = current_mood; PARA_NOTICE_LOG("computing statistics of admissible files\n"); ret = audio_file_loop(&aa, add_if_admissible); - if (ret < 0) + if (ret < 0) { + if (errmsg) + *errmsg = make_message("audio file loop failed"); return ret; - log_statistics(); - PARA_INFO_LOG("%d admissible files \n", statistics.num); + } for (i = 0; i < statistics.num; i++) { struct admissible_file_info *a = aa.array + i; ret = add_to_score_table(a->aft_row, a->score); - if (ret < 0) + if (ret < 0) { + if (errmsg) + *errmsg = make_message( + "could not add row to score table"); goto out; + } } - PARA_NOTICE_LOG("loaded mood %s\n", current_mood->name); + log_statistics(); ret = statistics.num; out: free(aa.array); return ret; } -/** + +/* * Close and re-open the current mood. * - * This function is used if changes to the audio file table or the - * attribute table were made that render the current list of admissible - * files useless. For example, if an attribute is removed from the - * attribute table, this function is called. + * 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. * - * \return Positive on success, negative on errors. If no mood is currently - * open, the function returns success. - * - * \sa mood_open(), mood_close(). + * If no mood is currently open, the function returns success. */ -int reload_current_mood(void) +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; PARA_NOTICE_LOG("reloading %s\n", current_mood->name? @@ -1051,11 +942,24 @@ int reload_current_mood(void) if (current_mood->name) mood_name = para_strdup(current_mood->name); close_current_mood(); - ret = change_current_mood(mood_name); + ret = change_current_mood(mood_name, NULL); free(mood_name); return ret; } +/** + * Notification callback for the moods table. + * + * \param event Type of the event just occurred. + * \param pb Unused. + * \param data Its type depends on the event. + * + * This function performs actions required due to the occurrence of the given + * event. Possible actions include reload of the current mood and update of the + * score of an audio file. + * + * \return Standard. + */ int moods_event_handler(enum afs_events event, __a_unused struct para_buffer *pb, void *data) { @@ -1092,4 +996,3 @@ int moods_event_handler(enum afs_events event, __a_unused struct para_buffer *pb return 1; } } -