Added %% format flag for %.
[shell-fm.git] / source / interface.c
blobc6420b6455aac65b0c66502bef8bf57e135db353
1 /*
2 Copyright (C) 2006 by Jonas Kramer
3 Published under the terms of the GNU General Public License (GPL).
4 */
6 #define _GNU_SOURCE
8 #include <stdio.h>
9 #include <termios.h>
10 #include <unistd.h>
11 #include <string.h>
12 #include <sys/types.h>
13 #include <stdlib.h>
14 #include <fcntl.h>
15 #include <sys/time.h>
16 #include <signal.h>
17 #include <stdarg.h>
18 #include <ctype.h>
19 #include <time.h>
20 #include <assert.h>
22 #include "service.h"
23 #include "hash.h"
24 #include "interface.h"
25 #include "autoban.h"
26 #include "settings.h"
27 #include "http.h"
28 #include "split.h"
29 #include "bookmark.h"
30 #include "radio.h"
31 #include "md5.h"
32 #include "submit.h"
33 #include "readline.h"
34 #include "xmlrpc.h"
35 #include "recommend.h"
36 #include "util.h"
38 #include "globals.h"
40 extern time_t pausetime;
42 struct hash track;
44 char * shellescape(const char *);
46 void interface(int interactive) {
47 if(interactive) {
48 int key, result;
49 char customkey[8] = { 0 }, * marked = NULL;
51 canon(0);
52 fflush(stderr);
53 if((key = fetchkey(1000000)) == -1)
54 return;
56 if(key == 27) {
57 int ch;
58 while((ch = fetchkey(100000)) != -1 && !strchr("ABCDEFGHMPQRSZojmk~", ch));
59 return;
62 switch(key) {
63 case 'l':
64 puts(rate("L") ? "Loved." : "Sorry, failed.");
65 break;
67 case 'U':
68 puts(rate("U") ? "Unloved." : "Sorry, failed.");
69 break;
71 case 'B':
72 puts(rate("B") ? "Banned." : "Sorry, failed.");
73 fflush(stdout);
74 enable(INTERRUPTED);
75 kill(playfork, SIGUSR1);
76 break;
78 case 'n':
79 rate("S");
80 break;
82 case 'q':
83 if(haskey(& rc, "delay-change")) {
84 delayquit = !delayquit;
85 if(delayquit)
86 fputs("Going to quit soon.\n", stderr);
87 else
88 fputs("Delayed quit cancelled.\n", stderr);
90 break;
92 case 'Q':
93 unlink(rcpath("session"));
94 exit(EXIT_SUCCESS);
96 case 'i':
97 if(playfork) {
98 const char * path = rcpath("i-template");
99 if(path && !access(path, R_OK)) {
100 char ** template = slurp(path);
101 if(template != NULL) {
102 unsigned n = 0;
103 while(template[n]) {
104 puts(meta(template[n], M_COLORED, & track));
105 free(template[n++]);
107 free(template);
110 else {
111 puts(meta("Track: \"%t\" (%T)", M_COLORED, & track));
112 puts(meta("Artist: \"%a\" (%A)", M_COLORED, & track));
113 puts(meta("Album: \"%l\" (%L)", M_COLORED, & track));
114 puts(meta("Station: %s", M_COLORED, & track));
117 break;
119 case 'r':
120 radioprompt("radio url> ");
121 break;
123 case 'd':
124 toggle(DISCOVERY);
125 printf("Discovery mode %s.\n", enabled(DISCOVERY) ? "enabled" : "disabled");
126 if(playfork) {
127 printf(
128 "%u track(s) left to play/skip until change comes into affect.\n",
129 playlist.left
132 break;
134 case 'A':
135 printf(meta("Really ban all tracks by artist %a? [yN]", M_COLORED, & track));
136 fflush(stdout);
137 if(fetchkey(5000000) != 'y')
138 puts("\nAbort.");
139 else if(autoban(value(& track, "creator"))) {
140 printf("\n%s banned.\n", meta("%a", M_COLORED, & track));
141 rate("B");
143 fflush(stdout);
144 break;
146 case 'a':
147 result = xmlrpc(
148 "addTrackToUserPlaylist", "ss",
149 value(& track, "creator"),
150 value(& track, "title")
153 puts(result ? "Added to playlist." : "Sorry, failed.");
154 break;
156 case 'P':
157 toggle(RTP);
158 printf("%s RTP.\n", enabled(RTP) ? "Enabled" : "Disabled");
159 break;
161 case 'f':
162 if(playfork) {
163 station(meta("lastfm://artist/%a/fans", 0, & track));
165 break;
167 case 's':
168 if(playfork) {
169 station(meta("lastfm://artist/%a/similarartists", 0, & track));
171 break;
173 case 'h':
174 printmarks();
175 break;
177 case 'H':
178 if(playfork && currentStation) {
179 puts("What number do you want to bookmark this stream as? [0-9]");
180 fflush(stdout);
181 key = fetchkey(5000000);
182 setmark(currentStation, key - 0x30);
184 break;
186 case 'S':
187 if(playfork) {
188 enable(STOPPED);
189 kill(playfork, SIGUSR1);
191 break;
193 case 'T':
194 if(playfork)
195 tag(track);
196 break;
198 case 'p':
199 if(playfork) {
200 if(pausetime) {
201 kill(playfork, SIGCONT);
203 else {
204 time(& pausetime);
205 kill(playfork, SIGSTOP);
208 break;
210 case 'R':
211 if(playfork) {
212 recommend(track);
214 break;
216 case '+':
217 if(volume < MAX_VOLUME)
218 volume += 1;
219 if(playpipe != 0)
220 write(playpipe, &key, 1);
221 break;
223 case '-':
224 if(volume > 0)
225 volume -= 1;
226 if(playpipe != 0)
227 write(playpipe, &key, 1);
228 break;
230 case 'u':
231 preview(playlist);
232 break;
234 case 'E':
235 expand(& playlist);
236 break;
238 case '?':
239 fputs(
240 "a = add the track to the playlist | A = autoban artist\n"
241 "B = ban Track | d = discovery mode\n"
242 "E = manually expand playlist | f = fan Station\n"
243 "h = list bookmarks | H = bookmark current radio\n"
244 "i = current track information | l = love track\n"
245 "n = skip track | p = pause\n"
246 "P = enable/disable RTP | Q = quit\n"
247 "r = change radio station | R = recommend track/artist/album\n"
248 "S = stop | s = similiar artist\n"
249 "T = tag track/artist/album | u = show upcoming tracks in playlist\n"
250 "U = unlove track | + = increase volume\n"
251 "- = decrease volume\n",
252 stderr
254 break;
256 case '0':
257 case '1':
258 case '2':
259 case '3':
260 case '4':
261 case '5':
262 case '6':
263 case '7':
264 case '8':
265 case '9':
266 if((marked = getmark(key - 0x30))) {
267 station(marked);
268 free(marked);
269 } else {
270 puts("Bookmark not defined.");
272 break;
274 default:
275 snprintf(customkey, sizeof(customkey), "key0x%02X", key & 0xFF);
276 if(haskey(& rc, customkey))
277 run(meta(value(& rc, customkey), M_SHELLESC, & track));
282 int fetchkey(unsigned nsec) {
283 fd_set fdset;
284 struct timeval tv;
286 FD_ZERO(& fdset);
287 FD_SET(fileno(stdin), & fdset);
289 tv.tv_usec = nsec % 1000000;
290 tv.tv_sec = nsec / 1000000;
292 if(select(fileno(stdin) + 1, & fdset, NULL, NULL, & tv) > 0) {
293 char ch = -1;
294 if(read(fileno(stdin), & ch, sizeof(char)) == sizeof(char))
295 return ch;
298 return -1;
302 #define remn (sizeof(string) - length - 1)
303 const char * meta(const char * fmt, int flags, struct hash * track) {
304 static char string[4096];
305 unsigned length = 0, x = 0;
307 /* Switch off coloring when in batch mode */
308 if(batch)
309 flags &= ~M_COLORED;
311 if(!fmt)
312 return NULL;
314 memset(string, 0, sizeof(string));
316 while(fmt[x] && remn > 0) {
317 if(fmt[x] != '%')
318 string[length++] = fmt[x++];
319 else if(fmt[++x]) {
320 if(fmt[x] == '%') {
321 string[length++] = fmt[x++];
323 else {
324 const char * keys [] = {
325 "acreator",
326 "ttitle",
327 "lalbum",
328 "Aartistpage",
329 "Ttrackpage",
330 "Lalbumpage",
331 "dduration",
332 "sstation",
333 "SstationURL",
334 "Rremain",
335 "Iimage"
338 register unsigned i = sizeof(keys) / sizeof(char *);
340 /* Look for a track value with that format flag. */
341 while(i--) {
342 if(fmt[x] == keys[i][0]) {
343 char * val = strdup(value(track, keys[i] + 1));
344 const char * color = NULL;
346 if(flags & M_COLORED) {
347 char colorkey[64] = { 0 };
348 snprintf(colorkey, sizeof(colorkey), "%c-color", keys[i][0]);
349 color = value(& rc, colorkey);
351 if(color) {
352 /* Strip leading spaces from end of color (Author: Ondrej Novy) */
353 char * color_st = strdup(color);
354 size_t len = strlen(color_st) - 1;
356 assert(color_st != NULL);
358 while(isspace(color_st[len]) && len > 0) {
359 color_st[len] = 0;
360 len--;
362 length += snprintf(string + length, remn, "\x1B[%sm", color_st);
363 free(color_st);
367 if((flags & M_RELAXPATH) && val) {
368 unsigned n;
369 size_t l = strlen(val);
371 for(n = 0; n < l; ++n) {
372 if(val[n] == '/')
373 val[n] = '|';
377 if(flags & M_SHELLESC) {
378 char * escaped = shellescape(val);
379 free(val);
380 val = escaped;
383 length = strlen(strncat(string, val ? val : "(unknown)", remn));
385 free(val);
387 if(color)
388 length = strlen(strncat(string, "\x1B[0m", remn));
390 break;
394 ++x;
399 return string;
401 #undef remn
404 void run(const char * cmd) {
405 if(!fork()) {
406 _exit(system(cmd));
411 int rate(const char * rating) {
412 if(playfork && rating != NULL) {
414 if(rating[0] != 'U')
415 set(& track, "rating", rating);
417 switch(rating[0]) {
418 case 'B':
419 kill(playfork, SIGUSR1);
420 enable(INTERRUPTED);
421 return xmlrpc(
422 "banTrack",
423 "ss",
424 value(& track, "creator"),
425 value(& track, "title")
428 case 'L':
429 return xmlrpc(
430 "loveTrack",
431 "ss",
432 value(& track, "creator"),
433 value(& track, "title")
436 case 'U':
437 return xmlrpc(
438 "unLoveTrack",
439 "ss",
440 value(& track, "creator"),
441 value(& track, "title")
444 case 'S':
445 enable(INTERRUPTED);
446 kill(playfork, SIGUSR1);
447 return !0;
451 return 0;
455 char * shellescape(const char * string) {
456 char * escaped;
457 unsigned length = 0, n, size;
459 assert(string != NULL);
461 size = strlen(string) * 2 + 1;
462 escaped = malloc(size);
463 memset(escaped, 0, size);
465 assert(string != NULL);
467 for(n = 0; n < strlen(string); ++n) {
468 if(!isalnum(string[n]))
469 escaped[length++] = '\\';
471 escaped[length++] = string[n];
472 escaped[length] = 0;
475 return escaped;