diff options
Diffstat (limited to 'src/input')
-rw-r--r-- | src/input/curl_input_plugin.c | 27 | ||||
-rw-r--r-- | src/input/file_input_plugin.c | 5 | ||||
-rw-r--r-- | src/input/lastfm_input_plugin.c | 340 | ||||
-rw-r--r-- | src/input/mms_input_plugin.c | 2 |
4 files changed, 305 insertions, 69 deletions
diff --git a/src/input/curl_input_plugin.c b/src/input/curl_input_plugin.c index 95d269ce5..c3928a09b 100644 --- a/src/input/curl_input_plugin.c +++ b/src/input/curl_input_plugin.c @@ -42,7 +42,7 @@ #define G_LOG_DOMAIN "input_curl" /** rewinding is possible after up to 64 kB */ -static const off_t max_rewind_size = 64 * 1024; +static const goffset max_rewind_size = 64 * 1024; /** * Buffers created by input_curl_writefunction(). @@ -150,11 +150,6 @@ buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) g_free(data); } -/* g_queue_clear() was introduced in GLib 2.14 */ -#if !GLIB_CHECK_VERSION(2,14,0) -#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0) -#endif - /** * Frees the current "libcurl easy" handle, and everything associated * with it. @@ -407,8 +402,8 @@ copy_icy_tag(struct input_curl *c) if (c->tag != NULL) tag_free(c->tag); - if (c->meta_name != NULL && !tag_has_type(tag, TAG_ITEM_NAME)) - tag_add_item(tag, TAG_ITEM_NAME, c->meta_name); + if (c->meta_name != NULL && !tag_has_type(tag, TAG_NAME)) + tag_add_item(tag, TAG_NAME, c->meta_name); c->tag = tag; } @@ -425,7 +420,7 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size) #ifndef NDEBUG if (c->rewind != NULL && (!g_queue_is_empty(c->rewind) || is->offset == 0)) { - off_t offset = 0; + goffset offset = 0; struct buffer *buffer; for (GList *list = g_queue_peek_head_link(c->rewind); @@ -474,11 +469,11 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size) if (icy_defined(&c->icy_metadata)) copy_icy_tag(c); - is->offset += (off_t)nbytes; + is->offset += (goffset)nbytes; #ifndef NDEBUG if (rewind_buffers != NULL) { - off_t offset = 0; + goffset offset = 0; struct buffer *buffer; for (GList *list = g_queue_peek_head_link(c->rewind); @@ -614,7 +609,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) tag_free(c->tag); c->tag = tag_new(); - tag_add_item(c->tag, TAG_ITEM_NAME, c->meta_name); + tag_add_item(c->tag, TAG_NAME, c->meta_name); } else if (g_ascii_strcasecmp(name, "icy-metaint") == 0) { char buffer[64]; size_t icy_metaint; @@ -772,7 +767,7 @@ input_curl_can_rewind(struct input_stream *is) /* rewind is possible if this is the very first buffer of the resource */ buffer = (struct buffer*)g_queue_peek_head(c->buffers); - return (off_t)buffer->consumed == is->offset; + return (goffset)buffer->consumed == is->offset; } static void @@ -780,7 +775,7 @@ input_curl_rewind(struct input_stream *is) { struct input_curl *c = is->data; #ifndef NDEBUG - off_t offset = 0; + goffset offset = 0; #endif assert(c->rewind != NULL); @@ -818,7 +813,7 @@ input_curl_rewind(struct input_stream *is) } static bool -input_curl_seek(struct input_stream *is, off_t offset, int whence) +input_curl_seek(struct input_stream *is, goffset offset, int whence) { struct input_curl *c = is->data; bool ret; @@ -884,7 +879,7 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence) buffer = (struct buffer *)g_queue_pop_head(c->buffers); length = buffer->size - buffer->consumed; - if (offset - is->offset < (off_t)length) + if (offset - is->offset < (goffset)length) length = offset - is->offset; buffer = consume_buffer(buffer, length, rewind_buffers); diff --git a/src/input/file_input_plugin.c b/src/input/file_input_plugin.c index 64a4030ab..5dbbefcce 100644 --- a/src/input/file_input_plugin.c +++ b/src/input/file_input_plugin.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" /* must be first for large file support */ #include "input/file_input_plugin.h" #include "input_plugin.h" @@ -92,11 +93,11 @@ input_file_open(struct input_stream *is, const char *filename) } static bool -input_file_seek(struct input_stream *is, off_t offset, int whence) +input_file_seek(struct input_stream *is, goffset offset, int whence) { int fd = GPOINTER_TO_INT(is->data); - offset = lseek(fd, offset, whence); + offset = (goffset)lseek(fd, (off_t)offset, whence); if (offset < 0) { is->error = errno; return false; diff --git a/src/input/lastfm_input_plugin.c b/src/input/lastfm_input_plugin.c index 0039f7069..71e4b1ad4 100644 --- a/src/input/lastfm_input_plugin.c +++ b/src/input/lastfm_input_plugin.c @@ -18,26 +18,66 @@ */ #include "input/lastfm_input_plugin.h" -#include "input/curl_input_plugin.h" #include "input_plugin.h" +#include "tag.h" #include "conf.h" +#include <stdlib.h> #include <string.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "input_lastfm" -static const char *lastfm_user, *lastfm_password; +static struct lastfm_data { + char *user; + char *md5; +} lastfm_data; + +struct lastfm_input { + /* our very own plugin wrapper */ + struct input_plugin wrap_plugin; + + /* pointer to input stream's plugin */ + const struct input_plugin *plugin; + + /* pointer to input stream's data */ + void *data; + + /* current track's tag */ + struct tag *tag; +}; static bool lastfm_input_init(const struct config_param *param) { - lastfm_user = config_get_block_string(param, "user", NULL); - lastfm_password = config_get_block_string(param, "password", NULL); + const char *passwd = config_get_block_string(param, "password", NULL); + const char *user = config_get_block_string(param, "user", NULL); + if (passwd == NULL || user == NULL) + return false; - return lastfm_user != NULL && lastfm_password != NULL; + lastfm_data.user = g_uri_escape_string(user, NULL, false); + + if (strlen(passwd) != 32) + lastfm_data.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5, + passwd, strlen(passwd)); + else + lastfm_data.md5 = g_strdup(passwd); + + return true; +} + +static void +lastfm_input_finish(void) +{ + g_free(lastfm_data.user); + g_free(lastfm_data.md5); } +/** + * Simple data fetcher. + * @param url path or url of data to fetch. + * @return data fetched, or NULL on error. Must be freed with g_free. + */ static char * lastfm_get(const char *url) { @@ -78,6 +118,12 @@ lastfm_get(const char *url) return g_strndup(buffer, length); } +/** + * Ini-style value fetcher. + * @param response data through which to search. + * @param name name of value to search for. + * @return value for param name in param reponse or NULL on error. Free with g_free. + */ static char * lastfm_find(const char *response, const char *name) { @@ -98,10 +144,191 @@ lastfm_find(const char *response, const char *name) } } +/** + * Replace XML's five predefined entities with the equivalant characters. + * glib doesn't seem to have code to do this, even in the xml parser. + * We don't manage numerical character references such as &#nnnn; or &#xhhhh;. + * @param value XML text to decode. + * @return decoded string, which must be freed with g_free. + * @todo make sure this is ok for utf-8. + */ +static char * +lastfm_xmldecode(const char *value) +{ + struct entity { + const char *text; + char repl; + } entities[] = { + {"&", '&'}, + {""", '"'}, + {"'", '\''}, + {">", '>'}, + {"<", '<'} + }; + char *txt = g_strdup(value); + unsigned int i; + + for (i = 0; i < sizeof(entities)/sizeof(entities[0]); ++i) { + char *p; + int slen = strlen(entities[i].text); + while ((p = strstr(txt, entities[i].text))) { + *p = entities[i].repl; + g_strlcpy(p + 1, p + slen, strlen(p) - slen); + } + } + return txt; +} + +/** + * Extract the text between xml start and end tags specified by param tag. + * Caveat: This function does not handle nested tags properly. + * @param response XML to extract text from. + * @param tag name of tag of which text should be extracted from. + * @return text between tags specified by param tag, NULL on error; Must be freed with g_free. + */ +static char * +lastfm_xmltag(const char *response, const char *tag) +{ + char *tn = g_strconcat("<", tag, ">", NULL); + char *p, *q; + + if (!(p = strstr(response, tn))) { + g_free(tn); + return NULL; + } + + p += strlen(tn); + g_free(tn); + + tn = g_strconcat("</", tag, ">", NULL); + + if (!(q = strstr(p, tn))) { + g_free(tn); + return NULL; + } + + g_free(tn); + + return g_strndup(p, q - p); +} + +/** + * Parses xspf track and generates mpd tag. + * @return tag which must be freed with tag_free. + */ +static struct tag * +lastfm_read_tag(const char *response) +{ + struct tagalias { + enum tag_type type; + const char *xmltag; + } aliases[] = { + {TAG_ARTIST, "creator"}, + {TAG_TITLE, "title"}, + {TAG_ALBUM, "album"} + }; + struct tag *tag = tag_new(); + unsigned int i; + char *track_time = lastfm_xmltag(response, "duration"); + + if (track_time != NULL) { + int mtime = strtol(track_time, 0, 0); + g_free(track_time); + + /* make sure to round up */ + tag->time = ((mtime + 999) / 1000); + } + else + tag->time = 0; + + for (i = 0; i < sizeof(aliases)/sizeof(aliases[0]); ++i) { + char *p, *value = lastfm_xmltag(response, aliases[i].xmltag); + if (value == NULL) + continue; + + p = lastfm_xmldecode(value); + g_free(value); + value = p; + + tag_add_item(tag, aliases[i].type, value); + g_free(value); + } + return tag; +} + +static size_t +lastfm_input_read_wrap(struct input_stream *is, void *ptr, size_t size) +{ + size_t ret; + struct lastfm_input *d = is->data; + is->data = d->data; + ret = (* d->plugin->read)(is, ptr, size); + is->data = d; + return ret; +} + +static bool +lastfm_input_eof_wrap(struct input_stream *is) +{ + bool ret; + struct lastfm_input *d = is->data; + is->data = d->data; + ret = (* d->plugin->eof)(is); + is->data = d; + return ret; +} + +static bool +lastfm_input_seek_wrap(struct input_stream *is, goffset offset, int whence) +{ + bool ret; + struct lastfm_input *d = is->data; + is->data = d->data; + ret = (* d->plugin->seek)(is, offset, whence); + is->data = d; + return ret; +} + +static int +lastfm_input_buffer_wrap(struct input_stream *is) +{ + int ret; + struct lastfm_input *d = is->data; + is->data = d->data; + ret = (* d->plugin->buffer)(is); + is->data = d; + return ret; +} + +static struct tag * +lastfm_input_tag(struct input_stream *is) +{ + struct lastfm_input *d = is->data; + struct tag *tag = d->tag; + d->tag = NULL; + return tag; +} + +static void +lastfm_input_close(struct input_stream *is) +{ + struct lastfm_input *d = is->data; + + if (is->plugin->close) { + is->data = d->data; + is->plugin = d->plugin; + (* is->plugin->close)(is); + } + + if (d->tag) + tag_free(d->tag); + g_free(d); +} + static bool lastfm_input_open(struct input_stream *is, const char *url) { - char *md5, *p, *q, *response, *session, *stream_url; + char *p, *q, *response, *track, *session, *stream_url; bool success; if (strncmp(url, "lastfm://", 9) != 0) @@ -109,27 +336,11 @@ lastfm_input_open(struct input_stream *is, const char *url) /* handshake */ -#if GLIB_CHECK_VERSION(2,16,0) - q = g_uri_escape_string(lastfm_user, NULL, false); -#else - q = g_strdup(lastfm_username); -#endif - -#if GLIB_CHECK_VERSION(2,16,0) - if (strlen(lastfm_password) != 32) - md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5, - lastfm_password, - strlen(lastfm_password)); - else -#endif - md5 = g_strdup(lastfm_password); - p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?" "version=1.1.1&platform=linux&" - "username=", q, "&" - "passwordmd5=", md5, "&debug=0&partner=", NULL); - g_free(q); - g_free(md5); + "username=", lastfm_data.user, "&" + "passwordmd5=", lastfm_data.md5, "&" + "debug=0&partner=", NULL); response = lastfm_get(p); g_free(p); @@ -147,23 +358,16 @@ lastfm_input_open(struct input_stream *is, const char *url) return false; } -#if GLIB_CHECK_VERSION(2,16,0) - q = g_uri_escape_string(session, NULL, false); - g_free(session); - session = q; -#endif + q = g_uri_escape_string(session, NULL, false); + g_free(session); + session = q; /* "adjust" last.fm radio */ if (strlen(url) > 9) { char *escaped_url; -#if GLIB_CHECK_VERSION(2,16,0) escaped_url = g_uri_escape_string(url, NULL, false); -#else - escaped_url = g_strdup(url); -#endif - p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?" "session=", session, "&url=", escaped_url, "&debug=0", NULL); @@ -195,35 +399,71 @@ lastfm_input_open(struct input_stream *is, const char *url) return false; } - p = strstr(response, "<location>"); - if (p == NULL) { - g_free(response); - g_free(stream_url); - return false; - } + /* From here on, we only care about the first track, extract that + * + * Note: if you want to get information about the next track (needed + * for continuous playback) extract the other track info here too. + */ - p += 10; - q = strchr(p, '<'); + g_free(stream_url); + track = lastfm_xmltag(response, "track"); + g_free(response); - if (q == NULL) { - g_free(response); - g_free(stream_url); + /* If there are no tracks in the tracklist, it's possible that the + * station doesn't have enough content. + */ + + if (track == NULL) return false; - } - g_free(stream_url); - stream_url = g_strndup(p, q - p); - g_free(response); + stream_url = lastfm_xmltag(track, "location"); + if (stream_url == NULL) { + g_free(track); + return false; + } /* now really open the last.fm radio stream */ success = input_stream_open(is, stream_url); + if (success) { + /* instantiate our transparent wrapper plugin + * this is needed so that the backend knows what functions are + * properly available. + */ + + struct lastfm_input *d = g_new0(struct lastfm_input, 1); + d->wrap_plugin.name = "lastfm"; + d->wrap_plugin.open = lastfm_input_open; + d->wrap_plugin.close = lastfm_input_close; + d->wrap_plugin.read = lastfm_input_read_wrap; + d->wrap_plugin.eof = lastfm_input_eof_wrap; + d->wrap_plugin.tag = lastfm_input_tag; + if (is->seekable) + d->wrap_plugin.seek = lastfm_input_seek_wrap; + if (is->plugin->buffer) + d->wrap_plugin.buffer = lastfm_input_buffer_wrap; + + d->tag = lastfm_read_tag(track); + d->plugin = is->plugin; + d->data = is->data; + + /* give the backend our wrapper plugin */ + + is->plugin = &d->wrap_plugin; + is->data = d; + } + g_free(stream_url); + g_free(track); + return success; } const struct input_plugin lastfm_input_plugin = { .name = "lastfm", .init = lastfm_input_init, + .finish = lastfm_input_finish, .open = lastfm_input_open, + .close = lastfm_input_close, + .tag = lastfm_input_tag, }; diff --git a/src/input/mms_input_plugin.c b/src/input/mms_input_plugin.c index 2a3c53776..335571bef 100644 --- a/src/input/mms_input_plugin.c +++ b/src/input/mms_input_plugin.c @@ -110,7 +110,7 @@ input_mms_buffer(G_GNUC_UNUSED struct input_stream *is) static bool input_mms_seek(G_GNUC_UNUSED struct input_stream *is, - G_GNUC_UNUSED off_t offset, G_GNUC_UNUSED int whence) + G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence) { return false; } |