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