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