1 /* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
3 /** \file mood.c Paraslash's mood handling functions. */
19 * Mood parser API. It's overkill to have an own header file for
20 * these declarations as they are only needed in this .c file.
23 int mp_init(const char *definition, int nbytes, struct mp_context **result,
25 bool mp_eval_row(const struct osl_row *aft_row, struct mp_context *ctx);
26 void mp_shutdown(struct mp_context *ctx);
29 * Contains statistical data of the currently admissible audio files.
31 * It is used to assign normalized score values to each admissible audio file.
33 struct afs_statistics {
34 /** Sum of num played over all admissible files. */
35 int64_t num_played_sum;
36 /** Sum of last played times over all admissible files. */
37 int64_t last_played_sum;
38 /** Quadratic deviation of num played count. */
39 int64_t num_played_qd;
40 /** Quadratic deviation of last played time. */
41 int64_t last_played_qd;
42 /** Correction factor for the num played score. */
43 int64_t num_played_correction;
44 /** Correction factor for the last played score. */
45 int64_t last_played_correction;
46 /** Common divisor of the correction factors. */
47 int64_t normalization_divisor;
48 /** Number of admissible files */
51 static struct afs_statistics statistics = {.normalization_divisor = 1};
54 * Each line of the current mood corresponds to a mood_item.
57 /** The method this line is referring to. */
58 const struct mood_method *method;
59 /** The data structure computed by the mood parser. */
61 /** The given score value, or zero if none was given. */
63 /** Non-zero if random scoring was requested. */
65 /** Whether the "not" keyword was given in the mood line. */
67 /** The position in the list of items. */
68 struct list_head mood_item_node;
72 * Created from the mood definition by \ref change_current_mood().
74 * When a mood is opened, each line of its definition is investigated, and a
75 * corresponding mood item is produced. Each mood line starts with accept,
76 * deny, or score which determines the type of the mood line. For each such
77 * type a linked list is maintained whose entries are the mood items.
80 /** The name of this mood. */
82 /** The list of mood items of type \p accept. */
83 struct list_head accept_list;
84 /** The list of mood items of type \p deny. */
85 struct list_head deny_list;
86 /** The list of mood items of type \p score. */
87 struct list_head score_list;
88 /* Only used for version 2 moods. */
89 struct mp_context *parser_context;
93 * If current_mood is NULL then no mood is currently open. If
94 * current_mood->name is NULL, the dummy mood is currently open.
96 static struct mood *current_mood;
99 * Find the position of the most-significant set bit.
101 * Copied and slightly adapted from the linux source tree, version 4.9.39
104 __a_const static uint32_t fls64(uint64_t v)
107 const uint64_t ones = ~(uint64_t)0U;
109 if ((v & (ones << 32)) == 0) {
113 if ((v & (ones << (64 - 16))) == 0) {
117 if ((v & (ones << (64 - 8))) == 0) {
121 if ((v & (ones << (64 - 4))) == 0) {
125 if ((v & (ones << (64 - 2))) == 0) {
129 if ((v & (ones << (64 - 1))) == 0)
135 * Compute the integer square root floor(sqrt(x)).
137 * Taken 2007 from the linux source tree.
139 __a_const static uint64_t int_sqrt(uint64_t x)
141 uint64_t op = x, res = 0, one = 1;
143 one = one << (fls64(x) & ~one);
145 if (op >= res + one) {
146 op = op - (res + one);
156 * Returns true if row matches, false if it does not match. In any case score
157 * and score_arg_sum are set/increased accordingly.
159 static bool get_item_score(struct mood_item *item, const struct afs_info *afsi,
160 const struct afh_info *afhi, const char *path, long *score,
166 *score_arg_sum += item->random_score? 100 : PARA_ABS(item->score_arg);
169 ret = item->method->score_function(path, afsi, afhi,
171 if ((ret < 0 && !item->logical_not) || (ret >= 0 && item->logical_not))
174 if (item->random_score)
175 *score = PARA_ABS(ret) * para_random(100);
177 *score = PARA_ABS(ret) * item->score_arg;
181 /* returns 1 if row admissible, 0 if not, negative on errors */
182 static int row_is_admissible(const struct osl_row *aft_row, struct mood *m,
185 struct mood_item *item;
188 long score_arg_sum = 0, score = 0, item_score;
189 struct afs_info afsi;
190 struct afh_info afhi;
195 if (m->parser_context) {
197 return mp_eval_row(aft_row, m->parser_context);
199 ret = get_afsi_of_row(aft_row, &afsi);
202 ret = get_afhi_of_row(aft_row, &afhi);
205 ret = get_audio_file_path_of_row(aft_row, &path);
208 /* reject audio file if it matches any entry in the deny list */
209 list_for_each_entry(item, &m->deny_list, mood_item_node) {
210 match = get_item_score(item, &afsi, &afhi, path, &item_score,
212 if (match) /* not admissible */
217 list_for_each_entry(item, &m->accept_list, mood_item_node) {
218 ret = get_item_score(item, &afsi, &afhi, path, &item_score,
225 /* reject if there is no matching entry in the accept list */
226 if (!match && !list_empty(&m->accept_list))
228 list_for_each_entry(item, &m->score_list, mood_item_node) {
229 match = get_item_score(item, &afsi, &afhi, path, &item_score,
235 score /= score_arg_sum;
240 static void cleanup_list_entry(struct mood_item *item)
242 if (item->method && item->method->cleanup)
243 item->method->cleanup(item->parser_data);
245 free(item->parser_data);
246 list_del(&item->mood_item_node);
250 static void destroy_mood(struct mood *m)
252 struct mood_item *tmp, *item;
256 list_for_each_entry_safe(item, tmp, &m->accept_list, mood_item_node)
257 cleanup_list_entry(item);
258 list_for_each_entry_safe(item, tmp, &m->deny_list, mood_item_node)
259 cleanup_list_entry(item);
260 list_for_each_entry_safe(item, tmp, &m->score_list, mood_item_node)
261 cleanup_list_entry(item);
263 mp_shutdown(m->parser_context);
267 static struct mood *alloc_new_mood(const char *name)
269 struct mood *m = para_calloc(sizeof(struct mood));
271 m->name = para_strdup(name);
272 init_list_head(&m->accept_list);
273 init_list_head(&m->deny_list);
274 init_list_head(&m->score_list);
278 /** The different types of a mood line. */
279 enum mood_line_type {
290 /** Data passed to the parser of a mood line. */
291 struct mood_line_parser_data {
292 /** The mood this mood line belongs to. */
294 /** The line number in the mood definition. */
299 * <accept [with score <score>] | deny [with score <score>] | score <score>>
300 * [if] [not] <mood_method> [options]
301 * <score> is either an integer or "random" which assigns a random score to
304 static int parse_mood_line(char *mood_line, void *data)
306 struct mood_line_parser_data *mlpd = data;
311 enum mood_line_type mlt = ML_INVALID;
312 struct mood_item *mi = NULL;
315 ret = create_argv(mood_line, " \t", &argv);
319 if (!num_words) /* empty line */
322 if (**w == '#') /* comment */
324 if (!strcmp(*w, "accept"))
326 else if (!strcmp(*w, "deny"))
328 else if (!strcmp(*w, "score"))
330 ret = -E_MOOD_SYNTAX;
331 if (mlt == ML_INVALID)
333 mi = para_calloc(sizeof(struct mood_item));
334 if (mlt != ML_SCORE) {
335 ret = -E_MOOD_SYNTAX;
339 if (strcmp(*w, "with"))
344 if (strcmp(*w, "score"))
347 if (mlt == ML_SCORE || !strcmp(*w, "score")) {
348 ret = -E_MOOD_SYNTAX;
352 if (strcmp(*w, "random")) {
353 mi->random_score = 0;
354 ret = para_atoi32(*w, &mi->score_arg);
358 mi->random_score = 1;
360 goto success; /* the line "score random" is valid */
364 ret = -E_MOOD_SYNTAX;
369 if (!strcmp(*w, "if")) {
370 ret = -E_MOOD_SYNTAX;
375 if (!strcmp(*w, "not")) {
376 ret = -E_MOOD_SYNTAX;
383 for (i = 0; mood_methods[i].parser; i++) {
384 if (strcmp(*w, mood_methods[i].name))
388 ret = -E_MOOD_SYNTAX;
389 if (!mood_methods[i].parser)
391 ret = mood_methods[i].parser(num_words - 1 - (w - argv), w,
395 mi->method = &mood_methods[i];
398 if (mlt == ML_ACCEPT)
399 para_list_add(&mi->mood_item_node, &mlpd->m->accept_list);
400 else if (mlt == ML_DENY)
401 para_list_add(&mi->mood_item_node, &mlpd->m->deny_list);
403 para_list_add(&mi->mood_item_node, &mlpd->m->score_list);
405 PARA_DEBUG_LOG("%s entry added, method: %p\n", mlt == ML_ACCEPT? "accept" :
406 (mlt == ML_DENY? "deny" : "score"), mi->method);
410 if (mi && (ret < 0 || !mlpd->m)) { /* mi was not added to any list */
411 free(mi->parser_data);
417 static int load_mood(const struct osl_row *mood_row, struct mood **m,
421 struct osl_object mood_def;
422 struct mood_line_parser_data mlpd = {.line_num = 0};
426 ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
429 *errmsg = make_message(
430 "could not read mood definition");
434 mlpd.m = alloc_new_mood(mood_name);
435 ret = for_each_line(FELF_READ_ONLY, mood_def.data, mood_def.size,
436 parse_mood_line, &mlpd);
438 PARA_INFO_LOG("opening version 2 mood %s\n", mlpd.m->name);
439 ret = mp_init(mood_def.data, mood_def.size, &mlpd.m->parser_context,
442 destroy_mood(mlpd.m);
444 PARA_WARNING_LOG("loaded version 1 mood %s\n", mlpd.m->name);
445 PARA_WARNING_LOG("please convert to version 2\n");
448 osl_close_disk_object(&mood_def);
454 static int check_mood(struct osl_row *mood_row, void *data)
456 struct para_buffer *pb = data;
458 struct osl_object mood_def;
459 struct mood_line_parser_data mlpd = {.line_num = 0};
461 int ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
464 para_printf(pb, "cannot read mood\n");
467 if (!*mood_name) /* ignore dummy row */
469 ret = for_each_line(FELF_READ_ONLY, mood_def.data, mood_def.size,
470 parse_mood_line, &mlpd);
473 struct mood *m = alloc_new_mood("check");
474 ret = mp_init(mood_def.data, mood_def.size, &m->parser_context,
477 para_printf(pb, "%s: %s\n", mood_name, errmsg);
479 para_printf(pb, "%s\n", para_strerror(-ret));
483 para_printf(pb, "%s: v1 mood, please convert to v2\n",
487 ret = 1; /* don't fail the loop on invalid mood definitions */
489 osl_close_disk_object(&mood_def);
494 * Check all moods for syntax errors.
496 * \param aca Only ->pbout is used for diagnostics.
498 * \return Negative on fatal errors. Inconsistent mood definitions are not
499 * considered an error.
501 int mood_check_callback(struct afs_callback_arg *aca)
503 para_printf(&aca->pbout, "checking moods...\n");
504 return osl(osl_rbtree_loop(moods_table, BLOBCOL_ID, &aca->pbout,
509 * The normalized num_played and last_played values are defined as
511 * nn := -(np - mean_n) / sigma_n and nl := -(lp - mean_l) / sigma_l
513 * For a (hypothetical) file with np = 0 and lp = now we thus have
515 * nn = mean_n / sigma_n =: hn > 0
516 * nl = -(now - mean_l) / sigma_l =: hl < 0
518 * We design the score function so that both contributions get the same
519 * weight. Define the np and lp score of an arbitrary file as
521 * sn := nn * -hl and sl := nl * hn
524 * num_played mean/sigma: 87/14
525 * last_played mean/sigma: 45/32 days
527 * We have hn = 87 / 14 = 6.21 and hl = -45 / 32 = -1.41. Multiplying
528 * nn of every file with the correction factor 1.41 and nl with
529 * 6.21 makes the weight of the two contributions equal.
531 * The total score s := sn + sl has the representation
533 * s = -cn * (np - mean_n) - cl * (lp - mean_l)
535 * with positive correction factors
537 * cn = (now - mean_l) / (sqrt(ql) * sqrt(qn) / n)
538 * cl = mean_n / (sqrt(ql) * sqrt(qn) / n)
540 * where ql and qn are the quadratic deviations stored in the statistics
541 * structure and n is the number of admissible files. To avoid integer
542 * overflows and rounding errors we store the common divisor of the
543 * correction factors separately.
545 static long compute_score(struct afs_info *afsi, long mood_score)
547 int64_t mean_n, mean_l,score_n, score_l;
549 assert(statistics.normalization_divisor > 0);
550 assert(statistics.num > 0);
551 mean_n = statistics.num_played_sum / statistics.num;
552 mean_l = statistics.last_played_sum / statistics.num;
554 score_n = -((int64_t)afsi->num_played - mean_n)
555 * statistics.num_played_correction
556 / statistics.normalization_divisor;
557 score_l = -((int64_t)afsi->last_played - mean_l)
558 * statistics.last_played_correction
559 / statistics.normalization_divisor;
560 return (mood_score + score_n + score_l) / 3;
563 static int add_afs_statistics(const struct osl_row *row)
566 struct afs_info afsi;
569 ret = get_afsi_of_row(row, &afsi);
573 x = afsi.last_played;
574 s = statistics.last_played_sum;
576 q = (x > s / n)? x - s / n : s / n - x;
577 statistics.last_played_qd += q * q * n / (n + 1);
579 statistics.last_played_sum += x;
582 s = statistics.num_played_sum;
584 q = (x > s / n)? x - s / n : s / n - x;
585 statistics.num_played_qd += q * q * n / (n + 1);
587 statistics.num_played_sum += x;
592 static int del_afs_statistics(const struct osl_row *row)
594 uint64_t n, s, q, a, new_s;
595 struct afs_info afsi;
597 ret = get_afsi_of_row(row, &afsi);
603 memset(&statistics, 0, sizeof(statistics));
604 statistics.normalization_divisor = 1;
608 s = statistics.last_played_sum;
609 q = statistics.last_played_qd;
610 a = afsi.last_played;
612 statistics.last_played_sum = new_s;
613 statistics.last_played_qd = q + s * s / n - a * a
614 - new_s * new_s / (n - 1);
616 s = statistics.num_played_sum;
617 q = statistics.num_played_qd;
620 statistics.num_played_sum = new_s;
621 statistics.num_played_qd = q + s * s / n - a * a
622 - new_s * new_s / (n - 1);
629 * At mood open time we determine the set of admissible files for the given
630 * mood. The mood score of each admissible file is computed by adding up all
631 * mood item scores. Next, we update the afs statistics and append a struct
632 * admissible_file_info to a temporary array.
634 * When all files have been processed in this way, the final score of each
635 * admissible file is computed by adding the dynamic score (which depends on
636 * the afs_statistics and the current time) to the mood score. Finally, all
637 * audio files in the temporary array are added to the score table and the
640 struct admissible_file_info
642 /** The admissible audio file. */
643 struct osl_row *aft_row;
648 /** The temporary array of admissible files. */
649 struct admissible_array {
650 /** Files are admissible wrt. this mood. */
652 /** The size of the array */
654 /** Pointer to the array of admissible files. */
655 struct admissible_file_info *array;
659 * Add an entry to the array of admissible files.
661 * \param aft_row The audio file to be added.
662 * \param private_data Pointer to a struct admissible_file_info.
664 * \return 1 if row admissible, 0 if not, negative on errors.
666 static int add_if_admissible(struct osl_row *aft_row, void *data)
668 struct admissible_array *aa = data;
672 ret = row_is_admissible(aft_row, aa->m, &score);
675 if (statistics.num >= aa->size) {
678 aa->array = para_realloc(aa->array,
679 aa->size * sizeof(struct admissible_file_info));
681 aa->array[statistics.num].aft_row = aft_row;
682 aa->array[statistics.num].score = score;
683 ret = add_afs_statistics(aft_row);
690 * Compute the new quadratic deviation in case one element changes.
692 * \param n Number of elements.
693 * \param old_qd The quadratic deviation before the change.
694 * \param old_val The value that was replaced.
695 * \param new_val The replacement value.
696 * \param old_sum The sum of all elements before the update.
698 * \return The new quadratic deviation resulting from replacing old_val
701 * Given n real numbers a_1, ..., a_n, their sum S = a_1 + ... + a_n,
702 * their quadratic deviation
704 * q = (a_1 - S/n)^2 + ... + (a_n - S/n)^2,
706 * and a real number b, the quadratic deviation q' of a_1,...a_{n-1}, b (ie.
707 * the last number a_n was replaced by b) may be computed in O(1) time in terms
708 * of n, q, a_n, b, and S as
710 * q' = q + d * s - (2 * S + d) * d / n
711 * = q + d * (s - 2 * S / n - d /n),
713 * where d = b - a_n, and s = b + a_n.
715 * Example: n = 3, a_1 = 3, a_2 = 5, a_3 = 7, b = 10. Then S = 15, q = 8, d = 3,
718 * q + d * s - (2 * S + d) * d / n = 8 + 51 - 33 = 26,
720 * which equals q' = (3 - 6)^2 + (5 - 6)^2 + (10 - 6)^2.
723 _static_inline_ int64_t update_quadratic_deviation(int64_t n, int64_t old_qd,
724 int64_t old_val, int64_t new_val, int64_t old_sum)
726 int64_t delta = new_val - old_val;
727 int64_t sigma = new_val + old_val;
728 return old_qd + delta * (sigma - 2 * old_sum / n - delta / n);
731 static int update_afs_statistics(struct afs_info *old_afsi,
732 struct afs_info *new_afsi)
735 int ret = get_num_admissible_files(&n);
741 statistics.last_played_qd = update_quadratic_deviation(n,
742 statistics.last_played_qd, old_afsi->last_played,
743 new_afsi->last_played, statistics.last_played_sum);
744 statistics.last_played_sum += new_afsi->last_played - old_afsi->last_played;
746 statistics.num_played_qd = update_quadratic_deviation(n,
747 statistics.num_played_qd, old_afsi->num_played,
748 new_afsi->num_played, statistics.num_played_sum);
749 statistics.num_played_sum += new_afsi->num_played - old_afsi->num_played;
753 static int add_to_score_table(const struct osl_row *aft_row, long mood_score)
756 struct afs_info afsi;
757 int ret = get_afsi_of_row(aft_row, &afsi);
761 score = compute_score(&afsi, mood_score);
762 return score_add(aft_row, score);
765 static int delete_from_statistics_and_score_table(const struct osl_row *aft_row)
767 int ret = del_afs_statistics(aft_row);
770 return score_delete(aft_row);
774 * Delete one entry from the statistics and from the score table.
776 * \param aft_row The audio file which is no longer admissible.
778 * \return Positive on success, negative on errors.
780 * \sa \ref score_delete().
782 static int mood_delete_audio_file(const struct osl_row *aft_row)
786 ret = row_belongs_to_score_table(aft_row, NULL);
789 if (!ret) /* not admissible, nothing to do */
791 return delete_from_statistics_and_score_table(aft_row);
795 * Compute the new score of an audio file wrt. the current mood.
797 * \param aft_row Determines the audio file.
798 * \param old_afsi The audio file selector info before updating.
800 * The \a old_afsi argument may be \p NULL which indicates that no changes to
801 * the audio file info were made.
803 * \return Positive on success, negative on errors.
805 static int mood_update_audio_file(const struct osl_row *aft_row,
806 struct afs_info *old_afsi)
809 int ret, is_admissible, was_admissible = 0;
810 struct afs_info afsi;
814 return 1; /* nothing to do */
815 ret = row_belongs_to_score_table(aft_row, &rank);
818 was_admissible = ret;
819 ret = row_is_admissible(aft_row, current_mood, &score);
822 is_admissible = (ret > 0);
823 if (!was_admissible && !is_admissible)
825 if (was_admissible && !is_admissible)
826 return delete_from_statistics_and_score_table(aft_row);
827 if (!was_admissible && is_admissible) {
828 ret = add_afs_statistics(aft_row);
831 return add_to_score_table(aft_row, score);
834 ret = get_afsi_of_row(aft_row, &afsi);
838 ret = update_afs_statistics(old_afsi, &afsi);
842 score = compute_score(&afsi, score);
843 PARA_DEBUG_LOG("score: %li\n", score);
844 percent = (score + 100) / 3;
847 else if (percent < 0)
849 PARA_DEBUG_LOG("moving from rank %u to %li%%\n", rank, percent);
850 return score_update(aft_row, percent);
853 /* sse: seconds since epoch. */
854 static void log_statistics(int64_t sse)
856 unsigned n = statistics.num;
857 int mean_days, sigma_days;
859 assert(current_mood);
860 PARA_NOTICE_LOG("loaded mood %s\n", current_mood->name?
861 current_mood->name : "(dummy)");
863 PARA_WARNING_LOG("no admissible files\n");
866 PARA_NOTICE_LOG("%u admissible files\n", statistics.num);
867 mean_days = (sse - statistics.last_played_sum / n) / 3600 / 24;
868 sigma_days = int_sqrt(statistics.last_played_qd / n) / 3600 / 24;
869 PARA_NOTICE_LOG("last_played mean/sigma: %d/%d days\n", mean_days, sigma_days);
870 PARA_NOTICE_LOG("num_played mean/sigma: %" PRId64 "/%" PRIu64 "\n",
871 statistics.num_played_sum / n,
872 int_sqrt(statistics.num_played_qd / n));
873 PARA_NOTICE_LOG("num_played correction factor: %" PRId64 "\n",
874 statistics.num_played_correction);
875 PARA_NOTICE_LOG("last_played correction factor: %" PRId64 "\n",
876 statistics.last_played_correction);
877 PARA_NOTICE_LOG("normalization divisor: %" PRId64 "\n",
878 statistics.normalization_divisor);
882 * Close the current mood.
884 * Frees all resources of the current mood.
886 void close_current_mood(void)
888 destroy_mood(current_mood);
890 memset(&statistics, 0, sizeof(statistics));
891 statistics.normalization_divisor = 1;
894 static void compute_correction_factors(int64_t sse)
896 struct afs_statistics *s = &statistics;
899 s->normalization_divisor = int_sqrt(s->last_played_qd)
900 * int_sqrt(s->num_played_qd) / s->num / 100;
901 s->num_played_correction = sse - s->last_played_sum / s->num;
902 s->last_played_correction = s->num_played_sum / s->num;
904 if (s->num_played_correction == 0)
905 s->num_played_correction = 1;
906 if (s->normalization_divisor == 0)
907 s->normalization_divisor = 1;
908 if (s->last_played_correction == 0)
909 s->last_played_correction = 1;
913 * Change the current mood.
915 * \param mood_name The name of the mood to open.
916 * \param errmsg Error description is returned here.
918 * If \a mood_name is \a NULL, load the dummy mood that accepts every audio file
919 * and uses a scoring method based only on the \a last_played information.
921 * The errmsg pointer may be NULL, in which case no error message will be
922 * returned. If a non-NULL pointer is given, the caller must free *errmsg.
924 * If there is already an open mood, it will be closed first.
926 * \return Positive on success, negative on errors. Loading the dummy mood
929 * \sa struct \ref afs_info::last_played, \ref mp_eval_row().
931 int change_current_mood(const char *mood_name, char **errmsg)
934 struct admissible_array aa = {
939 * We can not use the "now" pointer from sched.c here because we are
940 * called before schedule(), which initializes "now".
947 struct osl_object obj = {
948 .data = (char *)mood_name,
949 .size = strlen(mood_name) + 1
951 ret = osl(osl_get_row(moods_table, BLOBCOL_NAME, &obj, &row));
954 *errmsg = make_message("no such mood: %s",
958 ret = load_mood(row, &m, errmsg);
961 close_current_mood();
963 } else { /* load dummy mood */
964 close_current_mood();
965 current_mood = alloc_new_mood(NULL);
968 PARA_NOTICE_LOG("computing statistics of admissible files\n");
969 ret = audio_file_loop(&aa, add_if_admissible);
972 *errmsg = make_message("audio file loop failed");
975 clock_get_realtime(&rnow);
976 compute_correction_factors(rnow.tv_sec);
977 log_statistics(rnow.tv_sec);
978 for (i = 0; i < statistics.num; i++) {
979 struct admissible_file_info *a = aa.array + i;
980 ret = add_to_score_table(a->aft_row, a->score);
983 *errmsg = make_message(
984 "could not add row to score table");
988 ret = statistics.num;
995 * Close and re-open the current mood.
997 * This function is called on events which render the current list of
998 * admissible files useless, for example if an attribute is removed from the
1001 * If no mood is currently open, the function returns success.
1003 static int reload_current_mood(void)
1006 char *mood_name = NULL;
1008 ret = clear_score_table();
1013 PARA_NOTICE_LOG("reloading %s\n", current_mood->name?
1014 current_mood->name : "(dummy)");
1015 if (current_mood->name)
1016 mood_name = para_strdup(current_mood->name);
1017 close_current_mood();
1018 ret = change_current_mood(mood_name, NULL);
1024 * Notification callback for the moods table.
1026 * \param event Type of the event just occurred.
1028 * \param data Its type depends on the event.
1030 * This function performs actions required due to the occurrence of the given
1031 * event. Possible actions include reload of the current mood and update of the
1032 * score of an audio file.
1036 int moods_event_handler(enum afs_events event, __a_unused struct para_buffer *pb,
1043 * The three blob events might change the set of admissible files,
1044 * so we must reload the score list.
1049 if (data == moods_table || data == playlists_table)
1050 return 1; /* no reload necessary for these */
1051 return reload_current_mood();
1052 /* these also require reload of the score table */
1054 case ATTRIBUTE_REMOVE:
1055 case ATTRIBUTE_RENAME:
1056 return reload_current_mood();
1057 /* changes to the aft only require to re-examine the audio file */
1059 struct afsi_change_event_data *aced = data;
1060 return mood_update_audio_file(aced->aft_row, aced->old_afsi);
1063 case AUDIO_FILE_RENAME:
1064 case AUDIO_FILE_ADD:
1065 return mood_update_audio_file(data, NULL);
1066 case AUDIO_FILE_REMOVE:
1067 return mood_delete_audio_file(data);