59d6bccfac67b3e147373583ad883f44d2e0a598
2 * This file is part of DisOrder.
3 * Copyright (C) 2004-2008 Richard Kettlewell
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.
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.
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
20 /** @file server/actions.c
21 * @brief DisOrder web actions
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.
29 #include "disorder-cgi.h"
31 /** @brief Redirect to some other action or URL */
32 static void redirect(const char *url
) {
33 /* By default use the 'back' argument */
35 url
= cgi_get("back");
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);
41 /* If back= is not set just go back to the front page */
44 if(printf("Location: %s\n"
46 "\n", url
, dcgi_cookie_header()) < 0)
47 fatal(errno
, "error writing to stdout");
50 /* 'playing' and 'manage' just add a Refresh: header */
51 static void act_playing(void) {
52 long refresh
= config
->refresh
;
58 dcgi_lookup(DCGI_PLAYING
|DCGI_QUEUE
|DCGI_ENABLED
|DCGI_RANDOM_ENABLED
);
60 && dcgi_playing
->state
== playing_started
/* i.e. not paused */
61 && !disorder_length(dcgi_client
, dcgi_playing
->track
, &length
)
63 && dcgi_playing
->sofar
>= 0) {
64 /* Try to put the next refresh at the start of the next track. */
66 fin
= now
+ length
- dcgi_playing
->sofar
+ config
->gap
;
67 if(now
+ refresh
> fin
)
70 if(dcgi_queue
&& dcgi_queue
->state
== playing_isscratch
) {
71 /* next track is a scratch, don't leave more than the inter-track gap */
72 if(refresh
> config
->gap
)
73 refresh
= config
->gap
;
77 && dcgi_queue
->state
!= playing_random
)
78 || dcgi_random_enabled
)
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
;
85 if((action
= cgi_get("action")))
86 url
= cgi_makeurl(config
->url
, "action", action
, (char *)0);
89 if(printf("Content-Type: text/html\n"
90 "Refresh: %ld;url=%s\n"
93 refresh
, url
, dcgi_cookie_header()) < 0)
94 fatal(errno
, "error writing to stdout");
95 dcgi_expand("playing");
98 static void act_disable(void) {
100 disorder_disable(dcgi_client
);
104 static void act_enable(void) {
106 disorder_enable(dcgi_client
);
110 static void act_random_disable(void) {
112 disorder_random_disable(dcgi_client
);
116 static void act_random_enable(void) {
118 disorder_random_enable(dcgi_client
);
122 static void act_pause(void) {
124 disorder_pause(dcgi_client
);
128 static void act_resume(void) {
130 disorder_resume(dcgi_client
);
134 static void act_remove(void) {
136 struct queue_entry
*q
;
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
:
146 case playing_no_player
:
148 case playing_quitting
:
149 case playing_scratched
:
150 error(0, "does not make sense to scratch %s", id
);
152 case playing_paused
: /* started but paused */
153 case playing_started
: /* started to play */
154 disorder_scratch(dcgi_client
, id
);
156 case playing_random
: /* unplayed randomly chosen track */
157 case playing_unplayed
: /* haven't played this track yet */
158 disorder_remove(dcgi_client
, id
);
165 static void act_move(void) {
166 const char *id
, *delta
;
167 struct queue_entry
*q
;
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
));
182 error(0, "does not make sense to scratch %s", id
);
189 static void act_play(void) {
190 const char *track
, *dir
;
193 struct dcgi_entry
*e
;
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
))
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");
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
);
215 static int clamp(int n
, int min
, int max
) {
223 static void act_volume(void) {
224 const char *l
, *r
, *d
;
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
));
240 /** @brief Table of actions */
241 static const struct action
{
242 /** @brief Action name */
244 /** @brief Action handler */
245 void (*handler
)(void);
247 { "disable", act_disable
},
248 { "enable", act_enable
},
249 { "manage", act_playing
},
250 { "move", act_move
},
251 { "pause", act_pause
},
252 { "play", act_play
},
253 { "playing", act_playing
},
254 { "randomdisable", act_random_disable
},
255 { "randomenable", act_random_enable
},
256 { "remove", act_remove
},
257 { "resume", act_resume
},
258 { "volume", act_volume
},
261 /** @brief Check that an action name is valid
263 * @return 1 if valid, 0 if not
265 static int dcgi_valid_action(const char *name
) {
268 /* First character must be letter or digit (this also requires there to _be_
269 * a first character) */
270 if(!isalnum((unsigned char)*name
))
272 /* Only letters, digits, '.' and '-' allowed */
273 while((c
= (unsigned char)*name
++)) {
282 /** @brief Expand a template
283 * @param name Base name of template, or NULL to consult CGI args
285 void dcgi_expand(const char *name
) {
286 const char *p
, *found
;
288 /* Parse macros first */
289 if((found
= mx_find("macros.tmpl")))
290 mx_expand_file(found
, sink_discard(), 0);
291 /* For unknown actions check that they aren't evil */
292 if(!dcgi_valid_action(name
))
293 fatal(0, "invalid action name '%s'", name
);
294 byte_xasprintf((char **)&p
, "%s.tmpl", name
);
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
298 || fflush(stdout
) < 0)
299 fatal(errno
, "error writing to stdout");
302 /** @brief Execute a web action
303 * @param action Action to perform, or NULL to consult CGI args
305 * If no recognized action is specified then 'playing' is assumed.
307 void dcgi_action(const char *action
) {
310 /* Consult CGI args if caller had no view */
312 action
= cgi_get("action");
313 /* Pick a default if nobody cares at all */
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
322 /* Make sure 'action' is always set */
323 cgi_set("action", action
);
325 if((n
= TABLE_FIND(actions
, struct action
, name
, action
)) >= 0)
326 /* Its a known action */
327 actions
[n
].handler();
329 /* Just expand the template */
330 if(printf("Content-Type: text/html\n"
332 "\n", dcgi_cookie_header()) < 0)
333 fatal(errno
, "error writing to stdout");
338 /** @brief Generate an error page */
339 void dcgi_error(const char *key
) {
340 dcgi_error_string
= xstrdup(key
);
341 dcgi_expand("error");