X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=mood.c;h=940d72a17f9cbeb1d8dec42ea26cd9a821f9cbb3;hp=c892ff5c15fcfb8abfe8a11fe0a85911f073c740;hb=698178774b857a8959fe70e360ae6ff755eacf8e;hpb=68cb0aef88594f3dea1f7dfdc279f125bf5bbecc diff --git a/mood.c b/mood.c index c892ff5c..940d72a1 100644 --- a/mood.c +++ b/mood.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007-2012 Andre Noll + * Copyright (C) 2007 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -17,6 +17,9 @@ #include "list.h" #include "ipc.h" #include "mm.h" +#include "sideband.h" +#include "mood.h" +#include "sched.h" /** * Contains statistical data of the currently admissible audio files. @@ -28,7 +31,7 @@ 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; @@ -76,6 +79,10 @@ struct mood { struct list_head score_list; }; +/* + * 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; /** @@ -107,12 +114,16 @@ __a_const static uint64_t int_sqrt(uint64_t x) return res; } -/* returns 1 if row matches score item, 0 if not. */ -static int get_item_score(struct mood_item *item, const struct afs_info *afsi, +/* + * 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) { - int ret, match = 1; + int ret; + bool match = true; *score_arg_sum += item->random_score? 100 : PARA_ABS(item->score_arg); ret = 100; @@ -120,7 +131,7 @@ static int get_item_score(struct mood_item *item, const struct afs_info *afsi, 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); @@ -134,7 +145,8 @@ static int compute_mood_score(const struct osl_row *aft_row, struct mood *m, long *result) { 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; @@ -153,27 +165,29 @@ static int compute_mood_score(const struct osl_row *aft_row, struct mood *m, 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(item, &afsi, &afhi, path, &item_score, + match = get_item_score(item, &afsi, &afhi, path, &item_score, &score_arg_sum); - 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(item, &afsi, &afhi, path, &item_score, &score_arg_sum); 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(item, &afsi, &afhi, path, &item_score, + match = get_item_score(item, &afsi, &afhi, path, &item_score, &score_arg_sum); - score += item_score; + if (match) + score += item_score; } if (score_arg_sum) score /= score_arg_sum; @@ -210,7 +224,8 @@ static void destroy_mood(struct mood *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); @@ -373,7 +388,7 @@ static int load_mood(const struct osl_row *mood_row, struct mood **m) if (!*mood_name) return -E_DUMMY_ROW; 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) { @@ -396,20 +411,18 @@ 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, + para_printf(pb, "checking mood %s...\n", mood_name); + 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)); + para_printf(pb, "mood %s: error in line %u: %s\n", mood_name, + mlpd.line_num, para_strerror(-ret)); + ret = 1; /* don't fail the loop on invalid mood definitions */ out: osl_close_disk_object(&mood_def); return ret; @@ -418,79 +431,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 = shm_get_shmmax(), - .private_data = &(struct afs_max_size_handler_data) { - .fd = fd, - }, - .max_size_handler = afs_max_size_handler - }; - - 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; @@ -500,14 +471,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; @@ -551,16 +526,16 @@ static int del_afs_statistics(const struct osl_row *row) /** * 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. + * 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. * * \sa mood_method, admissible_array. */ @@ -634,7 +609,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. * @@ -651,7 +627,7 @@ _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) @@ -677,7 +653,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); } @@ -758,9 +740,7 @@ 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) @@ -774,16 +754,27 @@ static int mood_update_audio_file(const struct osl_row *aft_row, 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)); } @@ -816,7 +807,7 @@ void close_current_mood(void) * \sa struct admissible_file_info, struct admissible_array, struct * afs_info::last_played, mood_close(). */ -int change_current_mood(char *mood_name) +int change_current_mood(const char *mood_name) { int i, ret; struct admissible_array aa = { @@ -828,7 +819,7 @@ 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(osl_get_row(moods_table, BLOBCOL_NAME, &obj, &row)); @@ -841,24 +832,22 @@ int change_current_mood(char *mood_name) 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) 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) goto out; } - PARA_NOTICE_LOG("loaded mood %s\n", current_mood->name); + log_statistics(); ret = statistics.num; out: free(aa.array); @@ -882,6 +871,9 @@ 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? @@ -904,12 +896,12 @@ static int reload_current_mood(void) * 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) { - int ret; - if (!current_mood) return 0; switch (event) { @@ -922,10 +914,6 @@ int moods_event_handler(enum afs_events event, __a_unused struct para_buffer *pb case BLOB_ADD: if (data == moods_table || data == playlists_table) return 1; /* no reload necessary for these */ - ret = clear_score_table(); - if (ret < 0) - PARA_CRIT_LOG("clear score table returned %s\n", - para_strerror(-ret)); return reload_current_mood(); /* these also require reload of the score table */ case ATTRIBUTE_ADD: