afs.c: Kill unused struct callback_data.
[paraslash.git] / mood.c
1 #include "para.h"
2 #include "error.h"
3 #include "afh.h"
4 #include "afs.h"
5 #include "list.h"
6 #include "string.h"
7
8 /** \file mood.c Paraslash's mood handling functions. */
9
10 /**
11  * Contains statistical data of the currently admissible audio files.
12  *
13  * It is used to assign normalized score values to each admissbile audio file.
14  */
15 struct afs_statistics {
16         /** sum of num played over all admissible files */
17         int64_t num_played_sum;
18         /** sum of last played times over all admissible files */
19         int64_t last_played_sum;
20         /** quadratic deviation of num played time */
21         int64_t num_played_qd;
22         /** quadratic deviation of last played time */
23         int64_t last_played_qd;
24         /** number of admissible files */
25         unsigned num;
26 };
27 struct afs_statistics statistics;
28
29 /**
30  * Assign scores according to a mood_method.
31  *
32  * Each mood_method has its own mood_score_function. The first parameter passed
33  * to that function is a pointer to a row of the audio file table.  It
34  * determines the audio file for which a score is to be assigned.  The second
35  * argument depends on the mood method this function is used for. It usually is
36  * the argument given at the end of a mood line.
37  *
38  * Mood score functions must return values between -100 and +100 inclisively.
39  * Boolean score functions should always return either -100 or +100.
40  *
41  * \sa struct mood_method, mood_parser.
42  */
43 typedef int mood_score_function(const struct osl_row*, void *);
44
45 /**
46  * Preprocess a mood line.
47  *
48  * The mood_parser of a mood_method is called once at mood open time for each
49  * line of the current mood definition that contains the mood_method's name as
50  * a keyword. The line is passed to the mood_parser as the first argument. The
51  * mood_parser must determine whether the line is syntactically correct and
52  * return a positive value if so and a negative value otherwise.
53  *
54  * Some mood parsers preprocess the data given in the mood line to compute a
55  * structure which depends of the particular mood_method and which is used
56  * later in the mood_score_function of the mood_method. The mood_parser may
57  * store a pointer to its structure via the second argument.
58  *
59  * \sa mood_open(), mood_cleanup_function, mood_score_function.
60  */
61 typedef int mood_parser(const char *, void **);
62
63 /**
64  * Deallocate resources which were allocated by the mood_parser.
65  *
66  * This optional function of a mood_method is used to free any resources
67  * allocated in mood_open() by the mood_parser. The argument passed is a
68  * pointer to the mood_method specific data structure that was returned by the
69  * mood_parser.
70  *
71  * \sa mood_parser.
72  */
73 typedef void mood_cleanup_function(void *);
74
75 /**
76  * Used for scoring and to determine whether a file is admissible.
77  */
78 struct mood_method {
79         /* The name of the method. */
80         const char *name;
81         /** Pointer to the mood parser. */
82         mood_parser *parser;
83         /** Pointer to the score function */
84         mood_score_function *score_function;
85         /** Optional cleanup function. */
86         mood_cleanup_function *cleanup;
87 };
88
89 /**
90  * Each line of the current mood corresponds to a mood_item.
91  */
92 struct mood_item {
93         /** The method this line is referring to. */
94         const struct mood_method *method;
95         /** The data structure computed by the mood parser. */
96         void *parser_data;
97         /** The given score value, or zero if none was given. */
98         long score_arg;
99         /** Non-zero if random scoring was requested. */
100         int random_score;
101         /** Whether the "not" keyword was given in the mood line. */
102         int logical_not;
103         /** The position in the list of items. */
104         struct list_head mood_item_node;
105 };
106
107 /**
108  * Created from the mood definition by mood_open().
109  *
110  * When a mood is opened, each line of its definition is investigated, and a
111  * corresponding mood item is produced. Each mood line starts with \p accept,
112  * \p deny, or \p score which determins the type of the mood line.  For each
113  * such type a linked list is maintained whose entries are the mood items.
114  *
115  * \sa mood_item, mood_open().
116  */
117 struct mood {
118         /** the name of this mood */
119         char *name;
120         /** The list of mood items of type \p accept. */
121         struct list_head accept_list;
122         /** The list of mood items of type \p deny. */
123         struct list_head deny_list;
124         /** The list of mood items of type \p score. */
125         struct list_head score_list;
126 };
127
128 static struct mood *current_mood;
129
130 /**
131  *  Rough approximation to sqrt.
132  *
133  *  \param x Integer of which to calculate the sqrt.
134  *
135  *  \return An integer res with res * res <= x.
136  */
137 static uint64_t int_sqrt(uint64_t x)
138 {
139         uint64_t op, res, one = 1;
140         op = x;
141         res = 0;
142
143         one = one << 62;
144         while (one > op)
145                 one >>= 2;
146
147         while (one != 0) {
148                 if (op >= res + one) {
149                         op = op - (res + one);
150                         res = res +  2 * one;
151                 }
152                 res /= 2;
153                 one /= 4;
154         }
155 //      PARA_NOTICE_LOG("sqrt(%llu) = %llu\n", x, res);
156         return res;
157 }
158
159 static int mm_played_rarely_score_function(const struct osl_row *row,
160         __a_unused void *ignored)
161 {
162         struct afs_info afsi;
163         unsigned num;
164         int ret = get_afsi_of_row(row, &afsi);
165
166         if (ret < 0)
167                 return 0;
168         ret = get_num_admissible_files(&num);
169         if (ret < 0)
170                 return 0;
171         if (statistics.num_played_sum - num * afsi.num_played
172                         > int_sqrt(statistics.num_played_qd * num))
173                 return 100;
174         return -100;
175 }
176
177 static int mm_played_rarely_parser(const char *arg, __a_unused void **ignored)
178 {
179         if (*arg)
180                 PARA_WARNING_LOG("ignored junk at eol: %s\n", arg);
181         return 1;
182 }
183
184 static int mm_name_like_score_function(const struct osl_row *row, void *preg)
185 {
186         char *path;
187         int ret = get_audio_file_path_of_row(row, &path);
188
189         if (ret < 0)
190                 return 0;
191         ret = regexec((regex_t *)preg, path, 42, NULL, 0);
192         return (ret == REG_NOMATCH)? -100 : 100;
193 }
194
195 static int mm_name_like_parser(const char *arg, void **regex)
196 {
197         regex_t *preg = para_malloc(sizeof(*preg));
198         int ret = regcomp(preg, arg, REG_NOSUB);
199
200         if (ret) {
201                 free(preg);
202                 return -E_MOOD_REGEX;
203         }
204         *regex = preg;
205         return 1;
206 }
207
208 static void mm_name_like_cleanup(void *preg)
209 {
210         regfree(preg);
211         free(preg);
212 }
213
214 static int mm_is_set_parser(const char *arg, void **bitnum)
215 {
216         unsigned char *c = para_malloc(1);
217         int ret = get_attribute_bitnum_by_name(arg, c);
218
219         if (ret >= 0)
220                 *bitnum = c;
221         else
222                 free(c);
223         return ret;
224 }
225
226 static int mm_is_set_score_function(const struct osl_row *row, void *bitnum)
227 {
228         unsigned char *bn = bitnum;
229         struct afs_info afsi;
230         int ret = get_afsi_of_row(row, &afsi);
231
232         if (ret < 0)
233                 return 0;
234         if (afsi.attributes & (1ULL << *bn))
235                 return 100;
236         return -100;
237 }
238
239 /* returns 1 if row matches score item, -1 otherwise */
240 static int add_item_score(const void *row, struct mood_item *item, long *score,
241                 long *score_arg_sum)
242 {
243         int ret = 100;
244
245         *score_arg_sum += item->random_score? 100 : PARA_ABS(item->score_arg);
246         if (item->method) {
247                 ret = item->method->score_function(row, item->parser_data);
248                 if ((ret < 0 && !item->logical_not) || (ret >= 0 && item->logical_not))
249                         return -1; /* no match */
250         }
251         if (item->random_score)
252                 *score += PARA_ABS(ret) * para_random(100);
253         else
254                 *score += PARA_ABS(ret) * item->score_arg;
255         return 1;
256 }
257
258 static int compute_mood_score(const void *row, long *result)
259 {
260         struct mood_item *item;
261         int match = 0;
262         long score_arg_sum = 0, score = 0;
263
264         if (!current_mood)
265                 return -E_NO_MOOD;
266         /* reject audio file if it matches any entry in the deny list */
267         list_for_each_entry(item, &current_mood->deny_list, mood_item_node)
268                 if (add_item_score(row, item, &score, &score_arg_sum) > 0)
269                         return -E_NOT_ADMISSIBLE;
270         list_for_each_entry(item, &current_mood->accept_list, mood_item_node)
271                 if (add_item_score(row, item, &score, &score_arg_sum) > 0)
272                         match = 1;
273         /* reject if there is no matching entry in the accept list */
274         if (!match && !list_empty(&current_mood->accept_list))
275                 return -E_NOT_ADMISSIBLE;
276         list_for_each_entry(item, &current_mood->score_list, mood_item_node)
277                 add_item_score(row, item, &score, &score_arg_sum);
278         if (score_arg_sum)
279                 score /= score_arg_sum;
280         *result = score;
281         return 1;
282 }
283
284 static const struct mood_method mood_methods[] = {
285 {
286         .parser = mm_played_rarely_parser,
287         .score_function = mm_played_rarely_score_function,
288         .name = "played_rarely"
289 },
290 {
291         .parser = mm_is_set_parser,
292         .score_function = mm_is_set_score_function,
293         .name = "is_set"
294 },
295 {
296         .parser = mm_name_like_parser,
297         .score_function = mm_name_like_score_function,
298         .cleanup = mm_name_like_cleanup,
299         .name = "name_like"
300 },
301 {
302         .parser = NULL
303 }
304 };
305
306 static void cleanup_list_entry(struct mood_item *item)
307 {
308         if (item->method && item->method->cleanup)
309                 item->method->cleanup(item->parser_data);
310         else
311                 free(item->parser_data);
312         list_del(&item->mood_item_node);
313         free(item);
314 }
315
316 static void destroy_mood(struct mood *m)
317 {
318         struct mood_item *tmp, *item;
319
320         if (!m)
321                 return;
322         list_for_each_entry_safe(item, tmp, &m->accept_list, mood_item_node)
323                 cleanup_list_entry(item);
324         list_for_each_entry_safe(item, tmp, &m->deny_list, mood_item_node)
325                 cleanup_list_entry(item);
326         list_for_each_entry_safe(item, tmp, &m->score_list, mood_item_node)
327                 cleanup_list_entry(item);
328         free(m->name);
329         free(m);
330 }
331
332 static struct mood *alloc_new_mood(const char *name)
333 {
334         struct mood *m = para_calloc(sizeof(struct mood));
335         m->name = para_strdup(name);
336         INIT_LIST_HEAD(&m->accept_list);
337         INIT_LIST_HEAD(&m->deny_list);
338         INIT_LIST_HEAD(&m->score_list);
339         return m;
340 }
341
342 /** The different types of a mood line. */
343 enum mood_line_type {
344         /** Invalid. */
345         ML_INVALID,
346         /** Accept line. */
347         ML_ACCEPT,
348         /** Deny line. */
349         ML_DENY,
350         /** Score line. */
351         ML_SCORE
352 };
353
354 /*
355  * <accept [with score <score>] | deny [with score <score>]  | score <score>>
356  *      [if] [not] <mood_method> [options]
357  * <score> is either an integer or "random" which assigns a random score to
358  * all matching files
359  */
360
361 /* TODO: Use current_mood as private_data*/
362 static int parse_mood_line(char *mood_line, __a_unused void *private_data)
363 {
364         char **argv;
365         char *delim = " \t";
366         unsigned num_words;
367         char **w;
368         int i, ret;
369         enum mood_line_type mlt = ML_INVALID;
370         struct mood_item *mi = NULL;
371         struct mood *m = current_mood;
372         char *buf = para_strdup(mood_line);
373
374         num_words = split_args(buf, &argv, delim);
375         ret = 1;
376         if (!num_words) /* empty line */
377                 goto out;
378         w = argv;
379         if (**w == '#') /* comment */
380                 goto out;
381         if (!strcmp(*w, "accept"))
382                 mlt = ML_ACCEPT;
383         else if (!strcmp(*w, "deny"))
384                 mlt = ML_DENY;
385         else if (!strcmp(*w, "score"))
386                 mlt = ML_SCORE;
387         ret = -E_MOOD_SYNTAX;
388         if (mlt == ML_INVALID)
389                 goto out;
390         mi = para_calloc(sizeof(struct mood_item));
391         if (mlt != ML_SCORE) {
392                 ret = -E_MOOD_SYNTAX;
393                 w++;
394                 if (!*w)
395                         goto out;
396                 if (!strcmp(*w, "with")) {
397                         w++;
398                         if (!*w)
399                                 goto out;
400                 }
401         }
402         if (mlt == ML_SCORE || !strcmp(*w, "score")) {
403                 ret = -E_MOOD_SYNTAX;
404                 w++;
405                 if (!*w)
406                         goto out;
407                 if (strcmp(*w, "random")) {
408                         mi->random_score = 0;
409                         ret = para_atol(*w, &mi->score_arg);
410                         if (ret < 0)
411                                 goto out;
412                 } else {
413                         mi->random_score = 1;
414                         if (!*(w + 1))
415                         goto success; /* the line "score random" is valid */
416                 }
417         } else
418                 mi->score_arg = 0;
419         ret = -E_MOOD_SYNTAX;
420         w++;
421         if (!*w)
422                 goto out;
423         if (!strcmp(*w, "if")) {
424                 ret = -E_MOOD_SYNTAX;
425                 w++;
426                 if (!*w)
427                         goto out;
428         }
429         if (!strcmp(*w, "not")) {
430                 ret = -E_MOOD_SYNTAX;
431                 w++;
432                 if (!*w)
433                         goto out;
434                 mi->logical_not = 1;
435         } else
436                 mi->logical_not = 0;
437         for (i = 0; mood_methods[i].parser; i++) {
438                 if (strcmp(*w, mood_methods[i].name))
439                         continue;
440                 break;
441         }
442         ret = -E_MOOD_SYNTAX;
443         if (!mood_methods[i].parser)
444                 goto out;
445         w++;
446         ret = mood_methods[i].parser(*w, &mi->parser_data);
447         if (ret < 0)
448                 goto out;
449         mi->method = &mood_methods[i];
450 success:
451         if (mlt == ML_ACCEPT)
452                 para_list_add(&mi->mood_item_node, &m->accept_list);
453         else if (mlt == ML_DENY)
454                 para_list_add(&mi->mood_item_node, &m->deny_list);
455         else
456                 para_list_add(&mi->mood_item_node, &m->score_list);
457         PARA_DEBUG_LOG("%s entry added, method: %p\n", mlt == ML_ACCEPT? "accept" :
458                 (mlt == ML_DENY? "deny" : "score"), mi->method);
459         ret = 1;
460 out:
461         free(argv);
462         free(buf);
463         if (ret >= 0)
464                 return ret;
465         if (mi) {
466                 free(mi->parser_data);
467                 free(mi);
468         }
469         return ret;
470 }
471
472 static int load_mood(const void *row)
473 {
474         int ret;
475         struct mood *new_mood, *old_mood = current_mood;
476         struct osl_object objs[NUM_BLOB_COLUMNS];
477
478         ret = osl_get_object(moods_table, row, BLOBCOL_NAME, &objs[BLOBCOL_NAME]);
479         if (ret < 0)
480                 return ret;
481         if (objs[BLOBCOL_NAME].size <= 1)
482                 return -E_DUMMY_ROW;
483         ret = osl_open_disk_object(moods_table, row, BLOBCOL_DEF, &objs[BLOBCOL_DEF]);
484         if (ret < 0)
485                 return ret;
486         new_mood = alloc_new_mood((char*)objs[BLOBCOL_NAME].data);
487         current_mood = new_mood;
488         ret = for_each_line_ro(objs[BLOBCOL_DEF].data, objs[BLOBCOL_DEF].size,
489                 parse_mood_line, NULL);
490         osl_close_disk_object(&objs[BLOBCOL_DEF]);
491         if (ret < 0) {
492                 PARA_ERROR_LOG("unable to load mood %s: %d\n",
493                         (char *)objs[BLOBCOL_NAME].data, ret);
494                 destroy_mood(new_mood);
495                 current_mood = old_mood;
496                 return ret;
497         }
498         destroy_mood(old_mood);
499         current_mood = new_mood;
500         PARA_INFO_LOG("loaded mood %s\n", current_mood->name);
501         return 1;
502 }
503
504 /* returns -E_MOOD_LOADED on _success_ to terminate the loop */
505 static int mood_loop(struct osl_row *row, __a_unused void *private_data)
506 {
507         int ret = load_mood(row);
508         if (ret < 0) {
509                 if (ret != -E_DUMMY_ROW)
510                         PARA_NOTICE_LOG("invalid mood (%d), trying next mood\n", ret);
511                 return 1;
512         }
513         return -E_MOOD_LOADED;
514 }
515
516 static int load_first_available_mood(void)
517 {
518         int ret = osl_rbtree_loop(moods_table, BLOBCOL_NAME, NULL,
519                 mood_loop);
520         if (ret == -E_MOOD_LOADED) /* success */
521                 return 1;
522         if (ret < 0)
523                 return ret; /* error */
524         PARA_NOTICE_LOG("no valid mood found\n");
525         return -E_NO_MOOD;
526 }
527
528 #if 0
529 static unsigned int_log2(uint64_t x)
530 {
531         unsigned res = 0;
532
533         while (x) {
534                 x /= 2;
535                 res++;
536         }
537         return res;
538 }
539 #endif
540
541 static int64_t normalized_value(int64_t x, int64_t n, int64_t sum, int64_t qd)
542 {
543         if (!n || !qd)
544                 return 0;
545         return 100 * (n * x - sum) / (int64_t)int_sqrt(n * qd);
546 }
547
548 static long compute_num_played_score(struct afs_info *afsi)
549 {
550         return -normalized_value(afsi->num_played, statistics.num,
551                 statistics.num_played_sum, statistics.num_played_qd);
552 }
553
554 static long compute_last_played_score(struct afs_info *afsi)
555 {
556         return -normalized_value(afsi->last_played, statistics.num,
557                 statistics.last_played_sum, statistics.last_played_qd);
558 }
559
560 static long compute_dynamic_score(const struct osl_row *aft_row)
561 {
562         struct afs_info afsi;
563         int64_t score, nscore = 0, lscore = 0;
564         int ret;
565
566         ret = get_afsi_of_row(aft_row, &afsi);
567         if (ret < 0)
568                 return -100;
569         nscore = compute_num_played_score(&afsi);
570         lscore = compute_last_played_score(&afsi);
571         score = nscore + lscore;
572         return score;
573 }
574
575 static int add_afs_statistics(const struct osl_row *row)
576 {
577         uint64_t n, x, s;
578         struct afs_info afsi;
579         int ret;
580
581         ret = get_afsi_of_row(row, &afsi);
582         if (ret < 0)
583                 return ret;
584         n = statistics.num;
585         x = afsi.last_played;
586         s = statistics.last_played_sum;
587         if (n > 0)
588                 statistics.last_played_qd += (x - s / n) * (x - s / n) * n / (n + 1);
589         statistics.last_played_sum += x;
590
591         x = afsi.num_played;
592         s = statistics.num_played_sum;
593         if (n > 0)
594                 statistics.num_played_qd += (x - s / n) * (x - s / n) * n / (n + 1);
595         statistics.num_played_sum += x;
596         statistics.num++;
597         return 1;
598 }
599
600 static int del_afs_statistics(const struct osl_row *row)
601 {
602         uint64_t n, s, q, a, new_s;
603         struct afs_info afsi;
604         int ret;
605         ret = get_afsi_of_row(row, &afsi);
606         if (ret < 0)
607                 return ret;
608         n = statistics.num;
609         assert(n);
610         if (n == 1) {
611                 memset(&statistics, 0, sizeof(statistics));
612                 return 1;
613         }
614
615         s = statistics.last_played_sum;
616         q = statistics.last_played_qd;
617         a = afsi.last_played;
618         new_s = s - a;
619         statistics.last_played_sum = new_s;
620         statistics.last_played_qd = q + s * s / n - a * a
621                 - new_s * new_s / (n - 1);
622
623         s = statistics.num_played_sum;
624         q = statistics.num_played_qd;
625         a = afsi.num_played;
626         new_s = s - a;
627         statistics.num_played_sum = new_s;
628         statistics.num_played_qd = q + s * s / n - a * a
629                 - new_s * new_s / (n - 1);
630
631         statistics.num--;
632         return 1;
633 }
634
635 /**
636  * Structure used during mood_open().
637  *
638  * At mood open time, we look at each file in the audio file table in order to
639  * determine whether it is admissible. If a file happens to be admissible, its
640  * mood score is computed by calling each relevant mood_score_function. Next,
641  * we update the afs_statistics and add a struct admissible_file_info to a
642  * temporary array.
643  *
644  * If all files have been processed that way, the final score of each
645  * admissible file is computed by adding the dynamic score (which depends on
646  * the afs_statistics) to the mood score.  Finally, all audio files in the
647  * array are added to the score table and the admissible array is freed.
648  *
649  * \sa mood_method, admissible_array.
650  */
651 struct admissible_file_info
652 {
653         /** The admissible audio file. */
654         void *aft_row;
655         /** Its score. */
656         long score;
657 };
658
659 /** The temporary array of admissible files. */
660 struct admissible_array {
661         /** The size of the array */
662         unsigned size;
663         /** Pointer to the array of admissible files. */
664         struct admissible_file_info *array;
665 };
666
667 /**
668  * Add an entry to the array of admissible files.
669  *
670  * \param aft_row The audio file to be added.
671  * \param private_data Pointer to a struct admissible_file_info.
672  *
673  * \return Negative on errors, positive on success.
674  */
675 static int add_if_admissible(struct osl_row *aft_row, void *private_data)
676 {
677         int ret;
678         struct admissible_array *aa = private_data;
679         long score = 0;
680
681         score = 0;
682         ret = compute_mood_score(aft_row, &score);
683         if (ret < 0)
684                 return (ret == -E_NOT_ADMISSIBLE)? 1 : ret;
685         if (statistics.num >= aa->size) {
686                 aa->size *= 2;
687                 aa->size += 100;
688                 aa->array = para_realloc(aa->array,
689                         aa->size * sizeof(struct admissible_file_info));
690         }
691         aa->array[statistics.num].aft_row = aft_row;
692         aa->array[statistics.num].score = score;
693         ret = add_afs_statistics(aft_row);
694         if (ret < 0)
695                 return ret;
696         return 1;
697 }
698
699 /**
700  * Compute the new quadratic deviation in case one element changes.
701  *
702  * \param n Number of elements.
703  * \param old_qd The quadratic deviation before the change.
704  * \param old_val The value that was repaced.
705  * \param new_val The replacement value.
706  * \param old_sum The sum of all elements before the update.
707  *
708  * \return The new quadratic deviation resulting from replacing old_val
709  * by new_val.
710  *
711  * Given n real numbers a_1, ..., a_n, their sum S = a_1 + ... + a_n,
712  * their quadratic deviation
713  *
714  * q = (a_1 - S/n)^2 + ... + (a_n - S/n)^2,
715  *
716  * and a real number b, the quadratic deviation q' of a_1,...a_{n-1}, b (ie.
717  * the last number a_n was replaced by b) may be computed in O(1) time in terms
718  * of n, q, a_n, b, and S as
719  *
720  *      q' = q + d * s - (2 * S + d) * d / n,
721  *
722  * where d = b - a_n, and s = b + a_n.
723  *
724  * Example: n = 3, a_1 = 3, a_2 = 5, a_3 = 7, b = 10. Then S = 15, q = 8, d = 3,
725  * s = 17, so
726  *
727  *      q + d * s - (2 * S + d) * d / n = 8 + 51 - 33 = 26,
728  *
729  * which equals q' = (3 - 6)^2 + (5 - 6)^2 + (10 - 6)^2.
730  *
731  */
732 _static_inline_ int64_t update_quadratic_deviation(int64_t n, int64_t old_qd,
733                 int64_t old_val, int64_t new_val, int64_t old_sum)
734 {
735         int64_t delta = new_val - old_val;
736         int64_t sigma = new_val + old_val;
737         return old_qd + delta * sigma - (2 * old_sum + delta) * delta / n;
738 }
739
740 static int update_afs_statistics(struct afs_info *old_afsi, struct afs_info *new_afsi)
741 {
742         unsigned n;
743         int ret = get_num_admissible_files(&n);
744
745         if (ret < 0)
746                 return ret;
747         assert(n);
748
749         statistics.last_played_qd = update_quadratic_deviation(n,
750                 statistics.last_played_qd, old_afsi->last_played,
751                 new_afsi->last_played, statistics.last_played_sum);
752         statistics.last_played_sum += new_afsi->last_played - old_afsi->last_played;
753
754         statistics.num_played_qd = update_quadratic_deviation(n,
755                 statistics.num_played_qd, old_afsi->num_played,
756                 new_afsi->num_played, statistics.num_played_sum);
757         statistics.num_played_sum += new_afsi->num_played - old_afsi->num_played;
758         return 1;
759 }
760
761 static int add_to_score_table(const struct osl_row *aft_row, long mood_score)
762 {
763         long score = (compute_dynamic_score(aft_row) + mood_score) / 3;
764         return score_add(aft_row, score);
765 }
766
767 static int delete_from_statistics_and_score_table(const struct osl_row *aft_row)
768 {
769         int ret = del_afs_statistics(aft_row);
770         if (ret < 0)
771                 return ret;
772         return score_delete(aft_row);
773 }
774
775 /**
776  * Delete one entry from the statitics and from the score table.
777  *
778  * \param aft_row The audio file which is no longer admissible.
779  *
780  * \return Positive on success, negative on errors.
781  *
782  * \sa score_delete(), mood_update_audio_file().
783  */
784 int mood_delete_audio_file(const struct osl_row *aft_row)
785 {
786         int ret;
787
788         ret = row_belongs_to_score_table(aft_row);
789         if (ret < 0)
790                 return ret;
791         if (!ret) /* not admissible, nothing to do */
792                 return 1;
793         return delete_from_statistics_and_score_table(aft_row);
794 }
795
796 /**
797  * Compute the new score of an audio file.
798  *
799  * \param aft_row Determines the audio file.
800  * \param old_afsi The audio file selector info before updating.
801  *
802  * The \a old_afsi argument may be \p NULL which indicates that no changes to
803  * the audio file info were made.
804  *
805  * \return Positive on success, negative on errors.
806  */
807 int mood_update_audio_file(const struct osl_row *aft_row, struct afs_info *old_afsi)
808 {
809         long score, percent;
810         int ret, is_admissible, was_admissible = 0;
811         struct afs_info afsi;
812
813         if (!current_mood)
814                 return 1; /* nothing to do */
815         ret = row_belongs_to_score_table(aft_row);
816         if (ret < 0)
817                 return ret;
818         was_admissible = ret;
819         ret = compute_mood_score(aft_row, &score);
820         is_admissible = (ret > 0);
821         if (!was_admissible && !is_admissible)
822                 return 1;
823         if (was_admissible && !is_admissible)
824                 return delete_from_statistics_and_score_table(aft_row);
825         if (!was_admissible && is_admissible) {
826                 ret = add_afs_statistics(aft_row);
827                 if (ret < 0)
828                         return ret;
829                 return add_to_score_table(aft_row, score);
830         }
831         /* update score */
832         ret = get_afsi_of_row(aft_row, &afsi);
833         if (ret < 0)
834                 return ret;
835         if (old_afsi) {
836                 ret = update_afs_statistics(old_afsi, &afsi);
837                 if (ret < 0)
838                         return ret;
839         }
840         score += compute_num_played_score(&afsi);
841         score += compute_last_played_score(&afsi);
842         score /= 3;
843         PARA_NOTICE_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_NOTICE_LOG("re-inserting at %lu%%\n", percent);
850         return score_update(aft_row, percent);
851 }
852
853 static void log_statistics(void)
854 {
855         unsigned n = statistics.num;
856
857         if (!n) {
858                 PARA_NOTICE_LOG("no admissible files\n");
859                 return;
860         }
861         PARA_NOTICE_LOG("last_played mean: %lli, last_played sigma: %lli\n",
862                 statistics.last_played_sum / n, int_sqrt(statistics.last_played_qd / n));
863         PARA_NOTICE_LOG("num_played mean: %lli, num_played sigma: %lli\n",
864                 statistics.num_played_sum / n, int_sqrt(statistics.num_played_qd / n));
865 }
866
867 /**
868  * Open the given mood.
869  *
870  * \param mood_name The name of the mood to open.
871  *
872  * There are two special cases: If \a mood_name is \a NULL, load the
873  * first available mood. If \a mood_name is the empty string "", load
874  * the dummy mood that accepts every audio file and uses a scoring method
875  * based only on the \a last_played information.
876  *
877  * \return Positive on success, negative on errors. Loading the dummy mood
878  * always succeeds.
879  *
880  * \sa struct admissible_file_info, struct admissible_array, struct
881  * afs_info::last_played, mood_close().
882  */
883 int mood_open(char *mood_name)
884 {
885         int i, ret;
886         struct admissible_array aa = {
887                 .size = 0,
888                 .array = NULL
889         };
890
891         if (!mood_name) {
892                 ret = load_first_available_mood();
893                 if (ret < 0)
894                         return ret;
895         } else if (*mood_name) {
896                 struct osl_row *row;
897                 struct osl_object obj = {
898                         .data = mood_name,
899                         .size = strlen(mood_name) + 1
900                 };
901                 ret = osl_get_row(moods_table, BLOBCOL_NAME, &obj, &row);
902                 if (ret < 0) {
903                         PARA_NOTICE_LOG("no such mood: %s\n", mood_name);
904                         return ret;
905                 }
906                 ret = load_mood(row);
907                 if (ret < 0)
908                         return ret;
909         } else {
910                 destroy_mood(current_mood);
911                 current_mood = alloc_new_mood("dummy");
912         }
913         PARA_NOTICE_LOG("loaded mood %s\n", current_mood->name);
914         PARA_INFO_LOG("%s\n", "computing statistics of admissible files");
915         ret = audio_file_loop(&aa, add_if_admissible);
916         if (ret < 0)
917                 return ret;
918         log_statistics();
919         PARA_NOTICE_LOG("%d admissible files \n", statistics.num);
920         for (i = 0; i < statistics.num; i++) {
921                 struct admissible_file_info *a = aa.array + i;
922                 ret = add_to_score_table(a->aft_row, a->score);
923                 if (ret < 0)
924                         goto out;
925         }
926         PARA_NOTICE_LOG("score add complete\n");
927         ret = 1;
928 out:
929         free(aa.array);
930         return ret;
931 }
932
933 /**
934  * Close the current mood.
935  *
936  * Free all resources of the current mood which were allocated during
937  * mood_open().
938  */
939 void mood_close(void)
940 {
941         destroy_mood(current_mood);
942         current_mood = NULL;
943         memset(&statistics, 0, sizeof(statistics));
944 }
945
946 /**
947  * Close and re-open the current mood.
948  *
949  * This function is used if changes to the audio file table or the
950  * attribute table were made that render the current list of admissible
951  * files useless. For example, if an attribute is removed from the
952  * attribute table, this function is called.
953  *
954  * \return Positive on success, negative on errors. If no mood is currently
955  * open, the function returns success.
956  *
957  * \sa mood_open(), mood_close().
958  */
959 int mood_reload(void)
960 {
961         int ret;
962         char *mood_name;
963
964         if (!current_mood)
965                 return 1;
966         score_shutdown(0);
967         mood_name = para_strdup(current_mood->name);
968         mood_close();
969         ret = mood_open(mood_name);
970         free(mood_name);
971         return ret;
972 }