From 86b0adc82ce64bb08518088c35ce754dc96d7062 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sat, 26 Jan 2013 01:04:02 +0100 Subject: playlist/*: convert to C++ --- src/playlist/DespotifyPlaylistPlugin.cxx | 2 +- src/playlist/EmbeddedCuePlaylistPlugin.cxx | 190 ++++++++++++ src/playlist/EmbeddedCuePlaylistPlugin.hxx | 25 ++ src/playlist/LastFMPlaylistPlugin.cxx | 299 +++++++++++++++++++ src/playlist/LastFMPlaylistPlugin.hxx | 25 ++ src/playlist/SoundCloudPlaylistPlugin.cxx | 451 +++++++++++++++++++++++++++++ src/playlist/SoundCloudPlaylistPlugin.hxx | 25 ++ src/playlist/embcue_playlist_plugin.c | 181 ------------ src/playlist/embcue_playlist_plugin.h | 25 -- src/playlist/lastfm_playlist_plugin.c | 296 ------------------- src/playlist/lastfm_playlist_plugin.h | 25 -- src/playlist/soundcloud_playlist_plugin.c | 448 ---------------------------- src/playlist/soundcloud_playlist_plugin.h | 25 -- 13 files changed, 1016 insertions(+), 1001 deletions(-) create mode 100644 src/playlist/EmbeddedCuePlaylistPlugin.cxx create mode 100644 src/playlist/EmbeddedCuePlaylistPlugin.hxx create mode 100644 src/playlist/LastFMPlaylistPlugin.cxx create mode 100644 src/playlist/LastFMPlaylistPlugin.hxx create mode 100644 src/playlist/SoundCloudPlaylistPlugin.cxx create mode 100644 src/playlist/SoundCloudPlaylistPlugin.hxx delete mode 100644 src/playlist/embcue_playlist_plugin.c delete mode 100644 src/playlist/embcue_playlist_plugin.h delete mode 100644 src/playlist/lastfm_playlist_plugin.c delete mode 100644 src/playlist/lastfm_playlist_plugin.h delete mode 100644 src/playlist/soundcloud_playlist_plugin.c delete mode 100644 src/playlist/soundcloud_playlist_plugin.h (limited to 'src/playlist') diff --git a/src/playlist/DespotifyPlaylistPlugin.cxx b/src/playlist/DespotifyPlaylistPlugin.cxx index 049c9499..3466d568 100644 --- a/src/playlist/DespotifyPlaylistPlugin.cxx +++ b/src/playlist/DespotifyPlaylistPlugin.cxx @@ -21,7 +21,7 @@ #include "DespotifyPlaylistPlugin.hxx" #include "DespotifyUtils.hxx" #include "playlist_plugin.h" -#include "playlist_list.h" +#include "PlaylistRegistry.hxx" #include "conf.h" #include "uri.h" #include "tag.h" diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.cxx b/src/playlist/EmbeddedCuePlaylistPlugin.cxx new file mode 100644 index 00000000..2337f806 --- /dev/null +++ b/src/playlist/EmbeddedCuePlaylistPlugin.cxx @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Playlist plugin that reads embedded cue sheets from the "CUESHEET" + * tag of a music file. + */ + +#include "config.h" +#include "EmbeddedCuePlaylistPlugin.hxx" +#include "playlist_plugin.h" +#include "tag.h" +#include "tag_handler.h" +#include "song.h" + +extern "C" { +#include "tag_file.h" +#include "tag_ape.h" +#include "tag_id3.h" +#include "cue/cue_parser.h" +} + +#include +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cue" + +struct embcue_playlist { + struct playlist_provider base; + + /** + * This is an override for the CUE's "FILE". An embedded CUE + * sheet must always point to the song file it is contained + * in. + */ + char *filename; + + /** + * The value of the file's "CUESHEET" tag. + */ + char *cuesheet; + + /** + * The offset of the next line within "cuesheet". + */ + char *next; + + struct cue_parser *parser; +}; + +static void +embcue_tag_pair(const char *name, const char *value, void *ctx) +{ + struct embcue_playlist *playlist = (struct embcue_playlist *)ctx; + + if (playlist->cuesheet == NULL && + g_ascii_strcasecmp(name, "cuesheet") == 0) + playlist->cuesheet = g_strdup(value); +} + +static const struct tag_handler embcue_tag_handler = { + nullptr, + nullptr, + embcue_tag_pair, +}; + +static struct playlist_provider * +embcue_playlist_open_uri(const char *uri, + G_GNUC_UNUSED GMutex *mutex, + G_GNUC_UNUSED GCond *cond) +{ + if (!g_path_is_absolute(uri)) + /* only local files supported */ + return NULL; + + struct embcue_playlist *playlist = g_new(struct embcue_playlist, 1); + playlist_provider_init(&playlist->base, &embcue_playlist_plugin); + playlist->cuesheet = NULL; + + tag_file_scan(uri, &embcue_tag_handler, playlist); + if (playlist->cuesheet == NULL) { + tag_ape_scan2(uri, &embcue_tag_handler, playlist); + if (playlist->cuesheet == NULL) + tag_id3_scan(uri, &embcue_tag_handler, playlist); + } + + if (playlist->cuesheet == NULL) { + /* no "CUESHEET" tag found */ + g_free(playlist); + return NULL; + } + + playlist->filename = g_path_get_basename(uri); + + playlist->next = playlist->cuesheet; + playlist->parser = cue_parser_new(); + + return &playlist->base; +} + +static void +embcue_playlist_close(struct playlist_provider *_playlist) +{ + struct embcue_playlist *playlist = (struct embcue_playlist *)_playlist; + + cue_parser_free(playlist->parser); + g_free(playlist->cuesheet); + g_free(playlist->filename); + g_free(playlist); +} + +static struct song * +embcue_playlist_read(struct playlist_provider *_playlist) +{ + struct embcue_playlist *playlist = (struct embcue_playlist *)_playlist; + + struct song *song = cue_parser_get(playlist->parser); + if (song != NULL) + return song; + + while (*playlist->next != 0) { + const char *line = playlist->next; + char *eol = strpbrk(playlist->next, "\r\n"); + if (eol != NULL) { + /* null-terminate the line */ + *eol = 0; + playlist->next = eol + 1; + } else + /* last line; put the "next" pointer to the + end of the buffer */ + playlist->next += strlen(line); + + cue_parser_feed(playlist->parser, line); + song = cue_parser_get(playlist->parser); + if (song != NULL) + return song_replace_uri(song, playlist->filename); + } + + cue_parser_finish(playlist->parser); + song = cue_parser_get(playlist->parser); + if (song != NULL) + song = song_replace_uri(song, playlist->filename); + return song; +} + +static const char *const embcue_playlist_suffixes[] = { + /* a few codecs that are known to be supported; there are + probably many more */ + "flac", + "mp3", "mp2", + "mp4", "mp4a", "m4b", + "ape", + "wv", + "ogg", "oga", + NULL +}; + +const struct playlist_plugin embcue_playlist_plugin = { + "cue", + + nullptr, + nullptr, + embcue_playlist_open_uri, + nullptr, + embcue_playlist_close, + embcue_playlist_read, + + embcue_playlist_suffixes, + nullptr, + nullptr, +}; diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.hxx b/src/playlist/EmbeddedCuePlaylistPlugin.hxx new file mode 100644 index 00000000..e306730f --- /dev/null +++ b/src/playlist/EmbeddedCuePlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_EMBCUE_PLAYLIST_PLUGIN_HXX +#define MPD_EMBCUE_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin embcue_playlist_plugin; + +#endif diff --git a/src/playlist/LastFMPlaylistPlugin.cxx b/src/playlist/LastFMPlaylistPlugin.cxx new file mode 100644 index 00000000..67192574 --- /dev/null +++ b/src/playlist/LastFMPlaylistPlugin.cxx @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "LastFMPlaylistPlugin.hxx" +#include "playlist_plugin.h" +#include "PlaylistRegistry.hxx" +#include "conf.h" +#include "uri.h" +#include "song.h" +#include "input_stream.h" + +#include + +#include +#include + +struct lastfm_playlist { + struct playlist_provider base; + + struct input_stream *is; + + struct playlist_provider *xspf; +}; + +static struct { + char *user; + char *md5; +} lastfm_config; + +static bool +lastfm_init(const struct config_param *param) +{ + const char *user = config_get_block_string(param, "user", NULL); + const char *passwd = config_get_block_string(param, "password", NULL); + + if (user == NULL || passwd == NULL) { + g_debug("disabling the last.fm playlist plugin " + "because account is not configured"); + return false; + } + + lastfm_config.user = g_uri_escape_string(user, NULL, false); + + if (strlen(passwd) != 32) + lastfm_config.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5, + passwd, strlen(passwd)); + else + lastfm_config.md5 = g_strdup(passwd); + + return true; +} + +static void +lastfm_finish(void) +{ + g_free(lastfm_config.user); + g_free(lastfm_config.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, GMutex *mutex, GCond *cond) +{ + struct input_stream *input_stream; + GError *error = NULL; + char buffer[4096]; + size_t length = 0, nbytes; + + input_stream = input_stream_open(url, mutex, cond, &error); + if (input_stream == NULL) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + return NULL; + } + + g_mutex_lock(mutex); + + input_stream_wait_ready(input_stream); + + do { + nbytes = input_stream_read(input_stream, buffer + length, + sizeof(buffer) - length, &error); + if (nbytes == 0) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + if (input_stream_eof(input_stream)) + break; + + /* I/O error */ + g_mutex_unlock(mutex); + input_stream_close(input_stream); + return NULL; + } + + length += nbytes; + } while (length < sizeof(buffer)); + + g_mutex_unlock(mutex); + + input_stream_close(input_stream); + 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 response or NULL on error. Free with g_free. + */ +static char * +lastfm_find(const char *response, const char *name) +{ + size_t name_length = strlen(name); + + while (true) { + const char *eol = strchr(response, '\n'); + if (eol == NULL) + return NULL; + + if (strncmp(response, name, name_length) == 0 && + response[name_length] == '=') { + response += name_length + 1; + return g_strndup(response, eol - response); + } + + response = eol + 1; + } +} + +static struct playlist_provider * +lastfm_open_uri(const char *uri, GMutex *mutex, GCond *cond) +{ + struct lastfm_playlist *playlist; + GError *error = NULL; + char *p, *q, *response, *session; + + /* handshake */ + + p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?" + "version=1.1.1&platform=linux&" + "username=", lastfm_config.user, "&" + "passwordmd5=", lastfm_config.md5, "&" + "debug=0&partner=", NULL); + response = lastfm_get(p, mutex, cond); + g_free(p); + if (response == NULL) + return NULL; + + /* extract session id from response */ + + session = lastfm_find(response, "session"); + g_free(response); + if (session == NULL) { + g_warning("last.fm handshake failed"); + return NULL; + } + + q = g_uri_escape_string(session, NULL, false); + g_free(session); + session = q; + + g_debug("session='%s'", session); + + /* "adjust" last.fm radio */ + + if (strlen(uri) > 9) { + char *escaped_uri; + + escaped_uri = g_uri_escape_string(uri, NULL, false); + + p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?" + "session=", session, "&url=", escaped_uri, "&debug=0", + NULL); + g_free(escaped_uri); + + response = lastfm_get(p, mutex, cond); + g_free(response); + g_free(p); + + if (response == NULL) { + g_free(session); + return NULL; + } + } + + /* create the playlist object */ + + playlist = g_new(struct lastfm_playlist, 1); + playlist_provider_init(&playlist->base, &lastfm_playlist_plugin); + + /* open the last.fm playlist */ + + p = g_strconcat("http://ws.audioscrobbler.com/radio/xspf.php?" + "sk=", session, "&discovery=0&desktop=1.5.1.31879", + NULL); + g_free(session); + + playlist->is = input_stream_open(p, mutex, cond, &error); + g_free(p); + + if (playlist->is == NULL) { + if (error != NULL) { + g_warning("Failed to load XSPF playlist: %s", + error->message); + g_error_free(error); + } else + g_warning("Failed to load XSPF playlist"); + g_free(playlist); + return NULL; + } + + g_mutex_lock(mutex); + + input_stream_wait_ready(playlist->is); + + /* last.fm does not send a MIME type, we have to fake it here + :-( */ + g_free(playlist->is->mime); + playlist->is->mime = g_strdup("application/xspf+xml"); + + g_mutex_unlock(mutex); + + /* parse the XSPF playlist */ + + playlist->xspf = playlist_list_open_stream(playlist->is, NULL); + if (playlist->xspf == NULL) { + input_stream_close(playlist->is); + g_free(playlist); + g_warning("Failed to parse XSPF playlist"); + return NULL; + } + + return &playlist->base; +} + +static void +lastfm_close(struct playlist_provider *_playlist) +{ + struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist; + + playlist_plugin_close(playlist->xspf); + input_stream_close(playlist->is); + g_free(playlist); +} + +static struct song * +lastfm_read(struct playlist_provider *_playlist) +{ + struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist; + + return playlist_plugin_read(playlist->xspf); +} + +static const char *const lastfm_schemes[] = { + "lastfm", + NULL +}; + +const struct playlist_plugin lastfm_playlist_plugin = { + "lastfm", + + lastfm_init, + lastfm_finish, + lastfm_open_uri, + nullptr, + lastfm_close, + lastfm_read, + + lastfm_schemes, + nullptr, + nullptr, +}; diff --git a/src/playlist/LastFMPlaylistPlugin.hxx b/src/playlist/LastFMPlaylistPlugin.hxx new file mode 100644 index 00000000..fe0e206d --- /dev/null +++ b/src/playlist/LastFMPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_LASTFM_PLAYLIST_PLUGIN_HXX +#define MPD_LASTFM_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin lastfm_playlist_plugin; + +#endif diff --git a/src/playlist/SoundCloudPlaylistPlugin.cxx b/src/playlist/SoundCloudPlaylistPlugin.cxx new file mode 100644 index 00000000..71a2af6c --- /dev/null +++ b/src/playlist/SoundCloudPlaylistPlugin.cxx @@ -0,0 +1,451 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SoundCloudPlaylistPlugin.hxx" +#include "conf.h" +#include "input_stream.h" +#include "playlist_plugin.h" +#include "song.h" +#include "tag.h" + +#include +#include + +#include + +struct soundcloud_playlist { + struct playlist_provider base; + + GSList *songs; +}; + +static struct { + char *apikey; +} soundcloud_config; + +static bool +soundcloud_init(const struct config_param *param) +{ + soundcloud_config.apikey = + config_dup_block_string(param, "apikey", NULL); + if (soundcloud_config.apikey == NULL) { + g_debug("disabling the soundcloud playlist plugin " + "because API key is not set"); + return false; + } + + return true; +} + +static void +soundcloud_finish(void) +{ + g_free(soundcloud_config.apikey); +} + +/** + * Construct a full soundcloud resolver URL from the given fragment. + * @param uri uri of a soundcloud page (or just the path) + * @return Constructed URL. Must be freed with g_free. + */ +static char * +soundcloud_resolve(const char* uri) { + char *u, *ru; + + if (g_str_has_prefix(uri, "http://")) { + u = g_strdup(uri); + } else if (g_str_has_prefix(uri, "soundcloud.com")) { + u = g_strconcat("http://", uri, NULL); + } else { + /* assume it's just a path on soundcloud.com */ + u = g_strconcat("http://soundcloud.com/", uri, NULL); + } + + ru = g_strconcat("http://api.soundcloud.com/resolve.json?url=", + u, "&client_id=", soundcloud_config.apikey, NULL); + g_free(u); + + return ru; +} + +/* YAJL parser for track data from both /tracks/ and /playlists/ JSON */ + +enum key { + Duration, + Title, + Stream_URL, + Other, +}; + +const char* key_str[] = { + "duration", + "title", + "stream_url", + NULL, +}; + +struct parse_data { + int key; + char* stream_url; + long duration; + char* title; + int got_url; /* nesting level of last stream_url */ + GSList* songs; +}; + +static int handle_integer(void *ctx, + long +#ifndef HAVE_YAJL1 + long +#endif + intval) +{ + struct parse_data *data = (struct parse_data *) ctx; + + switch (data->key) { + case Duration: + data->duration = intval; + break; + default: + break; + } + + return 1; +} + +static int handle_string(void *ctx, const unsigned char* stringval, +#ifdef HAVE_YAJL1 + unsigned int +#else + size_t +#endif + stringlen) +{ + struct parse_data *data = (struct parse_data *) ctx; + const char *s = (const char *) stringval; + + switch (data->key) { + case Title: + if (data->title != NULL) + g_free(data->title); + data->title = g_strndup(s, stringlen); + break; + case Stream_URL: + if (data->stream_url != NULL) + g_free(data->stream_url); + data->stream_url = g_strndup(s, stringlen); + data->got_url = 1; + break; + default: + break; + } + + return 1; +} + +static int handle_mapkey(void *ctx, const unsigned char* stringval, +#ifdef HAVE_YAJL1 + unsigned int +#else + size_t +#endif + stringlen) +{ + struct parse_data *data = (struct parse_data *) ctx; + + int i; + data->key = Other; + + for (i = 0; i < Other; ++i) { + if (strncmp((const char *)stringval, key_str[i], stringlen) == 0) { + data->key = i; + break; + } + } + + return 1; +} + +static int handle_start_map(void *ctx) +{ + struct parse_data *data = (struct parse_data *) ctx; + + if (data->got_url > 0) + data->got_url++; + + return 1; +} + +static int handle_end_map(void *ctx) +{ + struct parse_data *data = (struct parse_data *) ctx; + + if (data->got_url > 1) { + data->got_url--; + return 1; + } + + if (data->got_url == 0) + return 1; + + /* got_url == 1, track finished, make it into a song */ + data->got_url = 0; + + struct song *s; + struct tag *t; + char *u; + + u = g_strconcat(data->stream_url, "?client_id=", soundcloud_config.apikey, NULL); + s = song_remote_new(u); + g_free(u); + t = tag_new(); + t->time = data->duration / 1000; + if (data->title != NULL) + tag_add_item(t, TAG_NAME, data->title); + s->tag = t; + + data->songs = g_slist_prepend(data->songs, s); + + return 1; +} + +static yajl_callbacks parse_callbacks = { + NULL, + NULL, + handle_integer, + NULL, + NULL, + handle_string, + handle_start_map, + handle_mapkey, + handle_end_map, + NULL, + NULL, +}; + +/** + * Read JSON data and parse it using the given YAJL parser. + * @param url URL of the JSON data. + * @param hand YAJL parser handle. + * @return -1 on error, 0 on success. + */ +static int +soundcloud_parse_json(const char *url, yajl_handle hand, GMutex* mutex, GCond* cond) +{ + struct input_stream *input_stream; + GError *error = NULL; + char buffer[4096]; + unsigned char *ubuffer = (unsigned char *)buffer; + size_t nbytes; + + input_stream = input_stream_open(url, mutex, cond, &error); + if (input_stream == NULL) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + return -1; + } + + g_mutex_lock(mutex); + input_stream_wait_ready(input_stream); + + yajl_status stat; + int done = 0; + + while (!done) { + nbytes = input_stream_read(input_stream, buffer, sizeof(buffer), &error); + if (nbytes == 0) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + if (input_stream_eof(input_stream)) { + done = true; + } else { + g_mutex_unlock(mutex); + input_stream_close(input_stream); + return -1; + } + } + + if (done) { +#ifdef HAVE_YAJL1 + stat = yajl_parse_complete(hand); +#else + stat = yajl_complete_parse(hand); +#endif + } else + stat = yajl_parse(hand, ubuffer, nbytes); + + if (stat != yajl_status_ok +#ifdef HAVE_YAJL1 + && stat != yajl_status_insufficient_data +#endif + ) + { + unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes); + g_warning("%s", str); + yajl_free_error(hand, str); + break; + } + } + + g_mutex_unlock(mutex); + input_stream_close(input_stream); + + return 0; +} + +/** + * Parse a soundcloud:// URL and create a playlist. + * @param uri A soundcloud URL. Accepted forms: + * soundcloud://track/ + * soundcloud://playlist/ + * soundcloud://url/ + */ + +static struct playlist_provider * +soundcloud_open_uri(const char *uri, GMutex *mutex, GCond *cond) +{ + struct soundcloud_playlist *playlist = NULL; + + char *s, *p; + char *scheme, *arg, *rest; + s = g_strdup(uri); + scheme = s; + for (p = s; *p; p++) { + if (*p == ':' && *(p+1) == '/' && *(p+2) == '/') { + *p = 0; + p += 3; + break; + } + } + arg = p; + for (; *p; p++) { + if (*p == '/') { + *p = 0; + p++; + break; + } + } + rest = p; + + if (strcmp(scheme, "soundcloud") != 0) { + g_warning("incompatible scheme for soundcloud plugin: %s", scheme); + g_free(s); + return NULL; + } + + char *u = NULL; + if (strcmp(arg, "track") == 0) { + u = g_strconcat("http://api.soundcloud.com/tracks/", + rest, ".json?client_id=", soundcloud_config.apikey, NULL); + } else if (strcmp(arg, "playlist") == 0) { + u = g_strconcat("http://api.soundcloud.com/playlists/", + rest, ".json?client_id=", soundcloud_config.apikey, NULL); + } else if (strcmp(arg, "url") == 0) { + /* Translate to soundcloud resolver call. libcurl will automatically + follow the redirect to the right resource. */ + u = soundcloud_resolve(rest); + } + g_free(s); + + if (u == NULL) { + g_warning("unknown soundcloud URI"); + return NULL; + } + + yajl_handle hand; + struct parse_data data; + + data.got_url = 0; + data.songs = NULL; + data.title = NULL; + data.stream_url = NULL; +#ifdef HAVE_YAJL1 + hand = yajl_alloc(&parse_callbacks, NULL, NULL, (void *) &data); +#else + hand = yajl_alloc(&parse_callbacks, NULL, (void *) &data); +#endif + + int ret = soundcloud_parse_json(u, hand, mutex, cond); + + g_free(u); + yajl_free(hand); + if (data.title != NULL) + g_free(data.title); + if (data.stream_url != NULL) + g_free(data.stream_url); + + if (ret == -1) + return NULL; + + playlist = g_new(struct soundcloud_playlist, 1); + playlist_provider_init(&playlist->base, &soundcloud_playlist_plugin); + playlist->songs = g_slist_reverse(data.songs); + + return &playlist->base; +} + +static void +soundcloud_close(struct playlist_provider *_playlist) +{ + struct soundcloud_playlist *playlist = (struct soundcloud_playlist *)_playlist; + + g_free(playlist); +} + + +static struct song * +soundcloud_read(struct playlist_provider *_playlist) +{ + struct soundcloud_playlist *playlist = (struct soundcloud_playlist *)_playlist; + + if (playlist->songs == NULL) + return NULL; + + struct song* s; + s = (struct song *)playlist->songs->data; + playlist->songs = g_slist_remove(playlist->songs, s); + return s; +} + +static const char *const soundcloud_schemes[] = { + "soundcloud", + NULL +}; + +const struct playlist_plugin soundcloud_playlist_plugin = { + "soundcloud", + + soundcloud_init, + soundcloud_finish, + soundcloud_open_uri, + nullptr, + soundcloud_close, + soundcloud_read, + + soundcloud_schemes, + nullptr, + nullptr, +}; + + diff --git a/src/playlist/SoundCloudPlaylistPlugin.hxx b/src/playlist/SoundCloudPlaylistPlugin.hxx new file mode 100644 index 00000000..7c121328 --- /dev/null +++ b/src/playlist/SoundCloudPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX +#define MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin soundcloud_playlist_plugin; + +#endif diff --git a/src/playlist/embcue_playlist_plugin.c b/src/playlist/embcue_playlist_plugin.c deleted file mode 100644 index 6d9a957f..00000000 --- a/src/playlist/embcue_playlist_plugin.c +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Playlist plugin that reads embedded cue sheets from the "CUESHEET" - * tag of a music file. - */ - -#include "config.h" -#include "playlist/embcue_playlist_plugin.h" -#include "playlist_plugin.h" -#include "tag.h" -#include "tag_handler.h" -#include "tag_file.h" -#include "tag_ape.h" -#include "tag_id3.h" -#include "song.h" -#include "cue/cue_parser.h" - -#include -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "cue" - -struct embcue_playlist { - struct playlist_provider base; - - /** - * This is an override for the CUE's "FILE". An embedded CUE - * sheet must always point to the song file it is contained - * in. - */ - char *filename; - - /** - * The value of the file's "CUESHEET" tag. - */ - char *cuesheet; - - /** - * The offset of the next line within "cuesheet". - */ - char *next; - - struct cue_parser *parser; -}; - -static void -embcue_tag_pair(const char *name, const char *value, void *ctx) -{ - struct embcue_playlist *playlist = ctx; - - if (playlist->cuesheet == NULL && - g_ascii_strcasecmp(name, "cuesheet") == 0) - playlist->cuesheet = g_strdup(value); -} - -static const struct tag_handler embcue_tag_handler = { - .pair = embcue_tag_pair, -}; - -static struct playlist_provider * -embcue_playlist_open_uri(const char *uri, - G_GNUC_UNUSED GMutex *mutex, - G_GNUC_UNUSED GCond *cond) -{ - if (!g_path_is_absolute(uri)) - /* only local files supported */ - return NULL; - - struct embcue_playlist *playlist = g_new(struct embcue_playlist, 1); - playlist_provider_init(&playlist->base, &embcue_playlist_plugin); - playlist->cuesheet = NULL; - - tag_file_scan(uri, &embcue_tag_handler, playlist); - if (playlist->cuesheet == NULL) { - tag_ape_scan2(uri, &embcue_tag_handler, playlist); - if (playlist->cuesheet == NULL) - tag_id3_scan(uri, &embcue_tag_handler, playlist); - } - - if (playlist->cuesheet == NULL) { - /* no "CUESHEET" tag found */ - g_free(playlist); - return NULL; - } - - playlist->filename = g_path_get_basename(uri); - - playlist->next = playlist->cuesheet; - playlist->parser = cue_parser_new(); - - return &playlist->base; -} - -static void -embcue_playlist_close(struct playlist_provider *_playlist) -{ - struct embcue_playlist *playlist = (struct embcue_playlist *)_playlist; - - cue_parser_free(playlist->parser); - g_free(playlist->cuesheet); - g_free(playlist->filename); - g_free(playlist); -} - -static struct song * -embcue_playlist_read(struct playlist_provider *_playlist) -{ - struct embcue_playlist *playlist = (struct embcue_playlist *)_playlist; - - struct song *song = cue_parser_get(playlist->parser); - if (song != NULL) - return song; - - while (*playlist->next != 0) { - const char *line = playlist->next; - char *eol = strpbrk(playlist->next, "\r\n"); - if (eol != NULL) { - /* null-terminate the line */ - *eol = 0; - playlist->next = eol + 1; - } else - /* last line; put the "next" pointer to the - end of the buffer */ - playlist->next += strlen(line); - - cue_parser_feed(playlist->parser, line); - song = cue_parser_get(playlist->parser); - if (song != NULL) - return song_replace_uri(song, playlist->filename); - } - - cue_parser_finish(playlist->parser); - song = cue_parser_get(playlist->parser); - if (song != NULL) - song = song_replace_uri(song, playlist->filename); - return song; -} - -static const char *const embcue_playlist_suffixes[] = { - /* a few codecs that are known to be supported; there are - probably many more */ - "flac", - "mp3", "mp2", - "mp4", "mp4a", "m4b", - "ape", - "wv", - "ogg", "oga", - NULL -}; - -const struct playlist_plugin embcue_playlist_plugin = { - .name = "cue", - - .open_uri = embcue_playlist_open_uri, - .close = embcue_playlist_close, - .read = embcue_playlist_read, - - .suffixes = embcue_playlist_suffixes, - .mime_types = NULL, -}; diff --git a/src/playlist/embcue_playlist_plugin.h b/src/playlist/embcue_playlist_plugin.h deleted file mode 100644 index c5f21b27..00000000 --- a/src/playlist/embcue_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2012 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_EMBCUE_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_EMBCUE_PLAYLIST_PLUGIN_H - -extern const struct playlist_plugin embcue_playlist_plugin; - -#endif diff --git a/src/playlist/lastfm_playlist_plugin.c b/src/playlist/lastfm_playlist_plugin.c deleted file mode 100644 index ead14dea..00000000 --- a/src/playlist/lastfm_playlist_plugin.c +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/lastfm_playlist_plugin.h" -#include "playlist_plugin.h" -#include "playlist_list.h" -#include "conf.h" -#include "uri.h" -#include "song.h" -#include "input_stream.h" - -#include - -#include -#include - -struct lastfm_playlist { - struct playlist_provider base; - - struct input_stream *is; - - struct playlist_provider *xspf; -}; - -static struct { - char *user; - char *md5; -} lastfm_config; - -static bool -lastfm_init(const struct config_param *param) -{ - const char *user = config_get_block_string(param, "user", NULL); - const char *passwd = config_get_block_string(param, "password", NULL); - - if (user == NULL || passwd == NULL) { - g_debug("disabling the last.fm playlist plugin " - "because account is not configured"); - return false; - } - - lastfm_config.user = g_uri_escape_string(user, NULL, false); - - if (strlen(passwd) != 32) - lastfm_config.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5, - passwd, strlen(passwd)); - else - lastfm_config.md5 = g_strdup(passwd); - - return true; -} - -static void -lastfm_finish(void) -{ - g_free(lastfm_config.user); - g_free(lastfm_config.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, GMutex *mutex, GCond *cond) -{ - struct input_stream *input_stream; - GError *error = NULL; - char buffer[4096]; - size_t length = 0, nbytes; - - input_stream = input_stream_open(url, mutex, cond, &error); - if (input_stream == NULL) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } - - return NULL; - } - - g_mutex_lock(mutex); - - input_stream_wait_ready(input_stream); - - do { - nbytes = input_stream_read(input_stream, buffer + length, - sizeof(buffer) - length, &error); - if (nbytes == 0) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } - - if (input_stream_eof(input_stream)) - break; - - /* I/O error */ - g_mutex_unlock(mutex); - input_stream_close(input_stream); - return NULL; - } - - length += nbytes; - } while (length < sizeof(buffer)); - - g_mutex_unlock(mutex); - - input_stream_close(input_stream); - 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 response or NULL on error. Free with g_free. - */ -static char * -lastfm_find(const char *response, const char *name) -{ - size_t name_length = strlen(name); - - while (true) { - const char *eol = strchr(response, '\n'); - if (eol == NULL) - return NULL; - - if (strncmp(response, name, name_length) == 0 && - response[name_length] == '=') { - response += name_length + 1; - return g_strndup(response, eol - response); - } - - response = eol + 1; - } -} - -static struct playlist_provider * -lastfm_open_uri(const char *uri, GMutex *mutex, GCond *cond) -{ - struct lastfm_playlist *playlist; - GError *error = NULL; - char *p, *q, *response, *session; - - /* handshake */ - - p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?" - "version=1.1.1&platform=linux&" - "username=", lastfm_config.user, "&" - "passwordmd5=", lastfm_config.md5, "&" - "debug=0&partner=", NULL); - response = lastfm_get(p, mutex, cond); - g_free(p); - if (response == NULL) - return NULL; - - /* extract session id from response */ - - session = lastfm_find(response, "session"); - g_free(response); - if (session == NULL) { - g_warning("last.fm handshake failed"); - return NULL; - } - - q = g_uri_escape_string(session, NULL, false); - g_free(session); - session = q; - - g_debug("session='%s'", session); - - /* "adjust" last.fm radio */ - - if (strlen(uri) > 9) { - char *escaped_uri; - - escaped_uri = g_uri_escape_string(uri, NULL, false); - - p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?" - "session=", session, "&url=", escaped_uri, "&debug=0", - NULL); - g_free(escaped_uri); - - response = lastfm_get(p, mutex, cond); - g_free(response); - g_free(p); - - if (response == NULL) { - g_free(session); - return NULL; - } - } - - /* create the playlist object */ - - playlist = g_new(struct lastfm_playlist, 1); - playlist_provider_init(&playlist->base, &lastfm_playlist_plugin); - - /* open the last.fm playlist */ - - p = g_strconcat("http://ws.audioscrobbler.com/radio/xspf.php?" - "sk=", session, "&discovery=0&desktop=1.5.1.31879", - NULL); - g_free(session); - - playlist->is = input_stream_open(p, mutex, cond, &error); - g_free(p); - - if (playlist->is == NULL) { - if (error != NULL) { - g_warning("Failed to load XSPF playlist: %s", - error->message); - g_error_free(error); - } else - g_warning("Failed to load XSPF playlist"); - g_free(playlist); - return NULL; - } - - g_mutex_lock(mutex); - - input_stream_wait_ready(playlist->is); - - /* last.fm does not send a MIME type, we have to fake it here - :-( */ - g_free(playlist->is->mime); - playlist->is->mime = g_strdup("application/xspf+xml"); - - g_mutex_unlock(mutex); - - /* parse the XSPF playlist */ - - playlist->xspf = playlist_list_open_stream(playlist->is, NULL); - if (playlist->xspf == NULL) { - input_stream_close(playlist->is); - g_free(playlist); - g_warning("Failed to parse XSPF playlist"); - return NULL; - } - - return &playlist->base; -} - -static void -lastfm_close(struct playlist_provider *_playlist) -{ - struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist; - - playlist_plugin_close(playlist->xspf); - input_stream_close(playlist->is); - g_free(playlist); -} - -static struct song * -lastfm_read(struct playlist_provider *_playlist) -{ - struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist; - - return playlist_plugin_read(playlist->xspf); -} - -static const char *const lastfm_schemes[] = { - "lastfm", - NULL -}; - -const struct playlist_plugin lastfm_playlist_plugin = { - .name = "lastfm", - - .init = lastfm_init, - .finish = lastfm_finish, - .open_uri = lastfm_open_uri, - .close = lastfm_close, - .read = lastfm_read, - - .schemes = lastfm_schemes, -}; diff --git a/src/playlist/lastfm_playlist_plugin.h b/src/playlist/lastfm_playlist_plugin.h deleted file mode 100644 index 46a8b0ca..00000000 --- a/src/playlist/lastfm_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H - -extern const struct playlist_plugin lastfm_playlist_plugin; - -#endif diff --git a/src/playlist/soundcloud_playlist_plugin.c b/src/playlist/soundcloud_playlist_plugin.c deleted file mode 100644 index 7c79f880..00000000 --- a/src/playlist/soundcloud_playlist_plugin.c +++ /dev/null @@ -1,448 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "playlist/soundcloud_playlist_plugin.h" -#include "conf.h" -#include "input_stream.h" -#include "playlist_plugin.h" -#include "song.h" -#include "tag.h" - -#include -#include - -#include - -struct soundcloud_playlist { - struct playlist_provider base; - - GSList *songs; -}; - -static struct { - char *apikey; -} soundcloud_config; - -static bool -soundcloud_init(const struct config_param *param) -{ - soundcloud_config.apikey = - config_dup_block_string(param, "apikey", NULL); - if (soundcloud_config.apikey == NULL) { - g_debug("disabling the soundcloud playlist plugin " - "because API key is not set"); - return false; - } - - return true; -} - -static void -soundcloud_finish(void) -{ - g_free(soundcloud_config.apikey); -} - -/** - * Construct a full soundcloud resolver URL from the given fragment. - * @param uri uri of a soundcloud page (or just the path) - * @return Constructed URL. Must be freed with g_free. - */ -static char * -soundcloud_resolve(const char* uri) { - char *u, *ru; - - if (g_str_has_prefix(uri, "http://")) { - u = g_strdup(uri); - } else if (g_str_has_prefix(uri, "soundcloud.com")) { - u = g_strconcat("http://", uri, NULL); - } else { - /* assume it's just a path on soundcloud.com */ - u = g_strconcat("http://soundcloud.com/", uri, NULL); - } - - ru = g_strconcat("http://api.soundcloud.com/resolve.json?url=", - u, "&client_id=", soundcloud_config.apikey, NULL); - g_free(u); - - return ru; -} - -/* YAJL parser for track data from both /tracks/ and /playlists/ JSON */ - -enum key { - Duration, - Title, - Stream_URL, - Other, -}; - -const char* key_str[] = { - "duration", - "title", - "stream_url", - NULL, -}; - -struct parse_data { - int key; - char* stream_url; - long duration; - char* title; - int got_url; /* nesting level of last stream_url */ - GSList* songs; -}; - -static int handle_integer(void *ctx, - long -#ifndef HAVE_YAJL1 - long -#endif - intval) -{ - struct parse_data *data = (struct parse_data *) ctx; - - switch (data->key) { - case Duration: - data->duration = intval; - break; - default: - break; - } - - return 1; -} - -static int handle_string(void *ctx, const unsigned char* stringval, -#ifdef HAVE_YAJL1 - unsigned int -#else - size_t -#endif - stringlen) -{ - struct parse_data *data = (struct parse_data *) ctx; - const char *s = (const char *) stringval; - - switch (data->key) { - case Title: - if (data->title != NULL) - g_free(data->title); - data->title = g_strndup(s, stringlen); - break; - case Stream_URL: - if (data->stream_url != NULL) - g_free(data->stream_url); - data->stream_url = g_strndup(s, stringlen); - data->got_url = 1; - break; - default: - break; - } - - return 1; -} - -static int handle_mapkey(void *ctx, const unsigned char* stringval, -#ifdef HAVE_YAJL1 - unsigned int -#else - size_t -#endif - stringlen) -{ - struct parse_data *data = (struct parse_data *) ctx; - - int i; - data->key = Other; - - for (i = 0; i < Other; ++i) { - if (strncmp((const char *)stringval, key_str[i], stringlen) == 0) { - data->key = i; - break; - } - } - - return 1; -} - -static int handle_start_map(void *ctx) -{ - struct parse_data *data = (struct parse_data *) ctx; - - if (data->got_url > 0) - data->got_url++; - - return 1; -} - -static int handle_end_map(void *ctx) -{ - struct parse_data *data = (struct parse_data *) ctx; - - if (data->got_url > 1) { - data->got_url--; - return 1; - } - - if (data->got_url == 0) - return 1; - - /* got_url == 1, track finished, make it into a song */ - data->got_url = 0; - - struct song *s; - struct tag *t; - char *u; - - u = g_strconcat(data->stream_url, "?client_id=", soundcloud_config.apikey, NULL); - s = song_remote_new(u); - g_free(u); - t = tag_new(); - t->time = data->duration / 1000; - if (data->title != NULL) - tag_add_item(t, TAG_NAME, data->title); - s->tag = t; - - data->songs = g_slist_prepend(data->songs, s); - - return 1; -} - -static yajl_callbacks parse_callbacks = { - NULL, - NULL, - handle_integer, - NULL, - NULL, - handle_string, - handle_start_map, - handle_mapkey, - handle_end_map, - NULL, - NULL, -}; - -/** - * Read JSON data and parse it using the given YAJL parser. - * @param url URL of the JSON data. - * @param hand YAJL parser handle. - * @return -1 on error, 0 on success. - */ -static int -soundcloud_parse_json(const char *url, yajl_handle hand, GMutex* mutex, GCond* cond) -{ - struct input_stream *input_stream; - GError *error = NULL; - char buffer[4096]; - unsigned char *ubuffer = (unsigned char *)buffer; - size_t nbytes; - - input_stream = input_stream_open(url, mutex, cond, &error); - if (input_stream == NULL) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } - return -1; - } - - g_mutex_lock(mutex); - input_stream_wait_ready(input_stream); - - yajl_status stat; - int done = 0; - - while (!done) { - nbytes = input_stream_read(input_stream, buffer, sizeof(buffer), &error); - if (nbytes == 0) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } - if (input_stream_eof(input_stream)) { - done = true; - } else { - g_mutex_unlock(mutex); - input_stream_close(input_stream); - return -1; - } - } - - if (done) { -#ifdef HAVE_YAJL1 - stat = yajl_parse_complete(hand); -#else - stat = yajl_complete_parse(hand); -#endif - } else - stat = yajl_parse(hand, ubuffer, nbytes); - - if (stat != yajl_status_ok -#ifdef HAVE_YAJL1 - && stat != yajl_status_insufficient_data -#endif - ) - { - unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes); - g_warning("%s", str); - yajl_free_error(hand, str); - break; - } - } - - g_mutex_unlock(mutex); - input_stream_close(input_stream); - - return 0; -} - -/** - * Parse a soundcloud:// URL and create a playlist. - * @param uri A soundcloud URL. Accepted forms: - * soundcloud://track/ - * soundcloud://playlist/ - * soundcloud://url/ - */ - -static struct playlist_provider * -soundcloud_open_uri(const char *uri, GMutex *mutex, GCond *cond) -{ - struct soundcloud_playlist *playlist = NULL; - - char *s, *p; - char *scheme, *arg, *rest; - s = g_strdup(uri); - scheme = s; - for (p = s; *p; p++) { - if (*p == ':' && *(p+1) == '/' && *(p+2) == '/') { - *p = 0; - p += 3; - break; - } - } - arg = p; - for (; *p; p++) { - if (*p == '/') { - *p = 0; - p++; - break; - } - } - rest = p; - - if (strcmp(scheme, "soundcloud") != 0) { - g_warning("incompatible scheme for soundcloud plugin: %s", scheme); - g_free(s); - return NULL; - } - - char *u = NULL; - if (strcmp(arg, "track") == 0) { - u = g_strconcat("http://api.soundcloud.com/tracks/", - rest, ".json?client_id=", soundcloud_config.apikey, NULL); - } else if (strcmp(arg, "playlist") == 0) { - u = g_strconcat("http://api.soundcloud.com/playlists/", - rest, ".json?client_id=", soundcloud_config.apikey, NULL); - } else if (strcmp(arg, "url") == 0) { - /* Translate to soundcloud resolver call. libcurl will automatically - follow the redirect to the right resource. */ - u = soundcloud_resolve(rest); - } - g_free(s); - - if (u == NULL) { - g_warning("unknown soundcloud URI"); - return NULL; - } - - yajl_handle hand; - struct parse_data data; - - data.got_url = 0; - data.songs = NULL; - data.title = NULL; - data.stream_url = NULL; -#ifdef HAVE_YAJL1 - hand = yajl_alloc(&parse_callbacks, NULL, NULL, (void *) &data); -#else - hand = yajl_alloc(&parse_callbacks, NULL, (void *) &data); -#endif - - int ret = soundcloud_parse_json(u, hand, mutex, cond); - - g_free(u); - yajl_free(hand); - if (data.title != NULL) - g_free(data.title); - if (data.stream_url != NULL) - g_free(data.stream_url); - - if (ret == -1) - return NULL; - - playlist = g_new(struct soundcloud_playlist, 1); - playlist_provider_init(&playlist->base, &soundcloud_playlist_plugin); - playlist->songs = g_slist_reverse(data.songs); - - return &playlist->base; -} - -static void -soundcloud_close(struct playlist_provider *_playlist) -{ - struct soundcloud_playlist *playlist = (struct soundcloud_playlist *)_playlist; - - g_free(playlist); -} - - -static struct song * -soundcloud_read(struct playlist_provider *_playlist) -{ - struct soundcloud_playlist *playlist = (struct soundcloud_playlist *)_playlist; - - if (playlist->songs == NULL) - return NULL; - - struct song* s; - s = (struct song *)playlist->songs->data; - playlist->songs = g_slist_remove(playlist->songs, s); - return s; -} - -static const char *const soundcloud_schemes[] = { - "soundcloud", - NULL -}; - -const struct playlist_plugin soundcloud_playlist_plugin = { - .name = "soundcloud", - - .init = soundcloud_init, - .finish = soundcloud_finish, - .open_uri = soundcloud_open_uri, - .close = soundcloud_close, - .read = soundcloud_read, - - .schemes = soundcloud_schemes, -}; - - diff --git a/src/playlist/soundcloud_playlist_plugin.h b/src/playlist/soundcloud_playlist_plugin.h deleted file mode 100644 index e09e2dd4..00000000 --- a/src/playlist/soundcloud_playlist_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_SOUNDCLOUD_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_SOUNDCLOUD_PLAYLIST_PLUGIN_H - -extern const struct playlist_plugin soundcloud_playlist_plugin; - -#endif -- cgit v1.2.3