view src/scrobbler/xmms_scrobbler.c @ 344:03c1ae10bc8d trunk

[svn] - Merge audacious-scrobbler III, new features include: + Gerpok support + Better queue management
author nenolod
date Fri, 08 Dec 2006 19:31:43 -0800
parents 0eb1e99b7748
children
line wrap: on
line source

#include "settings.h"
#include "config.h"

#include <glib.h>
#include <glib/gi18n.h>

#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>

#include <audacious/plugin.h>
#include <audacious/prefswin.h>
#include <audacious/playlist.h>
#include <audacious/configdb.h>
#include <audacious/beepctrl.h>

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <wchar.h>
#include <sys/time.h>

#include "scrobbler.h"
#include "gerpok.h"
#include "gtkstuff.h"
#include "config.h"
#include "fmt.h"
#include "configure.h"

#define XS_CS xmms_scrobbler.xmms_session

typedef struct submit_t
{
	int dosubmit, pos_c, len;
} submit_t;

static void init(void);
static void cleanup(void);
static void *xs_thread(void *);
static void *hs_thread(void *);
static int sc_going, ge_going;
static GtkWidget *cfgdlg;

static GThread *pt_scrobbler;
static GMutex *m_scrobbler;
static GThread *pt_handshake;

static GeneralPlugin xmms_scrobbler =
{
	NULL,
	NULL,
	-1,
	NULL,
	init,
	about_show,
	NULL,
	cleanup
};

static void init(void)
{
	char *username = NULL, *password = NULL;
	char *ge_username = NULL, *ge_password = NULL;
	ConfigDb *cfgfile;
	sc_going = 1;
	ge_going = 1;
	GError **moo = NULL;
	cfgdlg = create_cfgdlg();

        prefswin_page_new(cfgdlg, "Scrobbler", DATA_DIR "/images/audioscrobbler.png");

	if ((cfgfile = bmp_cfg_db_open()) != NULL) {
		bmp_cfg_db_get_string(cfgfile, "audioscrobbler", "username",
				&username);
		bmp_cfg_db_get_string(cfgfile, "audioscrobbler", "password",
				&password);
		bmp_cfg_db_get_string(cfgfile, "audioscrobbler", "ge_username",
				&ge_username);
		bmp_cfg_db_get_string(cfgfile, "audioscrobbler", "ge_password",
				&ge_password);
		bmp_cfg_db_close(cfgfile);
	}

	if ((!username || !password) || (!*username || !*password))
	{
		pdebug("username/password not found - not starting last.fm support",
			DEBUG);
		sc_going = 0;
	}
	else
		sc_init(username, password);

	g_free(username);
	g_free(password);
	
	if ((!ge_username || !ge_password) || (!*ge_username || !*ge_password))
	{
		pdebug("username/password not found - not starting Gerpok support",
			DEBUG);
		ge_going = 0;
	}
	else
		gerpok_sc_init(ge_username, ge_password);

	g_free(ge_username);
	g_free(ge_password);

	m_scrobbler = g_mutex_new();
	if ((pt_scrobbler = g_thread_create(xs_thread, m_scrobbler, TRUE, moo)) == NULL)
	{
		pdebug(fmt_vastr("Error creating scrobbler thread: %s", moo), DEBUG);
		sc_going = 0;
		ge_going = 0;
		return;
	}

	if ((pt_handshake = g_thread_create(hs_thread, m_scrobbler, TRUE, NULL)) == NULL)
	{
		pdebug(fmt_vastr("Error creating handshake thread: %s", moo), DEBUG);
		sc_going = 0;
		ge_going = 0;
		return;
	}

	pdebug("plugin started", DEBUG);
}

