/* SPDX-License-Identifier: GPL-2.0 */ /** \file score.c Scoring functions to determine the audio file streaming order. */ #include #include #include "para.h" #include "error.h" #include "string.h" #include "afh.h" #include "afs.h" #include "list.h" static int ptr_compare(const struct osl_object *obj1, const struct osl_object *obj2) { void *d1 = *(void **)obj1->data; void *d2 = *(void **)obj2->data; return NUM_COMPARE(d1, d2); } /* * This function first compares the score values. If they are equal, the * addresses of the two objects are compared. Thus, the function returns * "equal" only if the two objects alias each other, i.e., point to the same * memory address. */ static int score_compare(const struct osl_object *obj1, const struct osl_object *obj2) { long d1 = *(long *)obj1->data; long d2 = *(long *)obj2->data; int ret = NUM_COMPARE(d2, d1); if (ret) return ret; return NUM_COMPARE(obj2->data, obj1->data); } /** * The score table consists of two columns: The \a aft_row column contains * pointers to the rows of the audio file table, and the score column contains * the current score of the audio file associated with that row. */ enum score_table_columns { /** The row of the audio file. */ SCORECOL_AFT_ROW, /** The score */ SCORECOL_SCORE, /** This table has two columns */ NUM_SCORE_COLUMNS }; static struct osl_column_description score_cols[] = { [SCORECOL_AFT_ROW] = { .storage_type = OSL_NO_STORAGE, .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE | OSL_DONT_FREE, .name = "aft_row", .compare_function = ptr_compare, .data_size = sizeof(void *) }, [SCORECOL_SCORE] = { .storage_type = OSL_NO_STORAGE, .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE, .name = "score", .compare_function = score_compare, .data_size = sizeof(long) } }; static struct osl_table_description score_table_desc = { .name = "score", .num_columns = NUM_SCORE_COLUMNS, .flags = 0, .column_descriptions = score_cols }; /* On errors (negative return value) the content of score is undefined. */ static int decode_row(struct osl_table *t, void *score_row, struct osl_row **aft_row, long *score) { struct osl_object obj; int ret = osl(osl_get_object(t, score_row, SCORECOL_SCORE, &obj)); if (ret < 0) return ret; *score = *(long *)obj.data; ret = osl(osl_get_object(t, score_row, SCORECOL_AFT_ROW, &obj)); if (ret < 0) return ret; *aft_row = obj.data; return 1; } /** * Add a (row, score) pair to the score table. * * \param aft_row Identifies the audio file to be added. * \param score The score value of the audio file. * \param t The score table to operate on. * * \return The return value of the underlying call to osl_add_row(). */ int score_add(const struct osl_row *aft_row, long score, struct osl_table *t) { int ret; struct osl_object score_objs[NUM_SCORE_COLUMNS]; size_t size; assert(aft_row); size = score_table_desc.column_descriptions[SCORECOL_AFT_ROW].data_size; score_objs[SCORECOL_AFT_ROW].data = (struct osl_row *)aft_row; score_objs[SCORECOL_AFT_ROW].size = size; size = score_table_desc.column_descriptions[SCORECOL_SCORE].data_size; score_objs[SCORECOL_SCORE].data = alloc(size); score_objs[SCORECOL_SCORE].size = size; *(long *)(score_objs[SCORECOL_SCORE].data) = score; // PARA_DEBUG_LOG("adding %p\n", *(void **) (score_objs[SCORECOL_AFT_ROW].data)); ret = osl(osl_add_row(t, score_objs)); if (ret < 0) { PARA_ERROR_LOG("%s\n", para_strerror(-ret)); free(score_objs[SCORECOL_SCORE].data); } return ret; } /** * Set the score of an admissible file to a low value. * * \param aft_row Determines the score row to update. * \param t The score table to operate on. * * If the audio file determined by the given row of the audio file table is * not admissible, the function does nothing. Otherwise it sets the score * to one less than the current lowest score value. This function always * operates on the global score table, i.e., the one that corresponds to * the currently open mood or playlist. * * \return Zero if the file is not admissible, one if the score was updated * successfully, negative on errors. */ int score_move_to_end(const struct osl_row *aft_row, struct osl_table *t) { struct osl_row *score_row, *rrow; /* score row, reference row */ long score; struct osl_object obj = {.data = (struct osl_row *)aft_row, .size = sizeof(aft_row)}; int ret = osl(osl_get_row(t, SCORECOL_AFT_ROW, &obj, &score_row)); if (ret == osl(-E_OSL_RB_KEY_NOT_FOUND)) /* not an error */ return 0; if (ret < 0) return ret; ret = osl(osl_rbtree_first_row(t, SCORECOL_SCORE, &rrow)); if (ret < 0) return ret; ret = osl(osl_get_object(t, rrow, SCORECOL_SCORE, &obj)); if (ret < 0) return ret; score = *(long *)obj.data - 1; PARA_DEBUG_LOG("new score: %ld\n", score); obj.size = sizeof(long); obj.data = alloc(obj.size); *(long *)obj.data = score; ret = osl(osl_update_object(t, score_row, SCORECOL_SCORE, &obj)); if (ret < 0) return ret; return 1; } static int get_score_row_from_aft_row(struct osl_table *t, const struct osl_row *aft_row, struct osl_row **score_row) { struct osl_object obj = {.data = (struct osl_row *)aft_row, .size = sizeof(aft_row)}; return osl(osl_get_row(t, SCORECOL_AFT_ROW, &obj, score_row)); } struct score_callback_data { int (*cb)(struct osl_row *aft_row, long score, void *data); struct osl_table *t; void *data; }; static int call_callback(struct osl_row *score_row, void *data) { struct score_callback_data *scd = data; long score; struct osl_row *aft_row; int ret = decode_row(scd->t, score_row, &aft_row, &score); if (ret < 0) return ret; return scd->cb(aft_row, score, scd->data); } /** * Call the given function for each row of the score table. * * \param cb Callback, called once per row. * \param t The score table to operate on. * \param data Passed verbatim to the callback. * * \return The return value of the underlying call to osl_rbtree_loop(). The * loop terminates early if the callback returns negative. */ int score_loop(int (*cb)(struct osl_row *, long, void *), struct osl_table *t, void *data) { struct score_callback_data scd = {.cb = cb, .t = t, .data = data}; return osl(osl_rbtree_loop(t, SCORECOL_SCORE, &scd, call_callback)); } /** * Get the admissible audio file with highest score. * * \param aft_row Points to the row in the aft of the "best" audio file. * \param score Highest score value in the score table. * \param t The score table to operate on. * * \return Standard. */ int score_get_best(struct osl_row **aft_row, long *score, struct osl_table *t) { struct osl_row *score_row; int ret = osl(osl_rbtree_last_row(t, SCORECOL_SCORE, &score_row)); if (ret < 0) return ret; return decode_row(t, score_row, aft_row, score); } /** * Make an admissible file non-admissible. * * \param aft_row Identifies the file which has become non-admissible. * \param t The score table to operate on. * * \return One if the row which corresponds to the given file was removed * from the score table. Zero if there was nothing to do because the given * file was not admissible. Negative error code otherwise. * * \sa \ref score_add(). */ int score_delete(const struct osl_row *aft_row, struct osl_table *t) { struct osl_row *score_row; int ret = get_score_row_from_aft_row(t, aft_row, &score_row); if (ret == osl(-E_OSL_RB_KEY_NOT_FOUND)) return 0; if (ret < 0) return ret; ret = osl(osl_del_row(t, score_row)); if (ret < 0) return ret; return 1; } /** * Free all volatile objects, then close the table. * * \param t As returned from \ref score_open(). * * This either succeeds or terminates the calling process. */ void score_close(struct osl_table *t) { if (!t) return; assert(osl_close_table(t, OSL_FREE_VOLATILE) >= 0); } /** * Allocate a score table instance. * * \param result NULL means to open the currently active score table. * * Since the score table does no filesystem I/O, this function always succeeds. * \sa \ref score_close(). */ void score_open(struct osl_table **result) { assert(osl(osl_open_table(&score_table_desc, result)) >= 0); }