chiark / gitweb /
Fill in some missing actions
[disorder] / server / actions.c
CommitLineData
bca4e2b7
RK
1/*
2 * This file is part of DisOrder.
3 * Copyright (C) 2004-2008 Richard Kettlewell
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18 * USA
19 */
1e97629d
RK
20/** @file server/actions.c
21 * @brief DisOrder web actions
22 *
23 * Actions are anything that the web interface does beyond passive template
24 * expansion and inspection of state recieved from the server. This means
25 * playing tracks, editing prefs etc but also setting extra headers e.g. to
26 * auto-refresh the playing list.
27 */
bca4e2b7 28
1e97629d 29#include "disorder-cgi.h"
5a7df048
RK
30
31/** @brief Redirect to some other action or URL */
32static void redirect(const char *url) {
33 /* By default use the 'back' argument */
34 if(!url)
71634563 35 url = cgi_get("back");
5a7df048
RK
36 if(url) {
37 if(!strncmp(url, "http", 4))
38 /* If the target is not a full URL assume it's the action */
39 url = cgi_makeurl(config->url, "action", url, (char *)0);
40 } else {
41 /* If back= is not set just go back to the front page */
42 url = config->url;
43 }
44 if(printf("Location: %s\n"
45 "%s\n"
1e97629d 46 "\n", url, dcgi_cookie_header()) < 0)
5a7df048
RK
47 fatal(errno, "error writing to stdout");
48}
49
448d3570
RK
50/* 'playing' and 'manage' just add a Refresh: header */
51static void act_playing(void) {
52 long refresh = config->refresh;
53 long length;
54 time_t now, fin;
55 char *url;
71634563 56 const char *action;
448d3570 57
1e97629d
RK
58 dcgi_lookup(DCGI_PLAYING|DCGI_QUEUE|DCGI_ENABLED|DCGI_RANDOM_ENABLED);
59 if(dcgi_playing
60 && dcgi_playing->state == playing_started /* i.e. not paused */
61 && !disorder_length(dcgi_client, dcgi_playing->track, &length)
448d3570 62 && length
1e97629d 63 && dcgi_playing->sofar >= 0) {
448d3570
RK
64 /* Try to put the next refresh at the start of the next track. */
65 time(&now);
1e97629d 66 fin = now + length - dcgi_playing->sofar + config->gap;
448d3570
RK
67 if(now + refresh > fin)
68 refresh = fin - now;
69 }
1e97629d 70 if(dcgi_queue && dcgi_queue->state == playing_isscratch) {
448d3570
RK
71 /* next track is a scratch, don't leave more than the inter-track gap */
72 if(refresh > config->gap)
73 refresh = config->gap;
74 }
1e97629d
RK
75 if(!dcgi_playing
76 && ((dcgi_queue
77 && dcgi_queue->state != playing_random)
78 || dcgi_random_enabled)
79 && dcgi_enabled) {
448d3570
RK
80 /* no track playing but playing is enabled and there is something coming
81 * up, must be in a gap */
82 if(refresh > config->gap)
83 refresh = config->gap;
84 }
85 if((action = cgi_get("action")))
86 url = cgi_makeurl(config->url, "action", action, (char *)0);
87 else
88 url = config->url;
89 if(printf("Content-Type: text/html\n"
90 "Refresh: %ld;url=%s\n"
5a7df048 91 "%s\n"
448d3570 92 "\n",
1e97629d 93 refresh, url, dcgi_cookie_header()) < 0)
448d3570 94 fatal(errno, "error writing to stdout");
2257512d 95 dcgi_expand("playing");
1e97629d
RK
96}
97
98static void act_disable(void) {
99 if(dcgi_client)
100 disorder_disable(dcgi_client);
101 redirect(0);
102}
103
104static void act_enable(void) {
105 if(dcgi_client)
106 disorder_enable(dcgi_client);
107 redirect(0);
108}
109
110static void act_random_disable(void) {
111 if(dcgi_client)
112 disorder_random_disable(dcgi_client);
113 redirect(0);
114}
115
116static void act_random_enable(void) {
117 if(dcgi_client)
118 disorder_random_enable(dcgi_client);
119 redirect(0);
448d3570
RK
120}
121
6d9dd8d9
RK
122static void act_pause(void) {
123 if(dcgi_client)
124 disorder_pause(dcgi_client);
125 redirect(0);
126}
127
128static void act_resume(void) {
129 if(dcgi_client)
130 disorder_resume(dcgi_client);
131 redirect(0);
132}
133
a2c4ad5f
RK
134static void act_remove(void) {
135 const char *id;
136 struct queue_entry *q;
137
138 if(dcgi_client) {
139 if(!(id = cgi_get("id")))
140 error(0, "missing 'id' argument");
141 else if(!(q = dcgi_findtrack(id)))
142 error(0, "unknown queue id %s", id);
143 else switch(q->state) {
144 case playing_isscratch:
145 case playing_failed:
146 case playing_no_player:
147 case playing_ok:
148 case playing_quitting:
149 case playing_scratched:
150 error(0, "does not make sense to scratch %s", id);
151 break;
152 case playing_paused: /* started but paused */
153 case playing_started: /* started to play */
154 disorder_scratch(dcgi_client, id);
155 break;
156 case playing_random: /* unplayed randomly chosen track */
157 case playing_unplayed: /* haven't played this track yet */
158 disorder_remove(dcgi_client, id);
159 break;
160 }
161 }
162 redirect(0);
163}
164
6d9dd8d9
RK
165static void act_move(void) {
166 const char *id, *delta;
167 struct queue_entry *q;
168
169 if(dcgi_client) {
170 if(!(id = cgi_get("id")))
171 error(0, "missing 'id' argument");
172 else if(!(delta = cgi_get("delta")))
173 error(0, "missing 'delta' argument");
174 else if(!(q = dcgi_findtrack(id)))
175 error(0, "unknown queue id %s", id);
176 else switch(q->state) {
177 case playing_random: /* unplayed randomly chosen track */
178 case playing_unplayed: /* haven't played this track yet */
179 disorder_move(dcgi_client, id, atol(delta));
180 break;
181 default:
182 error(0, "does not make sense to scratch %s", id);
183 break;
184 }
185 }
186 redirect(0);
187}
188
189static void act_play(void) {
190 const char *track, *dir;
191 char **tracks;
192 int ntracks, n;
193 struct dcgi_entry *e;
194
195 if(dcgi_client) {
196 if((track = cgi_get("file"))) {
197 disorder_play(dcgi_client, track);
198 } else if((dir = cgi_get("dir"))) {
199 if(disorder_files(dcgi_client, dir, 0, &tracks, &ntracks))
200 ntracks = 0;
201 e = xmalloc(ntracks * sizeof (struct dcgi_entry));
202 for(n = 0; n < ntracks; ++n) {
203 e[n].track = tracks[n];
204 e[n].sort = trackname_transform("track", tracks[n], "sort");
205 e[n].display = trackname_transform("track", tracks[n], "display");
206 }
207 qsort(e, ntracks, sizeof (struct dcgi_entry), dcgi_compare_entry);
208 for(n = 0; n < ntracks; ++n)
209 disorder_play(dcgi_client, e[n].track);
210 }
211 }
212 redirect(0);
213}
214
215static int clamp(int n, int min, int max) {
216 if(n < min)
217 return min;
218 if(n > max)
219 return max;
220 return n;
221}
222
223static void act_volume(void) {
224 const char *l, *r, *d;
225 int nd;
226
227 if(dcgi_client) {
228 if((d = cgi_get("delta"))) {
229 dcgi_lookup(DCGI_VOLUME);
230 nd = clamp(atoi(d), -255, 255);
231 disorder_set_volume(dcgi_client,
232 clamp(dcgi_volume_left + nd, 0, 255),
233 clamp(dcgi_volume_right + nd, 0, 255));
234 } else if((l = cgi_get("left")) && (r = cgi_get("right")))
235 disorder_set_volume(dcgi_client, atoi(l), atoi(r));
236 }
237 redirect(0);
238}
239
bca4e2b7
RK
240/** @brief Table of actions */
241static const struct action {
242 /** @brief Action name */
243 const char *name;
244 /** @brief Action handler */
245 void (*handler)(void);
246} actions[] = {
bca4e2b7 247 { "disable", act_disable },
bca4e2b7 248 { "enable", act_enable },
448d3570 249 { "manage", act_playing },
6d9dd8d9
RK
250 { "move", act_move },
251 { "pause", act_pause },
252 { "play", act_play },
bca4e2b7 253 { "playing", act_playing },
1a3dba67
RK
254 { "randomdisable", act_random_disable },
255 { "randomenable", act_random_enable },
a2c4ad5f 256 { "remove", act_remove },
6d9dd8d9
RK
257 { "resume", act_resume },
258 { "volume", act_volume },
bca4e2b7
RK
259};
260
40dcd866
RK
261/** @brief Check that an action name is valid
262 * @param name Action
263 * @return 1 if valid, 0 if not
264 */
265static int dcgi_valid_action(const char *name) {
266 int c;
267
268 /* First character must be letter or digit (this also requires there to _be_
269 * a first character) */
270 if(!isalnum((unsigned char)*name))
271 return 0;
272 /* Only letters, digits, '.' and '-' allowed */
273 while((c = (unsigned char)*name++)) {
274 if(!(isalnum(c)
275 || c == '.'
276 || c == '_'))
277 return 0;
278 }
279 return 1;
280}
281
bca4e2b7
RK
282/** @brief Expand a template
283 * @param name Base name of template, or NULL to consult CGI args
284 */
1e97629d 285void dcgi_expand(const char *name) {
99955407 286 const char *p, *found;
0d0253c9
RK
287
288 /* Parse macros first */
99955407
RK
289 if((found = mx_find("macros.tmpl")))
290 mx_expand_file(found, sink_discard(), 0);
bca4e2b7 291 /* For unknown actions check that they aren't evil */
40dcd866 292 if(!dcgi_valid_action(name))
71634563
RK
293 fatal(0, "invalid action name '%s'", name);
294 byte_xasprintf((char **)&p, "%s.tmpl", name);
99955407
RK
295 if(!(found = mx_find(p)))
296 fatal(errno, "cannot find %s", p);
297 if(mx_expand_file(found, sink_stdio("stdout", stdout), 0) == -1
bca4e2b7
RK
298 || fflush(stdout) < 0)
299 fatal(errno, "error writing to stdout");
300}
301
302/** @brief Execute a web action
303 * @param action Action to perform, or NULL to consult CGI args
304 *
305 * If no recognized action is specified then 'playing' is assumed.
306 */
1e97629d 307void dcgi_action(const char *action) {
bca4e2b7 308 int n;
bca4e2b7
RK
309
310 /* Consult CGI args if caller had no view */
311 if(!action)
312 action = cgi_get("action");
313 /* Pick a default if nobody cares at all */
314 if(!action) {
315 /* We allow URLs which are just c=... in order to keep confirmation URLs,
316 * which are user-facing, as short as possible. Actually we could lose the
317 * c= for this... */
318 if(cgi_get("c"))
319 action = "confirm";
320 else
321 action = "playing";
5a7df048
RK
322 /* Make sure 'action' is always set */
323 cgi_set("action", action);
bca4e2b7
RK
324 }
325 if((n = TABLE_FIND(actions, struct action, name, action)) >= 0)
326 /* Its a known action */
327 actions[n].handler();
448d3570 328 else {
bca4e2b7 329 /* Just expand the template */
448d3570 330 if(printf("Content-Type: text/html\n"
5a7df048 331 "%s\n"
1e97629d 332 "\n", dcgi_cookie_header()) < 0)
448d3570 333 fatal(errno, "error writing to stdout");
1e97629d 334 dcgi_expand(action);
448d3570 335 }
bca4e2b7
RK
336}
337
338/** @brief Generate an error page */
0d0253c9
RK
339void dcgi_error(const char *key) {
340 dcgi_error_string = xstrdup(key);
1e97629d 341 dcgi_expand("error");
bca4e2b7
RK
342}
343
344/*
345Local Variables:
346c-basic-offset:2
347comment-column:40
348fill-column:79
349indent-tabs-mode:nil
350End:
351*/