-/*
- * Copyright (C) 2007-2013 Andre Noll <maan@systemlinux.org>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
/** \file mood.c Paraslash's mood handling functions. */
#include <regex.h>
#include <osl.h>
+#include <lopsub.h>
#include "para.h"
#include "error.h"
#include "afh.h"
#include "afs.h"
#include "list.h"
-#include "ipc.h"
-#include "mm.h"
-#include "sideband.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.
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;
+ /** Correction factor for the num played score. */
+ int64_t num_played_correction;
+ /** Correction factor for the last played score. */
+ int64_t last_played_correction;
+ /** Common divisor of the correction factors. */
+ int64_t normalization_divisor;
/** Number of admissible files */
unsigned num;
};
-static struct afs_statistics statistics;
+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 mood_open().
- *
- * 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().
- */
struct mood {
/** The name of this 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;
+ /** Info for the bison parser. */
+ 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
+ * current_mood->name is NULL, the dummy mood is currently open.
*/
static struct mood *current_mood;
-/**
- * Rough approximation to sqrt.
+/*
+ * Find the position of the most-significant set bit.
*
- * \param x Integer of which to calculate the sqrt.
+ * Copied and slightly adapted from the linux source tree, version 4.9.39
+ * (2017-07).
+ */
+__a_const static uint32_t fls64(uint64_t v)
+{
+ int n = 63;
+ const uint64_t ones = ~(uint64_t)0U;
+
+ 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)).
*
- * \return An integer res with res * res <= x.
+ * Taken 2007 from the linux source tree.
*/
__a_const static uint64_t int_sqrt(uint64_t x)
{
- uint64_t op, res, one = 1;
- op = x;
- res = 0;
-
- one = one << 62;
- while (one > op)
- one >>= 2;
+ 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;
}
-/*
- * 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 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)
{
- 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;
- 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;
- *result = score;
- return 1;
-}
-
-static void cleanup_list_entry(struct mood_item *item)
-{
- 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);
+ return mp_eval_row(aft_row, m->parser_context);
}
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);
+ mp_shutdown(m->parser_context);
free(m->name);
free(m);
}
struct mood *m = para_calloc(sizeof(struct mood));
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);
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 (ret >= 0)
- return ret;
- if (mi) {
- 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;
- *m = NULL;
ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
- if (ret < 0)
- return ret;
- if (!*mood_name)
- return -E_DUMMY_ROW;
- mlpd.m = alloc_new_mood(mood_name);
- 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);
+ if (errmsg)
+ *errmsg = make_message(
+ "could not read mood definition");
return ret;
}
- *m = mlpd.m;
- return 1;
+ assert(*mood_name);
+ *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, errmsg);
+ osl_close_disk_object(&mood_def);
+ 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 *m;
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(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));
+ 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);
+ ret = 1; /* don't fail the loop on invalid mood definitions */
out:
osl_close_disk_object(&mood_def);
return ret;
/**
* 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)
-{
- struct para_buffer pb = {
- .max_size = shm_get_shmmax(),
- .private_data = &(struct afs_max_size_handler_data) {
- .fd = fd,
- .band = SBD_OUTPUT
- },
- .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(fd, SBD_OUTPUT, pb.buf, pb.offset);
- free(pb.buf);
-}
-
-#if 0
-static unsigned int_log2(uint64_t x)
+int mood_check_callback(struct afs_callback_arg *aca)
{
- unsigned res = 0;
-
- while (x) {
- x /= 2;
- res++;
- }
- return res;
+ para_printf(&aca->pbout, "checking moods...\n");
+ return osl(osl_rbtree_loop(moods_table, BLOBCOL_ID, &aca->pbout,
+ check_mood));
}
-#endif
+/*
+ * The normalized num_played and last_played values are defined as
+ *
+ * nn := -(np - mean_n) / sigma_n and nl := -(lp - mean_l) / sigma_l
+ *
+ * For a (hypothetical) file with np = 0 and lp = now we thus have
+ *
+ * nn = mean_n / sigma_n =: hn > 0
+ * nl = -(now - mean_l) / sigma_l =: hl < 0
+ *
+ * We design the score function so that both contributions get the same
+ * weight. Define the np and lp score of an arbitrary file as
+ *
+ * sn := nn * -hl and sl := nl * hn
+ *
+ * Example:
+ * num_played mean/sigma: 87/14
+ * last_played mean/sigma: 45/32 days
+ *
+ * We have hn = 87 / 14 = 6.21 and hl = -45 / 32 = -1.41. Multiplying
+ * nn of every file with the correction factor 1.41 and nl with
+ * 6.21 makes the weight of the two contributions equal.
+ *
+ * The total score s := sn + sl has the representation
+ *
+ * s = -cn * (np - mean_n) - cl * (lp - mean_l)
+ *
+ * with positive correction factors
+ *
+ * cn = (now - mean_l) / (sqrt(ql) * sqrt(qn) / n)
+ * cl = mean_n / (sqrt(ql) * sqrt(qn) / n)
+ *
+ * where ql and qn are the quadratic deviations stored in the statistics
+ * structure and n is the number of admissible files. To avoid integer
+ * 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 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)
{
- return -normalized_value(afsi->num_played, statistics.num,
+ long 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,
+ 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 score / 2;
}
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;
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;
assert(n);
if (n == 1) {
memset(&statistics, 0, sizeof(statistics));
+ statistics.normalization_divisor = 1;
return 1;
}
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.
- *
- * If all files have been processed that 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.
+/*
+ * At mood open 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
+ * 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;
/** 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;
- ret = compute_mood_score(aft_row, aa->m, &score);
+ ret = row_is_admissible(aft_row, aa->m);
if (ret <= 0)
return ret;
if (statistics.num >= aa->size) {
aa->size *= 2;
aa->size += 100;
aa->array = para_realloc(aa->array,
- aa->size * sizeof(struct admissible_file_info));
+ 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[statistics.num] = aft_row;
+ return add_afs_statistics(aft_row);
}
/**
* 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.
*
{
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);
return 1;
}
-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)
{
- 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);
return score_add(aft_row, score);
}
*
* \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)
{
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);
if (ret < 0)
return ret;
is_admissible = (ret > 0);
ret = add_afs_statistics(aft_row);
if (ret < 0)
return ret;
- return add_to_score_table(aft_row, score);
+ return add_to_score_table(aft_row);
}
/* update score */
ret = get_afsi_of_row(aft_row, &afsi);
if (ret < 0)
return ret;
}
- score += compute_num_played_score(&afsi);
- score += compute_last_played_score(&afsi);
- score /= 3;
+ score = compute_score(&afsi);
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)
+/* sse: seconds since epoch. */
+static void log_statistics(int64_t sse)
{
unsigned n = statistics.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_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,
- (long long unsigned)int_sqrt(statistics.num_played_qd / n));
+ 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);
}
/**
* 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)
{
destroy_mood(current_mood);
current_mood = NULL;
memset(&statistics, 0, sizeof(statistics));
+ statistics.normalization_divisor = 1;
+}
+
+static void compute_correction_factors(int64_t sse)
+{
+ 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;
+ s->num_played_correction = sse - s->last_played_sum / s->num;
+ s->last_played_correction = s->num_played_sum / s->num;
+ }
+ if (s->num_played_correction == 0)
+ s->num_played_correction = 1;
+ if (s->normalization_divisor == 0)
+ s->normalization_divisor = 1;
+ if (s->last_played_correction == 0)
+ s->last_played_correction = 1;
}
/**
* 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.
+ * \return Positive on success, negative on errors.
*
- * \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 = {
.size = 0,
.array = NULL
};
+ /*
+ * We can not use the "now" pointer from sched.c here because we are
+ * called before schedule(), which initializes "now".
+ */
+ struct timeval rnow;
if (mood_name) {
struct mood *m;
struct osl_row *row;
- struct osl_object obj = {
- .data = mood_name,
- .size = strlen(mood_name) + 1
- };
+ struct osl_object obj;
+
+ if (!*mood_name) {
+ *errmsg = make_message("empty mood name");
+ return -ERRNO_TO_PARA_ERROR(EINVAL);
+ }
+ obj.data = (char *)mood_name;
+ obj.size = strlen(mood_name) + 1;
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();
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);
+ if (ret < 0) {
+ if (errmsg)
+ *errmsg = make_message("audio file loop failed");
+ 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);
- if (ret < 0)
+ ret = add_to_score_table(aa.array[i]);
+ if (ret < 0) {
+ if (errmsg)
+ *errmsg = make_message(
+ "could not add row to score table");
goto out;
+ }
}
- PARA_NOTICE_LOG("loaded mood %s\n", mood_name? mood_name : "(dummy)");
ret = statistics.num;
out:
free(aa.array);
+ if (ret < 0)
+ close_current_mood();
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.
- *
- * \return Positive on success, negative on errors. If no mood is currently
- * open, the function returns success.
+ * 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.
*
- * \sa mood_open(), mood_close().
+ * If no mood is currently open, the function returns success.
*/
static 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;
}
* \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.
*/