typo
[paraslash.git] / playlist_selector.c
1 /*
2  * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
3  *
4  *     This program is free software; you can redistribute it and/or modify
5  *     it under the terms of the GNU General Public License as published by
6  *     the Free Software Foundation; either version 2 of the License, or
7  *     (at your option) any later version.
8  *
9  *     This program is distributed in the hope that it will be useful,
10  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *     GNU General Public License for more details.
13  *
14  *     You should have received a copy of the GNU General Public License
15  *     along with this program; if not, write to the Free Software
16  *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
17  */
18
19 /** \file playlist_selector.c The playlist audio file selector of paraslash  */
20
21 #include "server.h"
22 #include "db.h"
23 #include "error.h"
24 #include "net.h"
25 #include "string.h"
26 #include "ipc.h"
27
28 /**
29  * structure used for transmission of the playlist
30  *
31  * There's one such struct which gets initialized during startup. It lives in
32  * shared memory and is used by com_lpl().
33  */
34 struct pls_client_data {
35 /** allocated and set by com_lpl() (child) */
36         int shm_id;
37 /** the size of the shared memory area identified by \a shm_id */
38         size_t size;
39 /** initially locked, gets unlocked by parent when it is done */
40         int mutex;
41 /** return value, set by parent */
42         int retval;
43 };
44
45 /** data specific to the playlist selector */
46 struct private_pls_data {
47 /** guards against concurrent client access */
48         int client_mutex;
49 /** guards against concurrent parent-child access */
50         int server_mutex;
51 /** pointer to the client data */
52         struct pls_client_data *client_data;
53 /** id of the shm corresponding to \a client_data */
54         int client_data_shm_id;
55 };
56
57 /** we refuse to load playlists bigger than that */
58 #define MAX_PLAYLIST_BYTES (1024 * 1024)
59
60 static unsigned playlist_len, playlist_size, current_playlist_entry;
61 static char **playlist;
62 static struct audio_file_selector *self;
63
64 static int com_ppl(int, int, char **);
65 static int com_lpl(int, int, char **);
66 extern struct misc_meta_data *mmd;
67
68 /* array of supported commands */
69 static struct server_command cmds[] = {
70 {
71 .name = "ppl",
72 .handler = com_ppl,
73 .perms = DB_READ,
74 .description = "print playlist",
75 .synopsis = "ppl",
76 .help =
77 "Print out the current playlist"
78 }, {
79 .name = "lpl",
80 .handler = com_lpl,
81 .perms = DB_WRITE,
82 .description = "load playlist",
83 .synopsis = "lpl",
84 .help =
85 "Read a new playlist from stdin. Example:\n"
86 "\tfind /audio -name '*.mp3' | para_client lpl"
87 }, {
88 .name = NULL,
89 }
90 };
91
92 static void playlist_add(char *path)
93 {
94         if (playlist_len >= playlist_size) {
95                 playlist_size = 2 * playlist_size + 1;
96                 playlist = para_realloc(playlist, playlist_size * sizeof(char *));
97         }
98         PARA_DEBUG_LOG("adding #%d/%d: %s\n", playlist_len, playlist_size, path);
99         playlist[playlist_len++] = para_strdup(path);
100 }
101
102 static int send_playlist_to_server(const char *buf, size_t size)
103 {
104         struct private_pls_data *ppd = self->private_data;
105         int ret, shm_mutex = -1, shm_id = -1;
106         void *shm = NULL;
107
108         PARA_DEBUG_LOG("new playlist (%zd bytes)\n", size);
109
110         ret = mutex_new();
111         if (ret < 0)
112                 return ret;
113         shm_mutex = ret;
114
115         ret = shm_new(size);
116         if (ret < 0)
117                 goto out;
118         shm_id = ret;
119
120         ret = shm_attach(shm_id, ATTACH_RW, &shm);
121         if (ret < 0)
122                 goto out;
123         mutex_lock(shm_mutex);
124         memcpy(shm, buf, size);
125         mutex_lock(ppd->client_mutex);
126         mutex_lock(ppd->server_mutex);
127         ppd->client_data->size = size;
128         ppd->client_data->shm_id = shm_id;
129         ppd->client_data->mutex = shm_mutex;
130         kill(getppid(), SIGUSR1); /* wake up the server */
131         mutex_unlock(ppd->server_mutex);
132         mutex_lock(shm_mutex); /* wait until server is done */
133         mutex_unlock(shm_mutex);
134         ret = ppd->client_data->retval;
135         mutex_unlock(ppd->client_mutex);
136         shm_detach(shm);
137 out:
138         if (shm_id >= 0)
139                 shm_destroy(shm_id);
140         mutex_destroy(shm_mutex);
141         PARA_DEBUG_LOG("returning %d\n", ret);
142         return ret;
143 }
144
145 static int com_lpl(int fd, __a_unused int argc, __a_unused char *argv[])
146 {
147         unsigned loaded = 0;
148         size_t bufsize = 4096; /* guess that's enough */
149         char *buf = para_malloc(bufsize);
150         ssize_t ret;
151         ret = send_buffer(fd, AWAITING_DATA_MSG);
152         if (ret < 0)
153                 goto out;
154 again:
155         ret = recv_bin_buffer(fd, buf + loaded, bufsize - loaded);
156         if (ret < 0)
157                 goto out;
158         if (!ret) {
159                 ret = send_playlist_to_server(buf, loaded);
160                 goto out;
161         }
162         loaded += ret;
163         ret = -E_LOAD_PLAYLIST;
164         if (loaded >= MAX_PLAYLIST_BYTES)
165                 goto out;
166         if (loaded >= bufsize) {
167                 bufsize *= 2;
168                 buf = para_realloc(buf, bufsize);
169         }
170         goto again;
171 out:
172         free(buf);
173         return ret;
174 }
175
176 static int com_ppl(int fd, __a_unused int argc, __a_unused char *argv[])
177 {
178         unsigned i;
179
180         PARA_DEBUG_LOG("sending playlist to client (%d entries)\n", playlist_len);
181         for (i = 0; i < playlist_len; i++) {
182                 int ret = send_va_buffer(fd, "%s\n", playlist[
183                         (i + current_playlist_entry) % playlist_len]);
184                 if (ret < 0)
185                         return ret;
186         }
187         return 1;
188 }
189
190 static char **pls_get_audio_file_list(unsigned int num)
191 {
192         char **file_list;
193         unsigned i;
194
195         num = PARA_MIN(num, playlist_len);
196         if (!num)
197                 return NULL;
198         file_list = para_malloc((num + 1) * sizeof(char *));
199         for (i = 0; i < num; i++) {
200                 unsigned j = (current_playlist_entry + i + 1) % playlist_len;
201                 file_list[i] = para_strdup(playlist[j]);
202         }
203         file_list[i] = NULL;
204         return file_list;
205 }
206
207 static void free_playlist_contents(void)
208 {
209         int i;
210
211         PARA_DEBUG_LOG("freeing playlist (%d entries)\n", playlist_len);
212         for (i = 0; i < playlist_len; i++)
213                 free(playlist[i]);
214         current_playlist_entry = 0;
215         playlist_len = 0;
216 }
217
218 static void pls_shutdown(void)
219 {
220         struct private_pls_data *ppd = self->private_data;
221
222         shm_detach(ppd->client_data);
223         shm_destroy(ppd->client_data_shm_id);
224         mutex_destroy(ppd->server_mutex);
225         mutex_destroy(ppd->client_mutex);
226         free(ppd);
227         free_playlist_contents();
228         free(playlist);
229         playlist = NULL;
230         playlist_len = 0;
231         playlist_size = 0;
232 }
233
234 static void pls_post_select(__a_unused fd_set *rfds, __a_unused fd_set *wfds)
235 {
236         struct private_pls_data *ppd = self->private_data;
237         struct pls_client_data *pcd = ppd->client_data;
238         int ret;
239         void *shm;
240
241         mutex_lock(ppd->server_mutex);
242         if (!pcd->size)
243                 goto out;
244         free_playlist_contents();
245         ret = shm_attach(pcd->shm_id, ATTACH_RW, &shm);
246         if (ret < 0) {
247                 PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret));
248                 goto out;
249         }
250         PARA_DEBUG_LOG("loading new playlist (%zd bytes)\n", pcd->size);
251         ret = for_each_line((char *)shm, pcd->size, &playlist_add);
252         shm_detach(shm);
253         PARA_NOTICE_LOG("new playlist (%d entries)\n", playlist_len);
254         sprintf(mmd->selector_info, "dbinfo1:new playlist: %d files\n"
255                 "dbinfo2:\ndbinfo3:\n", playlist_len);
256         pcd->retval = 1;
257         pcd->size = 0;
258         mutex_unlock(pcd->mutex);
259 out:
260         mutex_unlock(ppd->server_mutex);
261 }
262
263 static size_t string_offset(const char *str, size_t max)
264 {
265         size_t l = strlen(str);
266
267         if (l <= max)
268                 return 0;
269         return l - max;
270 }
271
272 void pls_update_audio_file(char *audio_file)
273 {
274         unsigned i;
275         char *dir = para_dirname(audio_file),
276                 *prev = playlist[current_playlist_entry % playlist_len];
277         size_t dir_off = string_offset(dir, 50),
278                 prev_off = string_offset(prev, 70);
279
280         for (i = 1; i <= playlist_len; i++) {
281                 char *next;
282                 size_t next_off;
283                 unsigned j = (current_playlist_entry + i) % playlist_len;
284                 if (strcmp(playlist[j], audio_file))
285                         continue;
286                 current_playlist_entry = j;
287                 next = playlist[(j + 1) %playlist_len];
288                 next_off = string_offset(next, 70);
289                 snprintf(mmd->selector_info, MMD_INFO_SIZE,
290                         "dbinfo1: %d files, current dir: %s%s\n"
291                         "dbinfo2: prev: %s%s\n"
292                         "dbinfo3: next: %s%s\n",
293                         playlist_len,
294                         dir_off? "... " : "", dir + dir_off,
295                         prev_off? "... " : "", prev + prev_off,
296                         next_off? "... " : "", next + next_off
297                 );
298                 break;
299         }
300         free(dir);
301 }
302
303 /**
304  * the init function for the playlist selector
305  *
306  * Init all function pointers of \a db
307  *
308  * \sa struct audio_file_selector, misc_meta_data::selector_info, mysql.c
309  * random_selector.c.
310  */
311 int playlist_selector_init(struct audio_file_selector *db)
312 {
313         int ret;
314         struct private_pls_data *ppd = NULL;
315         void *shm = NULL;
316
317         self = db;
318         db->cmd_list = cmds;
319         db->get_audio_file_list = pls_get_audio_file_list;
320         db->shutdown = pls_shutdown;
321         db->post_select = pls_post_select;
322         db->update_audio_file = pls_update_audio_file;
323         ppd = para_calloc(sizeof(struct private_pls_data));
324         db->private_data = ppd;
325
326         ppd->client_mutex = -1;
327         ppd->server_mutex = -1;
328         ppd->client_data_shm_id = -1;
329         ppd->client_data = NULL;
330
331         ret = mutex_new();
332         if (ret < 0)
333                 goto err_out;
334         ppd->client_mutex = ret;
335
336         ret = mutex_new();
337         if (ret < 0)
338                 goto err_out;
339         ppd->server_mutex = ret;
340
341         ret = shm_new(sizeof(struct pls_client_data));
342         if (ret < 0)
343                 goto err_out;
344         ppd->client_data_shm_id = ret;
345
346         ret = shm_attach(ppd->client_data_shm_id, ATTACH_RW, &shm);
347         if (ret < 0)
348                 goto err_out;
349         ppd->client_data = shm;
350         ppd->client_data->size = 0;
351         sprintf(mmd->selector_info, "dbinfo1: Welcome to the playlist "
352                 "selector\ndbinfo2: no playlist loaded\ndbinfo3:\n");
353         return 1;
354 err_out:
355         if (ppd->client_data_shm_id >= 0)
356                 shm_destroy(ppd->client_data_shm_id);
357         if (ppd->client_mutex >= 0)
358                 mutex_destroy(ppd->client_mutex);
359         if (ppd->server_mutex >= 0)
360                 mutex_destroy(ppd->server_mutex);
361         free(ppd);
362         return ret;
363 }