]> git.tuebingen.mpg.de Git - paraslash.git/blobdiff - mood.c
mood.c: Rename struct mood to mood_instance.
[paraslash.git] / mood.c
diff --git a/mood.c b/mood.c
index a5d2b025c479bf7907a80e4df22a83fc06791d8e..9c2571aa054287859ca4f041f2384900999be33b 100644 (file)
--- a/mood.c
+++ b/mood.c
@@ -12,8 +12,6 @@
 #include "afh.h"
 #include "afs.h"
 #include "list.h"
-#include "mm.h"
-#include "mood.h"
 
 /*
  * Mood parser API. It's overkill to have an own header file for
@@ -48,52 +46,29 @@ struct afs_statistics {
        /** Number of admissible files */
        unsigned num;
 };
-static struct afs_statistics statistics = {.normalization_divisor = 1};
 
 /**
- * Each line of the current mood corresponds to a mood_item.
- */
-struct mood_item {
-       /** The method this line is referring to. */
-       const struct mood_method *method;
-       /** The data structure computed by the mood parser. */
-       void *parser_data;
-       /** The given score value, or zero if none was given. */
-       int32_t score_arg;
-       /** Non-zero if random scoring was requested. */
-       int random_score;
-       /** Whether the "not" keyword was given in the mood line. */
-       int logical_not;
-       /** The position in the list of items. */
-       struct list_head mood_item_node;
-};
-
-/*
- * Created from the mood definition by \ref change_current_mood().
+ * Stores an instance of an open mood (parser and statistics).
  *
- * When a mood is opened, each line of its definition is investigated, and a
- * 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.
+ * A structure of this type is allocated and initialized at mood open time.
  */
