changeset 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 5d3f4b156197
children 213ccf5b5ea5
files ChangeLog src/scrobbler/Makefile src/scrobbler/config.h src/scrobbler/configure.c src/scrobbler/gerpok.c src/scrobbler/gerpok.h src/scrobbler/gtkstuff.c src/scrobbler/queue.c src/scrobbler/queue.h src/scrobbler/scrobbler.c src/scrobbler/settings.h src/scrobbler/xmms_scrobbler.c
diffstat 12 files changed, 1288 insertions(+), 227 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Fri Dec 08 17:52:54 2006 -0800
+++ b/ChangeLog	Fri Dec 08 19:31:43 2006 -0800
@@ -1,3 +1,11 @@
+2006-12-09 01:52:54 +0000  William Pitcock <nenolod@nenolod.net>
+  revision [750]
+  - mute the soundcard if volume is 0.
+  
+  trunk/src/alsa/audio.c |    8 ++++++++
+  1 file changed, 8 insertions(+)
+
+
 2006-12-08 11:27:31 +0000  William Pitcock <nenolod@nenolod.net>
   revision [748]
   - port from fmemopen() to vfs_buffer_new_from_string().
--- a/src/scrobbler/Makefile	Fri Dec 08 17:52:54 2006 -0800
+++ b/src/scrobbler/Makefile	Fri Dec 08 19:31:43 2006 -0800
@@ -19,8 +19,8 @@
 	configure.c		\
 	gtkstuff.c		\
 	md5.c			\
-	queue.c			\
 	scrobbler.c		\
+	gerpok.c		\
 	xmms_scrobbler.c
 
 CFLAGS += $(PICFLAGS) $(GTK_CFLAGS) $(BEEP_DEFINES) $(CURL_CFLAGS) -I../../intl -I../..
--- a/src/scrobbler/config.h	Fri Dec 08 17:52:54 2006 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-#include "../../config.h"
-
-#ifndef __SCROBBLER_CONFIG_H__
-#define __SCROBBLER_CONFIG_H__
-
-#define DEBUG 0
-#define META_DEBUG 0
-#define SUB_DEBUG 0
-#define CLIENT "Audacious"
-#define USER_AGENT "AudioScrobbler/1.1" PACKAGE_NAME "/" PACKAGE_VERSION
-#define MAKE_BMP
-#define ALLOW_MULTIPLE
-
-#endif
--- a/src/scrobbler/configure.c	Fri Dec 08 17:52:54 2006 -0800
+++ b/src/scrobbler/configure.c	Fri Dec 08 19:31:43 2006 -0800
@@ -1,3 +1,5 @@
+#include "settings.h"
+
 #ifdef HAVE_CONFIG_H
 #  include <config.h>
 #endif
@@ -21,7 +23,7 @@
 
 #include "configure.h"
 
-GtkWidget *entry1, *entry2, *cfgdlg;
+GtkWidget *entry1, *entry2, *ge_entry1, *ge_entry2, *cfgdlg;
 
 static char *hexify(char *pass, int len)
 {
@@ -46,13 +48,16 @@
 
         const char *uid = gtk_entry_get_text(GTK_ENTRY(entry1));
         const char *pwd = gtk_entry_get_text(GTK_ENTRY(entry2));
+        const char *ge_uid = gtk_entry_get_text(GTK_ENTRY(entry1));
+        const char *ge_pwd = gtk_entry_get_text(GTK_ENTRY(entry2));
 
         if ((cfgfile = bmp_cfg_db_open()))
 	{
                 md5_state_t md5state;
-                unsigned char md5pword[16];
+                unsigned char md5pword[16], ge_md5pword[16];
 
                 bmp_cfg_db_set_string(cfgfile, "audioscrobbler", "username", (char *)uid);
+                bmp_cfg_db_set_string(cfgfile, "audioscrobbler", "ge_username", (char *)ge_uid);
 
                 if (pwd != NULL && pwd[0] != '\0')
 		{
@@ -62,6 +67,16 @@
                         bmp_cfg_db_set_string(cfgfile, "audioscrobbler", "password",
                                         hexify((char*)md5pword, sizeof(md5pword)));
                 }
+
+                if (ge_pwd != NULL && ge_pwd[0] != '\0')
+		{
+                        md5_init(&md5state);
+                        md5_append(&md5state, (unsigned const char *)ge_pwd, strlen(ge_pwd));
+                        md5_finish(&md5state, ge_md5pword);
+                        bmp_cfg_db_set_string(cfgfile, "audioscrobbler", "ge_password",
+                                        hexify((char*)ge_md5pword, sizeof(ge_md5pword)));
+                }
+
                 bmp_cfg_db_close(cfgfile);
         }
 }
@@ -78,16 +93,20 @@
   GtkWidget *label2;
   GtkWidget *himage1;
   GtkWidget *align1;
+  GtkWidget *notebook1;
 
   vbox2 = gtk_vbox_new (FALSE, 0);
 
-  label1 = gtk_label_new (_("<b>Scrobbler Preferences</b>"));
+  label1 = gtk_label_new (_("<b>Services</b>"));
   gtk_widget_show (label1);
   gtk_label_set_use_markup (GTK_LABEL (label1), TRUE);
   gtk_misc_set_alignment (GTK_MISC (label1), 0, 0.5);
 
   gtk_box_pack_start (GTK_BOX (vbox2), label1, FALSE, FALSE, 0);
 
+  notebook1 = gtk_notebook_new();
+  gtk_widget_show (notebook1);
+
   align1 = gtk_alignment_new(0, 0, 0, 0);
   gtk_widget_show (align1);
   gtk_alignment_set_padding(GTK_ALIGNMENT(align1), 0, 0, 12, 0);
@@ -95,7 +114,6 @@
   table1 = gtk_table_new (2, 2, FALSE);
   gtk_widget_show (table1);
   gtk_container_add(GTK_CONTAINER(align1), table1);
-  gtk_box_pack_start (GTK_BOX (vbox2), align1, TRUE, TRUE, 0);
   gtk_table_set_row_spacings (GTK_TABLE(table1), 6);
   gtk_table_set_col_spacings (GTK_TABLE(table1), 6);
 
@@ -123,6 +141,50 @@
   gtk_table_attach_defaults (GTK_TABLE (table1), entry2, 1, 2, 3, 4);
   g_signal_connect(entry2, "changed", (GCallback) saveconfig, NULL);
 
