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