-struct mood {
-       /** The name of this mood. */
+struct mood_instance {
+       /** NULL means that this is the "dummy" mood. */
        char *name;
-       /** The list of mood items of type \p accept. */
-       struct list_head accept_list;
-       /** The list of mood items of type \p deny. */
-       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. */
+       /** 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.
+ *
+ * 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.
@@ -152,312 +127,57 @@ __a_const static uint64_t int_sqrt(uint64_t x)
        return res;
 }
 
-/*
- * 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;
-       bool match = true;
-
-       *score_arg_sum += item->random_score? 100 : PARA_ABS(item->score_arg);
-       ret = 100;
-       if (item->method) {
-               ret = item->method->score_function(path, afsi, afhi,
-                       item->parser_data);
-               if ((ret < 0 && !item->logical_not) || (ret >= 0 && item->logical_not))
-                       match = false;
-       }
-       if (item->random_score)
-               *score = PARA_ABS(ret) * para_random(100);
-       else
-               *score = PARA_ABS(ret) * item->score_arg;
-       return match;
-}
-
-/* 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,
-               long *scorep)
-{
-       struct mood_item *item;
-       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) {
-               match = get_item_score(item, &afsi, &afhi, path, &item_score,
-                       &score_arg_sum);
-               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 = 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) {
-               match = get_item_score(item, &afsi, &afhi, path, &item_score,
-                       &score_arg_sum);
-               if (match)
-                       score += item_score;
-       }
-       if (score_arg_sum)
-               score /= score_arg_sum;
-       *scorep = score;
-       return 1;
-}
-
-static void cleanup_list_entry(struct mood_item *item)
+static void destroy_mood(struct mood_instance *m)
 {
-       if (item->method && item->method->cleanup)
-               item->method->cleanup(item->parser_data);
-       else
-               free(item->parser_data);
-       list_del(&item->mood_item_node);
-       free(item);
-}
-
-static void destroy_mood(struct mood *m)
-{
-       struct mood_item *tmp, *item;
-
        if (!m)
                return;
-       list_for_each_entry_safe(item, tmp, &m->accept_list, mood_item_node)
-               cleanup_list_entry(item);
-       list_for_each_entry_safe(item, tmp, &m->deny_list, mood_item_node)
-               cleanup_list_entry(item);
-       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->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 = para_calloc(sizeof(struct mood));
+       struct mood_instance *m = zalloc(sizeof(*m));
+
        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);
+       m->stats.normalization_divisor = 1;
        return m;
 }
 
-/** The different types of a mood line. */
-enum mood_line_type {
-       /** Invalid. */
-       ML_INVALID,
-       /** Accept line. */
-       ML_ACCEPT,
-       /** Deny line. */
-       ML_DENY,
-       /** Score line. */
-       ML_SCORE
-};
-
-/** Data passed to the parser of a mood line. */
-struct mood_line_parser_data {
-       /** The mood this mood line belongs to. */
-       struct mood *m;
-       /** The line number in the mood definition. */
-       unsigned line_num;
-};
-
-/*
- * <accept [with score <score>] | deny [with score <score>]  | score <score>>
- *     [if] [not] <mood_method> [options]
- * <score> 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;
-       unsigned num_words;
-       char **w;
-       int i, ret;
-       enum mood_line_type mlt = ML_INVALID;
-       struct mood_item *mi = NULL;
-
-       mlpd->line_num++;
-       ret = create_argv(mood_line, " \t", &argv);
-       if (ret < 0)
-               return ret;
-       num_words = ret;
-       if (!num_words) /* empty line */
-               goto out;
-       w = argv;
-       if (**w == '#') /* comment */
-               goto out;
-       if (!strcmp(*w, "accept"))
-               mlt = ML_ACCEPT;
-       else if (!strcmp(*w, "deny"))
-               mlt = ML_DENY;
-       else if (!strcmp(*w, "score"))
-               mlt = ML_SCORE;
-       ret = -E_MOOD_SYNTAX;
-       if (mlt == ML_INVALID)
-               goto out;
-       mi = para_calloc(sizeof(struct mood_item));
-       if (mlt != ML_SCORE) {
-               ret = -E_MOOD_SYNTAX;
-               w++;
-               if (!*w)
-                       goto out;
-               if (strcmp(*w, "with"))
-                       goto check_for_if;
-               w++;
-               if (!*w)
-                       goto out;
-               if (strcmp(*w, "score"))
-                       goto out;
-       }
-       if (mlt == ML_SCORE || !strcmp(*w, "score")) {
-               ret = -E_MOOD_SYNTAX;
-               w++;
-               if (!*w)
-                       goto out;
-               if (strcmp(*w, "random")) {
-                       mi->random_score = 0;
-                       ret = para_atoi32(*w, &mi->score_arg);
-                       if (ret < 0)
-                               goto out;
-               } else {
-                       mi->random_score = 1;
-                       if (!*(w + 1))
-                       goto success; /* the line "score random" is valid */
-               }
-       } else
-               mi->score_arg = 0;
-       ret = -E_MOOD_SYNTAX;
-       w++;
-       if (!*w)
-               goto out;
-check_for_if:
-       if (!strcmp(*w, "if")) {
-               ret = -E_MOOD_SYNTAX;
-               w++;
-               if (!*w)
-                       goto out;
-       }
-       if (!strcmp(*w, "not")) {
-               ret = -E_MOOD_SYNTAX;
-               w++;
-               if (!*w)
-                       goto out;
-               mi->logical_not = 1;
-       } else
-               mi->logical_not = 0;
-       for (i = 0; mood_methods[i].parser; i++) {
-               if (strcmp(*w, mood_methods[i].name))
-                       continue;
-               break;
-       }
-       ret = -E_MOOD_SYNTAX;
-       if (!mood_methods[i].parser)
-               goto out;
-       ret = mood_methods[i].parser(num_words - 1 - (w - argv), w,
-               &mi->parser_data);
-       if (ret < 0)
-               goto out;
-       mi->method = &mood_methods[i];
-success:
-       if (mlpd->m) {
-               if (mlt == ML_ACCEPT)
-                       para_list_add(&mi->mood_item_node, &mlpd->m->accept_list);
-               else if (mlt == ML_DENY)
-                       para_list_add(&mi->mood_item_node, &mlpd->m->deny_list);
-               else
-                       para_list_add(&mi->mood_item_node, &mlpd->m->score_list);
-       }
-       PARA_DEBUG_LOG("%s entry added, method: %p\n", mlt == ML_ACCEPT? "accept" :
-               (mlt == ML_DENY? "deny" : "score"), mi->method);
-       ret = 1;
-out:
-       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,
-               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;
-       struct mood_line_parser_data mlpd = {.line_num = 0};
        int ret;
 
-       *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) {
+               if (err)
+                       *err = make_message("empty mood name\n");
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
        }
-       assert(*mood_name);
-       mlpd.m = alloc_new_mood(mood_name);
-       ret = for_each_line(FELF_READ_ONLY, mood_def.data, mood_def.size,
-               parse_mood_line, &mlpd);
+       ret = mood_get_def_by_name(mood_name, &mood_def);
        if (ret < 0) {
-               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;
+               if (err)
+                       *err = make_message("could not read mood definition\n");
+               return ret;
        }
+       *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, err);
        osl_close_disk_object(&mood_def);