static void cleanup(void)
{
    g_free (xmms_scrobbler.description);
    xmms_scrobbler.description = NULL;

        prefswin_page_destroy(cfgdlg);

	if (!sc_going && !ge_going)
		return;
	pdebug("about to lock mutex", DEBUG);
	g_mutex_lock(m_scrobbler);
	pdebug("locked mutex", DEBUG);
	sc_going = 0;
	ge_going = 0;
	g_mutex_unlock(m_scrobbler);
	pdebug("joining threads", DEBUG);
	g_thread_join(pt_scrobbler);

	g_thread_join(pt_handshake);

	sc_cleaner();
	gerpok_sc_cleaner();
}

static char ishttp(const char *a)
{
	char *tmp, *bp;
	int status = 0;

	if (!a || !*a)
		return 0;

	tmp = strdup(a);
	for (bp = tmp; *bp; bp++)
		*bp = toupper((int) *bp);
	if (strstr(tmp, "HTTP://"))
		status = -1;
	free(tmp);
	return status;
}

/* Following code thanks to nosuke 
 *
 * It should probably be cleaned up
 */
static submit_t get_song_status(void)
{
	static int pos_c, playlistlen_c, playtime_c, time_c,
		pos_p = 0, playlistlen_p = 0, playtime_p = 0,
		playtime_i = 0, time_i = 0,
		playtime_ofs = 0;
	static char *file_c = NULL, *file_p = NULL; 

	static enum playstatus {
		ps_stop, ps_play, ps_pause
	} ps_c, ps_p = ps_stop;

	static int submitted = 0, changed, seeked, repeat,
		filechanged, rewind, len = 0;

	static enum state {
		start, stop, pause, restart, playing, pausing, stopping
	} playstate;

	submit_t dosubmit;

	struct timeval timetmp;

	/* clear dosubmit */
	dosubmit.dosubmit = dosubmit.pos_c = dosubmit.len = 0;

	/* current music number */
	pos_c = xmms_remote_get_playlist_pos(XS_CS);
	/* current file name */
	file_c = xmms_remote_get_playlist_file(XS_CS, pos_c); 
	/* total number */
	playlistlen_c = xmms_remote_get_playlist_length(XS_CS);
	/* current playtime */
	playtime_c = xmms_remote_get_output_time(XS_CS); 
	/* total length */
	len = xmms_remote_get_playlist_time(XS_CS, pos_c); 

	/* current time (ms) */
	gettimeofday(&timetmp, NULL);
	time_c = timetmp.tv_sec * 1000 + timetmp.tv_usec / 1000; 

	/* current status */
	if( xmms_remote_is_paused(XS_CS) ) {
		ps_c = ps_pause;
	}else if( xmms_remote_is_playing(XS_CS) ) {
		ps_c = ps_play;
	}else{
		ps_c = ps_stop;
	}

	/* repeat setting */
	repeat = xmms_remote_is_repeat(XS_CS);

	/*
#ifdef MAKE_XMMS
	// advance setting (required xmms-1.2.11 or over)
	advance = xmms_remote_is_advance(XS_CS);
#else
	advance = 1;
#endif
	*/

	if( ps_p == ps_stop && ps_c == ps_stop )        playstate = stopping;
	else if( ps_p == ps_stop && ps_c == ps_play )   playstate = start;
	/* else if( ps_p == ps_stop && ps_c == ps_pause ) ; */
	else if( ps_p == ps_play && ps_c == ps_play )   playstate = playing;
	else if( ps_p == ps_play && ps_c == ps_stop )   playstate = stop;
	else if( ps_p == ps_play && ps_c == ps_pause )  playstate = pause;
	else if( ps_p == ps_pause && ps_c == ps_pause ) playstate = pausing;
	else if( ps_p == ps_pause && ps_c == ps_play )  playstate = restart;
	else if( ps_p == ps_pause && ps_c == ps_stop )  playstate = stop;
	else playstate = stopping;

	/* filename has changed */
	if( !(file_p == NULL && file_c == NULL) &&
	    ((file_p == NULL && file_c != NULL) ||
		 (file_p != NULL && file_c == NULL) ||
		 (file_p != NULL && file_c != NULL && strcmp(file_c, file_p))) ){
		filechanged = 1;
		pdebug("*** filechange ***", SUB_DEBUG);
	}else{
		filechanged = 0;
	}
	if( file_c == NULL ){ len = 0; }

	/* whole rewind has occurred (maybe) */
	if( len != 0 && len - (playtime_p - playtime_c) < 3000 ){
		rewind = 1;
		pdebug("*** rewind ***", SUB_DEBUG);
	}else{
		rewind = 0;
	}


	changed = 0;
	seeked = 0;

	switch( playstate ){
	case start:
	  pdebug("*** START ***", SUB_DEBUG);
	  break;
	case stop:
	  pdebug("*** STOP ***", SUB_DEBUG);
	  len = 0;
	  break;
	case pause: 
	  pdebug("*** PAUSE ***", SUB_DEBUG);
	  playtime_ofs += playtime_c - playtime_i; /* save playtime */
	  break;
	case restart: 
	  pdebug("*** RESTART ***", SUB_DEBUG);
	  playtime_i  = playtime_c; /* restore playtime */
	  break;
	case playing:
	  if( (playtime_c < playtime_p) || /* back */
		  ( (playtime_c - playtime_i) - (time_c - time_i) > 3000 )
	          /* forward */
		  ) { 
		seeked = 1;
	  }

	  if( filechanged || /* filename has changed */
		  ( !filechanged && /* filename has not changed... */
			/* (( rewind && (repeat && (!advance ||
	                   (pos_c == 0 && playlistlen_c == 1 )))) || */
			/* looping with only one file */
			(( pos_c == 0 && playlistlen_c == 1 && repeat
				&& rewind ) || 
			 /* looping? */
			 ( pos_p == pos_c && rewind ) ||
			 
			 ( pos_p != pos_c && seeked ) ||
			 /* skip from current music to next music, 
			    which has the same filename as previous one */
			 ( pos_p < pos_c && playtime_c < playtime_p ) || 
			 /* current song has removed from playlist 
			    but the next (following) song has the same
	                    filename */
			 ( playlistlen_p > playlistlen_c
				&& playtime_c < playtime_p )))){
		pdebug("*** CHANGE ***",SUB_DEBUG);
		pdebug(fmt_vastr(" filechanged = %d",filechanged),SUB_DEBUG);
		pdebug(fmt_vastr(" pos_c = %d",pos_c),SUB_DEBUG);
		pdebug(fmt_vastr(" pos_p = %d",pos_p),SUB_DEBUG);
		pdebug(fmt_vastr(" rewind = %d", rewind),SUB_DEBUG);
		pdebug(fmt_vastr(" seeked = %d", seeked),SUB_DEBUG);
		pdebug(fmt_vastr(" playtime_c = %d", playtime_c),SUB_DEBUG);
		pdebug(fmt_vastr(" playtime_p = %d", playtime_p),SUB_DEBUG);
		pdebug(fmt_vastr(" playlistlen_c = %d", playlistlen_p),
			SUB_DEBUG);
		pdebug(fmt_vastr(" playlistlen_p = %d", playlistlen_p),
			SUB_DEBUG);
		changed = 1;
		seeked = 0;

		if (file_p != NULL)
		{
			g_free(file_p);
			file_p = NULL;
		}
	  }else if( seeked ) { 
		seeked = 1;
		pdebug("*** SEEK ***", SUB_DEBUG);
	  }

	  break;
	case pausing: 
	  if(playtime_c != playtime_p){
		pdebug("*** SEEK ***", SUB_DEBUG);
		seeked = 1;
	  }
	  break;
	case stopping:
	  len = 0;
	  break;
	default:
	  pdebug("*** unknown state tranfer!!! ***", SUB_DEBUG);
	  break;
	}

	
	if( playstate == start || changed || (seeked && !submitted) ){
	  /* reset counter */
	  pdebug(" <<< reset counter >>>", SUB_DEBUG);

	  submitted = 0;
	  playtime_ofs = 0;
	  playtime_i = playtime_c;
	  time_i = time_c;

	}else{
	  /* check playtime for submitting */
	  if( !submitted ){
		if( len > 30 * 1000 &&
			/* len < 30 *60 * 1000 &&  // crazy rule!!! */
			( 
			 (playtime_ofs + playtime_c - playtime_i > len / 2) ||
			 (playtime_ofs + playtime_c - playtime_i > 240 * 1000) 
			 /* (playtime_c - playtime_i > 10 * 1000)// for debug */
			 )){
		  pdebug("*** submitting requirements are satisfied.",
			SUB_DEBUG);
		  pdebug(fmt_vastr("    len = %d, playtime = %d",
			len / 1000, (playtime_c - playtime_i)/1000 ),
			SUB_DEBUG);
		  submitted = 1;
		  dosubmit.dosubmit = 1;
	          dosubmit.pos_c = pos_c;
	          dosubmit.len = len;
		}
	  }
	}

	g_free(file_p);

	/* keep current value for next iteration */
	ps_p = ps_c;
	file_p = file_c;
	playtime_p = playtime_c;
	pos_p = pos_c;
	playlistlen_p = playlistlen_c;

	return dosubmit;
}

