2 Copyright (C) 2006 by Jonas Kramer
3 Published under the terms of the GNU General Public License (GPL).
10 #include <sys/types.h>
28 #define ERROR (strerror(errno))
30 struct hash submission
, * queue
= NULL
;
31 unsigned qlength
= 0, submitting
= 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" };
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
]))
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"));
75 memcpy(& queue
[qlength
++], & post
, sizeof(struct hash
));
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
;
89 if(!qlength
|| subfork
> 0)
101 signal(SIGTERM
, SIG_IGN
);
103 if(!handshaked
&& !handshake(user
, password
)) {
104 fputs("Handshake failed.\n", stderr
);
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
) {
116 for(pair
= 0; pair
< queue
[ntrack
].size
; ++pair
) {
117 char key
[16], * encoded
= NULL
;
118 unsigned length
, bodysz
= strlen(body
);
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
);
134 snprintf(body
+ bodysz
, total
- bodysz
, "&%s=%s", key
, encoded
);
141 resp
= fetch(value(& submission
, "submissions"), NULL
, body
, NULL
);
144 if(resp
[0] && !strncmp(resp
[0], "OK", 2))
153 puts("Couldn't scrobble track(s).");
159 /* Remove a number of tracks from the scrobble queue. */
160 static void sliceq(unsigned tracks
) {
166 for(i
= 0; i
< tracks
; ++i
)
172 memmove(queue
, & queue
[tracks
], sizeof(struct hash
) * qlength
);
173 queue
= realloc(queue
, sizeof(struct hash
) * qlength
);
175 assert(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
;
187 const unsigned char * md5
;
189 time_t timestamp
= time(NULL
);
194 memset(& submission
, 0, sizeof(struct hash
));
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
]);
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. */
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;
239 void subdead(int exitcode
) {
248 /* Write the tracks from the scrobble queue to a file. */
249 void dumpqueue(int overwrite
) {
250 const char * path
= rcpath("scrobble-cache");
252 FILE * fd
= fopen(path
, overwrite
? "w" : "a+");
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
);
267 fprintf(stderr
, "Couldn't open scrobble cache. %s.\n", ERROR
);
270 fprintf(stderr
, "Can't find suitable scrobble queue path.\n");
277 void loadqueue(int overwrite
) {
278 const char * path
= rcpath("scrobble-cache");
284 FILE * fd
= fopen(path
, "r");
290 if(getln(& line
, & size
, fd
) >= 2) {
292 char ** splt
= split(line
, ";\n", & n
);
295 memset(& track
, 0, sizeof(struct hash
));
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
);
310 queue
= realloc(queue
, sizeof(struct hash
) * (++qlength
));
311 assert(queue
!= NULL
);
312 memcpy(& queue
[qlength
- 1], & track
, sizeof(struct hash
));