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