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

\n

Error: %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

\n

Retrieving saved tracks...\n"); - fclose(fp); - return -1; + ret = saved_music_get(&total, &err, spotify_tracks_uri); + if (ret < 0) + { + evbuffer_add_printf(evbuf, "failed

\n

Error: %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

\n

Retrieving 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

\n

Error: %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);