+  label1 = gtk_label_new (_("<b>Last.FM</b>"));
+  gtk_label_set_use_markup (GTK_LABEL (label1), TRUE);
+  gtk_notebook_append_page(GTK_NOTEBOOK(notebook1), GTK_WIDGET(align1), label1);
+
+  align1 = gtk_alignment_new(0, 0, 0, 0);
+  gtk_widget_show (align1);
+  gtk_alignment_set_padding(GTK_ALIGNMENT(align1), 0, 0, 12, 0);
+
+  table1 = gtk_table_new (2, 2, FALSE);
+  gtk_widget_show (table1);
+  gtk_container_add(GTK_CONTAINER(align1), table1);
+  gtk_table_set_row_spacings (GTK_TABLE(table1), 6);
+  gtk_table_set_col_spacings (GTK_TABLE(table1), 6);
+
+  label2 = gtk_label_new (_("Username:"));
+  gtk_widget_show (label2);
+  gtk_table_attach_defaults (GTK_TABLE (table1), label2, 0, 1, 2, 3);
+  gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_RIGHT);
+  gtk_misc_set_alignment (GTK_MISC (label2), 1, 0.5);
+
+  label3 = gtk_label_new (_("Password:"));
+  gtk_widget_show (label3);
+  gtk_table_attach (GTK_TABLE (table1), label3, 0, 1, 3, 4,
+                    (GtkAttachOptions) (GTK_FILL),
+                    (GtkAttachOptions) (0), 0, 0);
+  gtk_label_set_justify (GTK_LABEL (label3), GTK_JUSTIFY_RIGHT);
+  gtk_misc_set_alignment (GTK_MISC (label3), 1, 0.5);
+
+  ge_entry1 = gtk_entry_new ();
+  gtk_widget_show (ge_entry1);
+  gtk_table_attach_defaults (GTK_TABLE (table1), ge_entry1, 1, 2, 2, 3);
+
+  ge_entry2 = gtk_entry_new ();
+  gtk_entry_set_visibility(GTK_ENTRY(ge_entry2), FALSE);
+  gtk_widget_show (ge_entry2);
+  gtk_table_attach_defaults (GTK_TABLE (table1), ge_entry2, 1, 2, 3, 4);
+  g_signal_connect(ge_entry2, "changed", (GCallback) saveconfig, NULL);
+
+  label1 = gtk_label_new (_("<b>Gerpok</b>"));
+  gtk_label_set_use_markup (GTK_LABEL (label1), TRUE);
+  gtk_notebook_append_page(GTK_NOTEBOOK(notebook1), GTK_WIDGET(align1), label1);
+
+  gtk_box_pack_start (GTK_BOX (vbox2), notebook1, TRUE, TRUE, 6);
+
   himage1 = gtk_image_new_from_file (DATA_DIR "/images/audioscrobbler_badge.png");
   gtk_widget_show (himage1);
   gtk_box_pack_start (GTK_BOX (vbox2), himage1, FALSE, FALSE, 0);
@@ -139,6 +201,12 @@
                         gtk_entry_set_text(GTK_ENTRY(entry1), username);
                         g_free(username);
                 }
+                bmp_cfg_db_get_string(db, "audioscrobbler", "ge_username",
+                        &username);
+                if (username) {
+                        gtk_entry_set_text(GTK_ENTRY(ge_entry1), username);
+                        g_free(username);
+                }
                 bmp_cfg_db_close(db);
         }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/scrobbler/gerpok.c	Fri Dec 08 19:31:43 2006 -0800
