diff src/scrobbler/plugin.c @ 346:76047737ea49 trunk

[svn] - rename plugin glue to plugin.c
author nenolod
date Fri, 08 Dec 2006 19:34:32 -0800
parents src/scrobbler/xmms_scrobbler.c@03c1ae10bc8d
children b4f1ad07fcd9
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/scrobbler/plugin.c	Fri Dec 08 19:34:32 2006 -0800
@@ -0,0 +1,492 @@
+#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;
+}