]> git.tuebingen.mpg.de Git - paraslash.git/blob - mood.c
openssl: Switch to evp API for sha1 and sha256.
[paraslash.git] / mood.c
1 /* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
2
3 /** \file mood.c Paraslash's mood handling functions. */
4
5 #include <regex.h>
6 #include <osl.h>
7 #include <lopsub.h>
8
9 #include "para.h"
10 #include "error.h"
11 #include "string.h"
12 #include "afh.h"
13 #include "afs.h"
14 #include "list.h"
15 #include "mood.h"
16
17 /*
18  * Mood parser API. It's overkill to have an own header file for
19  * these declarations as they are only needed in this .c file.
20  */
21 struct mp_context;
22 int mp_init(const char *definition, int nbytes, struct mp_context **result,
23                  char **errmsg);
24 bool mp_eval_row(const struct osl_row *aft_row, struct mp_context *ctx);
25 void mp_shutdown(struct mp_context *ctx);
26
27 /**
28  * Contains statistical data of the currently admissible audio files.
29  *
30  * It is used to assign normalized score values to each admissible audio file.
31  */
32 struct afs_statistics {
33         /** Sum of num played over all admissible files. */
34         int64_t num_played_sum;
35         /** Sum of last played times over all admissible files. */
36         int64_t last_played_sum;
37         /** Quadratic deviation of num played count. */
38         int64_t num_played_qd;
39         /** Quadratic deviation of last played time. */
40         int64_t last_played_qd;
41         /** Correction factor for the num played score. */
42         int64_t num_played_correction;
43         /** Correction factor for the last played score. */
44         int64_t last_played_correction;
45         /** Common divisor of the correction factors. */
46         int64_t normalization_divisor;
47         /** Number of admissible files */
48         unsigned num;
49 };
50 static struct afs_statistics statistics = {.normalization_divisor = 1};
51
52 struct mood {
53         /** The name of this mood. */
54         char *name;
55         /** Info for the bison parser. */
56         struct mp_context *parser_context;
57 };
58
59 /*
60  * If current_mood is NULL then no mood is currently open. If
61  * current_mood->name is NULL, the dummy mood is currently open.
62  */
63 static struct mood *current_mood;
64
65 /*
66  * Find the position of the most-significant set bit.
67  *
68  * Copied and slightly adapted from the linux source tree, version 4.9.39
69  * (2017-07).
70  */
71 __a_const static uint32_t fls64(uint64_t v)
72 {
73         int n = 63;
74         const uint64_t ones = ~(uint64_t)0U;
75
76         if ((v & (ones << 32)) == 0) {
77                 n -= 32;
78                 v <<= 32;
79         }
80         if ((v & (ones << (64 - 16))) == 0) {
81                 n -= 16;
82                 v <<= 16;
83         }
84         if ((v & (ones << (64 - 8))) == 0) {
85                 n -= 8;
86                 v <<= 8;
87         }
88         if ((v & (ones << (64 - 4))) == 0) {
89                 n -= 4;
90                 v <<= 4;
91         }
92         if ((v & (ones << (64 - 2))) == 0) {
93                 n -= 2;
94                 v <<= 2;
95         }
96         if ((v & (ones << (64 - 1))) == 0)
97                 n -= 1;
98         return n;
99 }
100
101 /*
102  * Compute the integer square root floor(sqrt(x)).
103  *
104  * Taken 2007 from the linux source tree.
105  */
106 __a_const static uint64_t int_sqrt(uint64_t x)
107 {
108         uint64_t op = x, res = 0, one = 1;
109
110         one = one << (fls64(x) & ~one);
111         while (one != 0) {
112                 if (op >= res + one) {
113                         op = op - (res + one);
114                         res = res + 2 * one;
115                 }
116                 res /= 2;
117                 one /= 4;
118         }
119         return res;
120 }
121
122 /* returns 1 if row admissible, 0 if not, negative on errors */
123 static int row_is_admissible(const struct osl_row *aft_row, struct mood *m)
124 {
125         if (!m)
126                 return -E_NO_MOOD;
127         return mp_eval_row(aft_row, m->parser_context);
128 }
129
130 static void destroy_mood(struct mood *m)
131 {
132         if (!m)
133                 return;
134         mp_shutdown(m->parser_context);
135         free(m->name);
136         free(m);
137 }
138
139 static struct mood *alloc_new_mood(const char *name)
140 {
141         struct mood *m = zalloc(sizeof(struct mood));
142         if (name)
143                 m->name = para_strdup(name);
144         return m;
145 }
146
147 static int load_mood(const struct osl_row *mood_row, struct mood **m,
148                 char **errmsg)
149 {
150         char *mood_name;
151         struct osl_object mood_def;
152         int ret;
153
154         ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
155         if (ret < 0) {
156                 if (errmsg)
157                         *errmsg = make_message(
158                                 "could not read mood definition");
159                 return ret;
160         }
161         assert(*mood_name);
162         *m = alloc_new_mood(mood_name);
163         PARA_INFO_LOG("opening mood %s\n", mood_name);
164         ret = mp_init(mood_def.data, mood_def.size, &(*m)->parser_context, errmsg);
165         osl_close_disk_object(&mood_def);
166         if (ret < 0)
167                 destroy_mood(*m);
168         return ret;
169 }
170
171 static int check_mood(struct osl_row *mood_row, void *data)
172 {
173         struct para_buffer *pb = data;
174         char *mood_name, *errmsg;
175         struct osl_object mood_def;
176         struct mood *m;
177         int ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
178
179         if (ret < 0) {
180                 para_printf(pb, "cannot read mood\n");
181                 return ret;
182         }
183         if (!*mood_name) /* ignore dummy row */
184                 goto out;
185         m = alloc_new_mood("check");
186         ret = mp_init(mood_def.data, mood_def.size, &m->parser_context,
187                 &errmsg);
188         if (ret < 0) {
189                 para_printf(pb, "%s: %s\n", mood_name, errmsg);
190                 free(errmsg);
191                 para_printf(pb, "%s\n", para_strerror(-ret));
192         } else
193                 destroy_mood(m);
194         ret = 1; /* don't fail the loop on invalid mood definitions */
195 out:
196         osl_close_disk_object(&mood_def);
197         return ret;
198 }
199
200 /**
201  * Check all moods for syntax errors.
202  *
203  * \param aca Only ->pbout is used for diagnostics.
204  *
205  * \return Negative on fatal errors. Inconsistent mood definitions are not
206  * considered an error.
207  */
208 int mood_check_callback(struct afs_callback_arg *aca)
209 {
210         para_printf(&aca->pbout, "checking moods...\n");
211         return osl(osl_rbtree_loop(moods_table, BLOBCOL_ID, &aca->pbout,
212                 check_mood));
213 }
214
215 /*
216  * The normalized num_played and last_played values are defined as
217  *
218  *      nn := -(np - mean_n) / sigma_n and nl := -(lp - mean_l) / sigma_l
219  *
220  *  For a (hypothetical) file with np = 0 and lp = now we thus have
221  *
222  *      nn =  mean_n / sigma_n =: hn > 0
223  *      nl = -(now - mean_l) / sigma_l =: hl < 0
224  *
225  * We design the score function so that both contributions get the same
226  * weight. Define the np and lp score of an arbitrary file as
227  *
228  *      sn := nn * -hl and sl := nl * hn
229  *
230  * Example:
231  *      num_played mean/sigma: 87/14
232  *      last_played mean/sigma: 45/32 days
233  *
234  *      We have hn = 87 / 14 = 6.21 and hl = -45 / 32 = -1.41. Multiplying
235  *      nn of every file with the correction factor 1.41 and nl with
236  *      6.21 makes the weight of the two contributions equal.
237  *
238  * The total score s := sn + sl has the representation
239  *
240  *      s = -cn * (np - mean_n) - cl * (lp - mean_l)
241  *
242  * with positive correction factors
243  *
244  *      cn = (now - mean_l) / (sqrt(ql) * sqrt(qn) / n)
245  *      cl = mean_n / (sqrt(ql) * sqrt(qn) / n)
246  *
247  * where ql and qn are the quadratic deviations stored in the statistics
248  * structure and n is the number of admissible files. To avoid integer
249  * overflows and rounding errors we store the common divisor of the
250  * correction factors separately.
251  */
252 static long compute_score(struct afs_info *afsi)
253 {
254         int64_t mean_n, mean_l,score_n, score_l;
255
256         assert(statistics.normalization_divisor > 0);
257         assert(statistics.num > 0);
258         mean_n = statistics.num_played_sum / statistics.num;
259         mean_l = statistics.last_played_sum / statistics.num;
260
261         score_n = -((int64_t)afsi->num_played - mean_n)
262                 * statistics.num_played_correction
263                 / statistics.normalization_divisor;
264         score_l = -((int64_t)afsi->last_played - mean_l)
265                 * statistics.last_played_correction
266                 / statistics.normalization_divisor;
267         return (score_n + score_l) / 2;
268 }
269
270 static int add_afs_statistics(const struct osl_row *row)
271 {
272         uint64_t n, x, s, q;
273         struct afs_info afsi;
274         int ret;
275
276         ret = get_afsi_of_row(row, &afsi);
277         if (ret < 0)
278                 return ret;
279         n = statistics.num;
280         x = afsi.last_played;
281         s = statistics.last_played_sum;
282         if (n > 0) {
283                 q = (x > s / n)? x - s / n : s / n - x;
284                 statistics.last_played_qd += q * q * n / (n + 1);
285         }
286         statistics.last_played_sum += x;
287
288         x = afsi.num_played;
289         s = statistics.num_played_sum;
290         if (n > 0) {
291                 q = (x > s / n)? x - s / n : s / n - x;
292                 statistics.num_played_qd += q * q * n / (n + 1);
293         }
294         statistics.num_played_sum += x;
295         statistics.num++;
296         return 1;
297 }
298
299 static int del_afs_statistics(const struct osl_row *row)
300 {
301         uint64_t n, s, q, a, new_s;
302         struct afs_info afsi;
303         int ret;
304         ret = get_afsi_of_row(row, &afsi);
305         if (ret < 0)
306                 return ret;
307         n = statistics.num;
308         assert(n);
309         if (n == 1) {
310                 memset(&statistics, 0, sizeof(statistics));
311                 statistics.normalization_divisor = 1;
312                 return 1;
313         }
314
315         s = statistics.last_played_sum;
316         q = statistics.last_played_qd;
317         a = afsi.last_played;
318         new_s = s - a;
319         statistics.last_played_sum = new_s;
320         statistics.last_played_qd = q + s * s / n - a * a
321                 - new_s * new_s / (n - 1);
322
323         s = statistics.num_played_sum;
324         q = statistics.num_played_qd;
325         a = afsi.num_played;
326         new_s = s - a;
327         statistics.num_played_sum = new_s;
328         statistics.num_played_qd = q + s * s / n - a * a
329                 - new_s * new_s / (n - 1);
330
331         statistics.num--;
332         return 1;
333 }
334
335 /*
336  * At mood open time we determine the set of admissible files for the given
337  * mood where each file is identified by a pointer to a row of the audio file
338  * table. In the first pass the pointers are added to a temporary array and
339  * statistics are computed. When all admissible files have been processed in
340  * this way, the score of each admissible file is computed and the (row, score)
341  * pair is added to the score table. This has to be done in a second pass
342  * since the score depends on the statistics. Finally, the array is freed.
343  */
344 struct admissible_array {
345         /** Files are admissible wrt. this mood. */
346         struct mood *m;
347         /** The size of the array */
348         unsigned size;
349         /** Pointer to the array of admissible files. */
350         struct osl_row **array;
351 };
352
353 /*
354  * Check whether the given audio file is admissible. If it is, add it to array
355  * of admissible files.
356  */
357 static int add_if_admissible(struct osl_row *aft_row, void *data)
358 {
359         struct admissible_array *aa = data;
360         int ret;
361
362         ret = row_is_admissible(aft_row, aa->m);
363         if (ret <= 0)
364                 return ret;
365         if (statistics.num >= aa->size) {
366                 aa->size *= 2;
367                 aa->size += 100;
368                 aa->array = arr_realloc(aa->array, aa->size,
369                         sizeof(struct osl_row *));
370         }
371         aa->array[statistics.num] = aft_row;
372         return add_afs_statistics(aft_row);
373 }
374
375 /**
376  * Compute the new quadratic deviation in case one element changes.
377  *
378  * \param n Number of elements.
379  * \param old_qd The quadratic deviation before the change.
380  * \param old_val The value that was replaced.
381  * \param new_val The replacement value.
382  * \param old_sum The sum of all elements before the update.
383  *
384  * \return The new quadratic deviation resulting from replacing old_val
385  * by new_val.
386  *
387  * Given n real numbers a_1, ..., a_n, their sum S = a_1 + ... + a_n,
388  * their quadratic deviation
389  *
390  * q = (a_1 - S/n)^2 + ... + (a_n - S/n)^2,
391  *
392  * and a real number b, the quadratic deviation q' of a_1,...a_{n-1}, b (ie.
393  * the last number a_n was replaced by b) may be computed in O(1) time in terms
394  * of n, q, a_n, b, and S as
395  *
396  *      q' = q + d * s - (2 * S + d) * d / n
397  *         = q + d * (s - 2 * S / n - d /n),
398  *
399  * where d = b - a_n, and s = b + a_n.
400  *
401  * Example: n = 3, a_1 = 3, a_2 = 5, a_3 = 7, b = 10. Then S = 15, q = 8, d = 3,
402  * s = 17, so
403  *
404  *      q + d * s - (2 * S + d) * d / n = 8 + 51 - 33 = 26,
405  *
406  * which equals q' = (3 - 6)^2 + (5 - 6)^2 + (10 - 6)^2.
407  *
408  */
409 _static_inline_ int64_t update_quadratic_deviation(int64_t n, int64_t old_qd,
410                 int64_t old_val, int64_t new_val, int64_t old_sum)
411 {
412         int64_t delta = new_val - old_val;
413         int64_t sigma = new_val + old_val;
414         return old_qd + delta * (sigma - 2 * old_sum / n - delta / n);
415 }
416
417 static int update_afs_statistics(struct afs_info *old_afsi,
418                 struct afs_info *new_afsi)
419 {
420         unsigned n;
421         int ret = get_num_admissible_files(&n);
422
423         if (ret < 0)
424                 return ret;
425         assert(n);
426
427         statistics.last_played_qd = update_quadratic_deviation(n,
428                 statistics.last_played_qd, old_afsi->last_played,
429                 new_afsi->last_played, statistics.last_played_sum);
430         statistics.last_played_sum += new_afsi->last_played - old_afsi->last_played;
431
432         statistics.num_played_qd = update_quadratic_deviation(n,
433                 statistics.num_played_qd, old_afsi->num_played,
434                 new_afsi->num_played, statistics.num_played_sum);
435         statistics.num_played_sum += new_afsi->num_played - old_afsi->num_played;
436         return 1;
437 }
438
439 static int add_to_score_table(const struct osl_row *aft_row)
440 {
441         long score;
442         struct afs_info afsi;
443         int ret = get_afsi_of_row(aft_row, &afsi);
444
445         if (ret < 0)
446                 return ret;
447         score = compute_score(&afsi);
448         return score_add(aft_row, score);
449 }
450
451 static int delete_from_statistics_and_score_table(const struct osl_row *aft_row)
452 {
453         int ret = del_afs_statistics(aft_row);
454         if (ret < 0)
455                 return ret;
456         return score_delete(aft_row);
457 }
458
459 /**
460  * Delete one entry from the statistics and from the score table.
461  *
462  * \param aft_row The audio file which is no longer admissible.
463  *
464  * \return Positive on success, negative on errors.
465  *
466  * \sa \ref score_delete().
467  */
468 static int mood_delete_audio_file(const struct osl_row *aft_row)
469 {
470         int ret;
471
472         ret = row_belongs_to_score_table(aft_row, NULL);
473         if (ret < 0)
474                 return ret;
475         if (!ret) /* not admissible, nothing to do */
476                 return 1;
477         return delete_from_statistics_and_score_table(aft_row);
478 }
479
480 /**
481  * Compute the new score of an audio file wrt. the current mood.
482  *
483  * \param aft_row Determines the audio file.
484  * \param old_afsi The audio file selector info before updating.
485  *
486  * The \a old_afsi argument may be \p NULL which indicates that no changes to
487  * the audio file info were made.
488  *
489  * \return Positive on success, negative on errors.
490  */
491 static int mood_update_audio_file(const struct osl_row *aft_row,
492                 struct afs_info *old_afsi)
493 {
494         long score, percent;
495         int ret, is_admissible, was_admissible = 0;
496         struct afs_info afsi;
497         unsigned rank;
498
499         if (!current_mood)
500                 return 1; /* nothing to do */
501         ret = row_belongs_to_score_table(aft_row, &rank);
502         if (ret < 0)
503                 return ret;
504         was_admissible = ret;
505         ret = row_is_admissible(aft_row, current_mood);
506         if (ret < 0)
507                 return ret;
508         is_admissible = (ret > 0);
509         if (!was_admissible && !is_admissible)
510                 return 1;
511         if (was_admissible && !is_admissible)
512                 return delete_from_statistics_and_score_table(aft_row);
513         if (!was_admissible && is_admissible) {
514                 ret = add_afs_statistics(aft_row);
515                 if (ret < 0)
516                         return ret;
517                 return add_to_score_table(aft_row);
518         }
519         /* update score */
520         ret = get_afsi_of_row(aft_row, &afsi);
521         if (ret < 0)
522                 return ret;
523         if (old_afsi) {
524                 ret = update_afs_statistics(old_afsi, &afsi);
525                 if (ret < 0)
526                         return ret;
527         }
528         score = compute_score(&afsi);
529         PARA_DEBUG_LOG("score: %li\n", score);
530         percent = (score + 100) / 3;
531         if (percent > 100)
532                 percent = 100;
533         else if (percent < 0)
534                 percent = 0;
535         PARA_DEBUG_LOG("moving from rank %u to %li%%\n", rank, percent);
536         return score_update(aft_row, percent);
537 }
538
539 /* sse: seconds since epoch. */
540 static void log_statistics(int64_t sse)
541 {
542         unsigned n = statistics.num;
543         int mean_days, sigma_days;
544
545         assert(current_mood);
546         PARA_NOTICE_LOG("loaded mood %s\n", current_mood->name?
547                 current_mood->name : "(dummy)");
548         if (!n) {
549                 PARA_WARNING_LOG("no admissible files\n");
550                 return;
551         }
552         PARA_NOTICE_LOG("%u admissible files\n", statistics.num);
553         mean_days = (sse - statistics.last_played_sum / n) / 3600 / 24;
554         sigma_days = int_sqrt(statistics.last_played_qd / n) / 3600 / 24;
555         PARA_NOTICE_LOG("last_played mean/sigma: %d/%d days\n", mean_days, sigma_days);
556         PARA_NOTICE_LOG("num_played mean/sigma: %" PRId64 "/%" PRIu64 "\n",
557                 statistics.num_played_sum / n,
558                 int_sqrt(statistics.num_played_qd / n));
559         PARA_NOTICE_LOG("num_played correction factor: %" PRId64 "\n",
560                 statistics.num_played_correction);
561         PARA_NOTICE_LOG("last_played correction factor: %" PRId64 "\n",
562                 statistics.last_played_correction);
563         PARA_NOTICE_LOG("normalization divisor: %" PRId64 "\n",
564                 statistics.normalization_divisor);
565 }
566
567 /**
568  * Close the current mood.
569  *
570  * Frees all resources of the current mood.
571  */
572 void close_current_mood(void)
573 {
574         destroy_mood(current_mood);
575         current_mood = NULL;
576         memset(&statistics, 0, sizeof(statistics));
577         statistics.normalization_divisor = 1;
578 }
579
580 static void compute_correction_factors(int64_t sse)
581 {
582         struct afs_statistics *s = &statistics;
583
584         if (s->num > 0) {
585                 s->normalization_divisor = int_sqrt(s->last_played_qd)
586                         * int_sqrt(s->num_played_qd) / s->num / 100;
587                 s->num_played_correction = sse - s->last_played_sum / s->num;
588                 s->last_played_correction = s->num_played_sum / s->num;
589         }
590         if (s->num_played_correction == 0)
591                 s->num_played_correction = 1;
592         if (s->normalization_divisor == 0)
593                 s->normalization_divisor = 1;
594         if (s->last_played_correction == 0)
595                 s->last_played_correction = 1;
596 }
597
598 /**
599  * Change the current mood.
600  *
601  * \param mood_name The name of the mood to open.
602  * \param errmsg Error description is returned here.
603  *
604  * If \a mood_name is \a NULL, load the dummy mood that accepts every audio file
605  * and uses a scoring method based only on the \a last_played information.
606  *
607  * The errmsg pointer may be NULL, in which case no error message will be
608  * returned. If a non-NULL pointer is given, the caller must free *errmsg.
609  *
610  * If there is already an open mood, it will be closed first.
611  *
612  * \return Positive on success, negative on errors.
613  *
614  * \sa struct \ref afs_info::last_played, \ref mp_eval_row().
615  */
616 int change_current_mood(const char *mood_name, char **errmsg)
617 {
618         int i, ret;
619         struct admissible_array aa = {
620                 .size = 0,
621                 .array = NULL
622         };
623         /*
624          * We can not use the "now" pointer from sched.c here because we are
625          * called before schedule(), which initializes "now".
626          */
627         struct timeval rnow;
628
629         if (mood_name) {
630                 struct mood *m;
631                 struct osl_row *row;
632                 struct osl_object obj;
633
634                 if (!*mood_name) {
635                         *errmsg = make_message("empty mood name");
636                         return -ERRNO_TO_PARA_ERROR(EINVAL);
637                 }
638                 obj.data = (char *)mood_name;
639                 obj.size = strlen(mood_name) + 1;
640                 ret = osl(osl_get_row(moods_table, BLOBCOL_NAME, &obj, &row));
641                 if (ret < 0) {
642                         if (errmsg)
643                                 *errmsg = make_message("no such mood: %s",
644                                         mood_name);
645                         return ret;
646                 }
647                 ret = load_mood(row, &m, errmsg);
648                 if (ret < 0)
649                         return ret;
650                 close_current_mood();
651                 current_mood = m;
652         } else { /* load dummy mood */
653                 close_current_mood();
654                 current_mood = alloc_new_mood(NULL);
655         }
656         aa.m = current_mood;
657         PARA_NOTICE_LOG("computing statistics of admissible files\n");
658         ret = audio_file_loop(&aa, add_if_admissible);
659         if (ret < 0) {
660                 if (errmsg)
661                         *errmsg = make_message("audio file loop failed");
662                 goto out;
663         }
664         clock_get_realtime(&rnow);
665         compute_correction_factors(rnow.tv_sec);
666         log_statistics(rnow.tv_sec);
667         for (i = 0; i < statistics.num; i++) {
668                 ret = add_to_score_table(aa.array[i]);
669                 if (ret < 0) {
670                         if (errmsg)
671                                 *errmsg = make_message(
672                                         "could not add row to score table");
673                         goto out;
674                 }
675         }
676         ret = statistics.num;
677 out:
678         free(aa.array);
679         if (ret < 0)
680                 close_current_mood();
681         return ret;
682 }
683
684 /*
685  * Close and re-open the current mood.
686  *
687  * This function is called on events which render the current list of
688  * admissible files useless, for example if an attribute is removed from the
689  * attribute table.
690  *
691  * If no mood is currently open, the function returns success.
692  */
693 static int reload_current_mood(void)
694 {
695         int ret;
696         char *mood_name = NULL;
697
698         ret = clear_score_table();
699         if (ret < 0)
700                 return ret;
701         if (!current_mood)
702                 return 1;
703         PARA_NOTICE_LOG("reloading %s\n", current_mood->name?
704                 current_mood->name : "(dummy)");
705         if (current_mood->name)
706                 mood_name = para_strdup(current_mood->name);
707         close_current_mood();
708         ret = change_current_mood(mood_name, NULL);
709         free(mood_name);
710         return ret;
711 }
712
713 /**
714  * Notification callback for the moods table.
715  *
716  * \param event Type of the event just occurred.
717  * \param pb Unused.
718  * \param data Its type depends on the event.
719  *
720  * This function updates the score table according to the event that has
721  * occurred. Two actions are possible: (a) reload the current mood, or (b)
722  * add/remove/update the row of the score table which corresponds to the audio
723  * file that has been modified or whose afs info has been changed. It depends
724  * on the type of the event which action (if any) is performed.
725  *
726  * The callbacks of command handlers such as com_add() or com_touch() which
727  * modify the audio file table call this function. The virtual streaming system
728  * also calls this after it has updated the afs info of the file it is about to
729  * stream (the one with the highest score). If the file stays admissible, its
730  * score is recomputed so that a different file is picked next time.
731  *
732  * \return Standard.
733  */
734 int moods_event_handler(enum afs_events event, __a_unused struct para_buffer *pb,
735                 void *data)
736 {
737         if (!current_mood)
738                 return 0;
739         switch (event) {
740         /*
741          * The three blob events might change the set of admissible files,
742          * so we must reload the score list.
743          */
744         case BLOB_RENAME:
745         case BLOB_REMOVE:
746         case BLOB_ADD:
747                 if (data == moods_table || data == playlists_table)
748                         return 1; /* no reload necessary for these */
749                 return reload_current_mood();
750         /* these also require reload of the score table */
751         case ATTRIBUTE_ADD:
752         case ATTRIBUTE_REMOVE:
753         case ATTRIBUTE_RENAME:
754                 return reload_current_mood();
755         /* changes to the aft only require to re-examine the audio file */
756         case AFSI_CHANGE: {
757                 struct afsi_change_event_data *aced = data;
758                 return mood_update_audio_file(aced->aft_row, aced->old_afsi);
759                 }
760         case AFHI_CHANGE:
761         case AUDIO_FILE_RENAME:
762         case AUDIO_FILE_ADD:
763                 return mood_update_audio_file(data, NULL);
764         case AUDIO_FILE_REMOVE:
765                 return mood_delete_audio_file(data);
766         default:
767                 return 1;
768         }
769 }