Mercurial > audlegacy-plugins
changeset 2441:166397a7efe1
Fixes for scrobbler plugin - skipped tracks, inappropriate scrobbling, re-tabs (Bugzilla #200)
author | CJ Kucera <pez@apocalyptech.com> |
---|---|
date | Sat, 08 Mar 2008 09:57:38 +0100 |
parents | 5e87f7cc8d13 |
children | 1286f65395d7 |
files | src/scrobbler/gerpok.c src/scrobbler/scrobbler.c src/scrobbler/settings.h |
diffstat | 3 files changed, 646 insertions(+), 593 deletions(-) [+] |
line wrap: on
line diff
--- a/src/scrobbler/gerpok.c Fri Mar 07 15:56:40 2008 -0600 +++ b/src/scrobbler/gerpok.c Sat Mar 08 09:57:38 2008 +0100 @@ -24,6 +24,7 @@ #define SCROBBLER_IMPLEMENTATION "0.1" /* This is the implementation, not the player version. */ #define SCROBBLER_SB_MAXLEN 1024 #define CACHE_SIZE 1024 +#define ALLOW_MULTIPLE /* Scrobblerbackend for xmms plugin, first draft */
--- a/src/scrobbler/scrobbler.c Fri Mar 07 15:56:40 2008 -0600 +++ b/src/scrobbler/scrobbler.c Sat Mar 08 09:57:38 2008 +0100 @@ -20,33 +20,33 @@ #define SCROBBLER_HS_WAIT 1800 #define SCROBBLER_SB_WAIT 10 #define SCROBBLER_VERSION "1.2" -#define SCROBBLER_IMPLEMENTATION "0.2" /* This is the implementation, not the player version. */ +#define SCROBBLER_IMPLEMENTATION "0.2" /* 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 sc_hs_status, - sc_hs_timeout, - sc_hs_errors, - sc_sb_errors, - sc_bad_users, - sc_submit_interval, - sc_submit_timeout, - sc_srv_res_size, - sc_giveup, - sc_major_error_present; +static int sc_hs_status, + sc_hs_timeout, + sc_hs_errors, + sc_sb_errors, + sc_bad_users, + sc_submit_interval, + sc_submit_timeout, + sc_srv_res_size, + sc_giveup, + sc_major_error_present; -static char *sc_submit_url, /* queue */ - *sc_np_url, /* np */ - *sc_session_id, - *sc_username = NULL, - *sc_password = NULL, - *sc_challenge_hash, - sc_response_hash[65535], - *sc_srv_res, - sc_curl_errbuf[CURL_ERROR_SIZE], - *sc_major_error; +static char *sc_submit_url, /* queue */ + *sc_np_url, /* np */ + *sc_session_id, + *sc_username = NULL, + *sc_password = NULL, + *sc_challenge_hash, + sc_response_hash[65535], + *sc_srv_res, + sc_curl_errbuf[CURL_ERROR_SIZE], + *sc_major_error; static void dump_queue(); @@ -59,24 +59,17 @@ #define I_ALBUM(i) i->album typedef struct { - char *artist, *title, *album; - int utctime, track, len; - int timeplayed; - int numtries; - void *next; + char *artist, *title, *album; + int utctime, track, len; + int timeplayed; + int numtries; + void *next; } item_t; static item_t *q_queue = NULL; static item_t *q_queue_last = NULL; static int q_nitems; - -/* isn't there better way for that? --desowin */ -gboolean sc_timeout(gpointer data) { - if (q_queue_last && audacious_drct_get_playing()) - q_queue_last->timeplayed+=1; - - return TRUE; -} +static item_t *np_item = NULL; gchar * xmms_urldecode_plain(const gchar * encoded_path) @@ -118,518 +111,563 @@ static void q_item_free(item_t *item) { - if (item == NULL) - return; - curl_free(item->artist); - curl_free(item->title); - curl_free(item->album); - free(item); + if (item == NULL) + return; + curl_free(item->artist); + curl_free(item->title); + curl_free(item->album); + free(item); +} + +static item_t *q_additem(item_t *newitem) +{ + pdebug(fmt_vastr("Adding %s - %s to the queue", newitem->artist, newitem->title), DEBUG); + + q_nitems++; + newitem->next = NULL; + if (q_queue_last == NULL) + { + q_queue = q_queue_last = newitem; + } + else + { + q_queue_last->next = newitem; + q_queue_last = newitem; + } + return newitem; +} + +static item_t *create_item(Tuple *tuple, int len) +{ + item_t *item; + const gchar *album; + + item = malloc(sizeof(item_t)); + + item->artist = fmt_escape(aud_tuple_get_string(tuple, FIELD_ARTIST, NULL)); + item->title = fmt_escape(aud_tuple_get_string(tuple, FIELD_TITLE, NULL)); + item->len = len; + item->track = aud_tuple_get_int(tuple, FIELD_TRACK_NUMBER, NULL); + item->timeplayed = 0; + item->utctime = time(NULL); + + album = aud_tuple_get_string(tuple, FIELD_ALBUM, NULL); + if (album) + item->album = fmt_escape((char*) album); + else + item->album = fmt_escape(""); + + item->next = NULL; + + return item; } static item_t *q_put(Tuple *tuple, int t, int len) { - item_t *item; - const gchar *album; - - item = malloc(sizeof(item_t)); + item_t *item; - item->artist = fmt_escape(aud_tuple_get_string(tuple, FIELD_ARTIST, NULL)); - item->title = fmt_escape(aud_tuple_get_string(tuple, FIELD_TITLE, NULL)); - item->len = len; - item->track = aud_tuple_get_int(tuple, FIELD_TRACK_NUMBER, NULL); - if (t == -1) { /* now playing song */ - item->timeplayed = 0; - item->utctime = time(NULL); - } else { /* item from queue */ - item->timeplayed = len; - item->utctime = t; - } + item = create_item(tuple, len); + item->timeplayed = len; + item->utctime = t; + + return q_additem(item); +} - album = aud_tuple_get_string(tuple, FIELD_ALBUM, NULL); - if (album) - item->album = fmt_escape((char*) album); - else - item->album = fmt_escape(""); - - q_nitems++; - - item->next = NULL; +static item_t *set_np(Tuple *tuple, int len) +{ + q_item_free(np_item); + np_item = create_item(tuple, len); - if(q_queue_last == NULL) - q_queue = q_queue_last = item; - else - { - q_queue_last->next = item; - q_queue_last = item; - } + pdebug(fmt_vastr("Tracking now-playing track: %s - %s", np_item->artist, np_item->title), DEBUG); - return item; + return np_item; } #if 0 static item_t *q_peek(void) { - if (q_nitems == 0) - return NULL; - return q_queue; + if (q_nitems == 0) + return NULL; + return q_queue; } #endif static item_t *q_peekall(int rewind) { - static item_t *citem = NULL; - item_t *temp_item; + static item_t *citem = NULL; + item_t *temp_item; - if (rewind) { - citem = q_queue; - return NULL; - } + if (rewind) { + citem = q_queue; + return NULL; + } - temp_item = citem; + temp_item = citem; - if(citem != NULL) - citem = citem->next; + if(citem != NULL) + citem = citem->next; - return temp_item; + return temp_item; } static int q_get(void) { - item_t *item; + item_t *item; - if (q_nitems == 0) - return 0; - - item = q_queue; + if (q_nitems == 0) + return 0; + + item = q_queue; - if(item == NULL) - return 0; + if(item == NULL) + return 0; - q_nitems--; - q_queue = q_queue->next; + q_nitems--; + q_queue = q_queue->next; - q_item_free(item); + pdebug(fmt_vastr("Removing %s - %s from queue", item->artist, item->title), DEBUG); + + q_item_free(item); - if (q_nitems == 0) - { - q_queue_last = NULL; - return 0; - } + if (q_nitems == 0) + { + q_queue_last = NULL; + return 0; + } - return -1; + return -1; } static void q_free(void) { - while (q_get()); + while (q_get()); } static int q_len(void) { - return q_nitems; + return q_nitems; +} + +/* isn't there better way for that? --desowin */ +gboolean sc_timeout(gpointer data) { + if (np_item) + { + if (audacious_drct_get_playing() && !audacious_drct_get_paused()) + np_item->timeplayed+=1; + + /* + * Check our now-playing track to see if it should go into the queue + */ + if (((np_item->timeplayed >= (np_item->len / 2)) || + (np_item->timeplayed >= 240))) + { + q_additem(np_item); + np_item = NULL; + dump_queue(); + } + } + + return TRUE; } /* Error functions */ static void sc_throw_error(char *errortxt) { - sc_major_error_present = 1; - if(sc_major_error == NULL) - sc_major_error = strdup(errortxt); + sc_major_error_present = 1; + if(sc_major_error == NULL) + sc_major_error = strdup(errortxt); - return; + return; } int sc_catch_error(void) { - return sc_major_error_present; + return sc_major_error_present; } char *sc_fetch_error(void) { - return sc_major_error; + return sc_major_error; } void sc_clear_error(void) { - sc_major_error_present = 0; - if(sc_major_error != NULL) - free(sc_major_error); - sc_major_error = NULL; + sc_major_error_present = 0; + if(sc_major_error != NULL) + free(sc_major_error); + sc_major_error = NULL; - return; + return; } static size_t sc_store_res(void *ptr, size_t size, - size_t nmemb, - void *stream __attribute__((unused))) + size_t nmemb, + void *stream __attribute__((unused))) { - int len = size * nmemb; + int len = size * nmemb; - sc_srv_res = realloc(sc_srv_res, sc_srv_res_size + len + 1); - memcpy(sc_srv_res + sc_srv_res_size, - ptr, len); - sc_srv_res_size += len; - return len; + sc_srv_res = realloc(sc_srv_res, sc_srv_res_size + len + 1); + memcpy(sc_srv_res + sc_srv_res_size, + ptr, len); + sc_srv_res_size += len; + return len; } static void sc_free_res(void) { - if(sc_srv_res != NULL) - free(sc_srv_res); - sc_srv_res = NULL; - sc_srv_res_size = 0; + if(sc_srv_res != NULL) + free(sc_srv_res); + sc_srv_res = NULL; + sc_srv_res_size = 0; } static int sc_parse_hs_res(void) { - char *interval; + char *interval; - if (!sc_srv_res_size) { - pdebug("No reply from server", DEBUG); - return -1; - } - *(sc_srv_res + sc_srv_res_size) = 0; + if (!sc_srv_res_size) { + pdebug("No reply from server", DEBUG); + return -1; + } + *(sc_srv_res + sc_srv_res_size) = 0; - if (!strncmp(sc_srv_res, "OK\n", 3)) { - gchar *scratch = g_strdup(sc_srv_res); - gchar **split = g_strsplit(scratch, "\n", 5); + if (!strncmp(sc_srv_res, "OK\n", 3)) { + gchar *scratch = g_strdup(sc_srv_res); + gchar **split = g_strsplit(scratch, "\n", 5); - g_free(scratch); + g_free(scratch); - sc_session_id = g_strdup(split[1]); - sc_np_url = g_strdup(split[2]); - sc_submit_url = g_strdup(split[3]); + sc_session_id = g_strdup(split[1]); + sc_np_url = g_strdup(split[2]); + sc_submit_url = g_strdup(split[3]); - g_strfreev(split); - return 0; - } - if (!strncmp(sc_srv_res, "FAILED ", 7)) { - interval = strstr(sc_srv_res, "INTERVAL"); + g_strfreev(split); + return 0; + } + if (!strncmp(sc_srv_res, "FAILED ", 7)) { + interval = strstr(sc_srv_res, "INTERVAL"); - /* Throwing a major error, just in case */ - /* sc_throw_error(fmt_vastr("%s", sc_srv_res)); - sc_hs_errors++; */ - pdebug(fmt_vastr("error: %s", sc_srv_res), DEBUG); + /* Throwing a major error, just in case */ + /* sc_throw_error(fmt_vastr("%s", sc_srv_res)); + sc_hs_errors++; */ + pdebug(fmt_vastr("error: %s", sc_srv_res), DEBUG); - return -1; - } + return -1; + } - if (!strncmp(sc_srv_res, "UPDATE ", 7)) { - interval = strstr(sc_srv_res, "INTERVAL"); - if(!interval) - { - pdebug("missing INTERVAL", DEBUG); - } - else - { - *(interval - 1) = 0; - sc_submit_interval = strtol(interval + 8, NULL, 10); - } + if (!strncmp(sc_srv_res, "UPDATE ", 7)) { + interval = strstr(sc_srv_res, "INTERVAL"); + if(!interval) + { + pdebug("missing INTERVAL", DEBUG); + } + else + { + *(interval - 1) = 0; + sc_submit_interval = strtol(interval + 8, NULL, 10); + } - sc_submit_url = strchr(strchr(sc_srv_res, '\n') + 1, '\n') + 1; - *(sc_submit_url - 1) = 0; - sc_submit_url = strdup(sc_submit_url); - sc_challenge_hash = strchr(sc_srv_res, '\n') + 1; - *(sc_challenge_hash - 1) = 0; - sc_challenge_hash = strdup(sc_challenge_hash); + sc_submit_url = strchr(strchr(sc_srv_res, '\n') + 1, '\n') + 1; + *(sc_submit_url - 1) = 0; + sc_submit_url = strdup(sc_submit_url); + sc_challenge_hash = strchr(sc_srv_res, '\n') + 1; + *(sc_challenge_hash - 1) = 0; + sc_challenge_hash = strdup(sc_challenge_hash); - /* Throwing major error. Need to alert client to update. */ - sc_throw_error(fmt_vastr("Please update Audacious.\n" - "Update available at: http://audacious-media-player.org")); - pdebug(fmt_vastr("update client: %s", sc_srv_res + 7), DEBUG); + /* Throwing major error. Need to alert client to update. */ + sc_throw_error(fmt_vastr("Please update Audacious.\n" + "Update available at: http://audacious-media-player.org")); + pdebug(fmt_vastr("update client: %s", 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. - */ - sc_giveup = -1; - return -1; - } - if (!strncmp(sc_srv_res, "UPTODATE\n", 9)) { - sc_bad_users = 0; + /* + * Russ isn't clear on whether we can submit with a not-updated + * client. Neither is RJ. I use what we did before. + */ + sc_giveup = -1; + return -1; + } + if (!strncmp(sc_srv_res, "UPTODATE\n", 9)) { + sc_bad_users = 0; - interval = strstr(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; - sc_submit_interval = strtol(interval + 8, NULL, 10); - } + interval = strstr(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; + sc_submit_interval = strtol(interval + 8, NULL, 10); + } - sc_submit_url = strchr(strchr(sc_srv_res, '\n') + 1, '\n') + 1; - *(sc_submit_url - 1) = 0; - sc_submit_url = strdup(sc_submit_url); - sc_challenge_hash = strchr(sc_srv_res, '\n') + 1; - *(sc_challenge_hash - 1) = 0; - sc_challenge_hash = strdup(sc_challenge_hash); + sc_submit_url = strchr(strchr(sc_srv_res, '\n') + 1, '\n') + 1; + *(sc_submit_url - 1) = 0; + sc_submit_url = strdup(sc_submit_url); + sc_challenge_hash = strchr(sc_srv_res, '\n') + 1; + *(sc_challenge_hash - 1) = 0; + sc_challenge_hash = strdup(sc_challenge_hash); - return 0; - } - if(!strncmp(sc_srv_res, "BADAUTH", 7)) { - /* Throwing major error. */ - sc_throw_error("Incorrect username/password.\n" - "Please fix in configuration."); - pdebug("incorrect username/password", DEBUG); + return 0; + } + if(!strncmp(sc_srv_res, "BADAUTH", 7)) { + /* Throwing major error. */ + sc_throw_error("Incorrect username/password.\n" + "Please fix in configuration."); + pdebug("incorrect username/password", DEBUG); - interval = strstr(sc_srv_res, "INTERVAL"); - if(!interval) - { - pdebug("missing INTERVAL", DEBUG); - } - else - { - *(interval - 1) = 0; - sc_submit_interval = strtol(interval + 8, NULL, 10); - } + interval = strstr(sc_srv_res, "INTERVAL"); + if(!interval) + { + pdebug("missing INTERVAL", DEBUG); + } + else + { + *(interval - 1) = 0; + sc_submit_interval = strtol(interval + 8, NULL, 10); + } - return -1; - } + return -1; + } - pdebug(fmt_vastr("unknown server-reply '%s'", sc_srv_res), DEBUG); - return -1; + pdebug(fmt_vastr("unknown server-reply '%s'", sc_srv_res), DEBUG); + return -1; } static unsigned char *md5_string(char *pass, int len) { - md5_state_t md5state; - static unsigned char md5pword[16]; - - md5_init(&md5state); - md5_append(&md5state, (unsigned const char *)pass, len); - md5_finish(&md5state, md5pword); + md5_state_t md5state; + static unsigned char md5pword[16]; + + md5_init(&md5state); + md5_append(&md5state, (unsigned const char *)pass, len); + md5_finish(&md5state, md5pword); - return md5pword; + return md5pword; } static void hexify(char *pass, int len) { - char *bp = sc_response_hash; - char hexchars[] = "0123456789abcdef"; - int i; + char *bp = sc_response_hash; + char hexchars[] = "0123456789abcdef"; + int i; - memset(sc_response_hash, 0, sizeof(sc_response_hash)); - - for(i = 0; i < len; i++) { - *(bp++) = hexchars[(pass[i] >> 4) & 0x0f]; - *(bp++) = hexchars[pass[i] & 0x0f]; - } - *bp = 0; + memset(sc_response_hash, 0, sizeof(sc_response_hash)); + + for(i = 0; i < len; i++) { + *(bp++) = hexchars[(pass[i] >> 4) & 0x0f]; + *(bp++) = hexchars[pass[i] & 0x0f]; + } + *bp = 0; - return; + return; } static int sc_handshake(void) { - int status; - char buf[65535]; - CURL *curl; - time_t ts = time(NULL); - char *auth_tmp; - char *auth; + int status; + char buf[65535]; + CURL *curl; + time_t ts = time(NULL); + char *auth_tmp; + char *auth; - auth_tmp = g_strdup_printf("%s%ld", sc_password, ts); - auth = (char *)md5_string(auth_tmp, strlen(auth_tmp)); - g_free(auth_tmp); - hexify(auth, strlen(auth)); - auth_tmp = g_strdup(sc_response_hash); + auth_tmp = g_strdup_printf("%s%ld", sc_password, ts); + auth = (char *)md5_string(auth_tmp, strlen(auth_tmp)); + g_free(auth_tmp); + hexify(auth, strlen(auth)); + auth_tmp = g_strdup(sc_response_hash); - g_snprintf(buf, sizeof(buf), "%s/?hs=true&p=%s&c=%s&v=%s&u=%s&t=%ld&a=%s", - SCROBBLER_HS_URL, SCROBBLER_VERSION, - SCROBBLER_CLI_ID, SCROBBLER_IMPLEMENTATION, sc_username, time(NULL), - auth_tmp); - g_free(auth_tmp); + g_snprintf(buf, sizeof(buf), "%s/?hs=true&p=%s&c=%s&v=%s&u=%s&t=%ld&a=%s", + SCROBBLER_HS_URL, SCROBBLER_VERSION, + SCROBBLER_CLI_ID, SCROBBLER_IMPLEMENTATION, sc_username, time(NULL), + auth_tmp); + g_free(auth_tmp); - curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); - curl_easy_setopt(curl, CURLOPT_URL, buf); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, - sc_store_res); - memset(sc_curl_errbuf, 0, sizeof(sc_curl_errbuf)); - curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, sc_curl_errbuf); - curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); - curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, SC_CURL_TIMEOUT); - status = curl_easy_perform(curl); - curl_easy_cleanup(curl); + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(curl, CURLOPT_URL, buf); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, + sc_store_res); + memset(sc_curl_errbuf, 0, sizeof(sc_curl_errbuf)); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, sc_curl_errbuf); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, SC_CURL_TIMEOUT); + status = curl_easy_perform(curl); + curl_easy_cleanup(curl); - sc_hs_timeout = time(NULL) + SCROBBLER_HS_WAIT; + sc_hs_timeout = time(NULL) + SCROBBLER_HS_WAIT; - if (status) { - pdebug(sc_curl_errbuf, DEBUG); - sc_hs_errors++; - sc_free_res(); - return -1; - } + if (status) { + pdebug(sc_curl_errbuf, DEBUG); + sc_hs_errors++; + sc_free_res(); + return -1; + } - if (sc_parse_hs_res()) { - sc_hs_errors++; - sc_free_res(); - return -1; - } + if (sc_parse_hs_res()) { + sc_hs_errors++; + sc_free_res(); + return -1; + } - if (sc_challenge_hash != NULL) { - md5_state_t md5state; - unsigned char md5pword[16]; - - md5_init(&md5state); - /*pdebug(fmt_vastr("Pass Hash: %s", sc_password), DEBUG);*/ - md5_append(&md5state, (unsigned const char *)sc_password, - strlen(sc_password)); - /*pdebug(fmt_vastr("Challenge Hash: %s", sc_challenge_hash), DEBUG);*/ - md5_append(&md5state, (unsigned const char *)sc_challenge_hash, - strlen(sc_challenge_hash)); - md5_finish(&md5state, md5pword); - hexify((char*)md5pword, sizeof(md5pword)); - /*pdebug(fmt_vastr("Response Hash: %s", sc_response_hash), DEBUG);*/ - } + if (sc_challenge_hash != NULL) { + md5_state_t md5state; + unsigned char md5pword[16]; + + md5_init(&md5state); + /*pdebug(fmt_vastr("Pass Hash: %s", sc_password), DEBUG);*/ + md5_append(&md5state, (unsigned const char *)sc_password, + strlen(sc_password)); + /*pdebug(fmt_vastr("Challenge Hash: %s", sc_challenge_hash), DEBUG);*/ + md5_append(&md5state, (unsigned const char *)sc_challenge_hash, + strlen(sc_challenge_hash)); + md5_finish(&md5state, md5pword); + hexify((char*)md5pword, sizeof(md5pword)); + /*pdebug(fmt_vastr("Response Hash: %s", sc_response_hash), DEBUG);*/ + } - sc_hs_errors = 0; - sc_hs_status = 1; + sc_hs_errors = 0; + sc_hs_status = 1; - sc_free_res(); + sc_free_res(); - pdebug(fmt_vastr("submiturl: %s - interval: %d", - sc_submit_url, sc_submit_interval), DEBUG); + pdebug(fmt_vastr("submiturl: %s - interval: %d", + sc_submit_url, sc_submit_interval), DEBUG); - return 0; + return 0; } static int sc_parse_sb_res(void) { - char *ch, *ch2; + char *ch, *ch2; - if (!sc_srv_res_size) { - pdebug("No response from server", DEBUG); - return -1; - } - *(sc_srv_res + sc_srv_res_size) = 0; + if (!sc_srv_res_size) { + pdebug("No response from server", DEBUG); + return -1; + } + *(sc_srv_res + sc_srv_res_size) = 0; - if (!strncmp(sc_srv_res, "OK", 2)) { - if ((ch = strstr(sc_srv_res, "INTERVAL"))) { - sc_submit_interval = strtol(ch + 8, NULL, 10); - pdebug(fmt_vastr("got new interval: %d", - sc_submit_interval), DEBUG); - } + if (!strncmp(sc_srv_res, "OK", 2)) { + if ((ch = strstr(sc_srv_res, "INTERVAL"))) { + sc_submit_interval = strtol(ch + 8, NULL, 10); + pdebug(fmt_vastr("got new interval: %d", + sc_submit_interval), DEBUG); + } - pdebug(fmt_vastr("submission ok: %s", sc_srv_res), DEBUG); + pdebug(fmt_vastr("submission ok: %s", sc_srv_res), DEBUG); - return 0; - } + return 0; + } - if (!strncmp(sc_srv_res, "BADAUTH", 7)) { - if ((ch = strstr(sc_srv_res, "INTERVAL"))) { - sc_submit_interval = strtol(ch + 8, NULL, 10); - pdebug(fmt_vastr("got new interval: %d", - sc_submit_interval), DEBUG); - } + if (!strncmp(sc_srv_res, "BADAUTH", 7)) { + if ((ch = strstr(sc_srv_res, "INTERVAL"))) { + sc_submit_interval = strtol(ch + 8, NULL, 10); + pdebug(fmt_vastr("got new interval: %d", + sc_submit_interval), DEBUG); + } - pdebug("incorrect username/password", DEBUG); + pdebug("incorrect username/password", DEBUG); - sc_giveup = 0; + 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) - */ - sc_hs_status = 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) + */ + sc_hs_status = 0; - if(sc_challenge_hash != NULL) - free(sc_challenge_hash); - if(sc_submit_url != NULL) - free(sc_submit_url); + if(sc_challenge_hash != NULL) + free(sc_challenge_hash); + if(sc_submit_url != NULL) + free(sc_submit_url); - sc_challenge_hash = sc_submit_url = NULL; - sc_bad_users++; + sc_challenge_hash = sc_submit_url = NULL; + sc_bad_users++; - if(sc_bad_users > 2) - { - pdebug("3 BADAUTH returns on submission. Halting " - "submissions until login fixed.", DEBUG) - sc_throw_error("Incorrect username/password.\n" - "Please fix in configuration."); - } + if(sc_bad_users > 2) + { + pdebug("3 BADAUTH returns on submission. Halting " + "submissions until login fixed.", DEBUG) + sc_throw_error("Incorrect username/password.\n" + "Please fix in configuration."); + } - return -1; - } + return -1; + } - if (!strncmp(sc_srv_res, "FAILED", 6)) { - if ((ch = strstr(sc_srv_res, "INTERVAL"))) { - sc_submit_interval = strtol(ch + 8, NULL, 10); - pdebug(fmt_vastr("got new interval: %d", - sc_submit_interval), DEBUG); - } + if (!strncmp(sc_srv_res, "FAILED", 6)) { + if ((ch = strstr(sc_srv_res, "INTERVAL"))) { + sc_submit_interval = strtol(ch + 8, NULL, 10); + pdebug(fmt_vastr("got new interval: %d", + sc_submit_interval), DEBUG); + } - /* This could be important. (Such as FAILED - Get new plugin) */ - /*sc_throw_error(fmt_vastr("%s", sc_srv_res));*/ + /* This could be important. (Such as FAILED - Get new plugin) */ + /*sc_throw_error(fmt_vastr("%s", sc_srv_res));*/ - pdebug(sc_srv_res, DEBUG); + pdebug(sc_srv_res, DEBUG); - return -1; - } + return -1; + } - if (!strncmp(sc_srv_res, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">", 50)) { - ch = strstr(sc_srv_res, "<TITLE>"); - ch2 = strstr(sc_srv_res, "</TITLE>"); - if (ch != NULL && ch2 != NULL) { - ch += strlen("<TITLE>"); - *ch2 = '\0'; + if (!strncmp(sc_srv_res, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">", 50)) { + ch = strstr(sc_srv_res, "<TITLE>"); + ch2 = strstr(sc_srv_res, "</TITLE>"); + if (ch != NULL && ch2 != NULL) { + ch += strlen("<TITLE>"); + *ch2 = '\0'; - pdebug(fmt_vastr("HTTP Error (%d): '%s'", - atoi(ch), ch + 4), DEBUG); -// *ch2 = '<'; // needed? --yaz - } + pdebug(fmt_vastr("HTTP Error (%d): '%s'", + atoi(ch), ch + 4), DEBUG); +// *ch2 = '<'; // needed? --yaz + } - return -1; - } + return -1; + } - pdebug(fmt_vastr("unknown server-reply %s", sc_srv_res), DEBUG); + pdebug(fmt_vastr("unknown server-reply %s", sc_srv_res), DEBUG); - return -1; + return -1; } static gchar *sc_itemtag(char c, int n, char *str) { - static char buf[SCROBBLER_SB_MAXLEN]; - g_snprintf(buf, SCROBBLER_SB_MAXLEN, "&%c[%d]=%s", c, n, str); - return buf; + static char buf[SCROBBLER_SB_MAXLEN]; + g_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) + CURLFORM_PTRCONTENTS, v, CURLFORM_END) static int sc_generateentry(GString *submission) { - int i; - item_t *item; + int i; + item_t *item; - i = 0; + i = 0; - q_peekall(1); + q_peekall(1); - while ((item = q_peekall(0)) && i < 10) - { - /* - * don't submit queued tracks which don't yet meet audioscrobbler - * requirements... - */ - if ((item->timeplayed < (item->len / 2)) && - (item->timeplayed < 240)) - continue; + while ((item = q_peekall(0)) && i < 10) + { + /* + * We assume now that all tracks in the queue should be submitted. + * The check occurs in the sc_timeout() function now. + */ - if (!item) - return i; + if (!item) + return i; g_string_append(submission,sc_itemtag('a',i,I_ARTIST(item))); g_string_append(submission,sc_itemtag('t',i,I_TITLE(item))); @@ -647,192 +685,197 @@ g_free(tmp); g_string_append(submission,sc_itemtag('r',i,"")); - pdebug(fmt_vastr("a[%d]=%s t[%d]=%s l[%d]=%d i[%d]=%d b[%d]=%s", - i, I_ARTIST(item), - i, I_TITLE(item), - i, I_LEN(item), - i, I_TIME(item), - i, I_ALBUM(item)), DEBUG); -#ifdef ALLOW_MULTIPLE - i++; - } -#endif + pdebug(fmt_vastr("a[%d]=%s t[%d]=%s l[%d]=%d i[%d]=%d b[%d]=%s", + i, I_ARTIST(item), + i, I_TITLE(item), + i, I_LEN(item), + i, I_TIME(item), + i, I_ALBUM(item)), DEBUG); + i++; + } - return i; + return i; } static int sc_submit_np(Tuple *tuple) { - CURL *curl; - /* struct HttpPost *post = NULL , *last = NULL; */ - int status; - gchar *entry; + CURL *curl; + /* struct HttpPost *post = NULL , *last = NULL; */ + int status; + gchar *entry; - curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); - curl_easy_setopt(curl, CURLOPT_URL, sc_np_url); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, - 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");*/ + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(curl, CURLOPT_URL, sc_np_url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, + 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");*/ char *field_artist = fmt_escape(aud_tuple_get_string(tuple, FIELD_ARTIST, NULL)); char *field_title = fmt_escape(aud_tuple_get_string(tuple, FIELD_TITLE, NULL)); char *field_album = aud_tuple_get_string(tuple, FIELD_ALBUM, NULL) ? fmt_escape(aud_tuple_get_string(tuple, FIELD_ALBUM, NULL)) : fmt_escape(""); - entry = g_strdup_printf("s=%s&a=%s&t=%s&b=%s&l=%d&n=%d&m=", sc_session_id, - field_artist, - field_title, - field_album, - aud_tuple_get_int(tuple, FIELD_LENGTH, NULL) / 1000, - aud_tuple_get_int(tuple, FIELD_TRACK_NUMBER, NULL)); + entry = g_strdup_printf("s=%s&a=%s&t=%s&b=%s&l=%d&n=%d&m=", sc_session_id, + field_artist, + field_title, + field_album, + aud_tuple_get_int(tuple, FIELD_LENGTH, NULL) / 1000, + aud_tuple_get_int(tuple, FIELD_TRACK_NUMBER, NULL)); curl_free(field_artist); curl_free(field_title); curl_free(field_album); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, (char *) entry); - memset(sc_curl_errbuf, 0, sizeof(sc_curl_errbuf)); - curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, sc_curl_errbuf); - curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, SC_CURL_TIMEOUT); - curl_easy_setopt(curl, CURLOPT_TIMEOUT, SCROBBLER_SB_WAIT); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, (char *) entry); + memset(sc_curl_errbuf, 0, sizeof(sc_curl_errbuf)); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, sc_curl_errbuf); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, SC_CURL_TIMEOUT); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, SCROBBLER_SB_WAIT); - status = curl_easy_perform(curl); - curl_easy_cleanup(curl); + status = curl_easy_perform(curl); + curl_easy_cleanup(curl); - g_free(entry); + g_free(entry); - if (status) { - pdebug(sc_curl_errbuf, DEBUG); - sc_sb_errors++; - sc_free_res(); - return -1; - } + if (status) { + pdebug(sc_curl_errbuf, DEBUG); + sc_sb_errors++; + sc_free_res(); + return -1; + } - if (sc_parse_sb_res()) { - sc_sb_errors++; - sc_free_res(); - pdebug(fmt_vastr("Retrying in %d secs, %d elements in queue", - sc_submit_interval, q_len()), DEBUG); - return -1; - } - sc_free_res(); - return 0; + if (sc_parse_sb_res()) { + sc_sb_errors++; + sc_free_res(); + pdebug(fmt_vastr("Retrying in %d secs, %d elements in queue", + sc_submit_interval, q_len()), DEBUG); + return -1; + } + sc_free_res(); + return 0; } static int sc_submitentry(gchar *entry) { - CURL *curl; - /* struct HttpPost *post = NULL , *last = NULL; */ - int status; + 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, sc_submit_url); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, - 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");*/ + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(curl, CURLOPT_URL, sc_submit_url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, + 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("Response Hash: %s", sc_response_hash), DEBUG);*/ + /*pdebug(fmt_vastr("Response Hash: %s", sc_response_hash), DEBUG);*/ submission = g_string_new("s="); g_string_append(submission, (gchar *)sc_session_id); - g_string_append(submission, entry); + g_string_append(submission, entry); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, (char *)submission->str); - memset(sc_curl_errbuf, 0, sizeof(sc_curl_errbuf)); - curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, sc_curl_errbuf); - curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, SC_CURL_TIMEOUT); - curl_easy_setopt(curl, CURLOPT_TIMEOUT, SCROBBLER_SB_WAIT); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, (char *)submission->str); + memset(sc_curl_errbuf, 0, sizeof(sc_curl_errbuf)); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, sc_curl_errbuf); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, SC_CURL_TIMEOUT); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, SCROBBLER_SB_WAIT); - status = curl_easy_perform(curl); + status = curl_easy_perform(curl); - curl_easy_cleanup(curl); + curl_easy_cleanup(curl); g_string_free(submission, TRUE); - if (status) { - pdebug(sc_curl_errbuf, DEBUG); - sc_sb_errors++; - sc_free_res(); - return -1; - } + if (status) { + pdebug(sc_curl_errbuf, DEBUG); + sc_sb_errors++; + sc_free_res(); + return -1; + } - if (sc_parse_sb_res()) { - sc_sb_errors++; - sc_free_res(); - pdebug(fmt_vastr("Retrying in %d secs, %d elements in queue", - sc_submit_interval, q_len()), DEBUG); - return -1; - } - sc_free_res(); - return 0; + if (sc_parse_sb_res()) { + sc_sb_errors++; + sc_free_res(); + pdebug(fmt_vastr("Retrying in %d secs, %d elements in queue", + sc_submit_interval, q_len()), DEBUG); + return -1; + } + sc_free_res(); + return 0; } static void sc_handlequeue(GMutex *mutex) { - GString *submitentry; - int nsubmit; - int wait; + GString *submitentry; + int nsubmit; + int wait; + int i; - if(sc_submit_timeout < time(NULL) && sc_bad_users < 3) - { - submitentry = g_string_new(""); + if(sc_submit_timeout < time(NULL) && sc_bad_users < 3) + { + submitentry = g_string_new(""); - g_mutex_lock(mutex); + g_mutex_lock(mutex); + + nsubmit = sc_generateentry(submitentry); + + g_mutex_unlock(mutex); - nsubmit = 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 (nsubmit > 0) - { - pdebug(fmt_vastr("Number submitting: %d", nsubmit), DEBUG); - pdebug(fmt_vastr("Submission: %s", submitentry->str), DEBUG); + if(!sc_submitentry(submitentry->str)) + { + g_mutex_lock(mutex); - if(!sc_submitentry(submitentry->str)) - { - g_mutex_lock(mutex); + pdebug(fmt_vastr("Clearing out %d item(s) from the queue", nsubmit), DEBUG); + for (i=0; i<nsubmit; i++) + { + q_get(); + } -#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(); + /* + * This should make sure that the queue doesn't + * get submitted multiple times on a nasty + * segfault... + */ + dump_queue(); + + g_mutex_unlock(mutex); - g_mutex_unlock(mutex); + sc_sb_errors = 0; + } + if(sc_sb_errors) + { + /* Dump queue */ + g_mutex_lock(mutex); + dump_queue(); + g_mutex_unlock(mutex); - sc_sb_errors = 0; - } - if(sc_sb_errors) - { - if(sc_sb_errors < 5) - /* Retry after 1 min */ - wait = 60; - else - wait = /* sc_submit_interval + */ - ( ((sc_sb_errors - 5) < 7) ? - (60 << (sc_sb_errors-5)) : - 7200 ); - - sc_submit_timeout = time(NULL) + wait; + if(sc_sb_errors < 5) + /* Retry after 1 min */ + wait = 60; + else + wait = /* sc_submit_interval + */ + ( ((sc_sb_errors - 5) < 7) ? + (60 << (sc_sb_errors-5)) : + 7200 ); + + sc_submit_timeout = time(NULL) + wait; - pdebug(fmt_vastr("Error while submitting. " - "Retrying after %d seconds.", wait), - DEBUG); - } - } + pdebug(fmt_vastr("Error while submitting. " + "Retrying after %d seconds.", wait), + DEBUG); + } + } - g_string_free(submitentry, TRUE); - } + g_string_free(submitentry, TRUE); + } } static void read_cache(void) @@ -872,6 +915,10 @@ len = atoi(entry[4]); t = atoi(entry[6]); + /* + * All entries in the queue should have "L" as the sixth field now, but + * we'll continue to check the value anyway, for backwards-compatibility reasons. + */ if (!strncmp(entry[5], "L", 1)) { Tuple *tuple = aud_tuple_new(); gchar* string_value; @@ -938,6 +985,13 @@ q_peekall(1); + /* + * The sixth field used to be "L" for tracks which were valid submissions, + * and "S" for tracks which were still playing, and shouldn't be submitted + * yet. We only store valid submissions in the queue, now, but the "L" value + * is left in-place for backwards-compatibility reasons. It should probably + * be removed at some point. + */ while ((item = q_peekall(0))) { fprintf(fd, "%s\t%s\t%s\t%d\t%d\t%s\t%d\n", I_ARTIST(item), @@ -945,7 +999,7 @@ I_TITLE(item), item->track, I_LEN(item), - ((item->timeplayed > item->len/2) || (item->timeplayed > 240)) ? "L" : "S", + "L", I_TIME(item)); } @@ -956,51 +1010,51 @@ void sc_cleaner(void) { - if(sc_submit_url != NULL) - free(sc_submit_url); - if(sc_username != NULL) - free(sc_username); - if(sc_password != NULL) - free(sc_password); - if(sc_challenge_hash != NULL) - free(sc_challenge_hash); - if(sc_srv_res != NULL) - free(sc_srv_res); - if(sc_major_error != NULL) - free(sc_major_error); - dump_queue(); - q_free(); - pdebug("scrobbler shutting down", DEBUG); + if(sc_submit_url != NULL) + free(sc_submit_url); + if(sc_username != NULL) + free(sc_username); + if(sc_password != NULL) + free(sc_password); + if(sc_challenge_hash != NULL) + free(sc_challenge_hash); + if(sc_srv_res != NULL) + free(sc_srv_res); + if(sc_major_error != NULL) + free(sc_major_error); + dump_queue(); + q_free(); + pdebug("scrobbler shutting down", DEBUG); } static void sc_checkhandshake(void) { - int wait; + int wait; - if (!sc_username || !sc_password) - return; + if (!sc_username || !sc_password) + return; - if (sc_hs_status) - return; - if (sc_hs_timeout < time(NULL)) - { - sc_handshake(); + if (sc_hs_status) + return; + if (sc_hs_timeout < time(NULL)) + { + sc_handshake(); - if(sc_hs_errors) - { - if(sc_hs_errors < 5) - /* Retry after 60 seconds */ - wait = 60; - else - wait = /* sc_submit_interval + */ - ( ((sc_hs_errors - 5) < 7) ? - (60 << (sc_hs_errors-5)) : - 7200 ); - sc_hs_timeout = time(NULL) + wait; - pdebug(fmt_vastr("Error while handshaking. Retrying " - "after %d seconds.", wait), DEBUG); - } - } + if(sc_hs_errors) + { + if(sc_hs_errors < 5) + /* Retry after 60 seconds */ + wait = 60; + else + wait = /* sc_submit_interval + */ + ( ((sc_hs_errors - 5) < 7) ? + (60 << (sc_hs_errors-5)) : + 7200 ); + sc_hs_timeout = time(NULL) + wait; + pdebug(fmt_vastr("Error while handshaking. Retrying " + "after %d seconds.", wait), DEBUG); + } + } } /**** Public *****/ @@ -1009,41 +1063,41 @@ void sc_init(char *uname, char *pwd) { - sc_hs_status = sc_hs_timeout = sc_hs_errors = sc_submit_timeout = - sc_srv_res_size = sc_giveup = sc_major_error_present = - sc_bad_users = sc_sb_errors = 0; - sc_submit_interval = 1; + sc_hs_status = sc_hs_timeout = sc_hs_errors = sc_submit_timeout = + sc_srv_res_size = sc_giveup = sc_major_error_present = + sc_bad_users = sc_sb_errors = 0; + sc_submit_interval = 1; - sc_submit_url = sc_username = sc_password = sc_srv_res = - sc_challenge_hash = sc_major_error = NULL; - sc_username = strdup(uname); - sc_password = strdup(pwd); - read_cache(); - pdebug("scrobbler starting up", DEBUG); + sc_submit_url = sc_username = sc_password = sc_srv_res = + sc_challenge_hash = sc_major_error = NULL; + sc_username = strdup(uname); + sc_password = strdup(pwd); + read_cache(); + pdebug("scrobbler starting up", DEBUG); } void sc_addentry(GMutex *mutex, Tuple *tuple, int len) { - g_mutex_lock(mutex); + g_mutex_lock(mutex); - sc_submit_np(tuple); - q_put(tuple, -1, len); + sc_submit_np(tuple); + set_np(tuple, len); - /* - * This will help make sure the queue will be saved on a nasty - * segfault... - */ - dump_queue(); + /* + * This will help make sure the queue will be saved on a nasty + * segfault... + */ + dump_queue(); - g_mutex_unlock(mutex); + g_mutex_unlock(mutex); } /* Call periodically from the plugin */ int sc_idle(GMutex *mutex) { - sc_checkhandshake(); - if (sc_hs_status) - sc_handlequeue(mutex); + sc_checkhandshake(); + if (sc_hs_status) + sc_handlequeue(mutex); - return sc_giveup; + return sc_giveup; }
--- a/src/scrobbler/settings.h Fri Mar 07 15:56:40 2008 -0600 +++ b/src/scrobbler/settings.h Sat Mar 08 09:57:38 2008 +0100 @@ -8,7 +8,5 @@ #define SUB_DEBUG 0 #define CLIENT "Audacious" #define USER_AGENT "AudioScrobbler/1.1" PACKAGE_NAME "/" PACKAGE_VERSION -#define MAKE_BMP -#define ALLOW_MULTIPLE #endif