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