-       if (ret >= 0)
-               *m = mlpd.m;
+       if (ret < 0)
+               destroy_mood(*m);
        return ret;
 }
 
 static int check_mood(struct osl_row *mood_row, void *data)
 {
        struct para_buffer *pb = data;
-       char *mood_name;
+       char *mood_name, *errmsg;
        struct osl_object mood_def;
-       struct mood_line_parser_data mlpd = {.line_num = 0};
-
+       struct mood_instance *m;
        int ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
 
        if (ret < 0) {
@@ -466,24 +186,15 @@ static int check_mood(struct osl_row *mood_row, void *data)
        }
        if (!*mood_name) /* ignore dummy row */
                goto out;
-       ret = for_each_line(FELF_READ_ONLY, mood_def.data, mood_def.size,
-               parse_mood_line, &mlpd);
+       m = alloc_new_mood("check");
+       ret = mp_init(mood_def.data, mood_def.size, &m->parser_context,
+               &errmsg);
        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);
-
-       }
+               para_printf(pb, "%s: %s\n", mood_name, errmsg);
+               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 */
 out:
        osl_close_disk_object(&mood_def);
@@ -542,25 +253,25 @@ int mood_check_callback(struct afs_callback_arg *aca)
  * overflows and rounding errors we store the common divisor of the
  * correction factors separately.
  */
-static long compute_score(struct afs_info *afsi, long mood_score)
+static int64_t normalized_value(int64_t x, int64_t n, int64_t sum, int64_t qd)
 {
-       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;
+       if (!n || !qd)
+               return 0;
+       return 100 * (n * x - sum) / (int64_t)int_sqrt(n) / (int64_t)int_sqrt(qd);
+}
 
-       score_n = -((int64_t)afsi->num_played - mean_n)
-               * statistics.num_played_correction
-               / statistics.normalization_divisor;
-       score_l = -((int64_t)afsi->last_played - mean_l)
-               * statistics.last_played_correction
-               / statistics.normalization_divisor;
-       return (mood_score + score_n + score_l) / 3;
+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;
 }
 
-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;
@@ -569,121 +280,100 @@ static int add_afs_statistics(const struct osl_row *row)
        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 = &current_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
- * 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.
- *
- * 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 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.
+ * 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
+ * this way, the score of each admissible file is computed and the (row, score)
+ * pair is added to the score table. This has to be done in a second pass
+ * since the score depends on the statistics. Finally, the array is freed.
  */
-struct admissible_file_info
-{
-       /** The admissible audio file. */
-       struct osl_row *aft_row;
-       /** Its score. */
-       long score;
-};
-
-/** The temporary array of admissible files. */
 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. */
-       struct admissible_file_info *array;
+       struct osl_row **array;
 };
 
-/**
- * Add an entry to the array of admissible files.
- *
- * \param aft_row The audio file to be added.
- * \param private_data Pointer to a struct admissible_file_info.
- *
- * \return 1 if row admissible, 0 if not, negative on errors.
+/*
+ * Check whether the given audio file is admissible. If it is, add it to array
+ * of admissible files.
  */
 static int add_if_admissible(struct osl_row *aft_row, void *data)
 {
        struct admissible_array *aa = data;
-       int ret;
-       long score = 0;
+       struct afs_statistics *stats = &aa->m->stats;
 
-       ret = row_is_admissible(aft_row, aa->m, &score);
-       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 = para_realloc(aa->array,
-                       aa->size * sizeof(struct admissible_file_info));
+               aa->array = arr_realloc(aa->array, aa->size,
+                       sizeof(struct osl_row *));
        }
