Volume breaks output format, removed for now.
[shell-fm.git] / source / service.c
blob6154529aa633baecc7c0b0fa9b2b5a0d072ba977
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
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <assert.h>
13 #include <unistd.h>
14 #include <sys/types.h>
15 #include <signal.h>
16 #include <stdarg.h>
18 #include "hash.h"
19 #include "http.h"
20 #include "play.h"
21 #include "settings.h"
22 #include "md5.h"
23 #include "history.h"
24 #include "service.h"
25 #include "playlist.h"
26 #include "ropen.h"
27 #include "strary.h"
28 #include "sckif.h"
30 #include "globals.h"
32 struct hash data; /* Warning! MUST be bzero'd ASAP or we're all gonna die! */
34 pid_t playfork = 0; /* PID of the decoding & playing process, if running */
35 int playpipe = 0;
37 struct playlist playlist;
38 char * current_station = NULL;
40 int authenticate(const char * username, const char * password) {
41 const unsigned char * md5;
42 char hexmd5[32 + 1] = { 0 }, url[512] = { 0 }, ** response;
43 char * encuser = NULL;
44 unsigned ndigit, i = 0;
45 const char * session, * fmt =
46 "http://ws.audioscrobbler.com/radio/handshake.php"
47 "?version=0.1"
48 "&platform=linux"
49 "&username=%s"
50 "&passwordmd5=%s"
51 "&debug=0"
52 "&language=en";
54 memset(& data, 0, sizeof(struct hash));
56 /* create the hash, then convert to ASCII */
57 md5 = MD5((const unsigned char *) password, strlen(password));
58 for(ndigit = 0; ndigit < 16; ++ndigit)
59 sprintf(2 * ndigit + hexmd5, "%02x", md5[ndigit]);
61 set(& rc, "password", hexmd5);
63 /* escape username for URL */
64 encode(username, & encuser);
66 /* put handshake URL together and fetch initial data from server */
67 snprintf(url, sizeof(url), fmt, encuser, hexmd5);
68 free(encuser);
70 response = fetch(url, NULL, NULL, NULL);
71 if(!response) {
72 fputs("No response.\n", stderr);
73 return 0;
76 while(response[i]) {
77 char key[64] = { 0 }, val[256] = { 0 };
78 sscanf(response[i], "%63[^=]=%255[^\r\n]", key, val);
79 set(& data, key, val);
80 free(response[i++]);
82 free(response);
84 session = value(& data, "session");
85 if(!session || !strcmp(session, "FAILED")) {
86 fputs("Authentication failed.\n", stderr);
87 unset(& data, "session");
88 return 0;
91 return !0;
95 int station(const char * stationURL) {
96 char url[512] = { 0 }, * encodedURL = NULL, ** response, name[512], * completeURL;
97 unsigned i = 0, retval = !0, regular = !0;
98 const char * fmt;
99 const char * types[4] = {"play", "preview", "track", "playlist"};
101 delayquit = 0;
103 if(playfork && haskey(& rc, "delay-change")) {
104 if(nextstation) {
106 Cancel station change if url is empty or equal to the current
107 station.
109 free(nextstation);
110 nextstation = NULL;
112 if(!strlen(stationURL) || !strcmp(stationURL, current_station)) {
113 puts("Station change cancelled.");
114 return 0;
118 /* Do nothing if the station is already played. */
119 else if(current_station && !strcmp(current_station, stationURL)) {
120 return 0;
123 /* Do nothing if the station URL is empty. */
124 else if(!strlen(stationURL)) {
125 return 0;
128 puts("\rDelayed.");
129 nextstation = strdup(stationURL);
131 return 0;
134 /* Do nothing if the station is already played. */
135 else if(current_station && !strcmp(current_station, stationURL)) {
136 return 0;
139 freelist(& playlist);
141 if(!haskey(& data, "session")) {
142 fputs("Not authenticated, yet.\n", stderr);
143 return 0;
146 if(!stationURL || !strlen(stationURL))
147 return 0;
149 if(!strncasecmp(stationURL, "lastfm://", 9)) {
150 completeURL = strdup(stationURL);
151 stationURL += 9;
153 else {
154 int size = strlen(stationURL) + 10;
155 completeURL = malloc(size);
156 snprintf(completeURL, size, "lastfm://%s", stationURL);
159 /* Check if it's a static playlist of tracks or track previews. */
160 for(i = 0; i < 4; ++i)
161 if(!strncasecmp(types[i], stationURL, strlen(types[i]))) {
162 regular = 0;
163 break;
167 If this is not a special "one-time" stream, it's a regular radio
168 station and we request it using the good old /adjust.php URL.
169 If it's not a regular stream, the reply of this request already is
170 a XSPF playlist we have to parse.
172 if(regular) {
173 fmt = "http://ws.audioscrobbler.com/radio/adjust.php?session=%s&url=%s";
175 else {
176 fmt =
177 "http://ws.audioscrobbler.com/1.0/webclient/getresourceplaylist.php"
178 "?sk=%s&url=%s&desktop=1";
181 encode(completeURL, & encodedURL);
182 snprintf(url, sizeof(url), fmt, value(& data, "session"), encodedURL);
184 free(encodedURL);
185 free(completeURL);
187 if(!(response = fetch(url, NULL, NULL, NULL)))
188 return 0;
190 if(regular) {
191 for(i = 0; response[i]; ++i) {
192 char status[64] = { 0 };
194 if(sscanf(response[i], "response=%63[^\r\n]", status) > 0)
195 if(!strncmp(status, "FAILED", 6))
196 retval = 0;
198 if(sscanf(response[i], "stationname=%127[^\r\n]", name) > 0) {
199 if(playlist.title != NULL)
200 free(playlist.title);
202 decode(name, & playlist.title);
203 unhtml(playlist.title);
207 purge(response);
208 response = NULL;
210 if(!retval) {
211 printf("Sorry, couldn't set station to %s.\n", stationURL);
212 return 0;
215 expand(& playlist);
217 else {
218 char * xml = join(response, 0);
220 response = NULL;
222 freelist(& playlist);
224 if(!parsexspf(& playlist, xml))
225 retval = 0;
227 free(xml);
230 enable(CHANGED);
231 histapp(stationURL);
233 if(current_station)
234 free(current_station);
236 current_station = strdup(stationURL);
237 assert(current_station != NULL);
239 if(retval && playfork) {
240 enable(INTERRUPTED);
241 kill(playfork, SIGUSR1);
244 return retval;
249 Takes pointer to a playlist, forks off a playback process and tries to play
250 the next track in the playlist. If there's already a playback process, it's
251 killed first (which means the currently played track is skipped).
253 int play(struct playlist * list) {
254 unsigned i;
255 int pipefd[2];
256 char * keys [] = {
257 "creator", "title", "album", "duration", "station",
258 "lastfm:trackauth", "trackpage", "artistpage", "albumpage",
259 "image", "freeTrackURL",
262 assert(list != NULL);
264 if(!list->left)
265 return 0;
267 if(playfork) {
268 kill(playfork, SIGUSR1);
269 return !0;
272 enable(QUIET);
274 empty(& track);
276 for(i = 0; i < (sizeof(keys) / sizeof(char *)); ++i)
277 set(& track, keys[i], value(& list->track->track, keys[i]));
279 if(pipe(pipefd) != 0)
280 return !0;
282 playfork = fork();
284 if(!playfork) {
285 FILE * fd = NULL;
286 const char * location = value(& list->track->track, "location");
288 close(pipefd[1]);
289 rmsckif();
291 subfork = 0;
293 if(location != NULL) {
294 fetch(location, & fd, NULL, NULL);
296 if(fd != NULL) {
298 If there was an error, tell the main process about it by
299 sending SIGUSR2.
301 if(!playback(fd, pipefd[0]))
302 kill(getppid(), SIGUSR2);
304 close(pipefd[0]);
305 fshutdown(& fd);
309 exit(EXIT_SUCCESS);
312 close(pipefd[0]);
314 if(playpipe != 0)
315 close(playpipe);
317 playpipe = pipefd[1];
319 return !0;