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