[spotify] Make web api client thread safe

Protect http_session with a lock and don't use mutating globals in utility
functions where it is less clear if they are protected.
This commit is contained in:
ejurgensen 2023-02-06 21:43:10 +01:00
parent c8e46aad42
commit eee011180f

View File

@ -120,6 +120,7 @@ struct spotify_playlist
// Credentials for the web api // Credentials for the web api
struct spotify_credentials struct spotify_credentials
{ {
pthread_mutex_t lock;
char *access_token; char *access_token;
char *refresh_token; char *refresh_token;
char *granted_scope; char *granted_scope;
@ -130,10 +131,14 @@ struct spotify_credentials
time_t token_time_requested; time_t token_time_requested;
}; };
static struct spotify_credentials spotify_credentials; struct spotify_http_session
{
pthread_mutex_t lock;
struct http_client_session session;
};
// Mutex to avoid conflicting requests for access tokens and protects accessing the credentials from different threads static struct spotify_http_session spotify_http_session = { .lock = PTHREAD_MUTEX_INITIALIZER };
static pthread_mutex_t token_lck; static struct spotify_credentials spotify_credentials = { .lock = PTHREAD_MUTEX_INITIALIZER };
// The base playlist id for all Spotify playlists in the db // The base playlist id for all Spotify playlists in the db
@ -163,7 +168,6 @@ static const char *spotify_shows_uri = "https://api.spotify.com/v1/me/
static const char *spotify_shows_episodes_uri = "https://api.spotify.com/v1/shows/%s/episodes"; static const char *spotify_shows_episodes_uri = "https://api.spotify.com/v1/shows/%s/episodes";
static const char *spotify_episode_uri = "https://api.spotify.com/v1/episodes/%s"; static const char *spotify_episode_uri = "https://api.spotify.com/v1/episodes/%s";
static struct http_client_session session = { 0 };
static enum spotify_item_type static enum spotify_item_type
parse_type_from_uri(const char *uri) parse_type_from_uri(const char *uri)
@ -198,15 +202,18 @@ parse_type_from_uri(const char *uri)
} }
static void static void
free_credentials(void) credentials_clear(struct spotify_credentials *credentials)
{ {
free(spotify_credentials.access_token); if (!credentials)
free(spotify_credentials.refresh_token); return;
free(spotify_credentials.granted_scope);
free(spotify_credentials.user_country);
free(spotify_credentials.user);
memset(&spotify_credentials, 0, sizeof(struct spotify_credentials)); free(credentials->access_token);
free(credentials->refresh_token);
free(credentials->granted_scope);
free(credentials->user_country);
free(credentials->user);
memset(credentials, 0, sizeof(struct spotify_credentials));
} }
static void static void
@ -226,13 +233,13 @@ free_http_client_ctx(struct http_client_ctx *ctx)
} }
static bool static bool
token_valid(void) token_valid(struct spotify_credentials *credentials)
{ {
return spotify_credentials.access_token != NULL; return (credentials->access_token != NULL);
} }
static int static int
request_access_tokens(struct keyval *kv, const char **err) request_access_tokens(struct spotify_credentials *credentials, struct keyval *kv, const char **err)
{ {
struct http_client_ctx ctx; struct http_client_ctx ctx;
char *param; char *param;
@ -282,34 +289,34 @@ request_access_tokens(struct keyval *kv, const char **err)
goto out_free_input_body; goto out_free_input_body;
} }
free(spotify_credentials.access_token); free(credentials->access_token);
spotify_credentials.access_token = NULL; credentials->access_token = NULL;
tmp = jparse_str_from_obj(haystack, "access_token"); tmp = jparse_str_from_obj(haystack, "access_token");
if (tmp) if (tmp)
spotify_credentials.access_token = strdup(tmp); credentials->access_token = strdup(tmp);
tmp = jparse_str_from_obj(haystack, "refresh_token"); tmp = jparse_str_from_obj(haystack, "refresh_token");
if (tmp) if (tmp)
{ {
free(spotify_credentials.refresh_token); free(credentials->refresh_token);
spotify_credentials.refresh_token = strdup(tmp); credentials->refresh_token = strdup(tmp);
} }
tmp = jparse_str_from_obj(haystack, "scope"); tmp = jparse_str_from_obj(haystack, "scope");
if (tmp) if (tmp)
{ {
free(spotify_credentials.granted_scope); free(credentials->granted_scope);
spotify_credentials.granted_scope = strdup(tmp); credentials->granted_scope = strdup(tmp);
} }
spotify_credentials.token_expires_in = jparse_int_from_obj(haystack, "expires_in"); credentials->token_expires_in = jparse_int_from_obj(haystack, "expires_in");
if (spotify_credentials.token_expires_in == 0) if (credentials->token_expires_in == 0)
spotify_credentials.token_expires_in = 3600; credentials->token_expires_in = 3600;
jparse_free(haystack); jparse_free(haystack);
if (!spotify_credentials.access_token) if (!credentials->access_token)
{ {
DPRINTF(E_LOG, L_SPOTIFY, "Could not find access token in reply: %s\n", body); DPRINTF(E_LOG, L_SPOTIFY, "Could not find access token in reply: %s\n", body);
@ -318,10 +325,10 @@ request_access_tokens(struct keyval *kv, const char **err)
goto out_free_input_body; goto out_free_input_body;
} }
spotify_credentials.token_time_requested = time(NULL); credentials->token_time_requested = time(NULL);
if (spotify_credentials.refresh_token) if (credentials->refresh_token)
db_admin_set(DB_ADMIN_SPOTIFY_REFRESH_TOKEN, spotify_credentials.refresh_token); db_admin_set(DB_ADMIN_SPOTIFY_REFRESH_TOKEN, credentials->refresh_token);
ret = 0; ret = 0;
@ -341,7 +348,7 @@ request_access_tokens(struct keyval *kv, const char **err)
* @return Response as JSON object or NULL * @return Response as JSON object or NULL
*/ */
static json_object * static json_object *
request_endpoint(const char *uri) request_endpoint(const char *uri, const char *access_token)
{ {
struct http_client_ctx *ctx; struct http_client_ctx *ctx;
char bearer_token[1024]; char bearer_token[1024];
@ -355,7 +362,7 @@ request_endpoint(const char *uri)
ctx->url = uri; ctx->url = uri;
snprintf(bearer_token, sizeof(bearer_token), "Bearer %s", spotify_credentials.access_token); snprintf(bearer_token, sizeof(bearer_token), "Bearer %s", access_token);
if (keyval_add(ctx->output_headers, "Authorization", bearer_token) < 0) if (keyval_add(ctx->output_headers, "Authorization", bearer_token) < 0)
{ {
DPRINTF(E_LOG, L_SPOTIFY, "Add bearer_token to keyval failed for request '%s'\n", uri); DPRINTF(E_LOG, L_SPOTIFY, "Add bearer_token to keyval failed for request '%s'\n", uri);
@ -364,7 +371,9 @@ request_endpoint(const char *uri)
DPRINTF(E_DBG, L_SPOTIFY, "Request Spotify API endpoint: '%s')\n", uri); DPRINTF(E_DBG, L_SPOTIFY, "Request Spotify API endpoint: '%s')\n", uri);
ret = http_client_request(ctx, &session); CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&spotify_http_session.lock));
ret = http_client_request(ctx, &spotify_http_session.session);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&spotify_http_session.lock));
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_SPOTIFY, "Request for '%s' failed\n", uri); DPRINTF(E_LOG, L_SPOTIFY, "Request for '%s' failed\n", uri);
@ -401,25 +410,25 @@ request_endpoint(const char *uri)
* API endpoint: https://api.spotify.com/v1/me * API endpoint: https://api.spotify.com/v1/me
*/ */
static int static int
request_user_info(void) request_user_info(struct spotify_credentials *credentials)
{ {
json_object *response; json_object *response;
free(spotify_credentials.user_country); free(credentials->user_country);
spotify_credentials.user_country = NULL; credentials->user_country = NULL;
free(spotify_credentials.user); free(credentials->user);
spotify_credentials.user = NULL; credentials->user = NULL;
response = request_endpoint(spotify_me_uri); response = request_endpoint(spotify_me_uri, credentials->access_token);
if (response) if (response)
{ {
spotify_credentials.user = safe_strdup(jparse_str_from_obj(response, "id")); credentials->user = safe_strdup(jparse_str_from_obj(response, "id"));
spotify_credentials.user_country = safe_strdup(jparse_str_from_obj(response, "country")); credentials->user_country = safe_strdup(jparse_str_from_obj(response, "country"));
jparse_free(response); jparse_free(response);
DPRINTF(E_DBG, L_SPOTIFY, "User '%s', country '%s'\n", spotify_credentials.user, spotify_credentials.user_country); DPRINTF(E_DBG, L_SPOTIFY, "User '%s', country '%s'\n", credentials->user, credentials->user_country);
} }
return 0; return 0;
@ -431,15 +440,12 @@ request_user_info(void)
* @return 0 on success, -1 on failure * @return 0 on success, -1 on failure
*/ */
static int static int
token_get(const char *code, const char *redirect_uri, const char **err) token_get(struct spotify_credentials *credentials, const char *code, const char *redirect_uri, const char **err)
{ {
struct keyval kv; struct keyval kv = { 0 };
int ret; int ret;
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&token_lck));
*err = ""; *err = "";
memset(&kv, 0, sizeof(struct keyval));
ret = ( (keyval_add(&kv, "grant_type", "authorization_code") == 0) && ret = ( (keyval_add(&kv, "grant_type", "authorization_code") == 0) &&
(keyval_add(&kv, "code", code) == 0) && (keyval_add(&kv, "code", code) == 0) &&
(keyval_add(&kv, "client_id", spotify_client_id) == 0) && (keyval_add(&kv, "client_id", spotify_client_id) == 0) &&
@ -452,14 +458,12 @@ token_get(const char *code, const char *redirect_uri, const char **err)
ret = -1; ret = -1;
} }
else else
ret = request_access_tokens(&kv, err); ret = request_access_tokens(credentials, &kv, err);
keyval_clear(&kv); keyval_clear(&kv);
if (ret == 0) if (ret == 0)
request_user_info(); request_user_info(credentials);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&token_lck));
return ret; return ret;
} }
@ -474,22 +478,16 @@ token_get(const char *code, const char *redirect_uri, const char **err)
* @return 0 on success, -1 on failure * @return 0 on success, -1 on failure
*/ */
static int static int
token_refresh(void) token_refresh(struct spotify_credentials *credentials)
{ {
struct keyval kv; struct keyval kv = { 0 };
char *refresh_token = NULL; char *refresh_token = NULL;
const char *err; const char *err;
int ret; int ret;
memset(&kv, 0, sizeof(struct keyval)); if (credentials->token_time_requested && difftime(time(NULL), credentials->token_time_requested) < credentials->token_expires_in)
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&token_lck));
if (spotify_credentials.token_time_requested && difftime(time(NULL), spotify_credentials.token_time_requested) < spotify_credentials.token_expires_in)
{ {
DPRINTF(E_DBG, L_SPOTIFY, "Spotify token still valid\n"); DPRINTF(E_DBG, L_SPOTIFY, "Spotify token still valid\n");
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&token_lck));
return 0; return 0;
} }
@ -512,24 +510,19 @@ token_refresh(void)
goto error; goto error;
} }
ret = request_access_tokens(&kv, &err); ret = request_access_tokens(credentials, &kv, &err);
if (ret == 0) if (ret == 0)
request_user_info(); request_user_info(credentials);
free(refresh_token); free(refresh_token);
keyval_clear(&kv); keyval_clear(&kv);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&token_lck));
return ret; return ret;
error: error:
free(refresh_token); free(refresh_token);
keyval_clear(&kv); keyval_clear(&kv);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&token_lck));
return -1; return -1;
} }
@ -541,22 +534,23 @@ token_refresh(void)
* is checked and if necessary a token refresh request is issued before * is checked and if necessary a token refresh request is issued before
* requesting the given endpoint. * requesting the given endpoint.
* *
* @param credentials Updated credentials
* @param href The spotify endpoint uri * @param href The spotify endpoint uri
* @return Response as JSON object or NULL * @return Response as JSON object or NULL
*/ */
static json_object * static json_object *
request_endpoint_with_token_refresh(const char *href) request_endpoint_with_token_refresh(struct spotify_credentials *credentials, const char *href)
{ {
if (0 > token_refresh()) if (0 > token_refresh(credentials))
{ {
return NULL; return NULL;
} }
return request_endpoint(href); return request_endpoint(href, credentials->access_token);
} }
typedef int (*paging_request_cb)(void *arg); typedef int (*paging_request_cb)(void *arg);
typedef int (*paging_item_cb)(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg); typedef int (*paging_item_cb)(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg, struct spotify_credentials *);
/* /*
* Request the spotify endpoint at 'href' * Request the spotify endpoint at 'href'
@ -586,7 +580,8 @@ typedef int (*paging_item_cb)(json_object *item, int index, int total, enum spot
* @return 0 on success, -1 on failure * @return 0 on success, -1 on failure
*/ */
static int static int
request_pagingobject_endpoint(const char *href, paging_item_cb item_cb, paging_request_cb pre_request_cb, paging_request_cb post_request_cb, bool with_market, enum spotify_request_type request_type, void *arg) request_pagingobject_endpoint(const char *href, paging_item_cb item_cb, paging_request_cb pre_request_cb, paging_request_cb post_request_cb,
bool with_market, struct spotify_credentials *credentials, enum spotify_request_type request_type, void *arg)
{ {
char *next_href; char *next_href;
json_object *response; json_object *response;
@ -598,16 +593,16 @@ request_pagingobject_endpoint(const char *href, paging_item_cb item_cb, paging_r
int total; int total;
int ret; int ret;
if (!with_market || !spotify_credentials.user_country) if (!with_market || !credentials->user_country)
{ {
next_href = safe_strdup(href); next_href = safe_strdup(href);
} }
else else
{ {
if (strchr(href, '?')) if (strchr(href, '?'))
next_href = safe_asprintf("%s&market=%s", href, spotify_credentials.user_country); next_href = safe_asprintf("%s&market=%s", href, credentials->user_country);
else else
next_href = safe_asprintf("%s?market=%s", href, spotify_credentials.user_country); next_href = safe_asprintf("%s?market=%s", href, credentials->user_country);
} }
while (next_href) while (next_href)
@ -615,7 +610,7 @@ request_pagingobject_endpoint(const char *href, paging_item_cb item_cb, paging_r
if (pre_request_cb) if (pre_request_cb)
pre_request_cb(arg); pre_request_cb(arg);
response = request_endpoint_with_token_refresh(next_href); response = request_endpoint_with_token_refresh(credentials, next_href);
if (!response) if (!response)
{ {
@ -647,7 +642,7 @@ request_pagingobject_endpoint(const char *href, paging_item_cb item_cb, paging_r
continue; continue;
} }
ret = item_cb(item, (i + offset), total, request_type, arg); ret = item_cb(item, (i + offset), total, request_type, arg, credentials);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_SPOTIFY, "Unexpected JSON: error processing item at index %d '%s' (API endpoint: '%s')\n", DPRINTF(E_LOG, L_SPOTIFY, "Unexpected JSON: error processing item at index %d '%s' (API endpoint: '%s')\n",
@ -1030,26 +1025,26 @@ get_episode_endpoint_uri(const char *uri)
} }
static json_object * static json_object *
request_track(const char *path) request_track(const char *path, struct spotify_credentials *credentials)
{ {
char *endpoint_uri; char *endpoint_uri;
json_object *response; json_object *response;
endpoint_uri = get_track_endpoint_uri(path); endpoint_uri = get_track_endpoint_uri(path);
response = request_endpoint_with_token_refresh(endpoint_uri); response = request_endpoint_with_token_refresh(credentials, endpoint_uri);
free(endpoint_uri); free(endpoint_uri);
return response; return response;
} }
static json_object * static json_object *
request_episode(const char *path) request_episode(const char *path, struct spotify_credentials *credentials)
{ {
char *endpoint_uri; char *endpoint_uri;
json_object *response; json_object *response;
endpoint_uri = get_episode_endpoint_uri(path); endpoint_uri = get_episode_endpoint_uri(path);
response = request_endpoint_with_token_refresh(endpoint_uri); response = request_endpoint_with_token_refresh(credentials, endpoint_uri);
free(endpoint_uri); free(endpoint_uri);
return response; return response;
@ -1059,14 +1054,13 @@ request_episode(const char *path)
char * char *
spotifywebapi_oauth_uri_get(const char *redirect_uri) spotifywebapi_oauth_uri_get(const char *redirect_uri)
{ {
struct keyval kv; struct keyval kv = { 0 };
char *param; char *param;
char *uri; char *uri;
int uri_len; int uri_len;
int ret; int ret;
uri = NULL; uri = NULL;
memset(&kv, 0, sizeof(struct keyval));
ret = ( (keyval_add(&kv, "client_id", spotify_client_id) == 0) && ret = ( (keyval_add(&kv, "client_id", spotify_client_id) == 0) &&
(keyval_add(&kv, "response_type", "code") == 0) && (keyval_add(&kv, "response_type", "code") == 0) &&
(keyval_add(&kv, "redirect_uri", redirect_uri) == 0) && (keyval_add(&kv, "redirect_uri", redirect_uri) == 0) &&
@ -1114,13 +1108,17 @@ spotifywebapi_oauth_callback(struct evkeyvalq *param, const char *redirect_uri,
DPRINTF(E_DBG, L_SPOTIFY, "Received OAuth code: %s\n", code); DPRINTF(E_DBG, L_SPOTIFY, "Received OAuth code: %s\n", code);
ret = token_get(code, redirect_uri, errmsg); CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&spotify_credentials.lock));
ret = token_get(&spotify_credentials, code, redirect_uri, errmsg);
if (ret < 0) if (ret < 0)
return -1; goto error;
ret = spotify_login_token(spotify_credentials.user, spotify_credentials.access_token, errmsg); ret = spotify_login_token(spotify_credentials.user, spotify_credentials.access_token, errmsg);
if (ret < 0) if (ret < 0)
return -1; goto error;
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&spotify_credentials.lock));
// Trigger scan after successful access to spotifywebapi // Trigger scan after successful access to spotifywebapi
spotifywebapi_fullrescan(); spotifywebapi_fullrescan();
@ -1128,6 +1126,10 @@ spotifywebapi_oauth_callback(struct evkeyvalq *param, const char *redirect_uri,
listener_notify(LISTENER_SPOTIFY); listener_notify(LISTENER_SPOTIFY);
return 0; return 0;
error:
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&spotify_credentials.lock));
return -1;
} }
static int static int
@ -1182,7 +1184,7 @@ map_track_to_queueitem(struct db_queue_item *item, const struct spotify_track *t
} }
static int static int
queue_add_track(const char *uri, int position, char reshuffle, uint32_t item_id, int *count, int *new_item_id) queue_add_track(int *count, int *new_item_id, const char *uri, int position, char reshuffle, uint32_t item_id, struct spotify_credentials *credentials)
{ {
json_object *response = NULL; json_object *response = NULL;
struct spotify_track track; struct spotify_track track;
@ -1190,7 +1192,7 @@ queue_add_track(const char *uri, int position, char reshuffle, uint32_t item_id,
struct db_queue_add_info queue_add_info; struct db_queue_add_info queue_add_info;
int ret; int ret;
response = request_track(uri); response = request_track(uri, credentials);
if (!response) if (!response)
goto error; goto error;
@ -1230,7 +1232,7 @@ struct queue_add_album_param {
}; };
static int static int
queue_add_album_tracks(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg) queue_add_album_tracks(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg, struct spotify_credentials *credentials)
{ {
struct queue_add_album_param *param; struct queue_add_album_param *param;
struct spotify_track track; struct spotify_track track;
@ -1257,7 +1259,7 @@ queue_add_album_tracks(json_object *item, int index, int total, enum spotify_req
} }
static int static int
queue_add_album(const char *uri, int position, char reshuffle, uint32_t item_id, int *count, int *new_item_id) queue_add_album(int *count, int *new_item_id, const char *uri, int position, char reshuffle, uint32_t item_id, struct spotify_credentials *credentials)
{ {
char *album_endpoint_uri = NULL; char *album_endpoint_uri = NULL;
char *endpoint_uri = NULL; char *endpoint_uri = NULL;
@ -1266,7 +1268,7 @@ queue_add_album(const char *uri, int position, char reshuffle, uint32_t item_id,
int ret; int ret;
album_endpoint_uri = get_album_endpoint_uri(uri); album_endpoint_uri = get_album_endpoint_uri(uri);
json_album = request_endpoint_with_token_refresh(album_endpoint_uri); json_album = request_endpoint_with_token_refresh(credentials, album_endpoint_uri);
parse_metadata_album(json_album, &param.album, ART_DEFAULT_WIDTH); parse_metadata_album(json_album, &param.album, ART_DEFAULT_WIDTH);
ret = db_queue_add_start(&param.queue_add_info, position); ret = db_queue_add_start(&param.queue_add_info, position);
@ -1275,7 +1277,7 @@ queue_add_album(const char *uri, int position, char reshuffle, uint32_t item_id,
endpoint_uri = get_album_tracks_endpoint_uri(uri); endpoint_uri = get_album_tracks_endpoint_uri(uri);
ret = request_pagingobject_endpoint(endpoint_uri, queue_add_album_tracks, NULL, NULL, true, SPOTIFY_REQUEST_TYPE_DEFAULT, &param); ret = request_pagingobject_endpoint(endpoint_uri, queue_add_album_tracks, NULL, NULL, true, credentials, SPOTIFY_REQUEST_TYPE_DEFAULT, &param);
ret = db_queue_add_end(&param.queue_add_info, reshuffle, item_id, ret); ret = db_queue_add_end(&param.queue_add_info, reshuffle, item_id, ret);
if (ret < 0) if (ret < 0)
@ -1293,7 +1295,7 @@ queue_add_album(const char *uri, int position, char reshuffle, uint32_t item_id,
} }
static int static int
queue_add_albums(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg) queue_add_albums(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg, struct spotify_credentials *credentials)
{ {
struct db_queue_add_info *param; struct db_queue_add_info *param;
struct queue_add_album_param param_add_album; struct queue_add_album_param param_add_album;
@ -1306,7 +1308,7 @@ queue_add_albums(json_object *item, int index, int total, enum spotify_request_t
parse_metadata_album(item, &param_add_album.album, ART_DEFAULT_WIDTH); parse_metadata_album(item, &param_add_album.album, ART_DEFAULT_WIDTH);
endpoint_uri = get_album_tracks_endpoint_uri(param_add_album.album.uri); endpoint_uri = get_album_tracks_endpoint_uri(param_add_album.album.uri);
ret = request_pagingobject_endpoint(endpoint_uri, queue_add_album_tracks, NULL, NULL, true, SPOTIFY_REQUEST_TYPE_DEFAULT, &param_add_album); ret = request_pagingobject_endpoint(endpoint_uri, queue_add_album_tracks, NULL, NULL, true, credentials, SPOTIFY_REQUEST_TYPE_DEFAULT, &param_add_album);
*param = param_add_album.queue_add_info; *param = param_add_album.queue_add_info;
@ -1316,7 +1318,7 @@ queue_add_albums(json_object *item, int index, int total, enum spotify_request_t
} }
static int static int
queue_add_artist(const char *uri, int position, char reshuffle, uint32_t item_id, int *count, int *new_item_id) queue_add_artist(int *count, int *new_item_id, const char *uri, int position, char reshuffle, uint32_t item_id, struct spotify_credentials *credentials)
{ {
struct db_queue_add_info queue_add_info; struct db_queue_add_info queue_add_info;
char *endpoint_uri = NULL; char *endpoint_uri = NULL;
@ -1327,7 +1329,7 @@ queue_add_artist(const char *uri, int position, char reshuffle, uint32_t item_id
goto out; goto out;
endpoint_uri = get_artist_albums_endpoint_uri(uri); endpoint_uri = get_artist_albums_endpoint_uri(uri);
ret = request_pagingobject_endpoint(endpoint_uri, queue_add_albums, NULL, NULL, true, SPOTIFY_REQUEST_TYPE_DEFAULT, &queue_add_info); ret = request_pagingobject_endpoint(endpoint_uri, queue_add_albums, NULL, NULL, true, credentials, SPOTIFY_REQUEST_TYPE_DEFAULT, &queue_add_info);
ret = db_queue_add_end(&queue_add_info, reshuffle, item_id, ret); ret = db_queue_add_end(&queue_add_info, reshuffle, item_id, ret);
if (ret < 0) if (ret < 0)
@ -1342,7 +1344,7 @@ queue_add_artist(const char *uri, int position, char reshuffle, uint32_t item_id
} }
static int static int
queue_add_playlist_tracks(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg) queue_add_playlist_tracks(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg, struct spotify_credentials *credentials)
{ {
struct db_queue_add_info *queue_add_info; struct db_queue_add_info *queue_add_info;
struct spotify_track track; struct spotify_track track;
@ -1378,7 +1380,7 @@ queue_add_playlist_tracks(json_object *item, int index, int total, enum spotify_
} }
static int static int
queue_add_playlist(const char *uri, int position, char reshuffle, uint32_t item_id, int *count, int *new_item_id) queue_add_playlist(int *count, int *new_item_id, const char *uri, int position, char reshuffle, uint32_t item_id, struct spotify_credentials *credentials)
{ {
char *endpoint_uri = NULL; char *endpoint_uri = NULL;
struct db_queue_add_info queue_add_info; struct db_queue_add_info queue_add_info;
@ -1390,7 +1392,7 @@ queue_add_playlist(const char *uri, int position, char reshuffle, uint32_t item_
endpoint_uri = get_playlist_tracks_endpoint_uri(uri); endpoint_uri = get_playlist_tracks_endpoint_uri(uri);
ret = request_pagingobject_endpoint(endpoint_uri, queue_add_playlist_tracks, NULL, NULL, true, SPOTIFY_REQUEST_TYPE_DEFAULT, &queue_add_info); ret = request_pagingobject_endpoint(endpoint_uri, queue_add_playlist_tracks, NULL, NULL, true, credentials, SPOTIFY_REQUEST_TYPE_DEFAULT, &queue_add_info);
ret = db_queue_add_end(&queue_add_info, reshuffle, item_id, ret); ret = db_queue_add_end(&queue_add_info, reshuffle, item_id, ret);
if (ret < 0) if (ret < 0)
@ -1405,33 +1407,40 @@ queue_add_playlist(const char *uri, int position, char reshuffle, uint32_t item_
} }
static int static int
queue_item_add(const char *uri, int position, char reshuffle, uint32_t item_id, int *count, int *new_item_id) spotifywebapi_library_queue_item_add(const char *uri, int position, char reshuffle, uint32_t item_id, int *count, int *new_item_id)
{ {
enum spotify_item_type type; enum spotify_item_type type;
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&spotify_credentials.lock));
type = parse_type_from_uri(uri); type = parse_type_from_uri(uri);
if (type == SPOTIFY_ITEM_TYPE_TRACK) if (type == SPOTIFY_ITEM_TYPE_TRACK)
{ {
queue_add_track(uri, position, reshuffle, item_id, count, new_item_id); queue_add_track(count, new_item_id, uri, position, reshuffle, item_id, &spotify_credentials);
return LIBRARY_OK; goto out;
} }
else if (type == SPOTIFY_ITEM_TYPE_ARTIST) else if (type == SPOTIFY_ITEM_TYPE_ARTIST)
{ {
queue_add_artist(uri, position, reshuffle, item_id, count, new_item_id); queue_add_artist(count, new_item_id, uri, position, reshuffle, item_id, &spotify_credentials);
return LIBRARY_OK; goto out;
} }
else if (type == SPOTIFY_ITEM_TYPE_ALBUM) else if (type == SPOTIFY_ITEM_TYPE_ALBUM)
{ {
queue_add_album(uri, position, reshuffle, item_id, count, new_item_id); queue_add_album(count, new_item_id, uri, position, reshuffle, item_id, &spotify_credentials);
return LIBRARY_OK; goto out;
} }
else if (type == SPOTIFY_ITEM_TYPE_PLAYLIST) else if (type == SPOTIFY_ITEM_TYPE_PLAYLIST)
{ {
queue_add_playlist(uri, position, reshuffle, item_id, count, new_item_id); queue_add_playlist(count, new_item_id, uri, position, reshuffle, item_id, &spotify_credentials);
return LIBRARY_OK; goto out;
} }
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&spotify_credentials.lock));
return LIBRARY_PATH_INVALID; return LIBRARY_PATH_INVALID;
out:
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&spotify_credentials.lock));
return LIBRARY_OK;
} }
@ -1602,7 +1611,7 @@ playlist_add_or_update(struct playlist_info *pli)
* Add a saved album to the library * Add a saved album to the library
*/ */
static int static int
saved_album_add(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg) saved_album_add(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg, struct spotify_credentials *credentials)
{ {
json_object *jsonalbum; json_object *jsonalbum;
struct spotify_album album; struct spotify_album album;
@ -1668,11 +1677,11 @@ saved_album_add(json_object *item, int index, int total, enum spotify_request_ty
* Scan users saved albums into the library * Scan users saved albums into the library
*/ */
static int static int
scan_saved_albums(enum spotify_request_type request_type) scan_saved_albums(enum spotify_request_type request_type, struct spotify_credentials *credentials)
{ {
int ret; int ret;
ret = request_pagingobject_endpoint(spotify_albums_uri, saved_album_add, NULL, NULL, true, request_type, NULL); ret = request_pagingobject_endpoint(spotify_albums_uri, saved_album_add, NULL, NULL, true, credentials, request_type, NULL);
return ret; return ret;
} }
@ -1681,7 +1690,7 @@ scan_saved_albums(enum spotify_request_type request_type)
* Add a saved podcast show to the library * Add a saved podcast show to the library
*/ */
static int static int
saved_episodes_add(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg) saved_episodes_add(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg, struct spotify_credentials *credentials)
{ {
struct spotify_album *show = arg; struct spotify_album *show = arg;
struct spotify_track episode; struct spotify_track episode;
@ -1704,7 +1713,7 @@ saved_episodes_add(json_object *item, int index, int total, enum spotify_request
* Add a saved podcast show to the library * Add a saved podcast show to the library
*/ */
static int static int
saved_show_add(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg) saved_show_add(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg, struct spotify_credentials *credentials)
{ {
json_object *jsonshow; json_object *jsonshow;
struct spotify_album show; struct spotify_album show;
@ -1726,7 +1735,7 @@ saved_show_add(json_object *item, int index, int total, enum spotify_request_typ
// Now map the show episodes and insert/update them in the files database // Now map the show episodes and insert/update them in the files database
endpoint_uri = safe_asprintf(spotify_shows_episodes_uri, show.id); endpoint_uri = safe_asprintf(spotify_shows_episodes_uri, show.id);
request_pagingobject_endpoint(endpoint_uri, saved_episodes_add, transaction_start, transaction_end, true, request_type, &show); request_pagingobject_endpoint(endpoint_uri, saved_episodes_add, transaction_start, transaction_end, true, credentials, request_type, &show);
free(endpoint_uri); free(endpoint_uri);
if ((index + 1) >= total || ((index + 1) % 10 == 0)) if ((index + 1) >= total || ((index + 1) % 10 == 0))
@ -1741,11 +1750,11 @@ saved_show_add(json_object *item, int index, int total, enum spotify_request_typ
* Scan users saved podcast shows into the library * Scan users saved podcast shows into the library
*/ */
static int static int
scan_saved_shows(enum spotify_request_type request_type) scan_saved_shows(enum spotify_request_type request_type, struct spotify_credentials *credentials)
{ {
int ret; int ret;
ret = request_pagingobject_endpoint(spotify_shows_uri, saved_show_add, NULL, NULL, true, request_type, NULL); ret = request_pagingobject_endpoint(spotify_shows_uri, saved_show_add, NULL, NULL, true, credentials, request_type, NULL);
return ret; return ret;
} }
@ -1755,7 +1764,7 @@ scan_saved_shows(enum spotify_request_type request_type)
* Add a saved playlist tracks to the library * Add a saved playlist tracks to the library
*/ */
static int static int
saved_playlist_tracks_add(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg) saved_playlist_tracks_add(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg, struct spotify_credentials *credentials)
{ {
struct spotify_track track; struct spotify_track track;
struct spotify_album album; struct spotify_album album;
@ -1803,11 +1812,11 @@ saved_playlist_tracks_add(json_object *item, int index, int total, enum spotify_
/* Thread: library */ /* Thread: library */
static int static int
scan_playlist_tracks(const char *playlist_tracks_endpoint_uri, struct playlist_info *pli, enum spotify_request_type request_type) scan_playlist_tracks(const char *playlist_tracks_endpoint_uri, struct playlist_info *pli, enum spotify_request_type request_type, struct spotify_credentials *credentials)
{ {
int ret; int ret;
ret = request_pagingobject_endpoint(playlist_tracks_endpoint_uri, saved_playlist_tracks_add, transaction_start, transaction_end, true, request_type, pli); ret = request_pagingobject_endpoint(playlist_tracks_endpoint_uri, saved_playlist_tracks_add, transaction_start, transaction_end, true, credentials, request_type, pli);
return ret; return ret;
} }
@ -1835,7 +1844,7 @@ map_playlist_to_pli(struct playlist_info *pli, struct spotify_playlist *playlist
* Add a saved playlist to the library * Add a saved playlist to the library
*/ */
static int static int
saved_playlist_add(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg) saved_playlist_add(json_object *item, int index, int total, enum spotify_request_type request_type, void *arg, struct spotify_credentials *credentials)
{ {
struct spotify_playlist playlist; struct spotify_playlist playlist;
struct playlist_info pli; struct playlist_info pli;
@ -1858,7 +1867,7 @@ saved_playlist_add(json_object *item, int index, int total, enum spotify_request
pli.id = pl_id; pli.id = pl_id;
if (pl_id > 0) if (pl_id > 0)
scan_playlist_tracks(playlist.tracks_href, &pli, request_type); scan_playlist_tracks(playlist.tracks_href, &pli, request_type, credentials);
else else
DPRINTF(E_LOG, L_SPOTIFY, "Error adding playlist: '%s' (%s) \n", playlist.name, playlist.uri); DPRINTF(E_LOG, L_SPOTIFY, "Error adding playlist: '%s' (%s) \n", playlist.name, playlist.uri);
@ -1875,11 +1884,11 @@ saved_playlist_add(json_object *item, int index, int total, enum spotify_request
* Scan users saved playlists into the library * Scan users saved playlists into the library
*/ */
static int static int
scan_playlists(enum spotify_request_type request_type) scan_playlists(enum spotify_request_type request_type, struct spotify_credentials *credentials)
{ {
int ret; int ret;
ret = request_pagingobject_endpoint(spotify_playlists_uri, saved_playlist_add, NULL, NULL, false, request_type, NULL); ret = request_pagingobject_endpoint(spotify_playlists_uri, saved_playlist_add, NULL, NULL, false, credentials, request_type, NULL);
return ret; return ret;
} }
@ -1924,9 +1933,12 @@ scan(enum spotify_request_type request_type)
time_t start; time_t start;
time_t end; time_t end;
if (!token_valid() || scanning) CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&spotify_credentials.lock));
if (!token_valid(&spotify_credentials) || scanning)
{ {
DPRINTF(E_DBG, L_SPOTIFY, "No valid web api token or scan already in progress, rescan ignored\n"); DPRINTF(E_DBG, L_SPOTIFY, "No valid web api token or scan already in progress, rescan ignored\n");
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&spotify_credentials.lock));
return; return;
} }
@ -1935,26 +1947,31 @@ scan(enum spotify_request_type request_type)
db_directory_enable_bypath("/spotify:"); db_directory_enable_bypath("/spotify:");
create_base_playlist(); create_base_playlist();
scan_saved_albums(request_type);
scan_playlists(request_type); scan_saved_albums(request_type, &spotify_credentials);
scan_playlists(request_type, &spotify_credentials);
spotify_status_get(&sp_status); spotify_status_get(&sp_status);
if (sp_status.has_podcast_support) if (sp_status.has_podcast_support)
scan_saved_shows(request_type); scan_saved_shows(request_type, &spotify_credentials);
scanning = false; scanning = false;
end = time(NULL); end = time(NULL);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&spotify_credentials.lock));
DPRINTF(E_LOG, L_SPOTIFY, "Spotify scan completed in %.f sec\n", difftime(end, start)); DPRINTF(E_LOG, L_SPOTIFY, "Spotify scan completed in %.f sec\n", difftime(end, start));
} }
/* Thread: library */ /* Thread: library */
static int static int
initscan(void) spotifywebapi_library_initscan(void)
{ {
int ret; int ret;
/* Refresh access token for the spotify webapi */ /* Refresh access token for the spotify webapi */
ret = token_refresh(); CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&spotify_credentials.lock));
ret = token_refresh(&spotify_credentials);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&spotify_credentials.lock));
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_SPOTIFY, "Spotify webapi token refresh failed. " DPRINTF(E_LOG, L_SPOTIFY, "Spotify webapi token refresh failed. "
@ -1991,7 +2008,7 @@ initscan(void)
/* Thread: library */ /* Thread: library */
static int static int
rescan(void) spotifywebapi_library_rescan(void)
{ {
scan(SPOTIFY_REQUEST_TYPE_RESCAN); scan(SPOTIFY_REQUEST_TYPE_RESCAN);
return 0; return 0;
@ -1999,7 +2016,7 @@ rescan(void)
/* Thread: library */ /* Thread: library */
static int static int
metarescan(void) spotifywebapi_library_metarescan(void)
{ {
scan(SPOTIFY_REQUEST_TYPE_METARESCAN); scan(SPOTIFY_REQUEST_TYPE_METARESCAN);
return 0; return 0;
@ -2007,7 +2024,7 @@ metarescan(void)
/* Thread: library */ /* Thread: library */
static int static int
fullrescan(void) spotifywebapi_library_fullrescan(void)
{ {
db_spotify_purge(); db_spotify_purge();
scan(SPOTIFY_REQUEST_TYPE_RESCAN); scan(SPOTIFY_REQUEST_TYPE_RESCAN);
@ -2018,7 +2035,7 @@ fullrescan(void)
static enum command_state static enum command_state
webapi_fullrescan(void *arg, int *ret) webapi_fullrescan(void *arg, int *ret)
{ {
*ret = fullrescan(); *ret = spotifywebapi_library_fullrescan();
return COMMAND_END; return COMMAND_END;
} }
@ -2026,7 +2043,7 @@ webapi_fullrescan(void *arg, int *ret)
static enum command_state static enum command_state
webapi_rescan(void *arg, int *ret) webapi_rescan(void *arg, int *ret)
{ {
*ret = rescan(); *ret = spotifywebapi_library_rescan();
return COMMAND_END; return COMMAND_END;
} }
@ -2034,7 +2051,9 @@ webapi_rescan(void *arg, int *ret)
static enum command_state static enum command_state
webapi_purge(void *arg, int *ret) webapi_purge(void *arg, int *ret)
{ {
free_credentials(); CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&spotify_credentials.lock));
credentials_clear(&spotify_credentials);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&spotify_credentials.lock));
db_spotify_purge(); db_spotify_purge();
db_admin_delete(DB_ADMIN_SPOTIFY_REFRESH_TOKEN); db_admin_delete(DB_ADMIN_SPOTIFY_REFRESH_TOKEN);
@ -2072,13 +2091,17 @@ spotifywebapi_artwork_url_get(const char *uri, int max_w, int max_h)
type = parse_type_from_uri(uri); type = parse_type_from_uri(uri);
if (type == SPOTIFY_ITEM_TYPE_TRACK) if (type == SPOTIFY_ITEM_TYPE_TRACK)
{ {
response = request_track(uri); CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&spotify_credentials.lock));
response = request_track(uri, &spotify_credentials);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&spotify_credentials.lock));
if (response) if (response)
parse_metadata_track(response, &track, max_w); parse_metadata_track(response, &track, max_w);
} }
else if (type == SPOTIFY_ITEM_TYPE_EPISODE) else if (type == SPOTIFY_ITEM_TYPE_EPISODE)
{ {
response = request_episode(uri); CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&spotify_credentials.lock));
response = request_episode(uri, &spotify_credentials);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&spotify_credentials.lock));
if (response) if (response)
parse_metadata_episode(response, &track, max_w); parse_metadata_episode(response, &track, max_w);
} }
@ -2106,9 +2129,9 @@ spotifywebapi_status_info_get(struct spotifywebapi_status_info *info)
{ {
memset(info, 0, sizeof(struct spotifywebapi_status_info)); memset(info, 0, sizeof(struct spotifywebapi_status_info));
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&token_lck)); CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&spotify_credentials.lock));
info->token_valid = token_valid(); info->token_valid = token_valid(&spotify_credentials);
if (spotify_credentials.user) if (spotify_credentials.user)
{ {
strncpy(info->user, spotify_credentials.user, (sizeof(info->user) - 1)); strncpy(info->user, spotify_credentials.user, (sizeof(info->user) - 1));
@ -2126,17 +2149,16 @@ spotifywebapi_status_info_get(struct spotifywebapi_status_info *info)
strncpy(info->required_scope, spotify_scope, (sizeof(info->required_scope) - 1)); strncpy(info->required_scope, spotify_scope, (sizeof(info->required_scope) - 1));
} }
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&token_lck)); CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&spotify_credentials.lock));
} }
void void
spotifywebapi_access_token_get(struct spotifywebapi_access_token *info) spotifywebapi_access_token_get(struct spotifywebapi_access_token *info)
{ {
token_refresh();
memset(info, 0, sizeof(struct spotifywebapi_access_token)); memset(info, 0, sizeof(struct spotifywebapi_access_token));
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&token_lck)); CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&spotify_credentials.lock));
token_refresh(&spotify_credentials);
if (spotify_credentials.token_time_requested > 0) if (spotify_credentials.token_time_requested > 0)
info->expires_in = spotify_credentials.token_expires_in - difftime(time(NULL), spotify_credentials.token_time_requested); info->expires_in = spotify_credentials.token_expires_in - difftime(time(NULL), spotify_credentials.token_time_requested);
@ -2145,44 +2167,42 @@ spotifywebapi_access_token_get(struct spotifywebapi_access_token *info)
info->token = safe_strdup(spotify_credentials.access_token); info->token = safe_strdup(spotify_credentials.access_token);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&token_lck)); CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&spotify_credentials.lock));
} }
static int static int
spotifywebapi_init() spotifywebapi_library_init()
{ {
int ret; int ret;
CHECK_ERR(L_SPOTIFY, mutex_init(&token_lck));
ret = spotify_init(); ret = spotify_init();
if (ret < 0) if (ret < 0)
return -1; return -1;
http_client_session_init(&session); http_client_session_init(&spotify_http_session.session);
return 0; return 0;
} }
static void static void
spotifywebapi_deinit() spotifywebapi_library_deinit()
{ {
CHECK_ERR(L_SPOTIFY, pthread_mutex_destroy(&token_lck));
spotify_deinit(); spotify_deinit();
http_client_session_deinit(&session);
free_credentials(); http_client_session_deinit(&spotify_http_session.session);
credentials_clear(&spotify_credentials);
} }
struct library_source spotifyscanner = struct library_source spotifyscanner =
{ {
.scan_kind = SCAN_KIND_SPOTIFY, .scan_kind = SCAN_KIND_SPOTIFY,
.disabled = 0, .disabled = 0,
.init = spotifywebapi_init, .init = spotifywebapi_library_init,
.deinit = spotifywebapi_deinit, .deinit = spotifywebapi_library_deinit,
.rescan = rescan, .rescan = spotifywebapi_library_rescan,
.metarescan = metarescan, .metarescan = spotifywebapi_library_metarescan,
.initscan = initscan, .initscan = spotifywebapi_library_initscan,
.fullrescan = fullrescan, .fullrescan = spotifywebapi_library_fullrescan,
.queue_item_add = queue_item_add, .queue_item_add = spotifywebapi_library_queue_item_add,
}; };