@@ -0,0 +1,936 @@
+#include "settings.h"
+
+#include <pthread.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <curl/curl.h>
+#include <stdio.h>
+#include "fmt.h"
+#include "md5.h"
+#include "scrobbler.h"
+#include "config.h"
+#include <glib.h>
+
+#include <audacious/titlestring.h>
+
+#define SCROBBLER_HS_URL "http://post.gerpok.com"
+#define SCROBBLER_CLI_ID "aud"
+#define SCROBBLER_HS_WAIT 1800
+#define SCROBBLER_SB_WAIT 10
+#define SCROBBLER_VERSION "1.1"
+#define SCROBBLER_IMPLEMENTATION "0.1"		/* This is the implementation, not the player version. */
+#define SCROBBLER_SB_MAXLEN 1024
+#define CACHE_SIZE 1024
+
+/* Scrobblerbackend for xmms plugin, first draft */
+
+static int	gerpok_sc_hs_status,
+		gerpok_sc_hs_timeout,
+		gerpok_sc_hs_errors,
+		gerpok_sc_sb_errors,
+		gerpok_sc_bad_users,
+		gerpok_sc_submit_interval,
+		gerpok_sc_submit_timeout,
+		gerpok_sc_srv_res_size,
+		gerpok_sc_giveup,
+		gerpok_sc_major_error_present;
+
+static char 	*gerpok_sc_submit_url,
+		*gerpok_sc_username,
+		*gerpok_sc_password,
+		*gerpok_sc_challenge_hash,
+		gerpok_sc_response_hash[33],
+		*gerpok_sc_srv_res,
+		gerpok_sc_curl_errbuf[CURL_ERROR_SIZE],
+		*gerpok_sc_major_error;
+
+static void dump_queue();
+
+/**** Queue stuff ****/
+
+#define I_ARTIST(i) i->artist
+#define I_TITLE(i) i->title
+#define I_TIME(i) i->utctime
+#define I_LEN(i) i->len
+#define I_MB(i) i->mb
+#define I_ALBUM(i) i->album
+
+typedef struct {
+	char *artist,
+		*title,
+		*mb,
+		*album,
+		*utctime,
+		len[16];
+	int numtries;
+	void *next;
+} item_t;
+
+static item_t *q_queue = NULL;
+static item_t *q_queue_last = NULL;
+static int q_nitems;
+
+static void q_item_free(item_t *item)
+{
+	if (item == NULL)
+		return;
+	curl_free(item->artist);
+	curl_free(item->title);
+	curl_free(item->utctime);
+	curl_free(item->mb);
+	curl_free(item->album);
+	free(item);
+}
+
+static void q_put(TitleInput *tuple, int len)
+{
+	item_t *item;
+
+	item = malloc(sizeof(item_t));
+
+	item->artist = fmt_escape(tuple->performer);
+	item->title = fmt_escape(tuple->track_name);
+	item->utctime = fmt_escape(fmt_timestr(time(NULL), 1));
+	snprintf(item->len, sizeof(item->len), "%d", len);
+
+#ifdef NOTYET
+	if(tuple->mb == NULL)
+#endif
+		item->mb = fmt_escape("");
+#ifdef NOTYET
+	else
+		item->mb = fmt_escape((char*)tuple->mb);
+#endif
+
+	if(tuple->album_name == NULL)
+		item->album = fmt_escape("");
+	else
+		item->album = fmt_escape((char*)tuple->album_name);
+
+	q_nitems++;
+
+	item->next = NULL;
+
+	if(q_queue_last == NULL)
+		q_queue = q_queue_last = item;
+	else
+	{
+        	q_queue_last->next = item;
+		q_queue_last = item;
+	}
+}
+
+static item_t *q_put2(char *artist, char *title, char *len, char *time,
+		char *album, char *mb)
+{
+	char *temp = NULL;
+	item_t *item;
+
+	item = calloc(1, sizeof(item_t));
+	temp = fmt_unescape(artist);
+	item->artist = fmt_escape(temp);
+	curl_free(temp);
+	temp = fmt_unescape(title);
+	item->title = fmt_escape(temp);
+	curl_free(temp);
+	memcpy(item->len, len, sizeof(len));
+	temp = fmt_unescape(time);
+	item->utctime = fmt_escape(temp);
+	curl_free(temp);
+	temp = fmt_unescape(album);
+	item->album = fmt_escape(temp);
+	curl_free(temp);
+	temp = fmt_unescape(mb);
+	item->mb = fmt_escape(temp);
+	curl_free(temp);
+
+	q_nitems++;
+
+	item->next = NULL;
+	if(q_queue_last == NULL)
+		q_queue = q_queue_last = item;
+	else
+	{
+		q_queue_last->next = item;
+		q_queue_last = item;
+	}
+
+	return item;
+}
+
+static item_t *q_peek(void)
+{
+	if (q_nitems == 0)
+		return NULL;
+	return q_queue;
+}
+
+static item_t *q_peekall(int rewind)
+{
+	static item_t *citem = NULL;
+	item_t *temp_item;
+
+	if (rewind) {
+		citem = q_queue;
+		return NULL;
+	}
+
+	temp_item = citem;
+
+	if(citem != NULL)
+		citem = citem->next;
+
+	return temp_item;
+}
+
+static int q_get(void)
+{
+	item_t *item;
+
+	if (q_nitems == 0)
+		return 0;
+	
+	item = q_queue;
+
+	if(item == NULL)
+		return 0;
+
+	q_nitems--;
+	q_queue = q_queue->next;
+
+	q_item_free(item);
+
+	if (q_nitems == 0)
+	{
+		q_queue_last = NULL;
+		return 0;
+	}
+
+	return -1;
+}
+
+static void q_free(void)
+{
+	while (q_get());
+}
+
+static int q_len(void)
+{
+	return q_nitems;
+}
+
+/* Error functions */
+
+static void gerpok_sc_throw_error(char *errortxt)
+{
+	gerpok_sc_major_error_present = 1;
+	if(gerpok_sc_major_error == NULL)
+		gerpok_sc_major_error = strdup(errortxt);
+
+	return;
+}
+
+int gerpok_sc_catch_error(void)
+{
+	return gerpok_sc_major_error_present;
+}
+
+char *gerpok_sc_fetch_error(void)
+{
+	return gerpok_sc_major_error;
+}
+
+void gerpok_sc_clear_error(void)
+{
+	gerpok_sc_major_error_present = 0;
+	if(gerpok_sc_major_error != NULL)
+		free(gerpok_sc_major_error);
+	gerpok_sc_major_error = NULL;
+
+	return;
+}
+
+static size_t gerpok_sc_store_res(void *ptr, size_t size,
+		size_t nmemb,
+		void *stream __attribute__((unused)))
+{
+	int len = size * nmemb;
+
+	gerpok_sc_srv_res = realloc(gerpok_sc_srv_res, gerpok_sc_srv_res_size + len + 1);
+	memcpy(gerpok_sc_srv_res + gerpok_sc_srv_res_size,
+			ptr, len);
+	gerpok_sc_srv_res_size += len;
+	return len;
+}
+
+static void gerpok_sc_free_res(void)
+{
+	if(gerpok_sc_srv_res != NULL)
+		free(gerpok_sc_srv_res);
+	gerpok_sc_srv_res = NULL;
+	gerpok_sc_srv_res_size = 0;
+}
+
+static int gerpok_sc_parse_hs_res(void)
+{
+	char *interval;
+
+	if (!gerpok_sc_srv_res_size) {
+		pdebug("No reply from server", DEBUG);
+		return -1;
+	}
+	*(gerpok_sc_srv_res + gerpok_sc_srv_res_size) = 0;
+
+	if (!strncmp(gerpok_sc_srv_res, "FAILED ", 7)) {
+		interval = strstr(gerpok_sc_srv_res, "INTERVAL");
+		if(!interval) {
+			pdebug("missing INTERVAL", DEBUG);
+		}
+		else
+		{
+			*(interval - 1) = 0;
+			gerpok_sc_submit_interval = strtol(interval + 8, NULL, 10);
+		}
+
+		/* Throwing a major error, just in case */
+		/* gerpok_sc_throw_error(fmt_vastr("%s", gerpok_sc_srv_res));
+		   gerpok_sc_hs_errors++; */
+		pdebug(fmt_vastr("error: %s", gerpok_sc_srv_res), DEBUG);
+
+		return -1;
+	}
+
+	if (!strncmp(gerpok_sc_srv_res, "UPDATE ", 7)) {
+		interval = strstr(gerpok_sc_srv_res, "INTERVAL");
+		if(!interval)
+		{
+			pdebug("missing INTERVAL", DEBUG);
+		}
+		else
+		{
+			*(interval - 1) = 0;
+			gerpok_sc_submit_interval = strtol(interval + 8, NULL, 10);
+		}
+
+		gerpok_sc_submit_url = strchr(strchr(gerpok_sc_srv_res, '\n') + 1, '\n') + 1;
+		*(gerpok_sc_submit_url - 1) = 0;
+		gerpok_sc_submit_url = strdup(gerpok_sc_submit_url);
+		gerpok_sc_challenge_hash = strchr(gerpok_sc_srv_res, '\n') + 1;
+		*(gerpok_sc_challenge_hash - 1) = 0;
+		gerpok_sc_challenge_hash = strdup(gerpok_sc_challenge_hash);
+
+		/* Throwing major error. Need to alert client to update. */
+		gerpok_sc_throw_error(fmt_vastr("Please update Audacious.\n"
+			"Update available at: http://audacious-media-player.org"));
+		pdebug(fmt_vastr("update client: %s", gerpok_sc_srv_res + 7), DEBUG);
+
+		/*
+		 * Russ isn't clear on whether we can submit with a not-updated
+		 * client.  Neither is RJ.  I use what we did before.
+		 */
+		gerpok_sc_giveup = -1;
+		return -1;
+	}
+	if (!strncmp(gerpok_sc_srv_res, "UPTODATE\n", 9)) {
+		gerpok_sc_bad_users = 0;
+
+		interval = strstr(gerpok_sc_srv_res, "INTERVAL");
+		if (!interval) {
+			pdebug("missing INTERVAL", DEBUG);
+			/*
+			 * This is probably a bad thing, but Russ seems to
+			 * think its OK to assume that an UPTODATE response
+			 * may not have an INTERVAL...  We return -1 anyway.
+			 */
+			return -1;
+		}
+		else
+		{
+			*(interval - 1) = 0;
+			gerpok_sc_submit_interval = strtol(interval + 8, NULL, 10);
+		}
+
+		gerpok_sc_submit_url = strchr(strchr(gerpok_sc_srv_res, '\n') + 1, '\n') + 1;
+		*(gerpok_sc_submit_url - 1) = 0;
+		gerpok_sc_submit_url = strdup(gerpok_sc_submit_url);
+		gerpok_sc_challenge_hash = strchr(gerpok_sc_srv_res, '\n') + 1;
+		*(gerpok_sc_challenge_hash - 1) = 0;
+		gerpok_sc_challenge_hash = strdup(gerpok_sc_challenge_hash);
+
+		return 0;
+	}
+	if(!strncmp(gerpok_sc_srv_res, "BADUSER", 7)) {
+		/* Throwing major error. */
+		gerpok_sc_throw_error("Incorrect username/password.\n"
+				"Please fix in configuration.");
+		pdebug("incorrect username/password", DEBUG);
+
+		interval = strstr(gerpok_sc_srv_res, "INTERVAL");
+		if(!interval)
+		{
+			pdebug("missing INTERVAL", DEBUG);
+		}
+		else
+		{
+			*(interval - 1) = 0;
+			gerpok_sc_submit_interval = strtol(interval + 8, NULL, 10);
+		}
+
+		return -1;
+	}
+
+	pdebug(fmt_vastr("unknown server-reply '%s'", gerpok_sc_srv_res), DEBUG);
+	return -1;
+}
+
+static void hexify(char *pass, int len)
+{
+	char *bp = gerpok_sc_response_hash;
+	char hexchars[] = "0123456789abcdef";
+	int i;
+
+	memset(gerpok_sc_response_hash, 0, sizeof(gerpok_sc_response_hash));
+	
+	for(i = 0; i < len; i++) {
+		*(bp++) = hexchars[(pass[i] >> 4) & 0x0f];
+		*(bp++) = hexchars[pass[i] & 0x0f];
+	}
+	*bp = 0;
+
+	return;
+}
+
+static int gerpok_sc_handshake(void)
+{
+	int status;
+	char buf[4096];
+	CURL *curl;
+
+	snprintf(buf, sizeof(buf), "%s/?hs=true&p=%s&c=%s&v=%s&u=%s",
+			SCROBBLER_HS_URL, SCROBBLER_VERSION,
+			SCROBBLER_CLI_ID, SCROBBLER_IMPLEMENTATION, gerpok_sc_username);
+
+	curl = curl_easy_init();
+	curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
+	curl_easy_setopt(curl, CURLOPT_URL, buf);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, 
+			gerpok_sc_store_res);
+	memset(gerpok_sc_curl_errbuf, 0, sizeof(gerpok_sc_curl_errbuf));
+	curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, gerpok_sc_curl_errbuf);
+	curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+	status = curl_easy_perform(curl);
+	curl_easy_cleanup(curl);
+
+	gerpok_sc_hs_timeout = time(NULL) + SCROBBLER_HS_WAIT;
+
+	if (status) {
+		pdebug(gerpok_sc_curl_errbuf, DEBUG);
+		gerpok_sc_hs_errors++;
+		gerpok_sc_free_res();
+		return -1;
+	}
+
+	if (gerpok_sc_parse_hs_res()) {
+		gerpok_sc_hs_errors++;
+		gerpok_sc_free_res();
+		return -1;
+	}
+
+	if (gerpok_sc_challenge_hash != NULL) {
+		md5_state_t md5state;
+		unsigned char md5pword[16];
+		
+		md5_init(&md5state);
+		/*pdebug(fmt_vastr("Pass Hash: %s", gerpok_sc_password), DEBUG);*/
+		md5_append(&md5state, (unsigned const char *)gerpok_sc_password,
+				strlen(gerpok_sc_password));
+		/*pdebug(fmt_vastr("Challenge Hash: %s", gerpok_sc_challenge_hash), DEBUG);*/
+		md5_append(&md5state, (unsigned const char *)gerpok_sc_challenge_hash,
+				strlen(gerpok_sc_challenge_hash));
+		md5_finish(&md5state, md5pword);
+		hexify((char*)md5pword, sizeof(md5pword));
+		/*pdebug(fmt_vastr("Response Hash: %s", gerpok_sc_response_hash), DEBUG);*/
+	}
+
+	gerpok_sc_hs_errors = 0;
+	gerpok_sc_hs_status = 1;
+
+	gerpok_sc_free_res();
+
+	pdebug(fmt_vastr("submiturl: %s - interval: %d", 
+				gerpok_sc_submit_url, gerpok_sc_submit_interval), DEBUG);
+
+	return 0;
+}
+
+static int gerpok_sc_parse_sb_res(void)
+{
+	char *ch, *ch2;
+
+	if (!gerpok_sc_srv_res_size) {
+		pdebug("No response from server", DEBUG);
+		return -1;
+	}
+	*(gerpok_sc_srv_res + gerpok_sc_srv_res_size) = 0;
+
+	if (!strncmp(gerpok_sc_srv_res, "OK", 2)) {
+		if ((ch = strstr(gerpok_sc_srv_res, "INTERVAL"))) {
+			gerpok_sc_submit_interval = strtol(ch + 8, NULL, 10);
+			pdebug(fmt_vastr("got new interval: %d",
+						gerpok_sc_submit_interval), DEBUG);
+		}
+
+		pdebug(fmt_vastr("submission ok: %s", gerpok_sc_srv_res), DEBUG);
+
+		return 0;
+	}
+
+	if (!strncmp(gerpok_sc_srv_res, "BADAUTH", 7)) {
+		if ((ch = strstr(gerpok_sc_srv_res, "INTERVAL"))) {
+			gerpok_sc_submit_interval = strtol(ch + 8, NULL, 10);
+			pdebug(fmt_vastr("got new interval: %d",
+						gerpok_sc_submit_interval), DEBUG);
+		}
+
+		pdebug("incorrect username/password", DEBUG);
+
+		gerpok_sc_giveup = 0;
+
+		/*
+		 * We obviously aren't authenticated.  The server might have
+		 * lost our handshake status though, so let's try
+		 * re-handshaking...  This might not be proper.
+		 * (we don't give up)
+		 */
+		gerpok_sc_hs_status = 0;
+
+		if(gerpok_sc_challenge_hash != NULL)
+			free(gerpok_sc_challenge_hash);
+		if(gerpok_sc_submit_url != NULL)
+			free(gerpok_sc_submit_url);
+
+		gerpok_sc_challenge_hash = gerpok_sc_submit_url = NULL;
+		gerpok_sc_bad_users++;
+
+		if(gerpok_sc_bad_users > 2)
+		{
+			pdebug("3 BADAUTH returns on submission. Halting "
+				"submissions until login fixed.", DEBUG)
+			gerpok_sc_throw_error("Incorrect username/password.\n"
+				"Please fix in configuration.");
+		}
+
+		return -1;
+	}
+
+	if (!strncmp(gerpok_sc_srv_res, "FAILED", 6))  {
+		if ((ch = strstr(gerpok_sc_srv_res, "INTERVAL"))) {
+			gerpok_sc_submit_interval = strtol(ch + 8, NULL, 10);
+			pdebug(fmt_vastr("got new interval: %d",
+						gerpok_sc_submit_interval), DEBUG);
+		}
+
+		/* This could be important. (Such as FAILED - Get new plugin) */
+		/*gerpok_sc_throw_error(fmt_vastr("%s", gerpok_sc_srv_res));*/
+
+		pdebug(gerpok_sc_srv_res, DEBUG);
+
+		return -1;
+	}
+
+	if (!strncmp(gerpok_sc_srv_res, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">", 50)) {
+		ch = strstr(gerpok_sc_srv_res, "<TITLE>") + 7;
+		ch2 = strstr(gerpok_sc_srv_res, "</TITLE>");
+		*ch2 = '\0';
+
+		pdebug(fmt_vastr("HTTP Error (%d): '%s'",
+					atoi(ch), ch + 4), DEBUG);
+		*ch2 = '<';
+
+		return -1;
+	}
+
+	pdebug(fmt_vastr("unknown server-reply %s", gerpok_sc_srv_res), DEBUG);
+
+	return -1;
+}
+
+static gchar *gerpok_sc_itemtag(char c, int n, char *str)
+{
+    static char buf[SCROBBLER_SB_MAXLEN]; 
+    snprintf(buf, SCROBBLER_SB_MAXLEN, "&%c[%d]=%s", c, n, str);
+    return buf;
+}
+
+#define cfa(f, l, n, v) \
+curl_formadd(f, l, CURLFORM_COPYNAME, n, \
+		CURLFORM_PTRCONTENTS, v, CURLFORM_END)
+
+static int gerpok_sc_generateentry(GString *submission)
+{
+	int i;
+	item_t *item;
+
+	i = 0;
+#ifdef ALLOW_MULTIPLE
+	q_peekall(1);
+	while ((item = q_peekall(0)) && i < 10) {
+#else
+		item = q_peek();
+#endif
+		if (!item)
+			return i;
+
+                g_string_append(submission,gerpok_sc_itemtag('a',i,I_ARTIST(item)));
+                g_string_append(submission,gerpok_sc_itemtag('t',i,I_TITLE(item)));
+                g_string_append(submission,gerpok_sc_itemtag('l',i,I_LEN(item)));
+                g_string_append(submission,gerpok_sc_itemtag('i',i,I_TIME(item)));
+                g_string_append(submission,gerpok_sc_itemtag('m',i,I_MB(item)));
+                g_string_append(submission,gerpok_sc_itemtag('b',i,I_ALBUM(item)));
+
+		pdebug(fmt_vastr("a[%d]=%s t[%d]=%s l[%d]=%s i[%d]=%s m[%d]=%s b[%d]=%s",
+				i, I_ARTIST(item),
+				i, I_TITLE(item),
+				i, I_LEN(item),
+				i, I_TIME(item),
+				i, I_MB(item),
+				i, I_ALBUM(item)), DEBUG);
+#ifdef ALLOW_MULTIPLE
+		i++;
+	}
+#endif
+
+	return i;
+}
+
+static int gerpok_sc_submitentry(gchar *entry)
+{
+	CURL *curl;
+	/* struct HttpPost *post = NULL , *last = NULL; */
+	int status;
+        GString *submission;
+
+	curl = curl_easy_init();
+	curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
+	curl_easy_setopt(curl, CURLOPT_URL, gerpok_sc_submit_url);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
+			gerpok_sc_store_res);
+	curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
+	curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+	/*cfa(&post, &last, "debug", "failed");*/
+
+	/*pdebug(fmt_vastr("Username: %s", gerpok_sc_username), DEBUG);*/
+        submission = g_string_new("u=");
+        g_string_append(submission,(gchar *)gerpok_sc_username);
+
+	/*pdebug(fmt_vastr("Response Hash: %s", gerpok_sc_response_hash), DEBUG);*/
+        g_string_append(submission,"&s=");
+        g_string_append(submission,(gchar *)gerpok_sc_response_hash);
+
+	g_string_append(submission, entry);
+
+	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, (char *)submission->str);
+	memset(gerpok_sc_curl_errbuf, 0, sizeof(gerpok_sc_curl_errbuf));
+	curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, gerpok_sc_curl_errbuf);
+
+	/*
+	curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
+	curl_easy_setopt(curl, CURLOPT_TIMEOUT, SCROBBLER_SB_WAIT);
+	*/
+
+	status = curl_easy_perform(curl);
+
+	curl_easy_cleanup(curl);
+
+        g_string_free(submission,TRUE);
+
+	if (status) {
+		pdebug(gerpok_sc_curl_errbuf, DEBUG);
+		gerpok_sc_sb_errors++;
+		gerpok_sc_free_res();
+		return -1;
+	}
+
+	if (gerpok_sc_parse_sb_res()) {
+		gerpok_sc_sb_errors++;
+		gerpok_sc_free_res();
+		pdebug(fmt_vastr("Retrying in %d secs, %d elements in queue",
+					gerpok_sc_submit_interval, q_len()), DEBUG);
+		return -1;
+	}
+	gerpok_sc_free_res();
+	return 0;
+}
+
+static void gerpok_sc_handlequeue(GMutex *mutex)
+{
+	GString *submitentry;
+	int nsubmit;
+	int wait;
+
+	if(gerpok_sc_submit_timeout < time(NULL) && gerpok_sc_bad_users < 3)
+	{
+		submitentry = g_string_new("");
+
+		g_mutex_lock(mutex);
+
+		nsubmit = gerpok_sc_generateentry(submitentry);
+
+		g_mutex_unlock(mutex);
+
+		if (nsubmit > 0)
+		{
+			pdebug(fmt_vastr("Number submitting: %d", nsubmit), DEBUG);
+			pdebug(fmt_vastr("Submission: %s", submitentry->str), DEBUG);
+
+			if(!gerpok_sc_submitentry(submitentry->str))
+			{
+				g_mutex_lock(mutex);
+
+#ifdef ALLOW_MULTIPLE
+				q_free();
+#else
+				q_get();
+#endif
+				/*
+				 * This should make sure that the queue doesn't
+				 * get submitted multiple times on a nasty
+				 * segfault...
+				 */
+				dump_queue();
+
+				g_mutex_unlock(mutex);
+
+				gerpok_sc_sb_errors = 0;
+			}
+			if(gerpok_sc_sb_errors)
+			{
+				if(gerpok_sc_sb_errors < 5)
+					/* Retry after 1 min */
+					wait = 60;
+				else
+					wait = /* gerpok_sc_submit_interval + */
+						( ((gerpok_sc_sb_errors - 5) < 7) ?
+						(60 << (gerpok_sc_sb_errors-5)) :
+						7200 );
+				
+				gerpok_sc_submit_timeout = time(NULL) + wait;
+
+				pdebug(fmt_vastr("Error while submitting. "
+					"Retrying after %d seconds.", wait),
+					DEBUG);
+			}
+		}
+
+		g_string_free(submitentry, TRUE);
+	}
+}
+
+static void read_cache(void)
+{
+	FILE *fd;
+	char buf[PATH_MAX], *cache = NULL, *ptr1, *ptr2;
+	int cachesize, written, i = 0;
+	item_t *item;
+
+	cachesize = written = 0;
+
+	snprintf(buf, sizeof(buf), "%s/.audacious/gerpokqueue.txt", g_get_home_dir());
+
+	if (!(fd = fopen(buf, "r")))
+		return;
+	pdebug(fmt_vastr("Opening %s", buf), DEBUG);
+	while(!feof(fd))
+	{
+		cachesize += CACHE_SIZE;
+		cache = realloc(cache, cachesize + 1);
+		written += fread(cache + written, 1, CACHE_SIZE, fd);
+		cache[written] = '\0';
+	}
+	fclose(fd);
+	ptr1 = cache;
+	while(ptr1 < cache + written - 1)
+	{
+		char *artist, *title, *len, *time, *album, *mb;
+
+		pdebug("Pushed:", DEBUG);
+		ptr2 = strchr(ptr1, ' ');
+		artist = calloc(1, ptr2 - ptr1 + 1);
+		strncpy(artist, ptr1, ptr2 - ptr1);
+		ptr1 = ptr2 + 1;
+		ptr2 = strchr(ptr1, ' ');
+		title = calloc(1, ptr2 - ptr1 + 1);
+		strncpy(title, ptr1, ptr2 - ptr1);
+		ptr1 = ptr2 + 1;
+		ptr2 = strchr(ptr1, ' ');
+		len = calloc(1, ptr2 - ptr1 + 1);
+		strncpy(len, ptr1, ptr2 - ptr1);
+		ptr1 = ptr2 + 1;
+		ptr2 = strchr(ptr1, ' ');
+		time = calloc(1, ptr2 - ptr1 + 1);
+		strncpy(time, ptr1, ptr2 - ptr1);
+		ptr1 = ptr2 + 1;
+		ptr2 = strchr(ptr1, ' ');
+		album = calloc(1, ptr2 - ptr1 + 1);
+		strncpy(album, ptr1, ptr2 - ptr1);
+		ptr1 = ptr2 + 1;
+		ptr2 = strchr(ptr1, '\n');
+		if(ptr2 != NULL)
+			*ptr2 = '\0';
+		mb = calloc(1, strlen(ptr1) + 1);
+		strncpy(mb, ptr1, strlen(ptr1));
+		if(ptr2 != NULL)
+			*ptr2 = '\n';
+		/* Why is our save printing out CR/LF? */
+		ptr1 = ptr2 + 1;
+
+		item = q_put2(artist, title, len, time, album, mb);
+		pdebug(fmt_vastr("a[%d]=%s t[%d]=%s l[%d]=%s i[%d]=%s m[%d]=%s b[%d]=%s",
+				i, I_ARTIST(item),
+				i, I_TITLE(item),
+				i, I_LEN(item),
+				i, I_TIME(item),
+				i, I_MB(item),
+				i, I_ALBUM(item)), DEBUG);
+		free(artist);
+		free(title);
+		free(len);
+		free(time);
+		free(album);
+		free(mb);
+
+		i++;
+	}
+	pdebug("Done loading cache.", DEBUG);
+	free(cache);
+}
+
+static void dump_queue(void)
+{
+	FILE *fd;
+	item_t *item;
+	char *home, buf[PATH_MAX];
+
+	/*pdebug("Entering dump_queue();", DEBUG);*/
+
+	if (!(home = getenv("HOME")))
+	{
+		pdebug("No HOME directory found. Cannot dump queue.", DEBUG);
+		return;
+	}
+
+	snprintf(buf, sizeof(buf), "%s/.audacious/gerpokqueue.txt", home);
+
+	if (!(fd = fopen(buf, "w")))
+	{
+		pdebug(fmt_vastr("Failure opening %s", buf), DEBUG);
+		return;
+	}
+
+	pdebug(fmt_vastr("Opening %s", buf), DEBUG);
+
+	q_peekall(1);
+
+	while ((item = q_peekall(0))) {
+		fprintf(fd, "%s %s %s %s %s %s\n",
+					I_ARTIST(item),
+					I_TITLE(item),
+					I_LEN(item),
+					I_TIME(item),
+					I_ALBUM(item),
+					I_MB(item));
+	}
+
+	fclose(fd);
+}
+
+/* This was made public */
+
+void gerpok_sc_cleaner(void)
+{
+	if(gerpok_sc_submit_url != NULL)
+		free(gerpok_sc_submit_url);
+	if(gerpok_sc_username != NULL)
+		free(gerpok_sc_username);
+	if(gerpok_sc_password != NULL)
+		free(gerpok_sc_password);
+	if(gerpok_sc_challenge_hash != NULL)
+		free(gerpok_sc_challenge_hash);
+	if(gerpok_sc_srv_res != NULL)
+		free(gerpok_sc_srv_res);
+	if(gerpok_sc_major_error != NULL)
+		free(gerpok_sc_major_error);
+	dump_queue();
+	q_free();
+	pdebug("scrobbler shutting down", DEBUG);
+}
+
+static void gerpok_sc_checkhandshake(void)
+{
+	int wait;
+
+	if (gerpok_sc_hs_status)
+		return;
+	if (gerpok_sc_hs_timeout < time(NULL))
+	{
+		gerpok_sc_handshake();
+
+		if(gerpok_sc_hs_errors)
+		{
+			if(gerpok_sc_hs_errors < 5)
+				/* Retry after 60 seconds */
+				wait = 60;
+			else
+				wait = /* gerpok_sc_submit_interval + */
+					( ((gerpok_sc_hs_errors - 5) < 7) ?
+					(60 << (gerpok_sc_hs_errors-5)) :
+					7200 );
+			gerpok_sc_hs_timeout = time(NULL) + wait;
+			pdebug(fmt_vastr("Error while handshaking. Retrying "
+				"after %d seconds.", wait), DEBUG);
+		}
+	}
+}
+
+/**** Public *****/
+
+/* Called at session startup*/
+
+void gerpok_sc_init(char *uname, char *pwd)
+{
+	gerpok_sc_hs_status = gerpok_sc_hs_timeout = gerpok_sc_hs_errors = gerpok_sc_submit_timeout =
+		gerpok_sc_srv_res_size = gerpok_sc_giveup = gerpok_sc_major_error_present =
+		gerpok_sc_bad_users = gerpok_sc_sb_errors = 0;
+	gerpok_sc_submit_interval = 100;
+
+	gerpok_sc_submit_url = gerpok_sc_username = gerpok_sc_password = gerpok_sc_srv_res =
+		gerpok_sc_challenge_hash = gerpok_sc_major_error = NULL;
+	gerpok_sc_username = strdup(uname);
+	gerpok_sc_password = strdup(pwd);
+	read_cache();
+	pdebug("scrobbler starting up", DEBUG);
+}
+
+void gerpok_sc_addentry(GMutex *mutex, TitleInput *tuple, int len)
+{
+	g_mutex_lock(mutex);
+	q_put(tuple, len);
+	/*
+	 * This will help make sure the queue will be saved on a nasty
+	 * segfault...
+	 */
+	dump_queue();
+	g_mutex_unlock(mutex);
+}
+
+/* Call periodically from the plugin */
+int gerpok_sc_idle(GMutex *mutex)
+{
+	gerpok_sc_checkhandshake();
+	if (gerpok_sc_hs_status)
+		gerpok_sc_handlequeue(mutex);
+
+	return gerpok_sc_giveup;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/scrobbler/gerpok.h	Fri Dec 08 19:31:43 2006 -0800
@@ -0,0 +1,13 @@
+#ifndef G_NET_H
+#define G_NET_H 1
+
+#include "audacious/titlestring.h"
+
+int gerpok_sc_idle(GMutex *);
+void gerpok_sc_init(char *, char *);
+void gerpok_sc_addentry(GMutex *, TitleInput *, int);
+void gerpok_sc_cleaner(void);
+int gerpok_sc_catch_error(void);
+char *gerpok_sc_fetch_error(void);
+void gerpok_sc_clear_error(void);
+#endif
--- a/src/scrobbler/gtkstuff.c	Fri Dec 08 17:52:54 2006 -0800
+++ b/src/scrobbler/gtkstuff.c	Fri Dec 08 19:31:43 2006 -0800
@@ -6,6 +6,7 @@
 
 #include <stdio.h>
 #include <string.h>
+#include "settings.h"
 #include "config.h"
 #include "md5.h"
 
--- a/src/scrobbler/queue.c	Fri Dec 08 17:52:54 2006 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,159 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <curl/curl.h>
-#include "queue.h"
-#include "fmt.h"
-
-static item_t *q_queue = NULL;
-static item_t *q_queue_last = NULL;
-static int q_nitems;
-
-static void q_item_free(item_t *item)
-{
-	if (item == NULL)
-		return;
-	curl_free(item->artist);
-	curl_free(item->title);
-	curl_free(item->utctime);
-	curl_free(item->mb);
-	curl_free(item->album);
-	free(item);
-}
-
-void q_put(TitleInput *tuple, int len)
-{
-	item_t *item;
-
-	item = malloc(sizeof(item_t));
-
-	item->artist = fmt_escape(tuple->performer);
-	item->title = fmt_escape(tuple->track_name);
-	item->utctime = fmt_escape(fmt_timestr(time(NULL), 1));
-	snprintf(item->len, sizeof(item->len), "%d", len);
-
-#ifdef NOTYET
-	if(tuple->mb == NULL)
-#endif
-		item->mb = fmt_escape("");
-#ifdef NOTYET
-	else
-		item->mb = fmt_escape((char*)tuple->mb);
-#endif
-
-	if(tuple->album_name == NULL)
-		item->album = fmt_escape("");
-	else
-		item->album = fmt_escape((char*)tuple->album_name);
-
-	q_nitems++;
-
-	item->next = NULL;
-
-	if(q_queue_last == NULL)
-		q_queue = q_queue_last = item;
-	else
-	{
-        	q_queue_last->next = item;
-		q_queue_last = item;
-	}
-}
-
-item_t *q_put2(char *artist, char *title, char *len, char *time,
-		char *album, char *mb)
-{
-	char *temp = NULL;
-	item_t *item;
-
-	item = calloc(1, sizeof(item_t));
-	temp = fmt_unescape(artist);
-	item->artist = fmt_escape(temp);
-	curl_free(temp);
-	temp = fmt_unescape(title);
-	item->title = fmt_escape(temp);
-	curl_free(temp);
-	memcpy(item->len, len, sizeof(len));
-	temp = fmt_unescape(time);
-	item->utctime = fmt_escape(temp);
-	curl_free(temp);
-	temp = fmt_unescape(album);
-	item->album = fmt_escape(temp);
-	curl_free(temp);
-	temp = fmt_unescape(mb);
-	item->mb = fmt_escape(temp);
-	curl_free(temp);
-
-	q_nitems++;
-
-	item->next = NULL;
-	if(q_queue_last == NULL)
-		q_queue = q_queue_last = item;
-	else
-	{
-		q_queue_last->next = item;
-		q_queue_last = item;
-	}
-
-	return item;
-}
-
-item_t *q_peek(void)
-{
-	if (q_nitems == 0)
-		return NULL;
-	return q_queue;
-}
-
-item_t *q_peekall(int rewind)
-{
-	static item_t *citem = NULL;
-	item_t *temp_item;
-
-	if (rewind) {
-		citem = q_queue;
-		return NULL;
-	}
-
-	temp_item = citem;
-
-	if(citem != NULL)
-		citem = citem->next;
-
-	return temp_item;
-}
-
-int q_get(void)
-{
-	item_t *item;
-
-	if (q_nitems == 0)
-		return 0;
-	
-	item = q_queue;
-
-	if(item == NULL)
-		return 0;
-
-	q_nitems--;
-	q_queue = q_queue->next;
-
-	q_item_free(item);
-
-	if (q_nitems == 0)
-	{
-		q_queue_last = NULL;
-		return 0;
-	}
-
-	return -1;
-}
-
-void q_free(void)
-{
-	while (q_get());
-}
-
-int q_len(void)
-{
-	return q_nitems;
-}
--- a/src/scrobbler/queue.h	Fri Dec 08 17:52:54 2006 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-#ifndef QUEUE_H
-#define QUEUE_H 1
-
-#include "audacious/titlestring.h"
-
-#define I_ARTIST(i) i->artist
-#define I_TITLE(i) i->title
-#define I_TIME(i) i->utctime
-#define I_LEN(i) i->len
-#define I_MB(i) i->mb
-#define I_ALBUM(i) i->album
-
-typedef struct {
-	char *artist,
-		*title,
-		*mb,
-		*album,
-		*utctime,
-		len[16];
-	int numtries;
-	void *next;
-} item_t;
-void q_put(TitleInput *, int);
-item_t *q_put2(char *, char *, char *, char *, char *, char *);
-item_t *q_peek(void);
-item_t *q_peekall(int);
-int q_get(void);
-void q_free(void);
-int q_len(void);
-#endif
--- a/src/scrobbler/scrobbler.c	Fri Dec 08 17:52:54 2006 -0800
+++ b/src/scrobbler/scrobbler.c	Fri Dec 08 19:31:43 2006 -0800
@@ -7,11 +7,13 @@
 #include <stdio.h>
 #include "fmt.h"
 #include "md5.h"
-#include "queue.h"
 #include "scrobbler.h"
 #include "config.h"
+#include "settings.h"
 #include <glib.h>
 
+#include <audacious/titlestring.h>
+
 #define SCROBBLER_HS_URL "http://post.audioscrobbler.com"
 #define SCROBBLER_CLI_ID "aud"
 #define SCROBBLER_HS_WAIT 1800
@@ -45,6 +47,179 @@
 
 static void dump_queue();
 
+/**** Queue stuff ****/
+
+#define I_ARTIST(i) i->artist
+#define I_TITLE(i) i->title
+#define I_TIME(i) i->utctime
+#define I_LEN(i) i->len
+#define I_MB(i) i->mb
+#define I_ALBUM(i) i->album
+
+typedef struct {
+	char *artist,
+		*title,
+		*mb,
+		*album,
+		*utctime,
+		len[16];
+	int numtries;
+	void *next;
+} item_t;
+
+static item_t *q_queue = NULL;
+static item_t *q_queue_last = NULL;
+static int q_nitems;
+
+static void q_item_free(item_t *item)
+{
+	if (item == NULL)
+		return;
+	curl_free(item->artist);
+	curl_free(item->title);
+	curl_free(item->utctime);
+	curl_free(item->mb);
+	curl_free(item->album);
+	free(item);
+}
+
+static void q_put(TitleInput *tuple, int len)
+{
+	item_t *item;
+
+	item = malloc(sizeof(item_t));
+
+	item->artist = fmt_escape(tuple->performer);
+	item->title = fmt_escape(tuple->track_name);
+	item->utctime = fmt_escape(fmt_timestr(time(NULL), 1));
+	snprintf(item->len, sizeof(item->len), "%d", len);
+
+#ifdef NOTYET
+	if(tuple->mb == NULL)
+#endif
+		item->mb = fmt_escape("");
+#ifdef NOTYET
+	else
+		item->mb = fmt_escape((char*)tuple->mb);
+#endif
+
+	if(tuple->album_name == NULL)
+		item->album = fmt_escape("");
+	else
+		item->album = fmt_escape((char*)tuple->album_name);
+
+	q_nitems++;
+
+	item->next = NULL;
+
+	if(q_queue_last == NULL)
+		q_queue = q_queue_last = item;
+	else
+	{
+        	q_queue_last->next = item;
+		q_queue_last = item;
+	}
+}
+
+static item_t *q_put2(char *artist, char *title, char *len, char *time,
+		char *album, char *mb)
+{
+	char *temp = NULL;
+	item_t *item;
+
+	item = calloc(1, sizeof(item_t));
+	temp = fmt_unescape(artist);
+	item->artist = fmt_escape(temp);
+	curl_free(temp);
+	temp = fmt_unescape(title);
+	item->title = fmt_escape(temp);
+	curl_free(temp);
+	memcpy(item->len, len, sizeof(len));
+	temp = fmt_unescape(time);
+	item->utctime = fmt_escape(temp);
+	curl_free(temp);
+	temp = fmt_unescape(album);
+	item->album = fmt_escape(temp);
+	curl_free(temp);
+	temp = fmt_unescape(mb);
+	item->mb = fmt_escape(temp);
+	curl_free(temp);
+
+	q_nitems++;
+
+	item->next = NULL;
+	if(q_queue_last == NULL)
+		q_queue = q_queue_last = item;
+	else
+	{
+		q_queue_last->next = item;
+		q_queue_last = item;
+	}
+
+	return item;
+}
+
+static item_t *q_peek(void)
+{
+	if (q_nitems == 0)
+		return NULL;
+	return q_queue;
+}
+
+static item_t *q_peekall(int rewind)
+{
+	static item_t *citem = NULL;
+	item_t *temp_item;
+
+	if (rewind) {
+		citem = q_queue;
+		return NULL;
+	}
+
+	temp_item = citem;
+
+	if(citem != NULL)
+		citem = citem->next;
+
+	return temp_item;
+}
+
+static int q_get(void)
+{
+	item_t *item;
+
+	if (q_nitems == 0)
+		return 0;
+	
+	item = q_queue;
+
+	if(item == NULL)
+		return 0;
+
+	q_nitems--;
+	q_queue = q_queue->next;
+
+	q_item_free(item);
+
+	if (q_nitems == 0)
+	{
+		q_queue_last = NULL;
+		return 0;
+	}
+
+	return -1;
+}
+
+static void q_free(void)
+{
+	while (q_get());
+}
+
+static int q_len(void)
+{
+	return q_nitems;
+}
+
 /* Error functions */
 
 static void sc_throw_error(char *errortxt)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/scrobbler/settings.h	Fri Dec 08 19:31:43 2006 -0800
@@ -0,0 +1,14 @@
+#include "../../config.h"
+
+#ifndef __SCROBBLER_CONFIG_H__
+#define __SCROBBLER_CONFIG_H__
+
+#define DEBUG 3
+#define META_DEBUG 3
+#define SUB_DEBUG 3
+#define CLIENT "Audacious"
+#define USER_AGENT "AudioScrobbler/1.1" PACKAGE_NAME "/" PACKAGE_VERSION
+#define MAKE_BMP
+#define ALLOW_MULTIPLE
+
+#endif
--- a/src/scrobbler/xmms_scrobbler.c	Fri Dec 08 17:52:54 2006 -0800
+++ b/src/scrobbler/xmms_scrobbler.c	Fri Dec 08 19:31:43 2006 -0800
@@ -1,3 +1,4 @@
+#include "settings.h"
 #include "config.h"
 
 #include <glib.h>
@@ -21,6 +22,7 @@
 #include <sys/time.h>
 
 #include "scrobbler.h"
+#include "gerpok.h"
 #include "gtkstuff.h"
 #include "config.h"
 #include "fmt.h"
@@ -37,7 +39,7 @@
 static void cleanup(void);
 static void *xs_thread(void *);
 static void *hs_thread(void *);
-static int going;
+static int sc_going, ge_going;
 static GtkWidget *cfgdlg;
 
 static GThread *pt_scrobbler;
@@ -59,40 +61,68 @@
 static void init(void)
 {
 	char *username = NULL, *password = NULL;
+	char *ge_username = NULL, *ge_password = NULL;
 	ConfigDb *cfgfile;
-	going = 1;
+	sc_going = 1;
+	ge_going = 1;
 	GError **moo = NULL;
 	cfgdlg = create_cfgdlg();
 
-        prefswin_page_new(cfgdlg, "Last.FM", DATA_DIR "/images/audioscrobbler.png");
+        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",
+
+	if ((!username || !password) || (!*username || !*password))
+	{
+		pdebug("username/password not found - not starting last.fm support",
 			DEBUG);
-		going = 0;
-		return;
+		sc_going = 0;
 	}
-	sc_init(username, password);
+	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) {
+	if ((pt_scrobbler = g_thread_create(xs_thread, m_scrobbler, TRUE, moo)) == NULL)
+	{
 		pdebug(fmt_vastr("Error creating scrobbler thread: %s", moo), DEBUG);
-		going = 0;
+		sc_going = 0;
+		ge_going = 0;
 		return;
 	}
-	if ((pt_handshake = g_thread_create(hs_thread, m_scrobbler, TRUE, NULL)) == NULL) {
-		pdebug("Error creating handshake thread", DEBUG);
-		going = 0;
+
+	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);
 }
 
@@ -103,12 +133,13 @@
 
         prefswin_page_destroy(cfgdlg);
 
-	if (!going)
+	if (!sc_going && !ge_going)
 		return;
 	pdebug("about to lock mutex", DEBUG);
 	g_mutex_lock(m_scrobbler);
 	pdebug("locked mutex", DEBUG);
-	going = 0;
+	sc_going = 0;
+	ge_going = 0;
 	g_mutex_unlock(m_scrobbler);
 	pdebug("joining threads", DEBUG);
 	g_thread_join(pt_scrobbler);
@@ -116,6 +147,7 @@
 	g_thread_join(pt_handshake);
 
 	sc_cleaner();
+	gerpok_sc_cleaner();
 }
 
 static char ishttp(const char *a)
@@ -377,6 +409,12 @@
 			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();
 
@@ -397,12 +435,14 @@
 					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 = going;
+		run = (sc_going != 0 || ge_going != 0);
 		g_mutex_unlock(m_scrobbler);
 		g_usleep(100000);
 	}
@@ -422,11 +462,20 @@
 		{
 			pdebug("Giving up due to fatal error", DEBUG);
 			g_mutex_lock(m_scrobbler);
-			going = 0;
+			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 = going;
+		run = (sc_going != 0 || ge_going != 0);
 		g_mutex_unlock(m_scrobbler);
 		g_usleep(1000000);
 	}