static void *xs_thread(void *data __attribute__((unused)))
{
	int run = 1;
	submit_t dosubmit;
	
	while (run) {
		/* Error catching */
		if(sc_catch_error())
		{
			errorbox_show(sc_fetch_error());
			sc_clear_error();
		}

		if(gerpok_sc_catch_error())
		{
			errorbox_show(gerpok_sc_fetch_error());
			gerpok_sc_clear_error();
		}

		/* Check for ability to submit */
		dosubmit = get_song_status();

		if(dosubmit.dosubmit) {
			TitleInput *tuple;

			pdebug("Submitting song.", DEBUG);

			tuple = playlist_get_tuple(dosubmit.pos_c);

			if (ishttp(tuple->file_name))
				continue;

			if(tuple->performer != NULL && tuple->track_name != NULL)
			{
				pdebug(fmt_vastr(
					"submitting artist: %s, title: %s",
					tuple->performer, tuple->track_name), DEBUG);
				sc_addentry(m_scrobbler, tuple,
					dosubmit.len/1000);
				gerpok_sc_addentry(m_scrobbler, tuple,
					dosubmit.len/1000);
			}
			else
				pdebug("tuple does not contain an artist or a title, not submitting.", DEBUG);
		}
		g_mutex_lock(m_scrobbler);
		run = (sc_going != 0 || ge_going != 0);
		g_mutex_unlock(m_scrobbler);
		g_usleep(100000);
	}
	pdebug("scrobbler thread: exiting", DEBUG);
	g_thread_exit(NULL);

	return NULL;
}

static void *hs_thread(void *data __attribute__((unused)))
{
	int run = 1;
	
	while(run)
	{
		if(sc_idle(m_scrobbler))
		{
			pdebug("Giving up due to fatal error", DEBUG);
			g_mutex_lock(m_scrobbler);
			sc_going = 0;
			g_mutex_lock(m_scrobbler);
		}

		if(gerpok_sc_idle(m_scrobbler))
		{
			pdebug("Giving up due to fatal error", DEBUG);
			g_mutex_lock(m_scrobbler);
			ge_going = 0;
			g_mutex_lock(m_scrobbler);
		}

		g_mutex_lock(m_scrobbler);
		run = (sc_going != 0 || ge_going != 0);
		g_mutex_unlock(m_scrobbler);
		g_usleep(1000000);
	}
	pdebug("handshake thread: exiting", DEBUG);
	g_thread_exit(NULL);

	return NULL;
}

GeneralPlugin *get_gplugin_info(void)
{
	xmms_scrobbler.description = g_strdup_printf(_("Scrobbler Plugin"));
	return &xmms_scrobbler;
}