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