1 /* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
15 /** \file playlist.c Functions for loading and saving playlists. */
18 * The state of a playlist instance.
20 * A structure of this type is allocated and initialized at playlist load time.
22 struct playlist_instance {
23 /** The name of the playlist. */
25 /** The number of entries currently in the playlist. */
27 /** Contains all valid paths of the playlist. */
28 struct osl_table *score_table;
30 static struct playlist_instance current_playlist;
33 * Re-insert an audio file into the tree of admissible files.
35 * \param aft_row Determines the audio file.
37 * \return The return value of score_update().
39 static int playlist_update_audio_file(const struct osl_row *aft_row)
41 /* always re-insert to the top of the tree */
42 return score_update(aft_row, 0);
45 static int add_playlist_entry(char *path, void *data)
47 struct playlist_instance *pi = data;
48 struct osl_row *aft_row;
49 int ret = aft_get_row_of_path(path, &aft_row);
52 PARA_NOTICE_LOG("%s: %s\n", path, para_strerror(-ret));
55 ret = score_add(aft_row, -pi->length, pi->score_table);
57 PARA_ERROR_LOG("failed to add %s: %s\n", path, para_strerror(-ret));
64 static int check_playlist_path(char *path, void *data)
66 struct afs_callback_arg *aca = data;
67 struct osl_row *aft_row;
68 int ret = aft_get_row_of_path(path, &aft_row);
71 afs_error(aca, "%s: %s\n", path, para_strerror(-ret));
72 return 1; /* do not fail the loop on bad paths */
75 static int check_playlist(struct osl_row *row, void *data)
77 struct afs_callback_arg *aca = data;
78 struct para_buffer *pb = &aca->pbout;
79 struct osl_object playlist_def;
81 int ret = pl_get_name_and_def_by_row(row, &playlist_name, &playlist_def);
83 if (ret < 0) { /* log error, but continue */
84 afs_error(aca, "failed to get playlist data: %s\n",
88 if (*playlist_name) { /* skip dummy row */
89 para_printf(pb, "checking playlist %s...\n", playlist_name);
90 for_each_line(FELF_READ_ONLY, playlist_def.data,
91 playlist_def.size, check_playlist_path, aca);
93 osl_close_disk_object(&playlist_def);
98 * Check the playlist table for inconsistencies.
100 * \param aca This callback ignores ->query.
102 * \return Standard. Invalid paths are reported, but are not considered an
105 int playlist_check_callback(struct afs_callback_arg *aca)
107 para_printf(&aca->pbout, "checking playlists...\n");
108 return osl(osl_rbtree_loop(playlists_table, BLOBCOL_ID, aca,
113 * Free all resources of the given/current playlist.
115 * \param pi NULL means to unload the current playlist.
117 void playlist_unload(struct playlist_instance *pi)
120 score_close(pi->score_table);
125 if (!current_playlist.name)
128 free(current_playlist.name);
129 current_playlist.name = NULL;
130 current_playlist.length = 0;
134 * Populate the score table from the paths of a playlist database object.
136 * This loads the blob object which corresponds to the given name from the
137 * playlist table. Each line of the blob is regarded as a path which is looked
138 * up in the audio file table. If the path lookup succeeds, a reference to the
139 * corresponding row of the audio file table is added to the score table.
141 * \param name The name of the playlist to load.
142 * \param result Opaque, refers to the underlying score table.
143 * \param msg Error message or playlist info is returned here.
145 * \return The length of the loaded playlist on success, negative error code
146 * else. Files which are listed in the playlist, but are not contained in the
147 * database are ignored. This is not considered an error.
149 int playlist_load(const char *name, struct playlist_instance **result, char **msg)
152 struct playlist_instance *pi;
153 struct osl_object playlist_def;
155 if (!name || !*name) {
157 *msg = make_message("empty playlist name\n");
158 return -ERRNO_TO_PARA_ERROR(EINVAL);
160 ret = pl_get_def_by_name(name, &playlist_def);
163 pi = zalloc(sizeof(*pi));
165 score_open(&pi->score_table);
166 ret = for_each_line(FELF_READ_ONLY, playlist_def.data,
167 playlist_def.size, add_playlist_entry, pi);
168 osl_close_disk_object(&playlist_def);
170 goto close_score_table;
171 ret = -E_PLAYLIST_EMPTY;
173 goto close_score_table;
176 *msg = make_message("loaded playlist %s (%u files)\n", name,
178 pi->name = para_strdup(name);
182 playlist_unload(NULL);
183 current_playlist = *pi;
188 score_close(pi->score_table);
191 PARA_NOTICE_LOG("unable to load playlist %s\n", name);
193 *msg = make_message("unable to load playlist %s\n", name);
198 * Iterate over all admissible audio files of a playlist instance.
200 * This wrapper around \ref score_loop() is the playlist counterpart of \ref
203 * \param pi Determines the score table to iterate. Must not be NULL.
204 * \param func See \ref score_loop().
205 * \param data See \ref score_loop().
207 * \return See \ref score_loop(), \ref mood_loop().
209 int playlist_loop(struct playlist_instance *pi, osl_rbtree_loop_func *func, void *data)
211 return score_loop(func, pi->score_table, data);
214 static int search_path(char *path, void *data)
216 if (strcmp(path, data))
218 return -E_PATH_FOUND;
221 static int handle_audio_file_event(enum afs_events event, void *data)
224 bool was_admissible = false, is_admissible;
225 struct osl_object playlist_def;
227 const struct osl_row *row = data;
229 if (event == AUDIO_FILE_RENAME)
230 was_admissible = row_belongs_to_score_table(row);
231 ret = get_audio_file_path_of_row(row, &new_path);
234 ret = pl_get_def_by_name(current_playlist.name, &playlist_def);
237 ret = for_each_line(FELF_READ_ONLY, playlist_def.data,
238 playlist_def.size, search_path, new_path);
239 osl_close_disk_object(&playlist_def);
240 is_admissible = (ret < 0);
241 if (was_admissible && is_admissible)
243 if (!was_admissible && !is_admissible)
245 if (was_admissible && !is_admissible) {
246 current_playlist.length--;
247 return score_delete(row);
249 /* !was_admissible && is_admissible */
250 current_playlist.length++;
251 return score_add(row, 0, NULL); /* play it immediately */
255 * Handle afs events relevant to playlists.
257 * \param event The event type.
259 * \param data Depends on the event type.
263 int playlists_event_handler(enum afs_events event,
264 __a_unused struct para_buffer *pb, void *data)
266 struct afsi_change_event_data *aced = data;
268 if (!current_playlist.name)
272 return playlist_update_audio_file(aced->aft_row);
273 case AUDIO_FILE_RENAME:
275 return handle_audio_file_event(event, data);
276 case AUDIO_FILE_REMOVE:
277 if (!row_belongs_to_score_table(data))
279 current_playlist.length--;
280 return score_delete(data);