-       aa->array[statistics.num].aft_row = aft_row;
-       aa->array[statistics.num].score = score;
-       ret = add_afs_statistics(aft_row);
-       if (ret < 0)
-               return ret;
-       return 1;
+       aa->array[stats->num] = aft_row;
+       return add_afs_statistics(aft_row, stats);
 }
 
 /**
@@ -728,29 +418,25 @@ _static_inline_ int64_t update_quadratic_deviation(int64_t n, int64_t old_qd,
        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 = &current_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, long mood_score)
+static int add_to_score_table(const struct osl_row *aft_row,
+               const struct afs_statistics *stats)
 {
        long score;
        struct afs_info afsi;
@@ -758,7 +444,7 @@ static int add_to_score_table(const struct osl_row *aft_row, long mood_score)
 
        if (ret < 0)
                return ret;
-       score = compute_score(&afsi, mood_score);
+       score = compute_score(&afsi, stats);
        return score_add(aft_row, score);
 }
 
@@ -771,23 +457,18 @@ static int delete_from_statistics_and_score_table(const struct osl_row *aft_row)
 }
 
 /**
- * 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);
 }
 
@@ -806,76 +487,60 @@ static int mood_update_audio_file(const struct osl_row *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, &score);
-       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, &current_mood->stats);
                if (ret < 0)
                        return ret;
-               return add_to_score_table(aft_row, score);
+               return add_to_score_table(aft_row, &current_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, score);
+       if (old_afsi)
+               update_afs_statistics(old_afsi, &afsi);
+       score = compute_score(&afsi, &current_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)
+       );
 }
 
 /**
@@ -887,14 +552,10 @@ void close_current_mood(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;
@@ -912,29 +573,26 @@ static void compute_correction_factors(int64_t sse)
 /**
  * Change the current mood.
  *
+ * If there is already an open mood, it will be closed first.
+ *
  * \param mood_name The name of the mood to open.
- * \param errmsg Error description is returned here.
+ * \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 the message pointer is not NULL, a suitable message is returned there in
+ * all cases. The caller must free this string.
  *
- * 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.
+ * \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_switch(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".
@@ -942,52 +600,45 @@ int change_current_mood(const char *mood_name, char **errmsg)
        struct timeval rnow;
 
        if (mood_name) {
-               struct mood *m;
-               struct osl_row *row;
-               struct osl_object obj = {
-                       .data = (char *)mood_name,
-                       .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");
-               return ret;
+               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++) {
-               struct admissible_file_info *a = aa.array + i;
-               ret = add_to_score_table(a->aft_row, a->score);
+       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;
+       close_current_mood();
+       current_mood = aa.m;
 out:
        free(aa.array);
+       if (ret < 0)
+               destroy_mood(aa.m);
        return ret;
 }
 
@@ -997,25 +648,22 @@ out:
  * 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;
 
+       assert(current_mood);
        ret = clear_score_table();
        if (ret < 0)
                return ret;
-       if (!current_mood)
-               return 1;
        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);
+       ret = mood_switch(mood_name, NULL);
        free(mood_name);
        return ret;
 }
@@ -1027,9 +675,17 @@ static int reload_current_mood(void)
  * \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.
+ * This function updates the score table according to the event that has
+ * occurred. Two actions are possible: (a) reload the current mood, or (b)
+ * add/remove/update the row of the score table which corresponds to the audio
+ * file that has been modified or whose afs info has been changed. It depends
+ * on the type of the event which action (if any) is performed.
+ *
+ * The callbacks of command handlers such as com_add() or com_touch() which
+ * modify the audio file table call this function. The virtual streaming system
+ * also calls this after it has updated the afs info of the file it is about to
+ * stream (the one with the highest score). If the file stays admissible, its
+ * score is recomputed so that a different file is picked next time.
  *
  * \return Standard.
  */