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