X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=mood.c;h=732df0a32f8d1e7d19d08f04c773dc5026589caf;hp=239911fd50538322b844888687c587df02f9a51b;hb=fa578bf1d070161cafcbcc69ca7b810414ee1a4b;hpb=8ea8abb73199b32fdd7afdf8825afa42ed8de244;ds=sidebyside diff --git a/mood.c b/mood.c index 239911fd..732df0a3 100644 --- a/mood.c +++ b/mood.c @@ -1,14 +1,12 @@ /* - * Copyright (C) 2007-2009 Andre Noll + * Copyright (C) 2007-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file mood.c Paraslash's mood handling functions. */ -#include #include -#include #include #include "para.h" @@ -18,6 +16,7 @@ #include "afs.h" #include "list.h" #include "ipc.h" +#include "mm.h" /** * Contains statistical data of the currently admissible audio files. @@ -36,67 +35,7 @@ struct afs_statistics { /** Number of admissible files */ unsigned num; }; -struct afs_statistics statistics; - -/** - * Assign scores according to a mood_method. - * - * Each mood_method has its own mood_score_function. The first three parameters - * passed to that function are informations about the audio file whose score is - * to be computed. The data argument depends on the mood method this function - * is used for. It usually is the argument given at the end of a mood line. - * - * Mood score functions must return values between -100 and +100 inclusively. - * Boolean score functions should always return either -100 or +100. - * - * \sa struct mood_method, mood_parser. - */ -typedef int mood_score_function(const char *path, const struct afs_info *afsi, - const struct afh_info *afhi, const void *data); - -/** - * Pre-process a mood line. - * - * The mood_parser of a mood_method is called once at mood open time for each - * line of the current mood definition that contains the mood_method's name as - * a keyword. The line is passed to the mood_parser as the first argument. The - * mood_parser must determine whether the line is syntactically correct and - * return a positive value if so and a negative value otherwise. - * - * Some mood parsers pre-process the data given in the mood line to compute a - * structure which depends of the particular mood_method and which is used - * later in the mood_score_function of the mood_method. The mood_parser may - * store a pointer to its structure via the void** pointer. - * - * \sa mood_open(), mood_cleanup_function, mood_score_function. - */ -typedef int mood_parser(int, char **, void **); - -/** - * Deallocate resources which were allocated by the mood_parser. - * - * This optional function of a mood_method is used to free any resources - * allocated in mood_open() by the mood_parser. The argument passed is a - * pointer to the mood_method specific data structure that was returned by the - * mood_parser. - * - * \sa mood_parser. - */ -typedef void mood_cleanup_function(void *); - -/** - * Used for scoring and to determine whether a file is admissible. - */ -struct mood_method { - /** The name of the method. */ - const char *name; - /** Pointer to the mood parser. */ - mood_parser *parser; - /** Pointer to the score function */ - mood_score_function *score_function; - /** Optional cleanup function. */ - mood_cleanup_function *cleanup; -}; +static struct afs_statistics statistics; /** * Each line of the current mood corresponds to a mood_item. @@ -168,288 +107,80 @@ static uint64_t int_sqrt(uint64_t x) return res; } -#define MOOD_COMPARATORS \ - MC(LESS, <) \ - MC(LESS_OR_EQUAL, <=) \ - MC(EQUAL, =) \ - MC(EQUAL2, ==) \ - MC(NOT_EQUAL, !=) \ - MC(NOT_EQUAL2, <>) \ - MC(GREATER, >) \ - MC(GREATER_OR_EQUAL, >=) \ - -#define MC(a, b) MC_ ## a, -enum mood_comparator_id {MOOD_COMPARATORS NUM_MOOD_COMPARATORS}; -#undef MC -#define MC(a, b) # b, -const char const *mood_comparators[] = {MOOD_COMPARATORS}; -#undef MC - -static int parse_mood_comparator(const char *word) -{ - int i; - - for (i = 0; i < NUM_MOOD_COMPARATORS; i++) - if (!strcmp(word, mood_comparators[i])) - return i; - return -E_MOOD_SYNTAX; -} - -static int compare_int32(int32_t a, int32_t b, enum mood_comparator_id id) -{ - int res; - - switch (id) { - case MC_LESS: - res = a < b; break; - case MC_LESS_OR_EQUAL: - res = a <= b; break; - case MC_EQUAL: - case MC_EQUAL2: - res = a == b; break; - case MC_NOT_EQUAL: - case MC_NOT_EQUAL2: - res = a != b; break; - case MC_GREATER: - res = a > b; break; - case MC_GREATER_OR_EQUAL: - res = a >= b; break; - default: - PARA_EMERG_LOG("BUG: invalid mood comparator\n"); - exit(EXIT_FAILURE); - } - return res? 100 : -100; -} - -struct mm_year_data { - /** The year given at the mood line. */ - int32_t year; - /** Used to detect Y2K issues. */ - int32_t current_year; - /** <, <=, =, !=, >=, or >. */ - enum mood_comparator_id id; -}; - -static int mm_year_parser(int argc, char **argv, void **private) -{ - int ret = -E_MOOD_SYNTAX; - struct mm_year_data *mmyd = para_malloc(sizeof(*mmyd)); - time_t current_time; - struct tm *gmt; - - if (argc != 2) - goto err; - ret = parse_mood_comparator(argv[1]); - mmyd->id = ret; - if (ret < 0) - goto err; - ret = para_atoi32(argv[2], &mmyd->year); - if (ret < 0) - goto err; - current_time = time(NULL); - gmt = gmtime(¤t_time); - /* tm_year is the number of years since 1900 */ - mmyd->current_year = gmt->tm_year + 1900; - *private = mmyd; - return 1; -err: - free(mmyd); - return ret; -} - -static int mm_year_score_function(__a_unused const char *path, - __a_unused const struct afs_info *afsi, - const struct afh_info *afhi, - const void *private) -{ - const struct mm_year_data *mmyd = private; - int32_t tag_year; - int ret = para_atoi32(afhi->tags.year, &tag_year); - - if (ret < 0) /* year tag not present or not a number */ - return -100; - if (tag_year < 0) - return -100; - /* try to work around Y2K issues */ - if (tag_year < 100) { - tag_year += 1900; - if (tag_year + 100 <= mmyd->current_year) - tag_year += 100; /* assume tag_year >= 2000 */ - } - return compare_int32(tag_year, mmyd->year, mmyd->id); -} - -static void mm_year_cleanup(void *private) -{ - free(private); -} - -static int mm_no_attributes_set_parser(int argc, __a_unused char **argv, - __a_unused void **ignored) -{ - return argc? -E_MOOD_SYNTAX : 1; -} - -static int mm_no_attributes_set_score_function(__a_unused const char *path, - const struct afs_info *afsi, - __a_unused const struct afh_info *afhi, - __a_unused const void *data) -{ - if (!afsi->attributes) - return 100; - return -100; -} - -static int mm_played_rarely_score_function(__a_unused const char *path, - const struct afs_info *afsi, - __a_unused const struct afh_info *afhi, - __a_unused const void *data) -{ - unsigned num; - int ret = get_num_admissible_files(&num); - - if (ret < 0) - return 0; - if (statistics.num_played_sum - num * afsi->num_played - > int_sqrt(statistics.num_played_qd * num)) - return 100; - return -100; -} - -static int mm_played_rarely_parser(int argc, __a_unused char **argv, - __a_unused void **ignored) -{ - return argc? -E_MOOD_SYNTAX : 1; -} - -static int mm_path_matches_score_function(const char *path, - __a_unused const struct afs_info *afsi, - __a_unused const struct afh_info *afhi, - const void *data) -{ - if (fnmatch(data, path, 0)) - return -100; - return 100; -} - -static int mm_path_matches_parser(int argc, char **argv, void **data) -{ - if (argc != 1) - return -E_MOOD_SYNTAX; - *data = para_strdup(argv[1]); - return 1; -} - -static void mm_path_matches_cleanup(void *data) -{ - free(data); -} - -static int mm_is_set_parser(int argc, char **argv, void **bitnum) -{ - int ret; - unsigned char c, *res; - - if (argc != 1) - return -E_MOOD_SYNTAX; - ret = get_attribute_bitnum_by_name(argv[1], &c); - if (ret < 0) - return ret; - res = para_malloc(1); - *res = c; - *bitnum = res; - return 1; -} - -static int mm_is_set_score_function(__a_unused const char *path, - __a_unused const struct afs_info *afsi, - __a_unused const struct afh_info *afhi, - const void *data) -{ - const unsigned char *bn = data; - if (afsi->attributes & (1ULL << *bn)) - return 100; - return -100; -} - -/* returns 1 if row matches score item, negative otherwise */ -static int add_item_score(const struct osl_row *row, struct mood_item *item, long *score, +/* returns 1 if row matches score item, 0 if not. */ +static int get_item_score(struct mood_item *item, const struct afs_info *afsi, + const struct afh_info *afhi, const char *path, long *score, long *score_arg_sum) { - struct afs_info afsi; - struct afh_info afhi; - char *path; - int ret; + int ret, match = 1; *score_arg_sum += item->random_score? 100 : PARA_ABS(item->score_arg); ret = 100; if (item->method) { - ret = get_afsi_of_row(row, &afsi); - if (ret< 0) - return ret; - ret = get_afhi_of_row(row, &afhi); - if (ret< 0) - return ret; - ret = get_audio_file_path_of_row(row, &path); - if (ret< 0) - return ret; - ret = item->method->score_function(path, &afsi, &afhi, + ret = item->method->score_function(path, afsi, afhi, item->parser_data); if ((ret < 0 && !item->logical_not) || (ret >= 0 && item->logical_not)) - return -1; /* no match */ + match = 0; /* no match */ } if (item->random_score) - *score += PARA_ABS(ret) * para_random(100); + *score = PARA_ABS(ret) * para_random(100); else - *score += PARA_ABS(ret) * item->score_arg; - return 1; + *score = PARA_ABS(ret) * item->score_arg; + return match; } +/* returns 1 if row admissible, 0 if not, negative on errors */ static int compute_mood_score(const struct osl_row *aft_row, struct mood *m, long *result) { struct mood_item *item; - int match = 0; - long score_arg_sum = 0, score = 0; + int ret, match = 0; + long score_arg_sum = 0, score = 0, item_score; + struct afs_info afsi; + struct afh_info afhi; + char *path; if (!m) return -E_NO_MOOD; + ret = get_afsi_of_row(aft_row, &afsi); + if (ret< 0) + return ret; + ret = get_afhi_of_row(aft_row, &afhi); + if (ret< 0) + return ret; + ret = get_audio_file_path_of_row(aft_row, &path); + if (ret< 0) + return ret; /* reject audio file if it matches any entry in the deny list */ - list_for_each_entry(item, &m->deny_list, mood_item_node) - if (add_item_score(aft_row, item, &score, &score_arg_sum) > 0) - return -E_NOT_ADMISSIBLE; - list_for_each_entry(item, &m->accept_list, mood_item_node) - if (add_item_score(aft_row, item, &score, &score_arg_sum) > 0) - match = 1; + list_for_each_entry(item, &m->deny_list, mood_item_node) { + ret = get_item_score(item, &afsi, &afhi, path, &item_score, + &score_arg_sum); + if (ret > 0) /* not admissible */ + return 0; + score += item_score; + } + list_for_each_entry(item, &m->accept_list, mood_item_node) { + ret = get_item_score(item, &afsi, &afhi, path, &item_score, + &score_arg_sum); + if (ret == 0) + continue; + match = 1; + score += item_score; + } /* reject if there is no matching entry in the accept list */ if (!match && !list_empty(&m->accept_list)) - return -E_NOT_ADMISSIBLE; - list_for_each_entry(item, &m->score_list, mood_item_node) - add_item_score(aft_row, item, &score, &score_arg_sum); + return 0; + list_for_each_entry(item, &m->score_list, mood_item_node) { + ret = get_item_score(item, &afsi, &afhi, path, &item_score, + &score_arg_sum); + score += item_score; + } if (score_arg_sum) score /= score_arg_sum; *result = score; return 1; } -#define DEFINE_MOOD_METHOD(_name) \ -.parser = mm_ ## _name ## _parser, \ -.score_function = mm_ ## _name ## _score_function, \ -.name = #_name - -#define DEFINE_MOOD_METHOD_WITH_CLEANUP(_name) \ - DEFINE_MOOD_METHOD(_name), \ - .cleanup = mm_ ## _name ## _cleanup - -static const struct mood_method mood_methods[] = { - {DEFINE_MOOD_METHOD(no_attributes_set)}, - {DEFINE_MOOD_METHOD(played_rarely)}, - {DEFINE_MOOD_METHOD(is_set)}, - {DEFINE_MOOD_METHOD_WITH_CLEANUP(path_matches)}, - {DEFINE_MOOD_METHOD_WITH_CLEANUP(year)}, - {.parser = NULL} -}; - static void cleanup_list_entry(struct mood_item *item) { if (item->method && item->method->cleanup) @@ -853,7 +584,7 @@ struct admissible_array { * \param aft_row The audio file to be added. * \param private_data Pointer to a struct admissible_file_info. * - * \return Negative on errors, positive on success. + * \return 1 if row admissible, 0 if not, negative on errors. */ static int add_if_admissible(struct osl_row *aft_row, void *data) { @@ -862,8 +593,8 @@ static int add_if_admissible(struct osl_row *aft_row, void *data) long score = 0; ret = compute_mood_score(aft_row, aa->m, &score); - if (ret < 0) - return (ret == -E_NOT_ADMISSIBLE)? 1 : ret; + if (ret <= 0) + return ret; if (statistics.num >= aa->size) { aa->size *= 2; aa->size += 100; @@ -1001,6 +732,8 @@ static int mood_update_audio_file(const struct osl_row *aft_row, return ret; was_admissible = ret; ret = compute_mood_score(aft_row, current_mood, &score); + if (ret < 0) + return ret; is_admissible = (ret > 0); if (!was_admissible && !is_admissible) return 1; @@ -1063,7 +796,6 @@ void close_current_mood(void) memset(&statistics, 0, sizeof(statistics)); } - /** * Change the current mood. * @@ -1141,7 +873,7 @@ out: * * \sa mood_open(), mood_close(). */ -int reload_current_mood(void) +static int reload_current_mood(void) { int ret; char *mood_name = NULL; @@ -1158,10 +890,23 @@ int reload_current_mood(void) return ret; } +/** + * Notification callback for the moods table. + * + * \param event Type of the event just occurred. + * \param pb Unused. + * \param data Its type depends on the event. + * + * This function performs actions required due to the occurrence of the given + * event. Possible actions include reload of the current mood and update of the + * score of an audio file. + */ int moods_event_handler(enum afs_events event, __a_unused struct para_buffer *pb, void *data) { - switch(event) { + if (!current_mood) + return 0; + switch (event) { /* * The three blob events might change the set of admissible files, * so we must reload the score list. @@ -1192,4 +937,3 @@ int moods_event_handler(enum afs_events event, __a_unused struct para_buffer *pb return 1; } } -