diff --git a/src/spotify.c b/src/spotify.c
index a75be67d..1c333819 100644
--- a/src/spotify.c
+++ b/src/spotify.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 Espen Jürgensen
+ * Copyright (C) 2016 Espen Jürgensen
*
* Stiched together from libspotify examples
*
@@ -44,18 +44,36 @@
#include
#include
+#ifdef HAVE_JSON_C_OLD
+# include
+#else
+# include
+#endif
+
#include "spotify.h"
#include "logger.h"
+#include "misc.h"
+#include "http.h"
#include "conffile.h"
#include "filescanner.h"
#include "cache.h"
#include "commands.h"
+/* TODO for the web api:
+ * - remove tracks that are no longer in user lib
+ * - UI should be prettier
+ * - don't reload everything, just changed/new
+ * - support "added_at" tag
+ * - what to do about the lack of push?
+ */
-/* How long to wait for audio (in sec) before giving up */
+// How long to wait for audio (in sec) before giving up
#define SPOTIFY_TIMEOUT 20
-/* How long to wait for artwork (in sec) before giving up */
+// How long to wait for artwork (in sec) before giving up
#define SPOTIFY_ARTWORK_TIMEOUT 3
+// An upper limit on sequential requests to Spotify's web api
+// - each request will return 50 objects (tracks)
+#define SPOTIFY_WEB_REQUESTS_MAX 20
/* --- Types --- */
typedef struct audio_fifo_data
@@ -103,6 +121,12 @@ struct artwork_get_param
int is_loaded;
};
+struct pending_metadata
+{
+ sp_link *link;
+ sp_track *track;
+ struct pending_metadata *next;
+};
/* --- Globals --- */
// Spotify thread
@@ -119,14 +143,16 @@ static struct event *g_notifyev;
static struct commands_base *cmdbase;
-// The global session handle
+// The session handle
static sp_session *g_sess;
-// The global library handle
+// The library handle
static void *g_libhandle;
-// The global state telling us what the thread is currently doing
+// The state telling us what the thread is currently doing
static enum spotify_state g_state;
-// The global base playlist id (parent of all Spotify playlists in the db)
+// The base playlist id (parent of all Spotify playlists in the db)
static int g_base_plid;
+// Linked list of tracks where we are waiting for metadata
+static struct pending_metadata *spotify_pending_metadata;
// Audio fifo
static audio_fifo_t *g_audio_fifo;
@@ -159,6 +185,16 @@ const uint8_t g_appkey[] = {
0x09,
};
+// Endpoints and credentials for the web api
+static char *spotify_access_token;
+static char *spotify_refresh_token;
+static const char *spotify_client_id = "0e684a5422384114a8ae7ac020f01789";
+static const char *spotify_client_secret = "232af95f39014c9ba218285a5c11a239";
+static const char *spotify_auth_uri = "https://accounts.spotify.com/authorize";
+static const char *spotify_token_uri = "https://accounts.spotify.com/api/token";
+static const char *spotify_tracks_uri = "https://api.spotify.com/v1/me/tracks?limit=50";
+static const char *spotify_albums_uri = "https://api.spotify.com/v1/me/albums?limit=50";
+
// This section defines and assigns function pointers to the libspotify functions
// The arguments and return values must be in sync with the spotify api
// Please scroll through the ugliness which follows
@@ -382,6 +418,111 @@ fptr_assign_all()
// End of ugly part
+/* ------------------------------- MISC HELPERS ---------------------------- */
+
+static int
+spotify_file_read(char *path, char **username, char **password)
+{
+ FILE *fp;
+ char *u;
+ char *p;
+ char buf[256];
+ int len;
+
+ fp = fopen(path, "rb");
+ if (!fp)
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "Could not open Spotify credentials file %s: %s\n", path, strerror(errno));
+ return -1;
+ }
+
+ u = fgets(buf, sizeof(buf), fp);
+ if (!u)
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "Empty Spotify credentials file %s\n", path);
+
+ fclose(fp);
+ return -1;
+ }
+
+ len = strlen(u);
+ if (buf[len - 1] != '\n')
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify credentials file %s: username name too long or missing password\n", path);
+
+ fclose(fp);
+ return -1;
+ }
+
+ while (len)
+ {
+ if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
+ {
+ buf[len - 1] = '\0';
+ len--;
+ }
+ else
+ break;
+ }
+
+ if (!len)
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify credentials file %s: empty line where username expected\n", path);
+
+ fclose(fp);
+ return -1;
+ }
+
+ u = strdup(buf);
+ if (!u)
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for username while reading %s\n", path);
+
+ fclose(fp);
+ return -1;
+ }
+
+ p = fgets(buf, sizeof(buf), fp);
+ fclose(fp);
+ if (!p)
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify credentials file %s: no password\n", path);
+
+ free(u);
+ return -1;
+ }
+
+ len = strlen(p);
+
+ while (len)
+ {
+ if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
+ {
+ buf[len - 1] = '\0';
+ len--;
+ }
+ else
+ break;
+ }
+
+ p = strdup(buf);
+ if (!p)
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for password while reading %s\n", path);
+
+ free(u);
+ return -1;
+ }
+
+ DPRINTF(E_LOG, L_SPOTIFY, "Spotify credentials file OK, logging in with username %s\n", u);
+
+ *username = u;
+ *password = p;
+
+ return 0;
+}
+
+
/* -------------------------- PLAYLIST HELPERS ------------------------- */
/* Should only be called from within the spotify thread */
@@ -422,7 +563,7 @@ spotify_metadata_get(sp_track *track, struct media_file_info *mfi, const char *p
compilation = ((albumtype == SP_ALBUMTYPE_COMPILATION)
|| artist_override);
- if (album_override)
+ if (album_override && pltitle)
albumname = strdup(pltitle);
else
albumname = strdup(fptr_sp_album_name(album));
@@ -500,11 +641,14 @@ spotify_track_save(int plid, sp_track *track, const char *pltitle, int time_adde
fptr_sp_link_release(link);
/* Add to playlistitems table */
- ret = db_pl_add_item_bypath(plid, url);
- if (ret < 0)
+ if (plid)
{
- DPRINTF(E_LOG, L_SPOTIFY, "Could not save playlist item: '%s'\n", url);
- return -1;
+ ret = db_pl_add_item_bypath(plid, url);
+ if (ret < 0)
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "Could not save playlist item: '%s'\n", url);
+ return -1;
+ }
}
memset(&mfi, 0, sizeof(struct media_file_info));
@@ -546,6 +690,8 @@ spotify_track_save(int plid, sp_track *track, const char *pltitle, int time_adde
return -1;
}
+// DPRINTF(E_DBG, L_SPOTIFY, "Saving track '%s': '%s' by %s (%s)\n", url, mfi.title, mfi.artist, mfi.album);
+
filescanner_process_media(url, time(NULL), 0, F_SCAN_TYPE_SPOTIFY, &mfi, dir_id);
free_mfi(&mfi, 1);
@@ -629,7 +775,7 @@ spotify_playlist_save(sp_playlist *pl)
DPRINTF(E_LOG, L_SPOTIFY, "Saving playlist (%d tracks): '%s'\n", num_tracks, name);
- /* Save playlist (playlists table) */
+ // Save playlist (playlists table)
link = fptr_sp_link_create_from_playlist(pl);
if (!link)
{
@@ -718,7 +864,7 @@ spotify_playlist_save(sp_playlist *pl)
free_pli(pli, 0);
- /* Save tracks and playlistitems (files and playlistitems table) */
+ // Save tracks and playlistitems (files and playlistitems table)
db_transaction_begin();
for (i = 0; i < num_tracks; i++)
{
@@ -745,6 +891,455 @@ spotify_playlist_save(sp_playlist *pl)
return plid;
}
+// Registers a track with libspotify, which will make it start loading the track
+// metadata. When that is done metadata_updated() is called (but we won't be
+// told which track it was...). Note that this function will result in a ref
+// count on the sp_link, which the caller must decrease with sp_link_release.
+static enum command_state
+spotify_uri_register(void *arg, int *retval)
+{
+ struct pending_metadata *pm;
+ sp_link *link;
+ sp_track *track;
+
+ char *uri = arg;
+
+ if (SP_CONNECTION_STATE_LOGGED_IN != fptr_sp_session_connectionstate(g_sess))
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "Can't register music, not connected and logged in to Spotify\n");
+ *retval = -1;
+ return COMMAND_END;
+ }
+
+ link = fptr_sp_link_create_from_string(uri);
+ if (!link)
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify link: '%s'\n", uri);
+ *retval = -1;
+ return COMMAND_END;
+ }
+
+ track = fptr_sp_link_as_track(link);
+ if (!track)
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify track: '%s'\n", uri);
+ *retval = -1;
+ return COMMAND_END;
+ }
+
+ pm = malloc(sizeof(struct pending_metadata));
+ if (!pm)
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "Out of memory\n");
+ *retval = -1;
+ return COMMAND_END;
+ }
+
+ pm->link = link;
+ pm->track = track;
+ pm->next = spotify_pending_metadata;
+ spotify_pending_metadata = pm;
+
+ *retval = 0;
+ return COMMAND_END;
+}
+
+// TODO Maybe use the commands bh instead?
+static enum command_state
+spotify_pending_process(void *arg, int *retval)
+{
+ struct pending_metadata *pm;
+ struct pending_metadata *next;
+ int i;
+
+ *retval = 0;
+ if (!spotify_pending_metadata)
+ return COMMAND_END;
+
+ // Too early
+ i = 0;
+ for (pm = spotify_pending_metadata; pm; pm = pm->next)
+ {
+ i++;
+
+ if (!fptr_sp_track_is_loaded(pm->track))
+ return COMMAND_END;
+ }
+
+ DPRINTF(E_DBG, L_SPOTIFY, "All %d tracks loaded, now saving\n", i);
+
+ for (pm = spotify_pending_metadata; pm; pm = next)
+ {
+ spotify_track_save(0, pm->track, NULL, time(NULL));
+
+ next = pm->next;
+ free(pm);
+ }
+
+ spotify_pending_metadata = NULL;
+
+ return COMMAND_END;
+}
+
+
+/*--------------------- HELPERS FOR SPOTIFY WEB API -------------------------*/
+/* All the below is in the httpd thread */
+
+static char *
+jparse_str_from_obj(json_object *haystack, const char *key)
+{
+ json_object *needle;
+
+ if (json_object_object_get_ex(haystack, key, &needle) && json_object_get_type(needle) == json_type_string)
+ return strdup(json_object_get_string(needle));
+ else
+ return NULL;
+}
+
+static char *
+jparse_str_from_str(const char *s, const char *key)
+{
+ json_object *haystack;
+ char *val;
+
+ haystack = json_tokener_parse(s);
+ if (!haystack)
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "JSON parser returned an error\n");
+ return NULL;
+ }
+
+ val = jparse_str_from_obj(haystack, key);
+
+#ifdef HAVE_JSON_C_OLD
+ json_object_put(haystack);
+#else
+ if (json_object_put(haystack) != 1)
+ DPRINTF(E_LOG, L_SPOTIFY, "Memleak: JSON parser did not free object\n");
+#endif
+
+ return val;
+}
+
+// Will find all track Spotify uri's and register them with libspotify.
+// Returns the number of tracks found in the json input. "total" will be the
+// total reported by Spotify in the response, and "next" will be an allocated
+// string with the url of the next page, as reported by Spotify
+static int
+jparse_and_register_tracks(int *total, char **next, const char *s)
+{
+ json_object *haystack;
+ json_object *needle;
+ json_object *items;
+ json_object *item;
+ json_object *track;
+ char *uri;
+ int ret;
+ int len;
+ int i;
+
+ haystack = json_tokener_parse(s);
+ if (!haystack)
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "JSON parser returned an error\n");
+ return -1;
+ }
+
+ if (json_object_object_get_ex(haystack, "total", &needle) && json_object_get_type(needle) == json_type_int)
+ *total = json_object_get_int(needle);
+ else
+ *total = -1;
+
+ *next = jparse_str_from_obj(haystack, "next");
+
+ if (! (json_object_object_get_ex(haystack, "items", &items) && json_object_get_type(items) == json_type_array) )
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "No items in reply from Spotify. See:\n%s\n", s);
+ ret = -1;
+ goto out_free_json;
+ }
+
+ len = json_object_array_length(items);
+
+ DPRINTF(E_DBG, L_SPOTIFY, "Got %d saved tracks\n", len);
+
+ for (i = 0; i < len; i++)
+ {
+ item = json_object_array_get_idx(items, i);
+ if (! (item && json_object_object_get_ex(item, "track", &track)
+ && (uri = jparse_str_from_obj(track, "uri")) ))
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "Unexpected JSON: Item %d did not have 'track'->'uri'\n", i);
+ len--;
+ continue;
+ }
+
+ commands_exec_sync(cmdbase, spotify_uri_register, NULL, uri);
+
+ free(uri);
+ }
+
+ ret = len;
+
+ out_free_json:
+#ifdef HAVE_JSON_C_OLD
+ json_object_put(haystack);
+#else
+ if (json_object_put(haystack) != 1)
+ DPRINTF(E_LOG, L_SPOTIFY, "Memleak: JSON parser did not free object\n");
+#endif
+
+ return ret;
+}
+
+// Will find all track Spotify uri's among the saved albums. The tracks will be
+// registered with libspotify. Returns the number of albums found in the json
+// input. "total" will be the total reported by Spotify in the response, and
+// "next" will be an allocated string with the url of the next page
+static int
+jparse_and_register_albums(int *total, char **next, const char *s)
+{
+ json_object *haystack;
+ json_object *needle;
+ json_object *album_items;
+ json_object *album_item;
+ json_object *album;
+ json_object *tracks;
+ json_object *track_items;
+ json_object *track_item;
+ char *uri;
+ int ret;
+ int len;
+ int i;
+ int ntracks;
+ int n;
+
+ haystack = json_tokener_parse(s);
+ if (!haystack)
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "JSON parser returned an error\n");
+ return -1;
+ }
+
+ if (json_object_object_get_ex(haystack, "total", &needle) && json_object_get_type(needle) == json_type_int)
+ *total = json_object_get_int(needle);
+ else
+ *total = -1;
+
+ *next = jparse_str_from_obj(haystack, "next");
+
+ if (! (json_object_object_get_ex(haystack, "items", &album_items) && json_object_get_type(album_items) == json_type_array) )
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "No albums in reply from Spotify. See:\n%s\n", s);
+ ret = -1;
+ goto out_free_json;
+ }
+
+ len = json_object_array_length(album_items);
+
+ DPRINTF(E_DBG, L_SPOTIFY, "Got %d saved albums\n", len);
+
+ for (i = 0; i < len; i++)
+ {
+ album_item = json_object_array_get_idx(album_items, i);
+ if (! (album_item && json_object_object_get_ex(album_item, "album", &album)
+ && json_object_object_get_ex(album, "tracks", &tracks)
+ && json_object_object_get_ex(tracks, "items", &track_items)
+ && (json_object_get_type(track_items) == json_type_array) ))
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "Unexpected JSON: Album %d did not have the 'tracks'->'items' array\n", i);
+ len--;
+ continue;
+ }
+
+ ntracks = json_object_array_length(track_items);
+ for (n = 0; n < ntracks; n++)
+ {
+ track_item = json_object_array_get_idx(track_items, n);
+ if (! (uri = jparse_str_from_obj(track_item, "uri")) )
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "Unexpected JSON: Item %d did not have the 'uri' element\n", n);
+ continue;
+ }
+
+ commands_exec_sync(cmdbase, spotify_uri_register, NULL, uri);
+ free(uri);
+ }
+ }
+
+ ret = len;
+
+ out_free_json:
+#ifdef HAVE_JSON_C_OLD
+ json_object_put(haystack);
+#else
+ if (json_object_put(haystack) != 1)
+ DPRINTF(E_LOG, L_SPOTIFY, "Memleak: JSON parser did not free object\n");
+#endif
+
+ return ret;
+}
+
+static int
+tokens_get(const char *code, const char *redirect_uri, const char **err)
+{
+ struct http_client_ctx ctx;
+ struct keyval kv;
+ char *param;
+ char *body;
+ int ret;
+
+ memset(&kv, 0, sizeof(struct keyval));
+ ret = ( (keyval_add(&kv, "grant_type", "authorization_code") == 0) &&
+ (keyval_add(&kv, "code", code) == 0) &&
+ (keyval_add(&kv, "client_id", spotify_client_id) == 0) &&
+ (keyval_add(&kv, "client_secret", spotify_client_secret) == 0) &&
+ (keyval_add(&kv, "redirect_uri", redirect_uri) == 0) );
+ if (!ret)
+ {
+ *err = "Add parameters to keyval failed";
+ ret = -1;
+ goto out_clear_kv;
+ }
+
+ param = http_form_urlencode(&kv);
+ if (!param)
+ {
+ *err = "http_form_uriencode() failed";
+ ret = -1;
+ goto out_clear_kv;
+ }
+
+ memset(&ctx, 0, sizeof(struct http_client_ctx));
+ ctx.url = (char *)spotify_token_uri;
+ ctx.output_body = param;
+ ctx.input_body = evbuffer_new();
+
+ ret = http_client_request(&ctx);
+ if (ret < 0)
+ {
+ *err = "Did not get a reply from Spotify";
+ goto out_free_input_body;
+ }
+
+ // 0-terminate for safety
+ evbuffer_add(ctx.input_body, "", 1);
+
+ body = (char *)evbuffer_pullup(ctx.input_body, -1);
+ if (!body || (strlen(body) == 0))
+ {
+ *err = "The reply from Spotify is empty or invalid";
+ ret = -1;
+ goto out_free_input_body;
+ }
+
+ spotify_access_token = jparse_str_from_str(body, "access_token");
+ spotify_refresh_token = jparse_str_from_str(body, "refresh_token");
+
+ if (!spotify_access_token || !spotify_refresh_token)
+ {
+ DPRINTF(E_LOG, L_SPOTIFY, "Could not find token in reply: %s\n", body);
+
+ *err = "Could not find token in Spotify reply (see log)";
+ ret = -1;
+ goto out_free_input_body;
+ }
+
+ ret = 0;
+
+ out_free_input_body:
+ evbuffer_free(ctx.input_body);
+ free(param);
+ out_clear_kv:
+ keyval_clear(&kv);
+
+ return ret;
+}
+
+static int
+saved_music_get(int *total, const char **err, const char *uri)
+{
+ struct http_client_ctx ctx;
+ struct keyval kv;
+ char bearer_token[1024];
+ char *body;
+ char *next;
+ int ret;
+ int i;
+
+ *total = -1;
+
+ snprintf(bearer_token, sizeof(bearer_token), "Bearer %s", spotify_access_token);
+
+ memset(&kv, 0, sizeof(struct keyval));
+ if (keyval_add(&kv, "Authorization", bearer_token) < 0)
+ {
+ *err = "Add bearer_token to keyval failed";
+ return -1;
+ }
+
+ memset(&ctx, 0, sizeof(struct http_client_ctx));
+ ctx.output_headers = &kv;
+ ctx.input_body = evbuffer_new();
+ ctx.url = uri;
+
+ next = NULL;
+ for (i = 0; i < SPOTIFY_WEB_REQUESTS_MAX; i++)
+ {
+ ret = http_client_request(&ctx);
+ if (ret < 0)
+ {
+ *err = "Request for saved tracks/albums failed";
+ break;
+ }
+
+ // 0-terminate for safety
+ evbuffer_add(ctx.input_body, "", 1);
+
+ body = (char *)evbuffer_pullup(ctx.input_body, -1);
+ if (!body || (strlen(body) == 0))
+ {
+ *err = "Request for saved tracks/albums failed, response was empty";
+ ret = -1;
+ break;
+ }
+
+ if (next)
+ free(next);
+ next = NULL;
+
+ if (uri == spotify_tracks_uri)
+ ret = jparse_and_register_tracks(total, &next, body);
+ else if (uri == spotify_albums_uri)
+ ret = jparse_and_register_albums(total, &next, body);
+ else
+ ret = -1;
+
+ if (ret < 0)
+ {
+ *err = "Could not parse track/album response from Spotify";
+ break;
+ }
+
+ ret += 50 * i; // Equals total number of tracks/albums registered
+
+ if (!next || (strncmp(next, "null", 4) == 0))
+ break;
+
+ ctx.url = next;
+
+ evbuffer_drain(ctx.input_body, evbuffer_get_length(ctx.input_body));
+ }
+
+ if (next)
+ free(next);
+
+ evbuffer_free(ctx.input_body);
+ keyval_clear(&kv);
+
+ return ret;
+}
+
/* -------------------------- PLAYLIST CALLBACKS ------------------------- */
/**
@@ -952,7 +1547,7 @@ playback_setup(void *arg, int *retval)
*retval = -1;
return COMMAND_END;
}
-
+
err = fptr_sp_session_player_load(g_sess, track);
if (SP_ERROR_OK != err)
{
@@ -1503,6 +2098,8 @@ notify_main_thread(sp_session *sess)
static void metadata_updated(sp_session *session)
{
DPRINTF(E_DBG, L_SPOTIFY, "Session metadata updated\n");
+
+ commands_exec_async(cmdbase, spotify_pending_process, NULL);
}
/* Misc connection error callbacks */
@@ -1761,106 +2358,89 @@ spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h)
return ret;
}
-static int
-spotify_file_read(char *path, char **username, char **password)
+/* Thread: httpd */
+void
+spotify_oauth_interface(struct evbuffer *evbuf, const char *redirect_uri)
{
- FILE *fp;
- char *u;
- char *p;
- char buf[256];
- int len;
+ struct keyval kv;
+ char *param;
+ int ret;
- fp = fopen(path, "rb");
- if (!fp)
+ memset(&kv, 0, sizeof(struct keyval));
+ ret = ( (keyval_add(&kv, "client_id", spotify_client_id) == 0) &&
+ (keyval_add(&kv, "response_type", "code") == 0) &&
+ (keyval_add(&kv, "redirect_uri", redirect_uri) == 0) &&
+ (keyval_add(&kv, "scope", "playlist-read-private user-library-read") == 0) &&
+ (keyval_add(&kv, "show_dialog", "false") == 0) );
+ if (!ret)
{
- DPRINTF(E_LOG, L_SPOTIFY, "Could not open Spotify credentials file %s: %s\n", path, strerror(errno));
- return -1;
+ DPRINTF(E_LOG, L_SPOTIFY, "Cannot display Spotify oath interface (error adding parameters to keyval)\n");
+ goto out_clear_kv;
}
- u = fgets(buf, sizeof(buf), fp);
- if (!u)
+ param = http_form_urlencode(&kv);
+ if (!param)
{
- DPRINTF(E_LOG, L_SPOTIFY, "Empty Spotify credentials file %s\n", path);
-
- fclose(fp);
- return -1;
+ DPRINTF(E_LOG, L_SPOTIFY, "Cannot display Spotify oath interface (http_form_uriencode() failed)\n");
+ goto out_clear_kv;
}
- len = strlen(u);
- if (buf[len - 1] != '\n')
- {
- DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify credentials file %s: username name too long or missing password\n", path);
+ evbuffer_add_printf(evbuf, "Click here to authorize forked-daapd with Spotify\n", spotify_auth_uri, param);
- fclose(fp);
- return -1;
+ free(param);
+
+ out_clear_kv:
+ keyval_clear(&kv);
+}
+
+/* Thread: httpd */
+void
+spotify_oauth_callback(struct evbuffer *evbuf, struct evkeyvalq *param, const char *redirect_uri)
+{
+ const char *code;
+ const char *err;
+ int total;
+ int ret;
+
+ code = evhttp_find_header(param, "code");
+ if (!code)
+ {
+ evbuffer_add_printf(evbuf, "Error: Didn't receive a code from Spotify\n");
+ return;
}
- while (len)
+ DPRINTF(E_DBG, L_SPOTIFY, "Received OAuth code: %s\n", code);
+
+ evbuffer_add_printf(evbuf, "Requesting access token from Spotify...\n");
+
+ ret = tokens_get(code, redirect_uri, &err);
+ if (ret < 0)
{
- if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
- {
- buf[len - 1] = '\0';
- len--;
- }
- else
- break;
+ evbuffer_add_printf(evbuf, "failed
\nError: %s
\n", err);
+ return;
}
- if (!len)
- {
- DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify credentials file %s: empty line where username expected\n", path);
+ evbuffer_add_printf(evbuf, "ok
\nRetrieving saved tracks...\n");
- fclose(fp);
- return -1;
+ ret = saved_music_get(&total, &err, spotify_tracks_uri);
+ if (ret < 0)
+ {
+ evbuffer_add_printf(evbuf, "failed
\nError: %s
\n", err);
+ return;
}
- u = strdup(buf);
- if (!u)
- {
- DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for username while reading %s\n", path);
+ evbuffer_add_printf(evbuf, "ok, got %d out of %d tracks\nRetrieving saved albums...\n", ret, total);
- fclose(fp);
- return -1;
+ ret = saved_music_get(&total, &err, spotify_albums_uri);
+ if (ret < 0)
+ {
+ evbuffer_add_printf(evbuf, "failed
\nError: %s
\n", err);
+ return;
}
- p = fgets(buf, sizeof(buf), fp);
- fclose(fp);
- if (!p)
- {
- DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify credentials file %s: no password\n", path);
+ evbuffer_add_printf(evbuf, "ok, got %d out of %d albums\n", ret, total);
- free(u);
- return -1;
- }
-
- len = strlen(p);
-
- while (len)
- {
- if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
- {
- buf[len - 1] = '\0';
- len--;
- }
- else
- break;
- }
-
- p = strdup(buf);
- if (!p)
- {
- DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for password while reading %s\n", path);
-
- free(u);
- return -1;
- }
-
- DPRINTF(E_LOG, L_SPOTIFY, "Spotify credentials file OK, logging in with username %s\n", u);
-
- *username = u;
- *password = p;
-
- return 0;
+ return;
}
/* Thread: filescanner */
diff --git a/src/spotify.h b/src/spotify.h
index eb457a47..faa6dfb8 100644
--- a/src/spotify.h
+++ b/src/spotify.h
@@ -5,6 +5,7 @@
#include "db.h"
#include
#include
+#include
int
spotify_playback_setup(struct media_file_info *mfi);
@@ -33,6 +34,12 @@ spotify_audio_get(struct evbuffer *evbuf, int wanted);
int
spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h);
+void
+spotify_oauth_interface(struct evbuffer *evbuf, const char *redirect_uri);
+
+void
+spotify_oauth_callback(struct evbuffer *evbuf, struct evkeyvalq *param, const char *redirect_uri);
+
void
spotify_login(char *path);