]> git.tuebingen.mpg.de Git - paraslash.git/blob - playlist.c
paraslash 0.7.3
[paraslash.git] / playlist.c
1 /* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
2
3 #include <regex.h>
4 #include <osl.h>
5 #include <lopsub.h>
6
7 #include "para.h"
8 #include "error.h"
9 #include "string.h"
10 #include "afh.h"
11 #include "afs.h"
12 #include "ipc.h"
13 #include "sideband.h"
14
15 /** \file playlist.c Functions for loading and saving playlists. */
16
17 /**
18  * The state of a playlist instance.
19  *
20  * A structure of this type is allocated and initialized at playlist load time.
21  */
22 struct playlist_instance {
23         /** The name of the playlist. */
24         char *name;
25         /** The number of entries currently in the playlist. */
26         unsigned length;
27         /** Contains all valid paths of the playlist. */
28         struct osl_table *score_table;
29 };
30 static struct playlist_instance current_playlist;
31
32 /**
33  * Re-insert an audio file into the tree of admissible files.
34  *
35  * \param aft_row Determines the audio file.
36  *
37  * \return The return value of score_update().
38  */
39 static int playlist_update_audio_file(const struct osl_row *aft_row)
40 {
41         /* always re-insert to the top of the tree */
42         return score_update(aft_row, 0);
43 }
44
45 static int add_playlist_entry(char *path, void *data)
46 {
47         struct playlist_instance *pi = data;
48         struct osl_row *aft_row;
49         int ret = aft_get_row_of_path(path, &aft_row);
50
51         if (ret < 0) {
52                 PARA_NOTICE_LOG("%s: %s\n", path, para_strerror(-ret));
53                 return 1;
54         }
55         ret = score_add(aft_row, -pi->length, pi->score_table);
56         if (ret < 0) {
57                 PARA_ERROR_LOG("failed to add %s: %s\n", path, para_strerror(-ret));
58                 return ret;
59         }
60         pi->length++;
61         return 1;
62 }
63
64 static int check_playlist_path(char *path, void *data)
65 {
66         struct afs_callback_arg *aca = data;
67         struct osl_row *aft_row;
68         int ret = aft_get_row_of_path(path, &aft_row);
69
70         if (ret < 0)
71                 afs_error(aca, "%s: %s\n", path, para_strerror(-ret));
72         return 1; /* do not fail the loop on bad paths */
73 }
74
75 static int check_playlist(struct osl_row *row, void *data)
76 {
77         struct afs_callback_arg *aca = data;
78         struct para_buffer *pb = &aca->pbout;
79         struct osl_object playlist_def;
80         char *playlist_name;
81         int ret = pl_get_name_and_def_by_row(row, &playlist_name, &playlist_def);
82
83         if (ret < 0) { /* log error, but continue */
84                 afs_error(aca, "failed to get playlist data: %s\n",
85                         para_strerror(-ret));
86                 return 1;
87         }
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);
92         }
93         osl_close_disk_object(&playlist_def);
94         return 1;
95 }
96
97 /**
98  * Check the playlist table for inconsistencies.
99  *
100  * \param aca This callback ignores ->query.
101  *
102  * \return Standard. Invalid paths are reported, but are not considered an
103  * error.
104  */
105 int playlist_check_callback(struct afs_callback_arg *aca)
106 {
107         para_printf(&aca->pbout, "checking playlists...\n");
108         return osl(osl_rbtree_loop(playlists_table, BLOBCOL_ID, aca,
109                 check_playlist));
110 }
111
112 /**
113  * Free all resources of the given/current playlist.
114  *
115  * \param pi NULL means to unload the current playlist.
116  */
117 void playlist_unload(struct playlist_instance *pi)
118 {
119         if (pi) {
120                 score_close(pi->score_table);
121                 free(pi->name);
122                 free(pi);
123                 return;
124         }
125         if (!current_playlist.name)
126                 return;
127         score_clear();
128         free(current_playlist.name);
129         current_playlist.name = NULL;
130         current_playlist.length = 0;
131 }
132
133 /**
134  * Populate the score table from the paths of a playlist database object.
135  *
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.
140  *
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.
144  *
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.
148  */
149 int playlist_load(const char *name, struct playlist_instance **result, char **msg)
150 {
151         int ret;
152         struct playlist_instance *pi;
153         struct osl_object playlist_def;
154
155         if (!name || !*name) {
156                 if (msg)
157                         *msg = make_message("empty playlist name\n");
158                 return -ERRNO_TO_PARA_ERROR(EINVAL);
159         }
160         ret = pl_get_def_by_name(name, &playlist_def);
161         if (ret < 0)
162                 goto err;
163         pi = zalloc(sizeof(*pi));
164         if (result)
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);
169         if (ret < 0)
170                 goto close_score_table;
171         ret = -E_PLAYLIST_EMPTY;
172         if (pi->length == 0)
173                 goto close_score_table;
174         /* success */
175         if (msg)
176                 *msg = make_message("loaded playlist %s (%u files)\n", name,
177                         pi->length);
178         pi->name = para_strdup(name);
179         if (result)
180                 *result = pi;
181         else {
182                 playlist_unload(NULL);
183                 current_playlist = *pi;
184         }
185         return pi->length;
186 close_score_table:
187         if (result)
188                 score_close(pi->score_table);
189         free(pi);
190 err:
191         PARA_NOTICE_LOG("unable to load playlist %s\n", name);
192         if (msg)
193                 *msg = make_message("unable to load playlist %s\n", name);
194         return ret;
195 }
196
197 /**
198  * Iterate over all admissible audio files of a playlist instance.
199  *
200  * This wrapper around \ref score_loop() is the playlist counterpart of \ref
201  * mood_loop().
202  *
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().
206  *
207  * \return See \ref score_loop(), \ref mood_loop().
208  */
209 int playlist_loop(struct playlist_instance *pi, osl_rbtree_loop_func *func, void *data)
210 {
211         return score_loop(func, pi->score_table, data);
212 }
213
214 static int search_path(char *path, void *data)
215 {
216         if (strcmp(path, data))
217                 return 1;
218         return -E_PATH_FOUND;
219 }
220
221 static int handle_audio_file_event(enum afs_events event, void *data)
222 {
223         int ret;
224         bool was_admissible = false, is_admissible;
225         struct osl_object playlist_def;
226         char *new_path;
227         const struct osl_row *row = data;
228
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);
232         if (ret < 0)
233                 return ret;
234         ret = pl_get_def_by_name(current_playlist.name, &playlist_def);
235         if (ret < 0)
236                 return ret;
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)
242                 return 1;
243         if (!was_admissible && !is_admissible)
244                 return 1;
245         if (was_admissible && !is_admissible) {
246                 current_playlist.length--;
247                 return score_delete(row);
248         }
249         /* !was_admissible && is_admissible */
250         current_playlist.length++;
251         return score_add(row, 0, NULL); /* play it immediately */
252 }
253
254 /**
255  * Handle afs events relevant to playlists.
256  *
257  * \param event The event type.
258  * \param pb Unused.
259  * \param data Depends on the event type.
260  *
261  * \return Standard.
262  */
263 int playlists_event_handler(enum afs_events event,
264         __a_unused struct para_buffer *pb, void *data)
265 {
266         struct afsi_change_event_data *aced = data;
267
268         if (!current_playlist.name)
269                 return 1;
270         switch (event) {
271         case AFSI_CHANGE:
272                 return playlist_update_audio_file(aced->aft_row);
273         case AUDIO_FILE_RENAME:
274         case AUDIO_FILE_ADD:
275                 return handle_audio_file_event(event, data);
276         case AUDIO_FILE_REMOVE:
277                 if (!row_belongs_to_score_table(data))
278                         return 1;
279                 current_playlist.length--;
280                 return score_delete(data);
281         default:
282                 return 1;
283         }
284 }