aboutsummaryrefslogtreecommitdiffstats
path: root/src/input
diff options
context:
space:
mode:
Diffstat (limited to 'src/input')
-rw-r--r--src/input/curl_input_plugin.c27
-rw-r--r--src/input/file_input_plugin.c5
-rw-r--r--src/input/lastfm_input_plugin.c340
-rw-r--r--src/input/mms_input_plugin.c2
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[] = {
+ {"&amp;", '&'},
+ {"&quot;", '"'},
+ {"&apos;", '\''},
+ {"&gt;", '>'},
+ {"&lt;", '<'}
+ };
+ 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;
}