Volume breaks output format, removed for now.
[shell-fm.git] / source / submit.c
blobd29284eec70056ad4e25db6b8d1cb181c2bd360e
1 /*
2 Copyright (C) 2006 by Jonas Kramer
3 Published under the terms of the GNU General Public License (GPL).
4 */
6 #include <stdio.h>
7 #include <string.h>
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <sys/types.h>
11 #include <errno.h>
12 #include <signal.h>
14 #include <assert.h>
15 #include <time.h>
17 #include "hash.h"
18 #include "http.h"
19 #include "md5.h"
20 #include "settings.h"
21 #include "split.h"
22 #include "getln.h"
23 #include "strary.h"
25 #include "submit.h"
26 #include "globals.h"
28 #define ERROR (strerror(errno))
30 struct hash submission, * queue = NULL;
31 unsigned qlength = 0, submitting = 0;
33 int handshaked = 0;
34 pid_t subfork = 0;
36 extern pid_t playfork;
37 extern struct hash data, rc, track;
39 static int handshake(const char *, const char *);
40 static void sliceq(unsigned);
43 /* Add a track to the scrobble queue. */
44 int enqueue(struct hash * track) {
45 const char * keys [] = { "creator", "title", "album", "duration" };
46 unsigned i;
47 struct hash post;
48 char timestamp[16], lastid[8], duration[8];
50 assert(track != NULL);
52 memset(& post, 0, sizeof(struct hash));
54 for(i = 0; i < (sizeof(keys) / sizeof(char *)); ++i)
55 if(!haskey(track, keys[i]))
56 return 0;
58 queue = realloc(queue, sizeof(struct hash) * (qlength + 1));
59 assert(queue != NULL);
61 snprintf(timestamp, sizeof(timestamp), "%lu", (unsigned long) time(NULL));
62 snprintf(lastid, sizeof(lastid), "L%s", value(track, "lastfm:trackauth"));
63 snprintf(duration, sizeof(duration), "%d", atoi(value(track, "duration")));
65 set(& post, "a", value(track, "creator"));
66 set(& post, "t", value(track, "title"));
67 set(& post, "i", timestamp);
68 set(& post, "r", value(track, "rating"));
69 set(& post, "o", lastid);
70 set(& post, "l", duration);
71 set(& post, "b", value(track, "album"));
72 set(& post, "n", "");
73 set(& post, "m", "");
75 memcpy(& queue[qlength++], & post, sizeof(struct hash));
77 return !0;
81 /* Submit tracks from the queue. */
82 int submit(const char * user, const char * password) {
83 char * body = NULL, ** resp;
84 const unsigned stepsize = 1024 * sizeof(char);
85 unsigned total = stepsize, ntrack;
86 int retval = -1;
89 if(!qlength || subfork > 0)
90 return 0;
92 submitting = qlength;
93 subfork = fork();
95 if(subfork != 0)
96 return !0;
98 playfork = 0;
99 enable(QUIET);
101 signal(SIGTERM, SIG_IGN);
103 if(!handshaked && !handshake(user, password)) {
104 fputs("Handshake failed.\n", stderr);
105 exit(retval);
108 /* Prepare POST body. */
109 body = malloc(stepsize);
110 assert(body != NULL);
111 memset(body, 0, stepsize);
113 snprintf(body, stepsize, "s=%s", value(& submission, "session"));
114 for(ntrack = 0; ntrack < qlength; ++ntrack) {
115 unsigned pair;
116 for(pair = 0; pair < queue[ntrack].size; ++pair) {
117 char key[16], * encoded = NULL;
118 unsigned length, bodysz = strlen(body);
120 snprintf(
121 key, sizeof(key), "%s[%d]",
122 queue[ntrack].content[pair].key, ntrack
125 encode(queue[ntrack].content[pair].value, & encoded);
126 length = strlen(key) + strlen(encoded) + 2;
128 while(bodysz + length > total) {
129 body = realloc(body, total + stepsize);
130 assert(body != NULL);
131 total += stepsize;
134 snprintf(body + bodysz, total - bodysz, "&%s=%s", key, encoded);
135 free(encoded);
139 sliceq(qlength);
141 resp = fetch(value(& submission, "submissions"), NULL, body, NULL);
143 if(resp) {
144 if(resp[0] && !strncmp(resp[0], "OK", 2))
145 retval = 0;
147 purge(resp);
150 free(body);
152 if(retval)
153 puts("Couldn't scrobble track(s).");
155 exit(retval);
159 /* Remove a number of tracks from the scrobble queue. */
160 static void sliceq(unsigned tracks) {
161 unsigned i;
163 if(tracks > qlength)
164 tracks = qlength;
166 for(i = 0; i < tracks; ++i)
167 empty(& queue[i]);
169 qlength -= tracks;
171 if(qlength > 0) {
172 memmove(queue, & queue[tracks], sizeof(struct hash) * qlength);
173 queue = realloc(queue, sizeof(struct hash) * qlength);
175 assert(queue != NULL);
176 } else {
177 free(queue);
178 queue = NULL;
183 /* Authenticate to the submission server. */
184 static int handshake(const char * user, const char * password) {
185 char temp[10 + 32 + 1], hex[32 + 1], ** resp;
186 const char * url;
187 const unsigned char * md5;
188 int i, retval = 0;
189 time_t timestamp = time(NULL);
191 if(handshaked)
192 empty(& submission);
194 memset(& submission, 0, sizeof(struct hash));
195 handshaked = 0;
197 assert(user != NULL);
198 assert(password != NULL);
200 snprintf(temp, sizeof(temp), "%s%lu", password, (unsigned long) timestamp);
201 md5 = MD5((unsigned char *) temp, strlen(temp));
203 for(i = 0; i < 16; ++i)
204 sprintf(& hex[2 * i], "%02x", md5[i]);
206 url = makeurl(
207 "http://post.audioscrobbler.com/" /* Base URL. */
208 "?hs=true" /* Handshake? Yes! */
209 "&p=%s" /* Protocol 1.2. */
210 "&c=sfm" /* Client ID (get a real one later). */
211 "&v=%s" /* Client version. */
212 "&u=%s" /* Last.FM user name. */
213 "&t=%u" /* Timestamp. */
214 "&a=%s", /* Authentication token. */
216 "1.2",
217 "1.0",
218 user, /* Last.FM user name. */
219 timestamp, /* UNIX timestamp. */
220 hex /* The authentication MD5 token calculated above. */
223 if((resp = fetch(url, NULL, NULL, NULL)) != NULL) {
224 if(resp[0] != NULL && !strncmp("OK", resp[0], 2)) {
225 set(& submission, "session", resp[1]);
226 set(& submission, "now-playing", resp[2]);
227 set(& submission, "submissions", resp[3]);
229 retval = handshaked = !0;
232 purge(resp);
235 return retval;
239 void subdead(int exitcode) {
240 if(exitcode == 0)
241 sliceq(submitting);
243 subfork = 0;
244 submitting = 0;
248 /* Write the tracks from the scrobble queue to a file. */
249 void dumpqueue(int overwrite) {
250 const char * path = rcpath("scrobble-cache");
251 if(path != NULL) {
252 FILE * fd = fopen(path, overwrite ? "w" : "a+");
253 if(fd != NULL) {
254 unsigned n, x;
255 for(n = 0; n < qlength; ++n) {
256 const char keys [] = "atirolbnm";
257 for(x = 0; x < sizeof(keys) - 1; ++x) {
258 char key[2] = { keys[x], 0 }, * encoded = NULL;
259 encode(value(& queue[n], key), & encoded);
260 fprintf(fd, "%c=%s;", keys[x], encoded);
261 free(encoded);
263 fputc(10, fd);
265 fclose(fd);
266 } else {
267 fprintf(stderr, "Couldn't open scrobble cache. %s.\n", ERROR);
269 } else {
270 fprintf(stderr, "Can't find suitable scrobble queue path.\n");
273 sliceq(qlength);
277 void loadqueue(int overwrite) {
278 const char * path = rcpath("scrobble-cache");
280 if(overwrite)
281 sliceq(qlength);
283 if(path != NULL) {
284 FILE * fd = fopen(path, "r");
285 if(fd != NULL) {
286 while(!feof(fd)) {
287 char * line = NULL;
288 unsigned size = 0;
290 if(getln(& line, & size, fd) >= 2) {
291 unsigned n = 0;
292 char ** splt = split(line, ";\n", & n);
293 struct hash track;
295 memset(& track, 0, sizeof(struct hash));
297 while(n--) {
298 char key[2] = { 0 }, * value = NULL;
299 sscanf(splt[n], "%c=", & key[0]);
300 if(strchr("atirolbnm", key[0])) {
301 decode(splt[n] + 2, & value);
302 set(& track, key, value);
303 free(value);
305 free(splt[n]);
308 free(splt);
310 queue = realloc(queue, sizeof(struct hash) * (++qlength));
311 assert(queue != NULL);
312 memcpy(& queue[qlength - 1], & track, sizeof(struct hash));
315 if(line)
316 free(line);
318 fclose(fd);