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