comparison src/scrobbler/plugin.c @ 1026:ff0487e9d00d trunk

[svn] - first attempt at implementing AudioScrobbler 1.2 protocol
author nenolod
date Fri, 11 May 2007 22:49:11 -0700
parents b70103d4b5ba
children ef86db6345e4
comparison
equal deleted inserted replaced
1025:b70103d4b5ba 1026:ff0487e9d00d
9 9
10 #include <audacious/plugin.h> 10 #include <audacious/plugin.h>
11 #include <audacious/ui_preferences.h> 11 #include <audacious/ui_preferences.h>
12 #include <audacious/playlist.h> 12 #include <audacious/playlist.h>
13 #include <audacious/configdb.h> 13 #include <audacious/configdb.h>
14 #include <audacious/auddrct.h> 14 #include <audacious/beepctrl.h>
15 #include <audacious/strings.h> 15 #include <audacious/strings.h>
16 #include <audacious/main.h> 16 #include <audacious/main.h>
17 17
18 #include <unistd.h> 18 #include <unistd.h>
19 #include <stdio.h> 19 #include <stdio.h>
28 #include "gtkstuff.h" 28 #include "gtkstuff.h"
29 #include "config.h" 29 #include "config.h"
30 #include "fmt.h" 30 #include "fmt.h"
31 #include "configure.h" 31 #include "configure.h"
32 32
33 #define XS_CS xmms_scrobbler.xmms_session
33 #define XS_SLEEP 1 34 #define XS_SLEEP 1
34 #define HS_SLEEP 10 35 #define HS_SLEEP 10
35 36
36 typedef struct submit_t 37 typedef struct submit_t
37 { 38 {
42 static void cleanup(void); 43 static void cleanup(void);
43 static void *xs_thread(void *); 44 static void *xs_thread(void *);
44 static void *hs_thread(void *); 45 static void *hs_thread(void *);
45 static int sc_going, ge_going; 46 static int sc_going, ge_going;
46 static GtkWidget *cfgdlg; 47 static GtkWidget *cfgdlg;
48 static gboolean submit;
47 49
48 static GMutex *m_scrobbler; 50 static GMutex *m_scrobbler;
49 static GThread *pt_scrobbler; 51 static GThread *pt_scrobbler;
50 static GThread *pt_handshake; 52 static GThread *pt_handshake;
51 53
61 init, 63 init,
62 about_show, 64 about_show,
63 NULL, 65 NULL,
64 cleanup 66 cleanup
65 }; 67 };
68
69 static gboolean ishttp(const char *a)
70 {
71 g_return_val_if_fail(a != NULL, FALSE);
72 return str_has_prefix_nocase(a, "http://") || str_has_prefix_nocase(a, "https://");
73 }
74
75 static void hook_playback_begin(PlaylistEntry *entry)
76 {
77 g_return_if_fail(entry != NULL);
78
79 if (entry->length < 30)
80 {
81 pdebug(" *** not submitting due to entry->length < 30", DEBUG);
82 return;
83 }
84
85 if (ishttp(entry->filename))
86 {
87 pdebug(" *** not submitting due to HTTP source", DEBUG);
88 return;
89 }
90
91 /* wake up the scrobbler thread to submit or queue */
92 submit = TRUE;
93 g_cond_signal(xs_cond);
94 }
66 95
67 static void init(void) 96 static void init(void)
68 { 97 {
69 char *username = NULL, *password = NULL; 98 char *username = NULL, *password = NULL;
70 char *ge_username = NULL, *ge_password = NULL; 99 char *ge_username = NULL, *ge_password = NULL;
141 pdebug("plugin started", DEBUG); 170 pdebug("plugin started", DEBUG);
142 } 171 }
143 172
144 static void cleanup(void) 173 static void cleanup(void)
145 { 174 {
146 g_free (xmms_scrobbler.description); 175 g_free (xmms_scrobbler.description);
147 xmms_scrobbler.description = NULL; 176 xmms_scrobbler.description = NULL;
148 177
149 prefswin_page_destroy(cfgdlg); 178 prefswin_page_destroy(cfgdlg);
150 179
151 if (!sc_going && !ge_going) 180 if (!sc_going && !ge_going)
152 return; 181 return;
177 206
178 sc_cleaner(); 207 sc_cleaner();
179 gerpok_sc_cleaner(); 208 gerpok_sc_cleaner();
180 } 209 }
181 210
182 static gboolean ishttp(const char *a)
183 {
184 g_return_val_if_fail(a != NULL, FALSE);
185 return str_has_prefix_nocase(a, "http://");
186 }
187
188 /* Following code thanks to nosuke
189 *
190 * It should probably be cleaned up
191 */
192 static submit_t get_song_status(void)
193 {
194 static int pos_c, playlistlen_c, playtime_c, time_c,
195 pos_p = 0, playlistlen_p = 0, playtime_p = 0,
196 playtime_i = 0, time_i = 0,
197 playtime_ofs = 0;
198 static char *file_c = NULL, *file_p = NULL;
199 Playlist *playlist = playlist_get_active();
200
201 static enum playstatus {
202 ps_stop, ps_play, ps_pause
203 } ps_c, ps_p = ps_stop;
204
205 static int submitted = 0, changed, seeked, repeat,
206 filechanged, rewind, len = 0;
207
208 static enum state {
209 start, stop, pause, restart, playing, pausing, stopping
210 } playstate;
211
212 submit_t dosubmit;
213
214 struct timeval timetmp;
215
216 /* clear dosubmit */
217 dosubmit.dosubmit = dosubmit.pos_c = dosubmit.len = dosubmit.gerpok = 0;
218
219 /* make sure playlist != NULL */
220 if (!playlist)
221 return dosubmit;
222
223 /* current music number */
224 pos_c = playlist_get_position(playlist);
225 /* current file name */
226 file_c = playlist_get_filename(playlist, pos_c);
227
228 if ((file_c != NULL) && (ishttp(file_c)))
229 return dosubmit;
230
231 /* total number */
232 playlistlen_c = playlist_get_length(playlist);
233 /* current playtime */
234 playtime_c = audacious_drct_get_time();
235 /* total length */
236 len = playlist_get_songtime(playlist, pos_c);
237
238 /* current time (ms) */
239 gettimeofday(&timetmp, NULL);
240 time_c = timetmp.tv_sec * 1000 + timetmp.tv_usec / 1000;
241
242 /* current status */
243 if( audacious_drct_get_paused() ) {
244 ps_c = ps_pause;
245 }else if( audacious_drct_get_playing() ) {
246 ps_c = ps_play;
247 }else{
248 ps_c = ps_stop;
249 }
250
251 /* repeat setting */
252 repeat = cfg.repeat;
253
254 if( ps_p == ps_stop && ps_c == ps_stop ) playstate = stopping;
255 else if( ps_p == ps_stop && ps_c == ps_play ) playstate = start;
256 /* else if( ps_p == ps_stop && ps_c == ps_pause ) ; */
257 else if( ps_p == ps_play && ps_c == ps_play ) playstate = playing;
258 else if( ps_p == ps_play && ps_c == ps_stop ) playstate = stop;
259 else if( ps_p == ps_play && ps_c == ps_pause ) playstate = pause;
260 else if( ps_p == ps_pause && ps_c == ps_pause ) playstate = pausing;
261 else if( ps_p == ps_pause && ps_c == ps_play ) playstate = restart;
262 else if( ps_p == ps_pause && ps_c == ps_stop ) playstate = stop;
263 else playstate = stopping;
264
265 /* filename has changed */
266 if( !(file_p == NULL && file_c == NULL) &&
267 ((file_p == NULL && file_c != NULL) ||
268 (file_p != NULL && file_c == NULL) ||
269 (file_p != NULL && file_c != NULL && strcmp(file_c, file_p))) ){
270 filechanged = 1;
271 pdebug("*** filechange ***", SUB_DEBUG);
272 }else{
273 filechanged = 0;
274 }
275 if( file_c == NULL ){ len = 0; }
276
277 /* whole rewind has occurred (maybe) */
278 if( len != 0 && len - (playtime_p - playtime_c) < 3000 ){
279 rewind = 1;
280 pdebug("*** rewind ***", SUB_DEBUG);
281 }else{
282 rewind = 0;
283 }
284
285
286 changed = 0;
287 seeked = 0;
288
289 switch( playstate ){
290 case start:
291 pdebug("*** START ***", SUB_DEBUG);
292 dosubmit.gerpok = 1;
293 dosubmit.len = len;
294 break;
295 case stop:
296 pdebug("*** STOP ***", SUB_DEBUG);
297 len = 0;
298 break;
299 case pause:
300 pdebug("*** PAUSE ***", SUB_DEBUG);
301 playtime_ofs += playtime_c - playtime_i; /* save playtime */
302 break;
303 case restart:
304 pdebug("*** RESTART ***", SUB_DEBUG);
305 playtime_i = playtime_c; /* restore playtime */
306 break;
307 case playing:
308 if( (playtime_c < playtime_p) || /* back */
309 ( (playtime_c - playtime_i) - (time_c - time_i) > 3000 )
310 /* forward */
311 ) {
312 seeked = 1;
313 }
314
315 if( filechanged || /* filename has changed */
316 ( !filechanged && /* filename has not changed... */
317 /* (( rewind && (repeat && (!advance ||
318 (pos_c == 0 && playlistlen_c == 1 )))) || */
319 /* looping with only one file */
320 (( pos_c == 0 && playlistlen_c == 1 && repeat
321 && rewind ) ||
322 /* looping? */
323 ( pos_p == pos_c && rewind ) ||
324
325 ( pos_p != pos_c && seeked ) ||
326 /* skip from current music to next music,
327 which has the same filename as previous one */
328 ( pos_p < pos_c && playtime_c < playtime_p ) ||
329 /* current song has removed from playlist
330 but the next (following) song has the same
331 filename */
332 ( playlistlen_p > playlistlen_c
333 && playtime_c < playtime_p )))){
334 pdebug("*** CHANGE ***",SUB_DEBUG);
335 pdebug(fmt_vastr(" filechanged = %d",filechanged),SUB_DEBUG);
336 pdebug(fmt_vastr(" pos_c = %d",pos_c),SUB_DEBUG);
337 pdebug(fmt_vastr(" pos_p = %d",pos_p),SUB_DEBUG);
338 pdebug(fmt_vastr(" rewind = %d", rewind),SUB_DEBUG);
339 pdebug(fmt_vastr(" seeked = %d", seeked),SUB_DEBUG);
340 pdebug(fmt_vastr(" playtime_c = %d", playtime_c),SUB_DEBUG);
341 pdebug(fmt_vastr(" playtime_p = %d", playtime_p),SUB_DEBUG);
342 pdebug(fmt_vastr(" playlistlen_c = %d", playlistlen_p),
343 SUB_DEBUG);
344 pdebug(fmt_vastr(" playlistlen_p = %d", playlistlen_p),
345 SUB_DEBUG);
346 changed = 1;
347 seeked = 0;
348
349 if (file_p != NULL)
350 {
351 g_free(file_p);
352 file_p = NULL;
353 }
354 }else if( seeked ) {
355 seeked = 1;
356 pdebug("*** SEEK ***", SUB_DEBUG);
357 }
358
359 break;
360 case pausing:
361 if(playtime_c != playtime_p){
362 pdebug("*** SEEK ***", SUB_DEBUG);
363 seeked = 1;
364 }
365 break;
366 case stopping:
367 len = 0;
368 break;
369 default:
370 pdebug("*** unknown state tranfer!!! ***", SUB_DEBUG);
371 break;
372 }
373
374
375 if( playstate == start || changed || (seeked && !submitted) ){
376 /* reset counter */
377 pdebug(" <<< reset counter >>>", SUB_DEBUG);
378
379 submitted = 0;
380 playtime_ofs = 0;
381 playtime_i = playtime_c;
382 time_i = time_c;
383
384 }else{
385 /* check playtime for submitting */
386 if( !submitted ){
387 if( len > 30 * 1000 &&
388 /* len < 30 *60 * 1000 && // crazy rule!!! */
389 (
390 (playtime_ofs + playtime_c - playtime_i > len / 2) ||
391 (playtime_ofs + playtime_c - playtime_i > 240 * 1000)
392 /* (playtime_c - playtime_i > 10 * 1000)// for debug */
393 )){
394 pdebug("*** submitting requirements are satisfied.",
395 SUB_DEBUG);
396 pdebug(fmt_vastr(" len = %d, playtime = %d",
397 len / 1000, (playtime_c - playtime_i)/1000 ),
398 SUB_DEBUG);
399 submitted = 1;
400 dosubmit.dosubmit = 1;
401 dosubmit.pos_c = pos_c;
402 dosubmit.len = len;
403 }
404 }
405 }
406
407 if (playstate != start)
408 dosubmit.gerpok = 0;
409
410 g_free(file_p);
411
412 /* keep current value for next iteration */
413 ps_p = ps_c;
414 file_p = file_c;
415 playtime_p = playtime_c;
416 pos_p = pos_c;
417 playlistlen_p = playlistlen_c;
418
419 return dosubmit;
420 }
421
422 static void *xs_thread(void *data __attribute__((unused))) 211 static void *xs_thread(void *data __attribute__((unused)))
423 { 212 {
424 int run = 1; 213 int run = 1;
425 submit_t dosubmit;
426 GTimeVal sleeptime;
427 214
428 while (run) { 215 while (run) {
216 TitleInput *tuple;
217 GTimeVal sleeptime;
218
429 /* Error catching */ 219 /* Error catching */
430 if(sc_catch_error()) 220 if(sc_catch_error())
431 { 221 {
432 errorbox_show(sc_fetch_error()); 222 errorbox_show(sc_fetch_error());
433 sc_clear_error(); 223 sc_clear_error();
437 { 227 {
438 errorbox_show(gerpok_sc_fetch_error()); 228 errorbox_show(gerpok_sc_fetch_error());
439 gerpok_sc_clear_error(); 229 gerpok_sc_clear_error();
440 } 230 }
441 231
442 /* Check for ability to submit */ 232 if (submit)
443 dosubmit = get_song_status(); 233 {
444 234 Playlist *playlist;
445 if(dosubmit.gerpok) {
446 TitleInput *tuple;
447 235
448 pdebug("Submitting song.", DEBUG); 236 pdebug("Submitting song.", DEBUG);
449 237
450 tuple = playlist_get_tuple(playlist_get_active(), dosubmit.pos_c); 238 playlist = playlist_get_active();
239 tuple = playlist_get_tuple(playlist, playlist_get_position(playlist));
451 240
452 if (tuple == NULL) 241 if (tuple == NULL)
453 continue; 242 continue;
454 243
455 if (ishttp(tuple->file_name)) 244 if (ishttp(tuple->file_name))
458 if(tuple->performer != NULL && tuple->track_name != NULL) 247 if(tuple->performer != NULL && tuple->track_name != NULL)
459 { 248 {
460 pdebug(fmt_vastr( 249 pdebug(fmt_vastr(
461 "submitting artist: %s, title: %s", 250 "submitting artist: %s, title: %s",
462 tuple->performer, tuple->track_name), DEBUG); 251 tuple->performer, tuple->track_name), DEBUG);
463 gerpok_sc_addentry(m_scrobbler, tuple, 252 sc_addentry(m_scrobbler, tuple, tuple->length / 1000);
464 dosubmit.len/1000); 253 gerpok_sc_addentry(m_scrobbler, tuple, tuple->length / 1000);
465 }
466 else
467 pdebug("tuple does not contain an artist or a title, not submitting.", DEBUG);
468 }
469
470 if(dosubmit.dosubmit) {
471 TitleInput *tuple;
472
473 pdebug("Submitting song.", DEBUG);
474
475 tuple = playlist_get_tuple(playlist_get_active(), dosubmit.pos_c);
476
477 if (tuple == NULL)
478 continue;
479
480 if (ishttp(tuple->file_name))
481 continue;
482
483 if(tuple->performer != NULL && tuple->track_name != NULL)
484 {
485 pdebug(fmt_vastr(
486 "submitting artist: %s, title: %s",
487 tuple->performer, tuple->track_name), DEBUG);
488 sc_addentry(m_scrobbler, tuple,
489 dosubmit.len/1000);
490 } 254 }
491 else 255 else
492 pdebug("tuple does not contain an artist or a title, not submitting.", DEBUG); 256 pdebug("tuple does not contain an artist or a title, not submitting.", DEBUG);
493 } 257
258 submit = FALSE;
259 }
260
494 g_get_current_time(&sleeptime); 261 g_get_current_time(&sleeptime);
495 sleeptime.tv_sec += XS_SLEEP; 262 sleeptime.tv_sec += XS_SLEEP;
263
264 g_mutex_lock(xs_mutex);
265 g_cond_timed_wait(xs_cond, xs_mutex, &sleeptime);
266 g_mutex_unlock(xs_mutex);
496 267
497 g_mutex_lock(m_scrobbler); 268 g_mutex_lock(m_scrobbler);
498 run = (sc_going != 0 || ge_going != 0); 269 run = (sc_going != 0 || ge_going != 0);
499 g_mutex_unlock(m_scrobbler); 270 g_mutex_unlock(m_scrobbler);
500
501 g_mutex_lock(xs_mutex);
502 g_cond_timed_wait(xs_cond, xs_mutex, &sleeptime);
503 g_mutex_unlock(xs_mutex);
504 } 271 }
505 pdebug("scrobbler thread: exiting", DEBUG); 272 pdebug("scrobbler thread: exiting", DEBUG);
506 g_thread_exit(NULL); 273 g_thread_exit(NULL);
507 274
508 return NULL; 275 return NULL;