2 * This file is part of DisOrder.
3 * Copyright (C) 2004, 2005, 2006 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
26 #include <sys/types.h>
27 #include <sys/socket.h>
44 #include "configuration.h"
56 #include "trackname.h"
58 static void expand(cgi_sink
*output
,
61 static void expandstring(cgi_sink
*output
,
71 static const char *nonce(void) {
72 static unsigned long count
;
75 byte_xasprintf(&s
, "%lx%lx%lx",
76 (unsigned long)time(0),
77 (unsigned long)getpid(),
82 static int compare_entry(const void *a
, const void *b
) {
83 const struct entry
*ea
= a
, *eb
= b
;
85 return compare_tracks(ea
->sort
, eb
->sort
,
86 ea
->display
, eb
->display
,
90 static const char *front_url(void) {
94 /* preserve management interface visibility */
95 if((mgmt
= cgi_get("mgmt")) && !strcmp(mgmt
, "true")) {
96 byte_xasprintf(&url
, "%s?mgmt=true", config
->url
);
102 static void redirect(struct sink
*output
) {
105 cgi_header(output
, "Location",
106 (back
= cgi_get("back")) ? back
: front_url());
110 static void lookups(dcgi_state
*ds
, unsigned want
) {
112 struct queue_entry
*r
, *rnext
;
113 const char *dir
, *re
;
115 if(ds
->g
->client
&& (need
= want
^ (ds
->g
->flags
& want
)) != 0) {
117 disorder_queue(ds
->g
->client
, &ds
->g
->queue
);
118 if(need
& DC_PLAYING
)
119 disorder_playing(ds
->g
->client
, &ds
->g
->playing
);
120 if(need
& DC_RECENT
) {
121 /* we need to reverse the order of the list */
122 disorder_recent(ds
->g
->client
, &r
);
125 r
->next
= ds
->g
->recent
;
131 disorder_get_volume(ds
->g
->client
,
132 &ds
->g
->volume_left
, &ds
->g
->volume_right
);
133 if(need
& (DC_FILES
|DC_DIRS
)) {
134 if(!(dir
= cgi_get("directory")))
136 re
= cgi_get("regexp");
138 if(disorder_directories(ds
->g
->client
, dir
, re
,
139 &ds
->g
->dirs
, &ds
->g
->ndirs
))
142 if(disorder_files(ds
->g
->client
, dir
, re
,
143 &ds
->g
->files
, &ds
->g
->nfiles
))
146 ds
->g
->flags
|= need
;
150 /* actions ********************************************************************/
152 static void act_disable(cgi_sink
*output
,
155 disorder_disable(ds
->g
->client
);
156 redirect(output
->sink
);
159 static void act_enable(cgi_sink
*output
,
162 disorder_enable(ds
->g
->client
);
163 redirect(output
->sink
);
166 static void act_random_disable(cgi_sink
*output
,
169 disorder_random_disable(ds
->g
->client
);
170 redirect(output
->sink
);
173 static void act_random_enable(cgi_sink
*output
,
176 disorder_random_enable(ds
->g
->client
);
177 redirect(output
->sink
);
180 static void act_remove(cgi_sink
*output
,
184 if(!(id
= cgi_get("id"))) fatal(0, "missing id argument");
186 disorder_remove(ds
->g
->client
, id
);
187 redirect(output
->sink
);
190 static void act_move(cgi_sink
*output
,
192 const char *id
, *delta
;
194 if(!(id
= cgi_get("id"))) fatal(0, "missing id argument");
195 if(!(delta
= cgi_get("delta"))) fatal(0, "missing delta argument");
197 disorder_move(ds
->g
->client
, id
, atoi(delta
));
198 redirect(output
->sink
);
201 static void act_scratch(cgi_sink
*output
,
204 disorder_scratch(ds
->g
->client
, cgi_get("id"));
205 redirect(output
->sink
);
208 static void act_playing(cgi_sink
*output
, dcgi_state
*ds
) {
210 long refresh
= config
->refresh
, length
;
212 int random_enabled
= 0;
215 lookups(ds
, DC_PLAYING
|DC_QUEUE
);
216 cgi_header(output
->sink
, "Content-Type", "text/html");
217 disorder_random_enabled(ds
->g
->client
, &random_enabled
);
218 disorder_enabled(ds
->g
->client
, &enabled
);
220 && ds
->g
->playing
->state
== playing_started
/* i.e. not paused */
221 && !disorder_length(ds
->g
->client
, ds
->g
->playing
->track
, &length
)
223 && ds
->g
->playing
->sofar
>= 0) {
224 /* Try to put the next refresh at the start of the next track. */
226 fin
= now
+ length
- ds
->g
->playing
->sofar
+ config
->gap
;
227 if(now
+ refresh
> fin
)
230 if(ds
->g
->queue
&& ds
->g
->queue
->state
== playing_isscratch
) {
231 /* next track is a scratch, don't leave more than the inter-track gap */
232 if(refresh
> config
->gap
)
233 refresh
= config
->gap
;
235 if(!ds
->g
->playing
&& ((ds
->g
->queue
236 && ds
->g
->queue
->state
!= playing_random
)
237 || random_enabled
) && enabled
) {
238 /* no track playing but playing is enabled and there is something coming
239 * up, must be in a gap */
240 if(refresh
> config
->gap
)
241 refresh
= config
->gap
;
243 byte_snprintf(r
, sizeof r
, "%ld;url=%s", refresh
> 0 ? refresh
: 1,
245 cgi_header(output
->sink
, "Refresh", r
);
246 cgi_body(output
->sink
);
247 expand(output
, "playing", ds
);
250 static void act_play(cgi_sink
*output
,
252 const char *track
, *dir
;
257 if((track
= cgi_get("file"))) {
258 disorder_play(ds
->g
->client
, track
);
259 } else if((dir
= cgi_get("directory"))) {
260 if(disorder_files(ds
->g
->client
, dir
, 0, &tracks
, &ntracks
)) ntracks
= 0;
262 e
= xmalloc(ntracks
* sizeof (struct entry
));
263 for(n
= 0; n
< ntracks
; ++n
) {
264 e
[n
].path
= tracks
[n
];
265 e
[n
].sort
= trackname_transform("track", tracks
[n
], "sort");
266 e
[n
].display
= trackname_transform("track", tracks
[n
], "display");
268 qsort(e
, ntracks
, sizeof (struct entry
), compare_entry
);
269 for(n
= 0; n
< ntracks
; ++n
)
270 disorder_play(ds
->g
->client
, e
[n
].path
);
273 /* XXX error handling */
274 redirect(output
->sink
);
277 static int clamp(int n
, int min
, int max
) {
285 static const char *volume_url(void) {
288 byte_xasprintf(&url
, "%s?action=volume", config
->url
);
292 static void act_volume(cgi_sink
*output
, dcgi_state
*ds
) {
293 const char *l
, *r
, *d
, *back
;
294 int nd
, changed
= 0;;
296 if((d
= cgi_get("delta"))) {
297 lookups(ds
, DC_VOLUME
);
298 nd
= clamp(atoi(d
), -255, 255);
299 disorder_set_volume(ds
->g
->client
,
300 clamp(ds
->g
->volume_left
+ nd
, 0, 255),
301 clamp(ds
->g
->volume_right
+ nd
, 0, 255));
303 } else if((l
= cgi_get("left")) && (r
= cgi_get("right"))) {
304 disorder_set_volume(ds
->g
->client
, atoi(l
), atoi(r
));
308 /* redirect back to ourselves (but without the volume-changing bits in the
310 cgi_header(output
->sink
, "Location",
311 (back
= cgi_get("back")) ? back
: volume_url());
312 cgi_body(output
->sink
);
314 cgi_header(output
->sink
, "Content-Type", "text/html");
315 cgi_body(output
->sink
);
316 expand(output
, "volume", ds
);
320 static void act_prefs_errors(const char *msg
,
321 void attribute((unused
)) *u
) {
322 fatal(0, "error splitting parts list: %s", msg
);
325 static const char *numbered_arg(const char *argname
, int numfile
) {
328 byte_xasprintf(&fullname
, "%d_%s", numfile
, argname
);
329 return cgi_get(fullname
);
332 static void process_prefs(dcgi_state
*ds
, int numfile
) {
333 const char *file
, *name
, *value
, *part
, *parts
, *current
, *context
;
336 if(!(file
= numbered_arg("file", numfile
)))
337 /* The first file doesn't need numbering. */
338 if(numfile
> 0 || !(file
= cgi_get("file")))
340 if((parts
= numbered_arg("parts", numfile
))
341 || (parts
= cgi_get("parts"))) {
342 /* Default context is display. Other contexts not actually tested. */
343 if(!(context
= numbered_arg("context", numfile
))) context
= "display";
344 partslist
= split(parts
, 0, 0, act_prefs_errors
, 0);
345 while((part
= *partslist
++)) {
346 if(!(value
= numbered_arg(part
, numfile
)))
348 /* If it's already right (whether regexps or db) don't change anything,
349 * so we don't fill the database up with rubbish. */
350 if(disorder_part(ds
->g
->client
, (char **)¤t
,
351 file
, context
, part
))
352 fatal(0, "disorder_part() failed");
353 if(!strcmp(current
, value
))
355 byte_xasprintf((char **)&name
, "trackname_%s_%s", context
, part
);
356 disorder_set(ds
->g
->client
, file
, name
, value
);
358 if((value
= numbered_arg("random", numfile
)))
359 disorder_unset(ds
->g
->client
, file
, "pick_at_random");
361 disorder_set(ds
->g
->client
, file
, "pick_at_random", "0");
362 if((value
= numbered_arg("tags", numfile
)))
363 disorder_set(ds
->g
->client
, file
, "tags", value
);
364 } else if((name
= cgi_get("name"))) {
365 /* Raw preferences. Not well supported in the templates at the moment. */
366 value
= cgi_get("value");
368 disorder_set(ds
->g
->client
, file
, name
, value
);
370 disorder_unset(ds
->g
->client
, file
, name
);
374 static void act_prefs(cgi_sink
*output
, dcgi_state
*ds
) {
378 if((files
= cgi_get("files"))) nfiles
= atoi(files
);
380 for(numfile
= 0; numfile
< nfiles
; ++numfile
)
381 process_prefs(ds
, numfile
);
382 cgi_header(output
->sink
, "Content-Type", "text/html");
383 cgi_body(output
->sink
);
384 expand(output
, "prefs", ds
);
387 static void act_pause(cgi_sink
*output
,
390 disorder_pause(ds
->g
->client
);
391 redirect(output
->sink
);
394 static void act_resume(cgi_sink
*output
,
397 disorder_resume(ds
->g
->client
);
398 redirect(output
->sink
);
401 static const struct action
{
403 void (*handler
)(cgi_sink
*output
, dcgi_state
*ds
);
405 { "disable", act_disable
},
406 { "enable", act_enable
},
407 { "move", act_move
},
408 { "pause", act_pause
},
409 { "play", act_play
},
410 { "playing", act_playing
},
411 { "prefs", act_prefs
},
412 { "random-disable", act_random_disable
},
413 { "random-enable", act_random_enable
},
414 { "remove", act_remove
},
415 { "resume", act_resume
},
416 { "scratch", act_scratch
},
417 { "volume", act_volume
},
420 /* expansions *****************************************************************/
422 static void exp_include(int attribute((unused
)) nargs
,
426 expand(output
, args
[0], u
);
429 static void exp_server_version(int attribute((unused
)) nargs
,
430 char attribute((unused
)) **args
,
437 if(disorder_version(ds
->g
->client
, (char **)&v
)) v
= "(cannot get version)";
439 v
= "(server not running)";
440 cgi_output(output
, "%s", v
);
443 static void exp_version(int attribute((unused
)) nargs
,
444 char attribute((unused
)) **args
,
446 void attribute((unused
)) *u
) {
447 cgi_output(output
, "%s", disorder_version_string
);
450 static void exp_nonce(int attribute((unused
)) nargs
,
451 char attribute((unused
)) **args
,
453 void attribute((unused
)) *u
) {
454 cgi_output(output
, "%s", nonce());
457 static void exp_label(int attribute((unused
)) nargs
,
460 void attribute((unused
)) *u
) {
461 cgi_output(output
, "%s", cgi_label(args
[0]));
464 struct trackinfo_state
{
466 const struct queue_entry
*q
;
471 static void exp_who(int attribute((unused
)) nargs
,
472 char attribute((unused
)) **args
,
477 if(ds
->track
&& ds
->track
->submitter
)
478 cgi_output(output
, "%s", ds
->track
->submitter
);
481 static void exp_length(int attribute((unused
)) nargs
,
482 char attribute((unused
)) **args
,
489 && (ds
->track
->state
== playing_started
490 || ds
->track
->state
== playing_paused
)
491 && ds
->track
->sofar
>= 0)
492 cgi_output(output
, "%ld:%02ld/",
493 ds
->track
->sofar
/ 60, ds
->track
->sofar
% 60);
494 if(!ds
->track
|| disorder_length(ds
->g
->client
, ds
->track
->track
, &length
))
497 cgi_output(output
, "%ld:%02ld", length
/ 60, length
% 60);
499 sink_printf(output
->sink
, "%s", " ");
502 static void exp_when(int attribute((unused
)) nargs
,
503 char attribute((unused
)) **args
,
507 const struct tm
*w
= 0;
510 switch(ds
->track
->state
) {
511 case playing_isscratch
:
512 case playing_unplayed
:
514 if(ds
->track
->expected
)
515 w
= localtime(&ds
->track
->expected
);
518 case playing_no_player
:
520 case playing_scratched
:
521 case playing_started
:
523 case playing_quitting
:
524 if(ds
->track
->played
)
525 w
= localtime(&ds
->track
->played
);
529 cgi_output(output
, "%d:%02d", w
->tm_hour
, w
->tm_min
);
531 sink_printf(output
->sink
, " ");
534 static void exp_part(int nargs
,
539 const char *s
, *track
, *part
, *context
;
545 track
= ds
->track
->track
;
547 track
= ds
->tracks
[0];
565 if(disorder_part(ds
->g
->client
, (char **)&s
, track
, context
, part
))
566 fatal(0, "disorder_part() failed");
567 cgi_output(output
, "%s", s
);
569 sink_printf(output
->sink
, " ");
572 static void exp_playing(int attribute((unused
)) nargs
,
579 lookups(ds
, DC_PLAYING
);
580 memset(&s
, 0, sizeof s
);
583 s
.track
= ds
->g
->playing
;
584 expandstring(output
, args
[0], &s
);
588 static void exp_queue(int attribute((unused
)) nargs
,
594 struct queue_entry
*q
;
596 lookups(ds
, DC_QUEUE
);
597 memset(&s
, 0, sizeof s
);
600 for(q
= ds
->g
->queue
; q
; q
= q
->next
) {
603 expandstring(output
, args
[0], &s
);
609 static void exp_recent(int attribute((unused
)) nargs
,
615 struct queue_entry
*q
;
617 lookups(ds
, DC_RECENT
);
618 memset(&s
, 0, sizeof s
);
621 for(q
= ds
->g
->recent
; q
; q
= q
->next
) {
624 expandstring(output
, args
[0], &s
);
630 static void exp_url(int attribute((unused
)) nargs
,
631 char attribute((unused
)) **args
,
633 void attribute((unused
)) *u
) {
634 cgi_output(output
, "%s", config
->url
);
642 static int compare_result(const void *a
, const void *b
) {
643 const struct result
*ra
= a
, *rb
= b
;
646 if(!(c
= strcmp(ra
->sort
, rb
->sort
)))
647 c
= strcmp(ra
->track
, rb
->track
);
651 static void exp_search(int nargs
,
655 dcgi_state
*ds
= u
, substate
;
657 const char *q
, *context
, *part
, *template;
673 assert(!"should never happen");
674 part
= context
= template = 0; /* quieten compiler */
676 if(ds
->tracks
== 0) {
677 /* we are the top level, let's get some search results */
678 if(!(q
= cgi_get("query"))) return; /* no results yet */
679 if(disorder_search(ds
->g
->client
, q
, &tracks
, &ntracks
)) return;
683 ntracks
= ds
->ntracks
;
685 assert(ntracks
!= 0);
686 /* sort tracks by the appropriate part */
687 r
= xmalloc(ntracks
* sizeof *r
);
688 for(n
= 0; n
< ntracks
; ++n
) {
689 r
[n
].track
= tracks
[n
];
690 if(disorder_part(ds
->g
->client
, (char **)&r
[n
].sort
,
691 tracks
[n
], context
, part
))
692 fatal(0, "disorder_part() failed");
694 qsort(r
, ntracks
, sizeof (struct result
), compare_result
);
695 /* expand the 2nd arg once for each group. We re-use the passed-in tracks
696 * array as we know it's guaranteed to be big enough and isn't going to be
697 * used for anything else any more. */
698 memset(&substate
, 0, sizeof substate
);
703 substate
.tracks
= tracks
;
704 substate
.ntracks
= 0;
707 && !strcmp(r
[m
].sort
, r
[n
].sort
))
708 tracks
[substate
.ntracks
++] = r
[m
++].track
;
709 substate
.last
= (m
== ntracks
);
710 expandstring(output
, template, &substate
);
715 assert(substate
.last
!= 0);
718 static void exp_arg(int attribute((unused
)) nargs
,
721 void attribute((unused
)) *u
) {
724 if((v
= cgi_get(args
[0])))
725 cgi_output(output
, "%s", v
);
728 static void exp_stats(int attribute((unused
)) nargs
,
729 char attribute((unused
)) **args
,
735 cgi_opentag(output
->sink
, "pre", "class", "stats", (char *)0);
736 if(!disorder_stats(ds
->g
->client
, &v
, 0)) {
738 cgi_output(output
, "%s\n", *v
++);
740 cgi_closetag(output
->sink
, "pre");
743 static void exp_volume(int attribute((unused
)) nargs
,
749 lookups(ds
, DC_VOLUME
);
750 if(!strcmp(args
[0], "left"))
751 cgi_output(output
, "%d", ds
->g
->volume_left
);
753 cgi_output(output
, "%d", ds
->g
->volume_right
);
756 static void exp_shell(int attribute((unused
)) nargs
,
759 void attribute((unused
)) *u
) {
765 if(!(pid
= xfork())) {
770 execlp("sh", "sh", "-c", args
[0], (char *)0);
771 fatal(errno
, "error executing sh");
774 while((n
= read(p
[0], buffer
, sizeof buffer
))) {
776 if(errno
== EINTR
) continue;
777 else fatal(errno
, "error reading from pipe");
779 output
->sink
->write(output
->sink
, buffer
, n
);
782 while((n
= waitpid(pid
, &w
, 0)) < 0 && errno
== EINTR
)
784 if(n
< 0) fatal(errno
, "error calling waitpid");
786 error(0, "shell command '%s' %s", args
[0], wstat(w
));
789 static inline int str2bool(const char *s
) {
790 return !strcmp(s
, "true");
793 static inline const char *bool2str(int n
) {
794 return n ?
"true" : "false";
797 static char *expandarg(const char *arg
, dcgi_state
*ds
) {
803 output
.sink
= sink_dynstr(&d
);
804 expandstring(&output
, arg
, ds
);
805 dynstr_terminate(&d
);
809 static void exp_prefs(int attribute((unused
)) nargs
,
816 const char *file
= expandarg(args
[0], ds
);
818 memset(&substate
, 0, sizeof substate
);
821 if(disorder_prefs(ds
->g
->client
, file
, &k
)) return;
823 substate
.last
= !k
->next
;
825 expandstring(output
, args
[1], &substate
);
832 static void exp_pref(int attribute((unused
)) nargs
,
839 if(!disorder_get(ds
->g
->client
, args
[0], args
[1], &value
))
840 cgi_output(output
, "%s", value
);
843 static void exp_if(int nargs
,
848 int n
= str2bool(expandarg(args
[0], ds
)) ?
1 : 2;
851 expandstring(output
, args
[n
], ds
);
854 static void exp_and(int nargs
,
861 for(n
= 0; n
< nargs
; ++n
)
862 if(!str2bool(expandarg(args
[n
], ds
))) {
866 sink_printf(output
->sink
, "%s", bool2str(result
));
869 static void exp_or(int nargs
,
876 for(n
= 0; n
< nargs
; ++n
)
877 if(str2bool(expandarg(args
[n
], ds
))) {
881 sink_printf(output
->sink
, "%s", bool2str(result
));
884 static void exp_not(int attribute((unused
)) nargs
,
887 void attribute((unused
)) *u
) {
888 sink_printf(output
->sink
, "%s", bool2str(!str2bool(args
[0])));
891 static void exp_isplaying(int attribute((unused
)) nargs
,
892 char attribute((unused
)) **args
,
897 lookups(ds
, DC_PLAYING
);
898 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->playing
));
901 static void exp_isqueue(int attribute((unused
)) nargs
,
902 char attribute((unused
)) **args
,
907 lookups(ds
, DC_QUEUE
);
908 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->queue
));
911 static void exp_isrecent(int attribute((unused
)) nargs
,
912 char attribute((unused
)) **args
,
917 lookups(ds
, DC_RECENT
);
918 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->recent
));
921 static void exp_id(int attribute((unused
)) nargs
,
922 char attribute((unused
)) **args
,
928 cgi_output(output
, "%s", ds
->track
->id
);
931 static void exp_track(int attribute((unused
)) nargs
,
932 char attribute((unused
)) **args
,
938 cgi_output(output
, "%s", ds
->track
->track
);
941 static void exp_parity(int attribute((unused
)) nargs
,
942 char attribute((unused
)) **args
,
947 cgi_output(output
, "%s", ds
->index
% 2 ?
"odd" : "even");
950 static void exp_comment(int attribute((unused
)) nargs
,
951 char attribute((unused
)) **args
,
952 cgi_sink
attribute((unused
)) *output
,
953 void attribute((unused
)) *u
) {
957 static void exp_prefname(int attribute((unused
)) nargs
,
958 char attribute((unused
)) **args
,
963 if(ds
->pref
&& ds
->pref
->name
)
964 cgi_output(output
, "%s", ds
->pref
->name
);
967 static void exp_prefvalue(int attribute((unused
)) nargs
,
968 char attribute((unused
)) **args
,
973 if(ds
->pref
&& ds
->pref
->value
)
974 cgi_output(output
, "%s", ds
->pref
->value
);
977 static void exp_isfiles(int attribute((unused
)) nargs
,
978 char attribute((unused
)) **args
,
983 lookups(ds
, DC_FILES
);
984 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->nfiles
));
987 static void exp_isdirectories(int attribute((unused
)) nargs
,
988 char attribute((unused
)) **args
,
993 lookups(ds
, DC_DIRS
);
994 sink_printf(output
->sink
, "%s", bool2str(!!ds
->g
->ndirs
));
997 static void exp_choose(int attribute((unused
)) nargs
,
1002 dcgi_state substate
;
1006 const char *type
, *what
= expandarg(args
[0], ds
);
1008 if(!strcmp(what
, "files")) {
1009 lookups(ds
, DC_FILES
);
1010 files
= ds
->g
->files
;
1011 nfiles
= ds
->g
->nfiles
;
1013 } else if(!strcmp(what
, "directories")) {
1014 lookups(ds
, DC_DIRS
);
1015 files
= ds
->g
->dirs
;
1016 nfiles
= ds
->g
->ndirs
;
1019 error(0, "unknown @choose@ argument '%s'", what
);
1022 e
= xmalloc(nfiles
* sizeof (struct entry
));
1023 for(n
= 0; n
< nfiles
; ++n
) {
1024 e
[n
].path
= files
[n
];
1025 e
[n
].sort
= trackname_transform(type
, files
[n
], "sort");
1026 e
[n
].display
= trackname_transform(type
, files
[n
], "display");
1028 qsort(e
, nfiles
, sizeof (struct entry
), compare_entry
);
1029 memset(&substate
, 0, sizeof substate
);
1032 for(n
= 0; n
< nfiles
; ++n
) {
1033 substate
.last
= (n
== nfiles
- 1);
1035 substate
.entry
= &e
[n
];
1036 expandstring(output
, args
[1], &substate
);
1041 static void exp_file(int attribute((unused
)) nargs
,
1042 char attribute((unused
)) **args
,
1048 cgi_output(output
, "%s", ds
->entry
->path
);
1050 cgi_output(output
, "%s", ds
->track
->track
);
1052 cgi_output(output
, "%s", ds
->tracks
[0]);
1055 static void exp_transform(int nargs
,
1058 void attribute((unused
)) *u
) {
1059 const char *context
= nargs
> 2 ? args
[2] : "display";
1061 cgi_output(output
, "%s", trackname_transform(args
[1], args
[0], context
));
1064 static void exp_urlquote(int attribute((unused
)) nargs
,
1067 void attribute((unused
)) *u
) {
1068 cgi_output(output
, "%s", urlencodestring(args
[0]));
1071 static void exp_scratchable(int attribute((unused
)) nargs
,
1072 char attribute((unused
)) **args
,
1074 void attribute((unused
)) *u
) {
1078 if(config
->restrictions
& RESTRICT_SCRATCH
) {
1079 lookups(ds
, DC_PLAYING
);
1080 result
= (ds
->g
->playing
1081 && (!ds
->g
->playing
->submitter
1082 || !strcmp(ds
->g
->playing
->submitter
,
1083 disorder_user(ds
->g
->client
))));
1086 sink_printf(output
->sink
, "%s", bool2str(result
));
1089 static void exp_removable(int attribute((unused
)) nargs
,
1090 char attribute((unused
)) **args
,
1092 void attribute((unused
)) *u
) {
1096 if(config
->restrictions
& RESTRICT_REMOVE
)
1098 && ds
->track
->submitter
1099 && !strcmp(ds
->track
->submitter
,
1100 disorder_user(ds
->g
->client
)));
1103 sink_printf(output
->sink
, "%s", bool2str(result
));
1106 static void exp_navigate(int attribute((unused
)) nargs
,
1111 dcgi_state substate
;
1112 const char *path
= expandarg(args
[0], ds
);
1117 memset(&substate
, 0, sizeof substate
);
1119 ptr
= path
+ 1; /* skip root */
1121 substate
.nav_path
= path
;
1124 while(*ptr
&& *ptr
!= '/')
1126 substate
.last
= !*ptr
;
1127 substate
.nav_len
= ptr
- path
;
1128 substate
.nav_dirlen
= dirlen
;
1129 expandstring(output
, args
[1], &substate
);
1130 dirlen
= substate
.nav_len
;
1137 static void exp_fullname(int attribute((unused
)) nargs
,
1138 char attribute((unused
)) **args
,
1142 cgi_output(output
, "%.*s", ds
->nav_len
, ds
->nav_path
);
1145 static void exp_basename(int nargs
,
1153 if((s
= strrchr(args
[0], '/'))) ++s
;
1155 cgi_output(output
, "%s", s
);
1157 cgi_output(output
, "%.*s", ds
->nav_len
- ds
->nav_dirlen
- 1,
1158 ds
->nav_path
+ ds
->nav_dirlen
+ 1);
1161 static void exp_dirname(int nargs
,
1169 if((s
= strrchr(args
[0], '/')))
1170 cgi_output(output
, "%.*s", (int)(s
- args
[0]), args
[0]);
1172 cgi_output(output
, "%.*s", ds
->nav_dirlen
, ds
->nav_path
);
1175 static void exp_eq(int attribute((unused
)) nargs
,
1178 void attribute((unused
)) *u
) {
1179 cgi_output(output
, "%s", bool2str(!strcmp(args
[0], args
[1])));
1182 static void exp_ne(int attribute((unused
)) nargs
,
1185 void attribute((unused
)) *u
) {
1186 cgi_output(output
, "%s", bool2str(strcmp(args
[0], args
[1])));
1189 static void exp_enabled(int attribute((unused
)) nargs
,
1190 char attribute((unused
)) **args
,
1197 disorder_enabled(ds
->g
->client
, &enabled
);
1198 cgi_output(output
, "%s", bool2str(enabled
));
1201 static void exp_random_enabled(int attribute((unused
)) nargs
,
1202 char attribute((unused
)) **args
,
1209 disorder_random_enabled(ds
->g
->client
, &enabled
);
1210 cgi_output(output
, "%s", bool2str(enabled
));
1213 static void exp_trackstate(int attribute((unused
)) nargs
,
1218 struct queue_entry
*q
;
1221 if(disorder_resolve(ds
->g
->client
, &track
, args
[0])) return;
1222 lookups(ds
, DC_QUEUE
|DC_PLAYING
);
1223 if(ds
->g
->playing
&& !strcmp(ds
->g
->playing
->track
, track
))
1224 cgi_output(output
, "playing");
1226 for(q
= ds
->g
->queue
; q
&& strcmp(q
->track
, track
); q
= q
->next
)
1229 cgi_output(output
, "queued");
1233 static void exp_thisurl(int attribute((unused
)) nargs
,
1234 char attribute((unused
)) **args
,
1236 void attribute((unused
)) *u
) {
1237 kvp_set(&cgi_args
, "nonce", nonce()); /* nonces had better differ! */
1238 cgi_output(output
, "%s?%s", config
->url
, kvp_urlencode(cgi_args
, 0));
1241 static void exp_isfirst(int attribute((unused
)) nargs
,
1242 char attribute((unused
)) **args
,
1247 sink_printf(output
->sink
, "%s", bool2str(!!ds
->first
));
1250 static void exp_islast(int attribute((unused
)) nargs
,
1251 char attribute((unused
)) **args
,
1256 sink_printf(output
->sink
, "%s", bool2str(!!ds
->last
));
1259 static void exp_action(int attribute((unused
)) nargs
,
1260 char attribute((unused
)) **args
,
1262 void attribute((unused
)) *u
) {
1263 const char *action
= cgi_get("action"), *mgmt
;
1265 if(!action
) action
= "playing";
1266 if(!strcmp(action
, "playing")
1267 && (mgmt
= cgi_get("mgmt"))
1268 && !strcmp(mgmt
, "true"))
1270 sink_printf(output
->sink
, "%s", action
);
1273 static void exp_resolve(int attribute((unused
)) nargs
,
1276 void attribute((unused
)) *u
) {
1280 if(!disorder_resolve(ds
->g
->client
, &track
, args
[0]))
1281 sink_printf(output
->sink
, "%s", track
);
1284 static void exp_paused(int attribute((unused
)) nargs
,
1285 char attribute((unused
)) **args
,
1291 lookups(ds
, DC_PLAYING
);
1292 if(ds
->g
->playing
&& ds
->g
->playing
->state
== playing_paused
)
1294 cgi_output(output
, "%s", bool2str(paused
));
1297 static void exp_state(int attribute((unused
)) nargs
,
1298 char attribute((unused
)) **args
,
1304 cgi_output(output
, "%s", playing_states
[ds
->track
->state
]);
1307 static void exp_files(int attribute((unused
)) nargs
,
1312 dcgi_state substate
;
1313 const char *nfiles_arg
, *directory
;
1314 int nfiles
, numfile
;
1317 memset(&substate
, 0, sizeof substate
);
1319 if((directory
= cgi_get("directory"))) {
1320 /* Prefs for whole directory. */
1321 lookups(ds
, DC_FILES
);
1322 /* Synthesize args for the file list. */
1323 nfiles
= ds
->g
->nfiles
;
1324 for(numfile
= 0; numfile
< nfiles
; ++numfile
) {
1325 k
= xmalloc(sizeof *k
);
1326 byte_xasprintf((char **)&k
->name
, "%d_file", numfile
);
1327 k
->value
= ds
->g
->files
[numfile
];
1332 /* Args already present. */
1333 if((nfiles_arg
= cgi_get("files"))) nfiles
= atoi(nfiles_arg
);
1336 for(numfile
= 0; numfile
< nfiles
; ++numfile
) {
1337 substate
.index
= numfile
;
1338 expandstring(output
, args
[0], &substate
);
1342 static void exp_index(int attribute((unused
)) nargs
,
1343 char attribute((unused
)) **args
,
1348 cgi_output(output
, "%d", ds
->index
);
1351 static void exp_nfiles(int attribute((unused
)) nargs
,
1352 char attribute((unused
)) **args
,
1356 const char *files_arg
;
1358 if(cgi_get("directory")) {
1359 lookups(ds
, DC_FILES
);
1360 cgi_output(output
, "%d", ds
->g
->nfiles
);
1361 } else if((files_arg
= cgi_get("files")))
1362 cgi_output(output
, "%s", files_arg
);
1364 cgi_output(output
, "1");
1367 static const struct cgi_expansion expansions
[] = {
1368 { "#", 0, INT_MAX
, EXP_MAGIC
, exp_comment
},
1369 { "action", 0, 0, 0, exp_action
},
1370 { "and", 0, INT_MAX
, EXP_MAGIC
, exp_and
},
1371 { "arg", 1, 1, 0, exp_arg
},
1372 { "basename", 0, 1, 0, exp_basename
},
1373 { "choose", 2, 2, EXP_MAGIC
, exp_choose
},
1374 { "dirname", 0, 1, 0, exp_dirname
},
1375 { "enabled", 0, 0, 0, exp_enabled
},
1376 { "eq", 2, 2, 0, exp_eq
},
1377 { "file", 0, 0, 0, exp_file
},
1378 { "files", 1, 1, EXP_MAGIC
, exp_files
},
1379 { "fullname", 0, 0, 0, exp_fullname
},
1380 { "id", 0, 0, 0, exp_id
},
1381 { "if", 2, 3, EXP_MAGIC
, exp_if
},
1382 { "include", 1, 1, 0, exp_include
},
1383 { "index", 0, 0, 0, exp_index
},
1384 { "isdirectories", 0, 0, 0, exp_isdirectories
},
1385 { "isfiles", 0, 0, 0, exp_isfiles
},
1386 { "isfirst", 0, 0, 0, exp_isfirst
},
1387 { "islast", 0, 0, 0, exp_islast
},
1388 { "isplaying", 0, 0, 0, exp_isplaying
},
1389 { "isqueue", 0, 0, 0, exp_isqueue
},
1390 { "isrecent", 0, 0, 0, exp_isrecent
},
1391 { "label", 1, 1, 0, exp_label
},
1392 { "length", 0, 0, 0, exp_length
},
1393 { "navigate", 2, 2, EXP_MAGIC
, exp_navigate
},
1394 { "ne", 2, 2, 0, exp_ne
},
1395 { "nfiles", 0, 0, 0, exp_nfiles
},
1396 { "nonce", 0, 0, 0, exp_nonce
},
1397 { "not", 1, 1, 0, exp_not
},
1398 { "or", 0, INT_MAX
, EXP_MAGIC
, exp_or
},
1399 { "parity", 0, 0, 0, exp_parity
},
1400 { "part", 1, 3, 0, exp_part
},
1401 { "paused", 0, 0, 0, exp_paused
},
1402 { "playing", 1, 1, EXP_MAGIC
, exp_playing
},
1403 { "pref", 2, 2, 0, exp_pref
},
1404 { "prefname", 0, 0, 0, exp_prefname
},
1405 { "prefs", 2, 2, EXP_MAGIC
, exp_prefs
},
1406 { "prefvalue", 0, 0, 0, exp_prefvalue
},
1407 { "queue", 1, 1, EXP_MAGIC
, exp_queue
},
1408 { "random-enabled", 0, 0, 0, exp_random_enabled
},
1409 { "recent", 1, 1, EXP_MAGIC
, exp_recent
},
1410 { "removable", 0, 0, 0, exp_removable
},
1411 { "resolve", 1, 1, 0, exp_resolve
},
1412 { "scratchable", 0, 0, 0, exp_scratchable
},
1413 { "search", 2, 3, EXP_MAGIC
, exp_search
},
1414 { "server-version", 0, 0, 0, exp_server_version
},
1415 { "shell", 1, 1, 0, exp_shell
},
1416 { "state", 0, 0, 0, exp_state
},
1417 { "stats", 0, 0, 0, exp_stats
},
1418 { "thisurl", 0, 0, 0, exp_thisurl
},
1419 { "track", 0, 0, 0, exp_track
},
1420 { "trackstate", 1, 1, 0, exp_trackstate
},
1421 { "transform", 2, 3, 0, exp_transform
},
1422 { "url", 0, 0, 0, exp_url
},
1423 { "urlquote", 1, 1, 0, exp_urlquote
},
1424 { "version", 0, 0, 0, exp_version
},
1425 { "volume", 1, 1, 0, exp_volume
},
1426 { "when", 0, 0, 0, exp_when
},
1427 { "who", 0, 0, 0, exp_who
}
1430 static void expand(cgi_sink
*output
,
1431 const char *template,
1433 cgi_expand(template,
1434 expansions
, sizeof expansions
/ sizeof *expansions
,
1439 static void expandstring(cgi_sink
*output
,
1442 cgi_expand_string("",
1444 expansions
, sizeof expansions
/ sizeof *expansions
,
1449 static void perform_action(cgi_sink
*output
, dcgi_state
*ds
,
1450 const char *action
) {
1453 if((n
= TABLE_FIND(actions
, struct action
, name
, action
)) >= 0)
1454 actions
[n
].handler(output
, ds
);
1456 cgi_header(output
->sink
, "Content-Type", "text/html");
1457 cgi_body(output
->sink
);
1458 expand(output
, action
, ds
);
1462 void disorder_cgi(cgi_sink
*output
, dcgi_state
*ds
) {
1463 const char *action
= cgi_get("action");
1465 if(!action
) action
= "playing";
1466 perform_action(output
, ds
, action
);
1469 void disorder_cgi_error(cgi_sink
*output
, dcgi_state
*ds
,
1471 cgi_set_option("error", msg
);
1472 perform_action(output
, ds
, "error");