]> git.tuebingen.mpg.de Git - paraslash.git/blob - mood.c
paraslash 0.7.3
[paraslash.git] / mood.c
1 /* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
2
3 /** \file mood.c Paraslash's mood handling functions. */
4
5 #include <regex.h>
6 #include <osl.h>
7 #include <lopsub.h>
8
9 #include "para.h"
10 #include "error.h"
11 #include "string.h"
12 #include "afh.h"
13 #include "afs.h"
14 #include "list.h"
15 #include "mm.h"
16 #include "mood.h"
17
18 /*
19  * Mood parser API. It's overkill to have an own header file for
20  * these declarations as they are only needed in this .c file.
21  */
22 struct mp_context;
23 int mp_init(const char *definition, int nbytes, struct mp_context **result,
24                  char **errmsg);
25 bool mp_eval_row(const struct osl_row *aft_row, struct mp_context *ctx);
26 void mp_shutdown(struct mp_context *ctx);
27
28 /**
29  * Contains statistical data of the currently admissible audio files.
30  *
31  * It is used to assign normalized score values to each admissible audio file.
32  */
33 struct afs_statistics {
34         /** Sum of num played over all admissible files. */
35         int64_t num_played_sum;
36         /** Sum of last played times over all admissible files. */
37         int64_t last_played_sum;
38         /** Quadratic deviation of num played count. */
39         int64_t num_played_qd;
40         /** Quadratic deviation of last played time. */
41         int64_t last_played_qd;
42         /** Correction factor for the num played score. */
43         int64_t num_played_correction;
44         /** Correction factor for the last played score. */
45         int64_t last_played_correction;
46         /** Common divisor of the correction factors. */
47         int64_t normalization_divisor;
48         /** Number of admissible files */
49         unsigned num;
50 };
51 static struct afs_statistics statistics = {.normalization_divisor = 1};
52
53 /**
54  * Each line of the current mood corresponds to a mood_item.
55  */
56 struct mood_item {
57         /** The method this line is referring to. */
58         const struct mood_method *method;
59         /** The data structure computed by the mood parser. */
60         void *parser_data;
61         /** The given score value, or zero if none was given. */
62         int32_t score_arg;
63         /** Non-zero if random scoring was requested. */
64         int random_score;
65         /** Whether the "not" keyword was given in the mood line. */
66         int logical_not;
67         /** The position in the list of items. */
68         struct list_head mood_item_node;
69 };
70
71 /*
72  * Created from the mood definition by \ref change_current_mood().
73  *
74  * When a mood is opened, each line of its definition is investigated, and a
75  * corresponding mood item is produced. Each mood line starts with accept,
76  * deny, or score which determines the type of the mood line. For each such
77  * type a linked list is maintained whose entries are the mood items.
78  */
79 struct mood {
80         /** The name of this mood. */
81         char *name;
82         /** The list of mood items of type \p accept. */
83         struct list_head accept_list;
84         /** The list of mood items of type \p deny. */
85         struct list_head deny_list;
86         /** The list of mood items of type \p score. */
87         struct list_head score_list;
88         /* Only used for version 2 moods. */
89         struct mp_context *parser_context;
90 };
91
92 /*
93  * If current_mood is NULL then no mood is currently open. If
94  * current_mood->name is NULL, the dummy mood is currently open.
95  */
96 static struct mood *current_mood;
97
98 /*
99  * Find the position of the most-significant set bit.
100  *
101  * Copied and slightly adapted from the linux source tree, version 4.9.39
102  * (2017-07).
103  */
104 __a_const static uint32_t fls64(uint64_t v)
105 {
106         int n = 63;
107         const uint64_t ones = ~(uint64_t)0U;
108
109         if ((v & (ones << 32)) == 0) {
110                 n -= 32;
111                 v <<= 32;
112         }
113         if ((v & (ones << (64 - 16))) == 0) {
114                 n -= 16;
115                 v <<= 16;
116         }
117         if ((v & (ones << (64 - 8))) == 0) {
118                 n -= 8;
119                 v <<= 8;
120         }
121         if ((v & (ones << (64 - 4))) == 0) {
122                 n -= 4;
123                 v <<= 4;
124         }
125         if ((v & (ones << (64 - 2))) == 0) {
126                 n -= 2;
127                 v <<= 2;
128         }
129         if ((v & (ones << (64 - 1))) == 0)
130                 n -= 1;
131         return n;
132 }
133
134 /*
135  * Compute the integer square root floor(sqrt(x)).
136  *
137  * Taken 2007 from the linux source tree.
138  */
139 __a_const static uint64_t int_sqrt(uint64_t x)
140 {
141         uint64_t op = x, res = 0, one = 1;
142
143         one = one << (fls64(x) & ~one);
144         while (one != 0) {
145                 if (op >= res + one) {
146                         op = op - (res + one);
147                         res = res + 2 * one;
148                 }
149                 res /= 2;
150                 one /= 4;
151         }
152         return res;
153 }
154
155 /*
156  * Returns true if row matches, false if it does not match. In any case score
157  * and score_arg_sum are set/increased accordingly.
158  */
159 static bool get_item_score(struct mood_item *item, const struct afs_info *afsi,
160                 const struct afh_info *afhi, const char *path, long *score,
161                 long *score_arg_sum)
162 {
163         int ret;
164         bool match = true;
165
166         *score_arg_sum += item->random_score? 100 : PARA_ABS(item->score_arg);
167         ret = 100;
168         if (item->method) {
169                 ret = item->method->score_function(path, afsi, afhi,
170                         item->parser_data);
171                 if ((ret < 0 && !item->logical_not) || (ret >= 0 && item->logical_not))
172                         match = false;
173         }
174         if (item->random_score)
175                 *score = PARA_ABS(ret) * para_random(100);
176         else
177                 *score = PARA_ABS(ret) * item->score_arg;
178         return match;
179 }
180
181 /* returns 1 if row admissible, 0 if not, negative on errors */
182 static int row_is_admissible(const struct osl_row *aft_row, struct mood *m,
183                 long *scorep)
184 {
185         struct mood_item *item;
186         int ret;
187         bool match;
188         long score_arg_sum = 0, score = 0, item_score;
189         struct afs_info afsi;
190         struct afh_info afhi;
191         char *path;
192
193         if (!m)
194                 return -E_NO_MOOD;
195         if (m->parser_context) {
196                 *scorep = 0;
197                 return mp_eval_row(aft_row, m->parser_context);
198         }
199         ret = get_afsi_of_row(aft_row, &afsi);
200         if (ret < 0)
201                 return ret;
202         ret = get_afhi_of_row(aft_row, &afhi);
203         if (ret < 0)
204                 return ret;
205         ret = get_audio_file_path_of_row(aft_row, &path);
206         if (ret < 0)
207                 return ret;
208         /* reject audio file if it matches any entry in the deny list */
209         list_for_each_entry(item, &m->deny_list, mood_item_node) {
210                 match = get_item_score(item, &afsi, &afhi, path, &item_score,
211                         &score_arg_sum);
212                 if (match) /* not admissible */
213                         return 0;
214                 score += item_score;
215         }
216         match = false;
217         list_for_each_entry(item, &m->accept_list, mood_item_node) {
218                 ret = get_item_score(item, &afsi, &afhi, path, &item_score,
219                         &score_arg_sum);
220                 if (ret == 0)
221                         continue;
222                 match = true;
223                 score += item_score;
224         }
225         /* reject if there is no matching entry in the accept list */
226         if (!match && !list_empty(&m->accept_list))
227                 return 0;
228         list_for_each_entry(item, &m->score_list, mood_item_node) {
229                 match = get_item_score(item, &afsi, &afhi, path, &item_score,
230                         &score_arg_sum);
231                 if (match)
232                         score += item_score;
233         }
234         if (score_arg_sum)
235                 score /= score_arg_sum;
236         *scorep = score;
237         return 1;
238 }
239
240 static void cleanup_list_entry(struct mood_item *item)
241 {
242         if (item->method && item->method->cleanup)
243                 item->method->cleanup(item->parser_data);
244         else
245                 free(item->parser_data);
246         list_del(&item->mood_item_node);
247         free(item);
248 }
249
250 static void destroy_mood(struct mood *m)
251 {
252         struct mood_item *tmp, *item;
253
254         if (!m)
255                 return;
256         list_for_each_entry_safe(item, tmp, &m->accept_list, mood_item_node)
257                 cleanup_list_entry(item);
258         list_for_each_entry_safe(item, tmp, &m->deny_list, mood_item_node)
259                 cleanup_list_entry(item);
260         list_for_each_entry_safe(item, tmp, &m->score_list, mood_item_node)
261                 cleanup_list_entry(item);
262         free(m->name);
263         mp_shutdown(m->parser_context);
264         free(m);
265 }
266
267 static struct mood *alloc_new_mood(const char *name)
268 {
269         struct mood *m = para_calloc(sizeof(struct mood));
270         if (name)
271                 m->name = para_strdup(name);
272         init_list_head(&m->accept_list);
273         init_list_head(&m->deny_list);
274         init_list_head(&m->score_list);
275         return m;
276 }
277
278 /** The different types of a mood line. */
279 enum mood_line_type {
280         /** Invalid. */
281         ML_INVALID,
282         /** Accept line. */
283         ML_ACCEPT,
284         /** Deny line. */
285         ML_DENY,
286         /** Score line. */
287         ML_SCORE
288 };
289
290 /** Data passed to the parser of a mood line. */
291 struct mood_line_parser_data {
292         /** The mood this mood line belongs to. */
293         struct mood *m;
294         /** The line number in the mood definition. */
295         unsigned line_num;
296 };
297
298 /*
299  * <accept [with score <score>] | deny [with score <score>]  | score <score>>
300  *      [if] [not] <mood_method> [options]
301  * <score> is either an integer or "random" which assigns a random score to
302  * all matching files
303  */
304 static int parse_mood_line(char *mood_line, void *data)
305 {
306         struct mood_line_parser_data *mlpd = data;
307         char **argv;
308         unsigned num_words;
309         char **w;
310         int i, ret;
311         enum mood_line_type mlt = ML_INVALID;
312         struct mood_item *mi = NULL;
313
314         mlpd->line_num++;
315         ret = create_argv(mood_line, " \t", &argv);
316         if (ret < 0)
317                 return ret;
318         num_words = ret;
319         if (!num_words) /* empty line */
320                 goto out;
321         w = argv;
322         if (**w == '#') /* comment */
323                 goto out;
324         if (!strcmp(*w, "accept"))
325                 mlt = ML_ACCEPT;
326         else if (!strcmp(*w, "deny"))
327                 mlt = ML_DENY;
328         else if (!strcmp(*w, "score"))
329                 mlt = ML_SCORE;
330         ret = -E_MOOD_SYNTAX;
331         if (mlt == ML_INVALID)
332                 goto out;
333         mi = para_calloc(sizeof(struct mood_item));
334         if (mlt != ML_SCORE) {
335                 ret = -E_MOOD_SYNTAX;
336                 w++;
337                 if (!*w)
338                         goto out;
339                 if (strcmp(*w, "with"))
340                         goto check_for_if;
341                 w++;
342                 if (!*w)
343                         goto out;
344                 if (strcmp(*w, "score"))
345                         goto out;
346         }
347         if (mlt == ML_SCORE || !strcmp(*w, "score")) {
348                 ret = -E_MOOD_SYNTAX;
349                 w++;
350                 if (!*w)
351                         goto out;
352                 if (strcmp(*w, "random")) {
353                         mi->random_score = 0;
354                         ret = para_atoi32(*w, &mi->score_arg);
355                         if (ret < 0)
356                                 goto out;
357                 } else {
358                         mi->random_score = 1;
359                         if (!*(w + 1))
360                         goto success; /* the line "score random" is valid */
361                 }
362         } else
363                 mi->score_arg = 0;
364         ret = -E_MOOD_SYNTAX;
365         w++;
366         if (!*w)
367                 goto out;
368 check_for_if:
369         if (!strcmp(*w, "if")) {
370                 ret = -E_MOOD_SYNTAX;
371                 w++;
372                 if (!*w)
373                         goto out;
374         }
375         if (!strcmp(*w, "not")) {
376                 ret = -E_MOOD_SYNTAX;
377                 w++;
378                 if (!*w)
379                         goto out;
380                 mi->logical_not = 1;
381         } else
382                 mi->logical_not = 0;
383         for (i = 0; mood_methods[i].parser; i++) {
384                 if (strcmp(*w, mood_methods[i].name))
385                         continue;
386                 break;
387         }
388         ret = -E_MOOD_SYNTAX;
389         if (!mood_methods[i].parser)
390                 goto out;
391         ret = mood_methods[i].parser(num_words - 1 - (w - argv), w,
392                 &mi->parser_data);
393         if (ret < 0)
394                 goto out;
395         mi->method = &mood_methods[i];
396 success:
397         if (mlpd->m) {
398                 if (mlt == ML_ACCEPT)
399                         para_list_add(&mi->mood_item_node, &mlpd->m->accept_list);
400                 else if (mlt == ML_DENY)
401                         para_list_add(&mi->mood_item_node, &mlpd->m->deny_list);
402                 else
403                         para_list_add(&mi->mood_item_node, &mlpd->m->score_list);
404         }
405         PARA_DEBUG_LOG("%s entry added, method: %p\n", mlt == ML_ACCEPT? "accept" :
406                 (mlt == ML_DENY? "deny" : "score"), mi->method);
407         ret = 1;
408 out:
409         free_argv(argv);
410         if (mi && (ret < 0 || !mlpd->m)) { /* mi was not added to any list */
411                 free(mi->parser_data);
412                 free(mi);
413         }
414         return ret;
415 }
416
417 static int load_mood(const struct osl_row *mood_row, struct mood **m,
418                 char **errmsg)
419 {
420         char *mood_name;
421         struct osl_object mood_def;
422         struct mood_line_parser_data mlpd = {.line_num = 0};
423         int ret;
424
425         *m = NULL;
426         ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
427         if (ret < 0) {
428                 if (errmsg)
429                         *errmsg = make_message(
430                                 "could not read mood definition");
431                 return ret;
432         }
433         assert(*mood_name);
434         mlpd.m = alloc_new_mood(mood_name);
435         ret = for_each_line(FELF_READ_ONLY, mood_def.data, mood_def.size,
436                 parse_mood_line, &mlpd);
437         if (ret < 0) {
438                 PARA_INFO_LOG("opening version 2 mood %s\n", mlpd.m->name);
439                 ret = mp_init(mood_def.data, mood_def.size, &mlpd.m->parser_context,
440                         errmsg);
441                 if (ret < 0)
442                         destroy_mood(mlpd.m);
443         } else {
444                 PARA_WARNING_LOG("loaded version 1 mood %s\n", mlpd.m->name);
445                 PARA_WARNING_LOG("please convert to version 2\n");
446                 ret = 1;
447         }
448         osl_close_disk_object(&mood_def);
449         if (ret >= 0)
450                 *m = mlpd.m;
451         return ret;
452 }
453
454 static int check_mood(struct osl_row *mood_row, void *data)
455 {
456         struct para_buffer *pb = data;
457         char *mood_name;
458         struct osl_object mood_def;
459         struct mood_line_parser_data mlpd = {.line_num = 0};
460
461         int ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
462
463         if (ret < 0) {
464                 para_printf(pb, "cannot read mood\n");
465                 return ret;
466         }
467         if (!*mood_name) /* ignore dummy row */
468                 goto out;
469         ret = for_each_line(FELF_READ_ONLY, mood_def.data, mood_def.size,
470                 parse_mood_line, &mlpd);
471         if (ret < 0) {
472                 char *errmsg;
473                 struct mood *m = alloc_new_mood("check");
474                 ret = mp_init(mood_def.data, mood_def.size, &m->parser_context,
475                         &errmsg);
476                 if (ret < 0) {
477                         para_printf(pb, "%s: %s\n", mood_name, errmsg);
478                         free(errmsg);
479                         para_printf(pb, "%s\n", para_strerror(-ret));
480                 } else
481                         destroy_mood(m);
482         } else {
483                 para_printf(pb, "%s: v1 mood, please convert to v2\n",
484                         mood_name);
485
486         }
487         ret = 1; /* don't fail the loop on invalid mood definitions */
488 out:
489         osl_close_disk_object(&mood_def);
490         return ret;
491 }
492
493 /**
494  * Check all moods for syntax errors.
495  *
496  * \param aca Only ->pbout is used for diagnostics.
497  *
498  * \return Negative on fatal errors. Inconsistent mood definitions are not
499  * considered an error.
500  */
501 int mood_check_callback(struct afs_callback_arg *aca)
502 {
503         para_printf(&aca->pbout, "checking moods...\n");
504         return osl(osl_rbtree_loop(moods_table, BLOBCOL_ID, &aca->pbout,
505                 check_mood));
506 }
507
508 /*
509  * The normalized num_played and last_played values are defined as
510  *
511  *      nn := -(np - mean_n) / sigma_n and nl := -(lp - mean_l) / sigma_l
512  *
513  *  For a (hypothetical) file with np = 0 and lp = now we thus have
514  *
515  *      nn =  mean_n / sigma_n =: hn > 0
516  *      nl = -(now - mean_l) / sigma_l =: hl < 0
517  *
518  * We design the score function so that both contributions get the same
519  * weight. Define the np and lp score of an arbitrary file as
520  *
521  *      sn := nn * -hl and sl := nl * hn
522  *
523  * Example:
524  *      num_played mean/sigma: 87/14
525  *      last_played mean/sigma: 45/32 days
526  *
527  *      We have hn = 87 / 14 = 6.21 and hl = -45 / 32 = -1.41. Multiplying
528  *      nn of every file with the correction factor 1.41 and nl with
529  *      6.21 makes the weight of the two contributions equal.
530  *
531  * The total score s := sn + sl has the representation
532  *
533  *      s = -cn * (np - mean_n) - cl * (lp - mean_l)
534  *
535  * with positive correction factors
536  *
537  *      cn = (now - mean_l) / (sqrt(ql) * sqrt(qn) / n)
538  *      cl = mean_n / (sqrt(ql) * sqrt(qn) / n)
539  *
540  * where ql and qn are the quadratic deviations stored in the statistics
541  * structure and n is the number of admissible files. To avoid integer
542  * overflows and rounding errors we store the common divisor of the
543  * correction factors separately.
544  */
545 static long compute_score(struct afs_info *afsi, long mood_score)
546 {
547         int64_t mean_n, mean_l,score_n, score_l;
548
549         assert(statistics.normalization_divisor > 0);
550         assert(statistics.num > 0);
551         mean_n = statistics.num_played_sum / statistics.num;
552         mean_l = statistics.last_played_sum / statistics.num;
553
554         score_n = -((int64_t)afsi->num_played - mean_n)
555                 * statistics.num_played_correction
556                 / statistics.normalization_divisor;
557         score_l = -((int64_t)afsi->last_played - mean_l)
558                 * statistics.last_played_correction
559                 / statistics.normalization_divisor;
560         return (mood_score + score_n + score_l) / 3;
561 }
562
563 static int add_afs_statistics(const struct osl_row *row)
564 {
565         uint64_t n, x, s, q;
566         struct afs_info afsi;
567         int ret;
568
569         ret = get_afsi_of_row(row, &afsi);
570         if (ret < 0)
571                 return ret;
572         n = statistics.num;
573         x = afsi.last_played;
574         s = statistics.last_played_sum;
575         if (n > 0) {
576                 q = (x > s / n)? x - s / n : s / n - x;
577                 statistics.last_played_qd += q * q * n / (n + 1);
578         }
579         statistics.last_played_sum += x;
580
581         x = afsi.num_played;
582         s = statistics.num_played_sum;
583         if (n > 0) {
584                 q = (x > s / n)? x - s / n : s / n - x;
585                 statistics.num_played_qd += q * q * n / (n + 1);
586         }
587         statistics.num_played_sum += x;
588         statistics.num++;
589         return 1;
590 }
591
592 static int del_afs_statistics(const struct osl_row *row)
593 {
594         uint64_t n, s, q, a, new_s;
595         struct afs_info afsi;
596         int ret;
597         ret = get_afsi_of_row(row, &afsi);
598         if (ret < 0)
599                 return ret;
600         n = statistics.num;
601         assert(n);
602         if (n == 1) {
603                 memset(&statistics, 0, sizeof(statistics));
604                 statistics.normalization_divisor = 1;
605                 return 1;
606         }
607
608         s = statistics.last_played_sum;
609         q = statistics.last_played_qd;
610         a = afsi.last_played;
611         new_s = s - a;
612         statistics.last_played_sum = new_s;
613         statistics.last_played_qd = q + s * s / n - a * a
614                 - new_s * new_s / (n - 1);
615
616         s = statistics.num_played_sum;
617         q = statistics.num_played_qd;
618         a = afsi.num_played;
619         new_s = s - a;
620         statistics.num_played_sum = new_s;
621         statistics.num_played_qd = q + s * s / n - a * a
622                 - new_s * new_s / (n - 1);
623
624         statistics.num--;
625         return 1;
626 }
627
628 /*
629  * At mood open time we determine the set of admissible files for the given
630  * mood. The mood score of each admissible file is computed by adding up all
631  * mood item scores. Next, we update the afs statistics and append a struct
632  * admissible_file_info to a temporary array.
633  *
634  * When all files have been processed in this way, the final score of each
635  * admissible file is computed by adding the dynamic score (which depends on
636  * the afs_statistics and the current time) to the mood score. Finally, all
637  * audio files in the temporary array are added to the score table and the
638  * array is freed.
639  */
640 struct admissible_file_info
641 {
642         /** The admissible audio file. */
643         struct osl_row *aft_row;
644         /** Its score. */
645         long score;
646 };
647
648 /** The temporary array of admissible files. */
649 struct admissible_array {
650         /** Files are admissible wrt. this mood. */
651         struct mood *m;
652         /** The size of the array */
653         unsigned size;
654         /** Pointer to the array of admissible files. */
655         struct admissible_file_info *array;
656 };
657
658 /**
659  * Add an entry to the array of admissible files.
660  *
661  * \param aft_row The audio file to be added.
662  * \param private_data Pointer to a struct admissible_file_info.
663  *
664  * \return 1 if row admissible, 0 if not, negative on errors.
665  */
666 static int add_if_admissible(struct osl_row *aft_row, void *data)
667 {
668         struct admissible_array *aa = data;
669         int ret;
670         long score = 0;
671
672         ret = row_is_admissible(aft_row, aa->m, &score);
673         if (ret <= 0)
674                 return ret;
675         if (statistics.num >= aa->size) {
676                 aa->size *= 2;
677                 aa->size += 100;
678                 aa->array = para_realloc(aa->array,
679                         aa->size * sizeof(struct admissible_file_info));
680         }
681         aa->array[statistics.num].aft_row = aft_row;
682         aa->array[statistics.num].score = score;
683         ret = add_afs_statistics(aft_row);
684         if (ret < 0)
685                 return ret;
686         return 1;
687 }
688
689 /**
690  * Compute the new quadratic deviation in case one element changes.
691  *
692  * \param n Number of elements.
693  * \param old_qd The quadratic deviation before the change.
694  * \param old_val The value that was replaced.
695  * \param new_val The replacement value.
696  * \param old_sum The sum of all elements before the update.
697  *
698  * \return The new quadratic deviation resulting from replacing old_val
699  * by new_val.
700  *
701  * Given n real numbers a_1, ..., a_n, their sum S = a_1 + ... + a_n,
702  * their quadratic deviation
703  *
704  * q = (a_1 - S/n)^2 + ... + (a_n - S/n)^2,
705  *
706  * and a real number b, the quadratic deviation q' of a_1,...a_{n-1}, b (ie.
707  * the last number a_n was replaced by b) may be computed in O(1) time in terms
708  * of n, q, a_n, b, and S as
709  *
710  *      q' = q + d * s - (2 * S + d) * d / n
711  *         = q + d * (s - 2 * S / n - d /n),
712  *
713  * where d = b - a_n, and s = b + a_n.
714  *
715  * Example: n = 3, a_1 = 3, a_2 = 5, a_3 = 7, b = 10. Then S = 15, q = 8, d = 3,
716  * s = 17, so
717  *
718  *      q + d * s - (2 * S + d) * d / n = 8 + 51 - 33 = 26,
719  *
720  * which equals q' = (3 - 6)^2 + (5 - 6)^2 + (10 - 6)^2.
721  *
722  */
723 _static_inline_ int64_t update_quadratic_deviation(int64_t n, int64_t old_qd,
724                 int64_t old_val, int64_t new_val, int64_t old_sum)
725 {
726         int64_t delta = new_val - old_val;
727         int64_t sigma = new_val + old_val;
728         return old_qd + delta * (sigma - 2 * old_sum / n - delta / n);
729 }
730
731 static int update_afs_statistics(struct afs_info *old_afsi,
732                 struct afs_info *new_afsi)
733 {
734         unsigned n;
735         int ret = get_num_admissible_files(&n);
736
737         if (ret < 0)
738                 return ret;
739         assert(n);
740
741         statistics.last_played_qd = update_quadratic_deviation(n,
742                 statistics.last_played_qd, old_afsi->last_played,
743                 new_afsi->last_played, statistics.last_played_sum);
744         statistics.last_played_sum += new_afsi->last_played - old_afsi->last_played;
745
746         statistics.num_played_qd = update_quadratic_deviation(n,
747                 statistics.num_played_qd, old_afsi->num_played,
748                 new_afsi->num_played, statistics.num_played_sum);
749         statistics.num_played_sum += new_afsi->num_played - old_afsi->num_played;
750         return 1;
751 }
752
753 static int add_to_score_table(const struct osl_row *aft_row, long mood_score)
754 {
755         long score;
756         struct afs_info afsi;
757         int ret = get_afsi_of_row(aft_row, &afsi);
758
759         if (ret < 0)
760                 return ret;
761         score = compute_score(&afsi, mood_score);
762         return score_add(aft_row, score);
763 }
764
765 static int delete_from_statistics_and_score_table(const struct osl_row *aft_row)
766 {
767         int ret = del_afs_statistics(aft_row);
768         if (ret < 0)
769                 return ret;
770         return score_delete(aft_row);
771 }
772
773 /**
774  * Delete one entry from the statistics and from the score table.
775  *
776  * \param aft_row The audio file which is no longer admissible.
777  *
778  * \return Positive on success, negative on errors.
779  *
780  * \sa \ref score_delete().
781  */
782 static int mood_delete_audio_file(const struct osl_row *aft_row)
783 {
784         int ret;
785
786         ret = row_belongs_to_score_table(aft_row, NULL);
787         if (ret < 0)
788                 return ret;
789         if (!ret) /* not admissible, nothing to do */
790                 return 1;
791         return delete_from_statistics_and_score_table(aft_row);
792 }
793
794 /**
795  * Compute the new score of an audio file wrt. the current mood.
796  *
797  * \param aft_row Determines the audio file.
798  * \param old_afsi The audio file selector info before updating.
799  *
800  * The \a old_afsi argument may be \p NULL which indicates that no changes to
801  * the audio file info were made.
802  *
803  * \return Positive on success, negative on errors.
804  */
805 static int mood_update_audio_file(const struct osl_row *aft_row,
806                 struct afs_info *old_afsi)
807 {
808         long score, percent;
809         int ret, is_admissible, was_admissible = 0;
810         struct afs_info afsi;
811         unsigned rank;
812
813         if (!current_mood)
814                 return 1; /* nothing to do */
815         ret = row_belongs_to_score_table(aft_row, &rank);
816         if (ret < 0)
817                 return ret;
818         was_admissible = ret;
819         ret = row_is_admissible(aft_row, current_mood, &score);
820         if (ret < 0)
821                 return ret;
822         is_admissible = (ret > 0);
823         if (!was_admissible && !is_admissible)
824                 return 1;
825         if (was_admissible && !is_admissible)
826                 return delete_from_statistics_and_score_table(aft_row);
827         if (!was_admissible && is_admissible) {
828                 ret = add_afs_statistics(aft_row);
829                 if (ret < 0)
830                         return ret;
831                 return add_to_score_table(aft_row, score);
832         }
833         /* update score */
834         ret = get_afsi_of_row(aft_row, &afsi);
835         if (ret < 0)
836                 return ret;
837         if (old_afsi) {
838                 ret = update_afs_statistics(old_afsi, &afsi);
839                 if (ret < 0)
840                         return ret;
841         }
842         score = compute_score(&afsi, score);
843         PARA_DEBUG_LOG("score: %li\n", score);
844         percent = (score + 100) / 3;
845         if (percent > 100)
846                 percent = 100;
847         else if (percent < 0)
848                 percent = 0;
849         PARA_DEBUG_LOG("moving from rank %u to %li%%\n", rank, percent);
850         return score_update(aft_row, percent);
851 }
852
853 /* sse: seconds since epoch. */
854 static void log_statistics(int64_t sse)
855 {
856         unsigned n = statistics.num;
857         int mean_days, sigma_days;
858
859         assert(current_mood);
860         PARA_NOTICE_LOG("loaded mood %s\n", current_mood->name?
861                 current_mood->name : "(dummy)");
862         if (!n) {
863                 PARA_WARNING_LOG("no admissible files\n");
864                 return;
865         }
866         PARA_NOTICE_LOG("%u admissible files\n", statistics.num);
867         mean_days = (sse - statistics.last_played_sum / n) / 3600 / 24;
868         sigma_days = int_sqrt(statistics.last_played_qd / n) / 3600 / 24;
869         PARA_NOTICE_LOG("last_played mean/sigma: %d/%d days\n", mean_days, sigma_days);
870         PARA_NOTICE_LOG("num_played mean/sigma: %" PRId64 "/%" PRIu64 "\n",
871                 statistics.num_played_sum / n,
872                 int_sqrt(statistics.num_played_qd / n));
873         PARA_NOTICE_LOG("num_played correction factor: %" PRId64 "\n",
874                 statistics.num_played_correction);
875         PARA_NOTICE_LOG("last_played correction factor: %" PRId64 "\n",
876                 statistics.last_played_correction);
877         PARA_NOTICE_LOG("normalization divisor: %" PRId64 "\n",
878                 statistics.normalization_divisor);
879 }
880
881 /**
882  * Close the current mood.
883  *
884  * Frees all resources of the current mood.
885  */
886 void close_current_mood(void)
887 {
888         destroy_mood(current_mood);
889         current_mood = NULL;
890         memset(&statistics, 0, sizeof(statistics));
891         statistics.normalization_divisor = 1;
892 }
893
894 static void compute_correction_factors(int64_t sse)
895 {
896         struct afs_statistics *s = &statistics;
897
898         if (s->num > 0) {
899                 s->normalization_divisor = int_sqrt(s->last_played_qd)
900                         * int_sqrt(s->num_played_qd) / s->num / 100;
901                 s->num_played_correction = sse - s->last_played_sum / s->num;
902                 s->last_played_correction = s->num_played_sum / s->num;
903         }
904         if (s->num_played_correction == 0)
905                 s->num_played_correction = 1;
906         if (s->normalization_divisor == 0)
907                 s->normalization_divisor = 1;
908         if (s->last_played_correction == 0)
909                 s->last_played_correction = 1;
910 }
911
912 /**
913  * Change the current mood.
914  *
915  * \param mood_name The name of the mood to open.
916  * \param errmsg Error description is returned here.
917  *
918  * If \a mood_name is \a NULL, load the dummy mood that accepts every audio file
919  * and uses a scoring method based only on the \a last_played information.
920  *
921  * The errmsg pointer may be NULL, in which case no error message will be
922  * returned. If a non-NULL pointer is given, the caller must free *errmsg.
923  *
924  * If there is already an open mood, it will be closed first.
925  *
926  * \return Positive on success, negative on errors. Loading the dummy mood
927  * always succeeds.
928  *
929  * \sa struct \ref afs_info::last_played, \ref mp_eval_row().
930  */
931 int change_current_mood(const char *mood_name, char **errmsg)
932 {
933         int i, ret;
934         struct admissible_array aa = {
935                 .size = 0,
936                 .array = NULL
937         };
938         /*
939          * We can not use the "now" pointer from sched.c here because we are
940          * called before schedule(), which initializes "now".
941          */
942         struct timeval rnow;
943
944         if (mood_name) {
945                 struct mood *m;
946                 struct osl_row *row;
947                 struct osl_object obj = {
948                         .data = (char *)mood_name,
949                         .size = strlen(mood_name) + 1
950                 };
951                 ret = osl(osl_get_row(moods_table, BLOBCOL_NAME, &obj, &row));
952                 if (ret < 0) {
953                         if (errmsg)
954                                 *errmsg = make_message("no such mood: %s",
955                                         mood_name);
956                         return ret;
957                 }
958                 ret = load_mood(row, &m, errmsg);
959                 if (ret < 0)
960                         return ret;
961                 close_current_mood();
962                 current_mood = m;
963         } else { /* load dummy mood */
964                 close_current_mood();
965                 current_mood = alloc_new_mood(NULL);
966         }
967         aa.m = current_mood;
968         PARA_NOTICE_LOG("computing statistics of admissible files\n");
969         ret = audio_file_loop(&aa, add_if_admissible);
970         if (ret < 0) {
971                 if (errmsg)
972                         *errmsg = make_message("audio file loop failed");
973                 return ret;
974         }
975         clock_get_realtime(&rnow);
976         compute_correction_factors(rnow.tv_sec);
977         log_statistics(rnow.tv_sec);
978         for (i = 0; i < statistics.num; i++) {
979                 struct admissible_file_info *a = aa.array + i;
980                 ret = add_to_score_table(a->aft_row, a->score);
981                 if (ret < 0) {
982                         if (errmsg)
983                                 *errmsg = make_message(
984                                         "could not add row to score table");
985                         goto out;
986                 }
987         }
988         ret = statistics.num;
989 out:
990         free(aa.array);
991         return ret;
992 }
993
994 /*
995  * Close and re-open the current mood.
996  *
997  * This function is called on events which render the current list of
998  * admissible files useless, for example if an attribute is removed from the
999  * attribute table.
1000  *
1001  * If no mood is currently open, the function returns success.
1002  */
1003 static int reload_current_mood(void)
1004 {
1005         int ret;
1006         char *mood_name = NULL;
1007
1008         ret = clear_score_table();
1009         if (ret < 0)
1010                 return ret;
1011         if (!current_mood)
1012                 return 1;
1013         PARA_NOTICE_LOG("reloading %s\n", current_mood->name?
1014                 current_mood->name : "(dummy)");
1015         if (current_mood->name)
1016                 mood_name = para_strdup(current_mood->name);
1017         close_current_mood();
1018         ret = change_current_mood(mood_name, NULL);
1019         free(mood_name);
1020         return ret;
1021 }
1022
1023 /**
1024  * Notification callback for the moods table.
1025  *
1026  * \param event Type of the event just occurred.
1027  * \param pb Unused.
1028  * \param data Its type depends on the event.
1029  *
1030  * This function performs actions required due to the occurrence of the given
1031  * event. Possible actions include reload of the current mood and update of the
1032  * score of an audio file.
1033  *
1034  * \return Standard.
1035  */
1036 int moods_event_handler(enum afs_events event, __a_unused struct para_buffer *pb,
1037                 void *data)
1038 {
1039         if (!current_mood)
1040                 return 0;
1041         switch (event) {
1042         /*
1043          * The three blob events might change the set of admissible files,
1044          * so we must reload the score list.
1045          */
1046         case BLOB_RENAME:
1047         case BLOB_REMOVE:
1048         case BLOB_ADD:
1049                 if (data == moods_table || data == playlists_table)
1050                         return 1; /* no reload necessary for these */
1051                 return reload_current_mood();
1052         /* these also require reload of the score table */
1053         case ATTRIBUTE_ADD:
1054         case ATTRIBUTE_REMOVE:
1055         case ATTRIBUTE_RENAME:
1056                 return reload_current_mood();
1057         /* changes to the aft only require to re-examine the audio file */
1058         case AFSI_CHANGE: {
1059                 struct afsi_change_event_data *aced = data;
1060                 return mood_update_audio_file(aced->aft_row, aced->old_afsi);
1061                 }
1062         case AFHI_CHANGE:
1063         case AUDIO_FILE_RENAME:
1064         case AUDIO_FILE_ADD:
1065                 return mood_update_audio_file(data, NULL);
1066         case AUDIO_FILE_REMOVE:
1067                 return mood_delete_audio_file(data);
1068         default:
1069                 return 1;
1070         }
1071 }