/* SPDX-License-Identifier: GPL-2.0 */ #include #include #include "para.h" #include "error.h" #include "string.h" #include "afh.h" #include "afs.h" #include "ipc.h" /** \file playlist.c Functions for loading and saving playlists. */ /** * The state of a playlist instance. * * A structure of this type is allocated and initialized at playlist load time. */ struct selector_instance { /** The name of the playlist. */ char *name; /** The number of entries currently in the playlist. */ unsigned length; /** Contains all valid paths of the playlist. */ struct osl_table *score_table; }; static int add_playlist_entry(char *path, void *data) { struct selector_instance *si = data; struct osl_row *aft_row; int ret = aft_get_row_of_path(path, &aft_row); if (ret < 0) { PARA_NOTICE_LOG("%s: %s\n", path, para_strerror(-ret)); return 1; } ret = score_add(aft_row, -si->length, si->score_table); if (ret < 0) { PARA_ERROR_LOG("failed to add %s: %s\n", path, para_strerror(-ret)); return ret; } si->length++; return 1; } static int check_playlist_path(char *path, void *data) { struct afs_callback_arg *aca = data; struct osl_row *aft_row; int ret = aft_get_row_of_path(path, &aft_row); if (ret < 0) para_printf(&aca->pbout, "%s: %s\n", path, para_strerror(-ret)); return 1; /* do not fail the loop on bad paths */ } static int check_playlist(struct osl_row *row, void *data) { struct afs_callback_arg *aca = data; struct para_buffer *pb = &aca->pbout; struct osl_object playlist_def; char *playlist_name; int ret = pl_get_name_and_def_by_row(row, &playlist_name, &playlist_def); if (ret < 0) { if (ret == osl(-E_OSL_EMPTY)) { para_printf(&aca->pbout, "not checking empty playlist %s\n", playlist_name); return 0; } para_printf(pb, "failed to get playlist data: %s\n", para_strerror(-ret)); return ret; } if (*playlist_name) { /* skip dummy row */ para_printf(pb, "checking playlist %s...\n", playlist_name); for_each_line(FELF_READ_ONLY, playlist_def.data, playlist_def.size, check_playlist_path, aca); } osl_close_disk_object(&playlist_def); return 1; } /* Invalid paths are reported, but are not considered an error. */ static int playlist_check(struct afs_callback_arg *aca) { para_printf(&aca->pbout, "checking playlists...\n"); return osl(osl_rbtree_loop(playlists_table, BLOBCOL_ID, aca, check_playlist)); } static void playlist_unload(struct selector_instance *si) { score_close(si->score_table); free(si->name); free(si); } /* * Populate the score table from the paths of a playlist database object. * * This loads the blob object which corresponds to the given name from the * playlist table. Each line of the blob is regarded as a path to be looked * up in the audio file table. If the path lookup succeeds, a reference to * the corresponding row of the audio file table is added to the score table. * * Returns the length of the loaded playlist on success, negative error code * else. Files which are listed in the playlist, but are not contained in * the database are ignored. This is not considered an error. */ static int playlist_load(const char *name, struct para_buffer *pbout, struct afs_callback_arg *aca, struct selector_instance **result) { int ret; struct selector_instance *si; struct osl_object playlist_def; *result = NULL; if (!name || !*name) { afs_error(aca, "empty playlist name\n"); return -ERRNO_TO_PARA_ERROR(EINVAL); } ret = pl_get_def_by_name(name, &playlist_def); if (ret < 0) goto err; si = zalloc(sizeof(*si)); score_open(&si->score_table); ret = for_each_line(FELF_READ_ONLY, playlist_def.data, playlist_def.size, add_playlist_entry, si); osl_close_disk_object(&playlist_def); if (ret < 0) goto close_score_table; ret = -E_PLAYLIST_EMPTY; if (si->length == 0) goto close_score_table; /* success */ if (pbout) para_printf(pbout, "playlist %s: %u files\n", name, si->length); si->name = para_strdup(name); *result = si; return si->length; close_score_table: score_close(si->score_table); free(si); err: afs_error(aca, "unable to load playlist %s\n", name); return ret; } static int playlist_loop(int (*cb)(struct osl_row *aft_row, long score, void *data), struct selector_instance *si, void *data) { return score_loop(cb, si->score_table, data); } static int search_path(char *path, void *data) { if (strcmp(path, data)) return 1; return -E_PATH_FOUND; } static int handle_add_or_rename(struct osl_row *aft_row, struct osl_table *score_table) { int ret; struct osl_object playlist_def; char *path; ret = score_delete(aft_row, score_table); if (ret < 0) return ret; if (ret > 0) current_selector_instance->length--; ret = get_audio_file_path_of_row(aft_row, &path); if (ret < 0) return ret; ret = pl_get_def_by_name(current_selector_instance->name, &playlist_def); if (ret < 0) return ret; ret = for_each_line(FELF_READ_ONLY, playlist_def.data, playlist_def.size, search_path, path); osl_close_disk_object(&playlist_def); if (ret == -E_PATH_FOUND) { /* path is in playlist */ ret = score_add(aft_row, 0, NULL); /* play it immediately */ if (ret >= 0) current_selector_instance->length++; } return ret; } /** * Handle afs events relevant to playlists. * * \param event The event type. * \param data Depends on the event type. * * \return Standard. */ int playlists_event_handler(enum afs_events event, void *data) { struct osl_table *score_table; if (!current_selector_instance || current_selector_id != SEL_PLAYLIST) return 1; score_table = current_selector_instance->score_table; switch (event) { case VSS_NEW_AUDIO_FILE: return score_move_to_end(data, score_table); case AUDIO_FILE_ADD: case AUDIO_FILE_RENAME: return handle_add_or_rename(data, score_table); case AUDIO_FILE_REMOVE: { int ret = score_delete(data, score_table); if (ret > 0) /* file was in the playlist */ current_selector_instance->length--; return ret; } default: return 1; } } static int playlist_get_best(struct osl_row **aft_row, long *score, struct selector_instance *si) { return score_get_best(aft_row, score, si->score_table); } static int playlist_invalidate(struct osl_row *aft_row, struct selector_instance *si) { return score_delete(aft_row, si->score_table); } /** * Functions for playlist handling. * * The playlist selector reads a playlist stored in the playlist blob table * and considers an audio file amissible if it is listed there. * * \sa \ref playlist_selector_operations. */ const struct selector_operations playlist_selector_operations = { .load = playlist_load, .loop = playlist_loop, .unload = playlist_unload, .check = playlist_check, .get_best = playlist_get_best, .invalidate = playlist_invalidate, };