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