2016-12-31 10:46:59 -05:00
/*
* Copyright ( C ) 2016 Espen Jürgensen < espenjurgensen @ gmail . com >
* Copyright ( C ) 2016 Christian Meffert < christian . meffert @ googlemail . com >
*
* 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 . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include "spotify_webapi.h"
# include <event2/event.h>
2017-01-13 12:28:36 -05:00
# include <json.h>
2016-12-31 10:46:59 -05:00
# include <stddef.h>
# include <stdio.h>
# include <string.h>
# include <time.h>
2018-03-02 13:24:35 -05:00
# include "cache.h"
# include "conffile.h"
2016-12-31 10:46:59 -05:00
# include "db.h"
# include "http.h"
# include "library.h"
2018-03-02 13:24:35 -05:00
# include "listener.h"
2016-12-31 10:46:59 -05:00
# include "logger.h"
2017-08-27 02:44:00 -04:00
# include "misc_json.h"
2018-03-02 13:24:35 -05:00
# include "spotify.h"
2016-12-31 10:46:59 -05:00
2018-03-02 13:24:35 -05:00
struct spotify_album
{
const char * added_at ;
time_t mtime ;
const char * album_type ;
bool is_compilation ;
const char * artist ;
const char * genre ;
const char * id ;
const char * label ;
const char * name ;
const char * release_date ;
const char * release_date_precision ;
int release_year ;
const char * uri ;
} ;
struct spotify_track
{
const char * added_at ;
time_t mtime ;
const char * album ;
const char * album_artist ;
const char * artist ;
int disc_number ;
const char * album_type ;
bool is_compilation ;
int duration_ms ;
const char * id ;
const char * name ;
int track_number ;
const char * uri ;
bool is_playable ;
const char * restrictions ;
const char * linked_from_uri ;
} ;
struct spotify_playlist
{
const char * id ;
const char * name ;
const char * owner ;
const char * uri ;
const char * href ;
const char * tracks_href ;
int tracks_count ;
} ;
struct spotify_request
{
struct http_client_ctx * ctx ;
char * response_body ;
json_object * haystack ;
json_object * items ;
int count ;
int total ;
const char * next_uri ;
int index ;
} ;
2016-12-31 10:46:59 -05:00
// Credentials for the web api
static char * spotify_access_token ;
static char * spotify_refresh_token ;
2017-02-05 07:56:12 -05:00
static char * spotify_user_country ;
2017-08-19 02:36:43 -04:00
static char * spotify_user ;
2016-12-31 10:46:59 -05:00
2018-03-02 13:24:35 -05:00
2016-12-31 10:46:59 -05:00
static int32_t expires_in = 3600 ;
static time_t token_requested = 0 ;
2018-03-02 13:24:35 -05:00
static pthread_mutex_t token_lck ;
// The base playlist id for all Spotify playlists in the db
static int spotify_base_plid ;
// The base playlist id for Spotify saved tracks in the db
static int spotify_saved_plid ;
// Flag to avoid triggering playlist change events while the (re)scan is running
static bool scanning ;
2016-12-31 10:46:59 -05:00
// Endpoints and credentials for the web api
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 " ;
2017-01-05 16:09:19 -05:00
static const char * spotify_playlist_uri = " https://api.spotify.com/v1/users/%s/playlists/%s " ;
2017-02-05 07:56:12 -05:00
static const char * spotify_me_uri = " https://api.spotify.com/v1/me " ;
2018-03-02 13:24:35 -05:00
static const char * spotify_albums_uri = " https://api.spotify.com/v1/me/albums?limit=50 " ;
static const char * spotify_playlists_uri = " https://api.spotify.com/v1/me/playlists?limit=50 " ;
2016-12-31 10:46:59 -05:00
2018-03-02 13:24:35 -05:00
static int
spotifywebapi_token_get ( const char * code , const char * redirect_uri , char * * user , const char * * err ) ;
static int
spotifywebapi_token_refresh ( char * * user ) ;
static enum command_state
webapi_fullrescan ( void * arg , int * ret ) ;
static bool
token_valid ( void )
{
return spotify_access_token ! = NULL ;
}
2016-12-31 10:46:59 -05:00
/*--------------------- HELPERS FOR SPOTIFY WEB API -------------------------*/
/* All the below is in the httpd thread */
static void
2017-01-20 10:31:33 -05:00
free_http_client_ctx ( struct http_client_ctx * ctx )
2016-12-31 10:46:59 -05:00
{
if ( ! ctx )
return ;
if ( ctx - > input_body )
evbuffer_free ( ctx - > input_body ) ;
if ( ctx - > output_headers )
{
keyval_clear ( ctx - > output_headers ) ;
free ( ctx - > output_headers ) ;
}
free ( ctx ) ;
}
2017-01-06 11:08:30 -05:00
static int
2017-12-27 12:51:11 -05:00
request_uri ( struct spotify_request * request , const char * uri )
2016-12-31 10:46:59 -05:00
{
char bearer_token [ 1024 ] ;
int ret ;
memset ( request , 0 , sizeof ( struct spotify_request ) ) ;
2017-12-27 12:51:11 -05:00
if ( 0 > spotifywebapi_token_refresh ( NULL ) )
2016-12-31 10:46:59 -05:00
{
return - 1 ;
}
request - > ctx = calloc ( 1 , sizeof ( struct http_client_ctx ) ) ;
request - > ctx - > output_headers = calloc ( 1 , sizeof ( struct keyval ) ) ;
request - > ctx - > input_body = evbuffer_new ( ) ;
request - > ctx - > url = uri ;
snprintf ( bearer_token , sizeof ( bearer_token ) , " Bearer %s " , spotify_access_token ) ;
if ( keyval_add ( request - > ctx - > output_headers , " Authorization " , bearer_token ) < 0 )
{
2017-01-01 05:06:00 -05:00
DPRINTF ( E_LOG , L_SPOTIFY , " Add bearer_token to keyval failed \n " ) ;
2016-12-31 10:46:59 -05:00
return - 1 ;
}
ret = http_client_request ( request - > ctx ) ;
if ( ret < 0 )
{
2017-01-01 05:06:00 -05:00
DPRINTF ( E_LOG , L_SPOTIFY , " Request for saved tracks/albums failed \n " ) ;
2016-12-31 10:46:59 -05:00
return - 1 ;
}
// 0-terminate for safety
evbuffer_add ( request - > ctx - > input_body , " " , 1 ) ;
request - > response_body = ( char * ) evbuffer_pullup ( request - > ctx - > input_body , - 1 ) ;
if ( ! request - > response_body | | ( strlen ( request - > response_body ) = = 0 ) )
{
2017-01-01 05:06:00 -05:00
DPRINTF ( E_LOG , L_SPOTIFY , " Request for saved tracks/albums failed, response was empty \n " ) ;
2016-12-31 10:46:59 -05:00
return - 1 ;
}
2017-01-07 01:22:40 -05:00
// DPRINTF(E_DBG, L_SPOTIFY, "Wep api response for '%s'\n%s\n", uri, request->response_body);
2017-01-01 05:06:00 -05:00
2016-12-31 10:46:59 -05:00
request - > haystack = json_tokener_parse ( request - > response_body ) ;
if ( ! request - > haystack )
{
DPRINTF ( E_LOG , L_SPOTIFY , " JSON parser returned an error \n " ) ;
return - 1 ;
}
DPRINTF ( E_DBG , L_SPOTIFY , " Got response for '%s' \n " , uri ) ;
return 0 ;
}
2018-03-02 13:24:35 -05:00
static void
2016-12-31 10:46:59 -05:00
spotifywebapi_request_end ( struct spotify_request * request )
{
2017-01-20 10:31:33 -05:00
free_http_client_ctx ( request - > ctx ) ;
2016-12-31 10:46:59 -05:00
jparse_free ( request - > haystack ) ;
}
2018-03-02 13:24:35 -05:00
static int
2017-02-05 07:56:12 -05:00
spotifywebapi_request_next ( struct spotify_request * request , const char * uri , bool append_market )
2016-12-31 10:46:59 -05:00
{
char * next_uri ;
int ret ;
if ( request - > ctx & & ! request - > next_uri )
{
// Reached end of paging requests, terminate loop
return - 1 ;
}
if ( ! request - > ctx )
{
// First paging request
2017-02-05 07:56:12 -05:00
if ( append_market & & spotify_user_country )
{
if ( strchr ( uri , ' ? ' ) )
2017-05-05 14:00:51 -04:00
next_uri = safe_asprintf ( " %s&market=%s " , uri , spotify_user_country ) ;
2017-02-05 07:56:12 -05:00
else
2017-05-05 14:00:51 -04:00
next_uri = safe_asprintf ( " %s?market=%s " , uri , spotify_user_country ) ;
2017-02-05 07:56:12 -05:00
}
else
next_uri = strdup ( uri ) ;
2016-12-31 10:46:59 -05:00
}
else
{
// Next paging request
next_uri = strdup ( request - > next_uri ) ;
spotifywebapi_request_end ( request ) ;
}
2017-12-27 12:51:11 -05:00
ret = request_uri ( request , next_uri ) ;
2016-12-31 10:46:59 -05:00
free ( next_uri ) ;
if ( ret < 0 )
return ret ;
request - > total = jparse_int_from_obj ( request - > haystack , " total " ) ;
2017-01-20 10:31:33 -05:00
request - > next_uri = jparse_str_from_obj ( request - > haystack , " next " ) ;
2016-12-31 10:46:59 -05:00
if ( jparse_array_from_obj ( request - > haystack , " items " , & request - > items ) < 0 )
{
DPRINTF ( E_LOG , L_SPOTIFY , " No items in reply from Spotify. See: \n %s \n " , request - > response_body ) ;
return - 1 ;
}
request - > count = json_object_array_length ( request - > items ) ;
DPRINTF ( E_DBG , L_SPOTIFY , " Got %d items \n " , request - > count ) ;
return 0 ;
}
2017-01-13 12:28:36 -05:00
static void
parse_metadata_track ( json_object * jsontrack , struct spotify_track * track )
2016-12-31 10:46:59 -05:00
{
json_object * jsonalbum ;
json_object * jsonartists ;
2017-02-05 07:56:12 -05:00
json_object * needle ;
2016-12-31 10:46:59 -05:00
if ( json_object_object_get_ex ( jsontrack , " album " , & jsonalbum ) )
{
track - > album = jparse_str_from_obj ( jsonalbum , " name " ) ;
2017-01-10 16:29:13 -05:00
if ( json_object_object_get_ex ( jsonalbum , " artists " , & jsonartists ) )
2016-12-31 10:46:59 -05:00
{
2017-01-10 16:29:13 -05:00
track - > album_artist = jparse_str_from_array ( jsonartists , 0 , " name " ) ;
2016-12-31 10:46:59 -05:00
}
}
2017-01-10 16:29:13 -05:00
if ( json_object_object_get_ex ( jsontrack , " artists " , & jsonartists ) )
2016-12-31 10:46:59 -05:00
{
2017-01-10 16:29:13 -05:00
track - > artist = jparse_str_from_array ( jsonartists , 0 , " name " ) ;
2016-12-31 10:46:59 -05:00
}
track - > disc_number = jparse_int_from_obj ( jsontrack , " disc_number " ) ;
2017-01-01 05:06:00 -05:00
track - > album_type = jparse_str_from_obj ( jsonalbum , " album_type " ) ;
track - > is_compilation = ( track - > album_type & & 0 = = strcmp ( track - > album_type , " compilation " ) ) ;
2016-12-31 10:46:59 -05:00
track - > duration_ms = jparse_int_from_obj ( jsontrack , " duration_ms " ) ;
track - > name = jparse_str_from_obj ( jsontrack , " name " ) ;
track - > track_number = jparse_int_from_obj ( jsontrack , " track_number " ) ;
track - > uri = jparse_str_from_obj ( jsontrack , " uri " ) ;
track - > id = jparse_str_from_obj ( jsontrack , " id " ) ;
2017-02-05 07:56:12 -05:00
// "is_playable" is only returned for a request with a market parameter, default to true if it is not in the response
if ( json_object_object_get_ex ( jsontrack , " is_playable " , NULL ) )
{
track - > is_playable = jparse_bool_from_obj ( jsontrack , " is_playable " ) ;
if ( json_object_object_get_ex ( jsontrack , " restrictions " , & needle ) )
track - > restrictions = json_object_to_json_string ( needle ) ;
if ( json_object_object_get_ex ( jsontrack , " linked_from " , & needle ) )
track - > linked_from_uri = jparse_str_from_obj ( needle , " uri " ) ;
}
else
track - > is_playable = true ;
2016-12-31 10:46:59 -05:00
}
2017-01-07 01:22:40 -05:00
static int
2017-01-13 12:28:36 -05:00
get_year_from_date ( const char * date )
2017-01-07 01:22:40 -05:00
{
char tmp [ 5 ] ;
uint32_t year = 0 ;
2017-01-13 12:28:36 -05:00
if ( date & & strlen ( date ) > = 4 )
2017-01-07 01:22:40 -05:00
{
2017-01-13 12:28:36 -05:00
strncpy ( tmp , date , sizeof ( tmp ) ) ;
2017-01-07 01:22:40 -05:00
tmp [ 4 ] = ' \0 ' ;
safe_atou32 ( tmp , & year ) ;
}
return year ;
}
2017-01-13 12:28:36 -05:00
static void
parse_metadata_album ( json_object * jsonalbum , struct spotify_album * album )
2016-12-31 10:46:59 -05:00
{
json_object * jsonartists ;
2017-01-10 16:29:13 -05:00
if ( json_object_object_get_ex ( jsonalbum , " artists " , & jsonartists ) )
2016-12-31 10:46:59 -05:00
{
2017-01-10 16:29:13 -05:00
album - > artist = jparse_str_from_array ( jsonartists , 0 , " name " ) ;
2016-12-31 10:46:59 -05:00
}
album - > name = jparse_str_from_obj ( jsonalbum , " name " ) ;
album - > uri = jparse_str_from_obj ( jsonalbum , " uri " ) ;
album - > id = jparse_str_from_obj ( jsonalbum , " id " ) ;
album - > album_type = jparse_str_from_obj ( jsonalbum , " album_type " ) ;
2017-01-01 05:06:00 -05:00
album - > is_compilation = ( album - > album_type & & 0 = = strcmp ( album - > album_type , " compilation " ) ) ;
2016-12-31 10:46:59 -05:00
album - > label = jparse_str_from_obj ( jsonalbum , " label " ) ;
2017-01-07 01:22:40 -05:00
2016-12-31 10:46:59 -05:00
album - > release_date = jparse_str_from_obj ( jsonalbum , " release_date " ) ;
2017-01-07 01:22:40 -05:00
album - > release_date_precision = jparse_str_from_obj ( jsonalbum , " release_date_precision " ) ;
2017-01-13 12:28:36 -05:00
album - > release_year = get_year_from_date ( album - > release_date ) ;
2017-01-01 05:06:00 -05:00
// TODO Genre is an array of strings ('genres'), but it is always empty (https://github.com/spotify/web-api/issues/157)
//album->genre = jparse_str_from_obj(jsonalbum, "genre");
2016-12-31 10:46:59 -05:00
}
2018-03-02 13:24:35 -05:00
static int
2016-12-31 10:46:59 -05:00
spotifywebapi_saved_albums_fetch ( struct spotify_request * request , json_object * * jsontracks , int * track_count , struct spotify_album * album )
{
json_object * jsonalbum ;
json_object * item ;
json_object * needle ;
memset ( album , 0 , sizeof ( struct spotify_album ) ) ;
* track_count = 0 ;
if ( request - > index > = request - > count )
{
return - 1 ;
}
item = json_object_array_get_idx ( request - > items , request - > index ) ;
if ( ! ( item & & json_object_object_get_ex ( item , " album " , & jsonalbum ) ) )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Unexpected JSON: Item %d did not have 'album'->'uri' \n " , request - > index ) ;
request - > index + + ;
return - 1 ;
}
2017-01-13 12:28:36 -05:00
parse_metadata_album ( jsonalbum , album ) ;
2016-12-31 10:46:59 -05:00
album - > added_at = jparse_str_from_obj ( item , " added_at " ) ;
album - > mtime = jparse_time_from_obj ( item , " added_at " ) ;
if ( json_object_object_get_ex ( jsonalbum , " tracks " , & needle ) )
{
if ( jparse_array_from_obj ( needle , " items " , jsontracks ) = = 0 )
{
* track_count = json_object_array_length ( * jsontracks ) ;
}
}
request - > index + + ;
return 0 ;
}
2018-03-02 13:24:35 -05:00
static int
2016-12-31 10:46:59 -05:00
spotifywebapi_album_track_fetch ( json_object * jsontracks , int index , struct spotify_track * track )
{
json_object * jsontrack ;
memset ( track , 0 , sizeof ( struct spotify_track ) ) ;
jsontrack = json_object_array_get_idx ( jsontracks , index ) ;
if ( ! jsontrack )
{
return - 1 ;
}
2017-01-13 12:28:36 -05:00
parse_metadata_track ( jsontrack , track ) ;
2016-12-31 10:46:59 -05:00
return 0 ;
}
2017-01-13 12:28:36 -05:00
static void
parse_metadata_playlist ( json_object * jsonplaylist , struct spotify_playlist * playlist )
2016-12-31 10:46:59 -05:00
{
json_object * needle ;
playlist - > name = jparse_str_from_obj ( jsonplaylist , " name " ) ;
playlist - > uri = jparse_str_from_obj ( jsonplaylist , " uri " ) ;
playlist - > id = jparse_str_from_obj ( jsonplaylist , " id " ) ;
playlist - > href = jparse_str_from_obj ( jsonplaylist , " href " ) ;
if ( json_object_object_get_ex ( jsonplaylist , " owner " , & needle ) )
{
playlist - > owner = jparse_str_from_obj ( needle , " id " ) ;
}
if ( json_object_object_get_ex ( jsonplaylist , " tracks " , & needle ) )
{
playlist - > tracks_href = jparse_str_from_obj ( needle , " href " ) ;
playlist - > tracks_count = jparse_int_from_obj ( needle , " total " ) ;
}
}
2018-03-02 13:24:35 -05:00
static int
2016-12-31 10:46:59 -05:00
spotifywebapi_playlists_fetch ( struct spotify_request * request , struct spotify_playlist * playlist )
{
json_object * jsonplaylist ;
memset ( playlist , 0 , sizeof ( struct spotify_playlist ) ) ;
if ( request - > index > = request - > count )
{
2017-01-05 16:09:19 -05:00
DPRINTF ( E_DBG , L_SPOTIFY , " All playlists processed \n " ) ;
2016-12-31 10:46:59 -05:00
return - 1 ;
}
jsonplaylist = json_object_array_get_idx ( request - > items , request - > index ) ;
2017-01-05 16:09:19 -05:00
if ( ! jsonplaylist )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Error fetching playlist at index '%d' \n " , request - > index ) ;
return - 1 ;
}
2016-12-31 10:46:59 -05:00
2017-01-13 12:28:36 -05:00
parse_metadata_playlist ( jsonplaylist , playlist ) ;
2016-12-31 10:46:59 -05:00
request - > index + + ;
return 0 ;
}
2017-01-05 16:09:19 -05:00
/*
* Extracts the owner and the id from a spotify playlist uri
*
* Playlist - uri has the following format : spotify : user : [ owner ] : playlist : [ id ]
* Owner and plid must be freed by the caller .
*/
static int
get_owner_plid_from_uri ( const char * uri , char * * owner , char * * plid )
{
char * ptr1 ;
char * ptr2 ;
char * tmp ;
size_t len ;
ptr1 = strchr ( uri , ' : ' ) ;
if ( ! ptr1 )
return - 1 ;
ptr1 + + ;
ptr1 = strchr ( ptr1 , ' : ' ) ;
if ( ! ptr1 )
return - 1 ;
ptr1 + + ;
ptr2 = strchr ( ptr1 , ' : ' ) ;
len = ptr2 - ptr1 ;
tmp = malloc ( sizeof ( char ) * ( len + 1 ) ) ;
strncpy ( tmp , ptr1 , len ) ;
tmp [ len ] = ' \0 ' ;
* owner = tmp ;
ptr2 + + ;
ptr1 = strchr ( ptr2 , ' : ' ) ;
if ( ! ptr1 )
{
free ( tmp ) ;
return - 1 ;
}
ptr1 + + ;
* plid = strdup ( ptr1 ) ;
return 0 ;
}
2018-03-02 13:24:35 -05:00
static int
2016-12-31 10:46:59 -05:00
spotifywebapi_playlisttracks_fetch ( struct spotify_request * request , struct spotify_track * track )
{
json_object * item ;
json_object * jsontrack ;
memset ( track , 0 , sizeof ( struct spotify_track ) ) ;
if ( request - > index > = request - > count )
{
return - 1 ;
}
item = json_object_array_get_idx ( request - > items , request - > index ) ;
if ( ! ( item & & json_object_object_get_ex ( item , " track " , & jsontrack ) ) )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Unexpected JSON: Item %d did not have 'track'->'uri' \n " , request - > index ) ;
request - > index + + ;
return - 1 ;
}
2017-01-13 12:28:36 -05:00
parse_metadata_track ( jsontrack , track ) ;
2016-12-31 10:46:59 -05:00
track - > added_at = jparse_str_from_obj ( item , " added_at " ) ;
track - > mtime = jparse_time_from_obj ( item , " added_at " ) ;
request - > index + + ;
return 0 ;
}
2017-01-05 16:09:19 -05:00
2018-03-02 13:24:35 -05:00
static int
2017-01-05 16:09:19 -05:00
spotifywebapi_playlist_start ( struct spotify_request * request , const char * path , struct spotify_playlist * playlist )
{
char uri [ 1024 ] ;
char * owner ;
char * id ;
int ret ;
ret = get_owner_plid_from_uri ( path , & owner , & id ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Error extracting owner and id from playlist uri '%s' \n " , path ) ;
return - 1 ;
}
ret = snprintf ( uri , sizeof ( uri ) , spotify_playlist_uri , owner , id ) ;
if ( ret < 0 | | ret > = sizeof ( uri ) )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Error creating playlist endpoint uri for playlist '%s' \n " , path ) ;
free ( owner ) ;
free ( id ) ;
return - 1 ;
}
2017-12-27 12:51:11 -05:00
ret = request_uri ( request , uri ) ;
2017-01-05 16:09:19 -05:00
if ( ret < 0 )
{
free ( owner ) ;
free ( id ) ;
return - 1 ;
}
request - > haystack = json_tokener_parse ( request - > response_body ) ;
2017-01-13 12:28:36 -05:00
parse_metadata_playlist ( request - > haystack , playlist ) ;
2017-01-05 16:09:19 -05:00
free ( owner ) ;
free ( id ) ;
return 0 ;
}
2017-02-05 07:56:12 -05:00
static int
2017-12-27 12:51:11 -05:00
request_user_info ( )
2017-02-05 07:56:12 -05:00
{
struct spotify_request request ;
int ret ;
free ( spotify_user_country ) ;
spotify_user_country = NULL ;
2017-08-19 02:36:43 -04:00
free ( spotify_user ) ;
spotify_user = NULL ;
2017-02-05 07:56:12 -05:00
2017-12-27 12:51:11 -05:00
ret = request_uri ( & request , spotify_me_uri ) ;
2017-02-05 07:56:12 -05:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Failed to read user country \n " ) ;
}
else
{
2017-08-19 02:36:43 -04:00
spotify_user = safe_strdup ( jparse_str_from_obj ( request . haystack , " id " ) ) ;
2017-02-05 07:56:12 -05:00
spotify_user_country = safe_strdup ( jparse_str_from_obj ( request . haystack , " country " ) ) ;
2017-08-19 02:36:43 -04:00
DPRINTF ( E_DBG , L_SPOTIFY , " User '%s', country '%s' \n " , spotify_user , spotify_user_country ) ;
2017-02-05 07:56:12 -05:00
}
spotifywebapi_request_end ( & request ) ;
return 0 ;
}
char *
spotifywebapi_oauth_uri_get ( const char * redirect_uri )
{
struct keyval kv ;
char * param ;
char * uri ;
int uri_len ;
int ret ;
uri = NULL ;
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 " , " user-read-private playlist-read-private user-library-read " ) = = 0 ) & &
( keyval_add ( & kv , " show_dialog " , " false " ) = = 0 ) ) ;
if ( ! ret )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Cannot display Spotify oath interface (error adding parameters to keyval) \n " ) ;
goto out_clear_kv ;
}
param = http_form_urlencode ( & kv ) ;
if ( param )
{
uri_len = strlen ( spotify_auth_uri ) + strlen ( param ) + 3 ;
uri = calloc ( uri_len , sizeof ( char ) ) ;
snprintf ( uri , uri_len , " %s/?%s " , spotify_auth_uri , param ) ;
free ( param ) ;
}
out_clear_kv :
keyval_clear ( & kv ) ;
return uri ;
}
2018-03-02 13:24:35 -05:00
/* Thread: httpd */
int
spotifywebapi_oauth_callback ( struct evkeyvalq * param , const char * redirect_uri , char * * errmsg )
{
const char * code ;
const char * err ;
char * user = NULL ;
int ret ;
* errmsg = NULL ;
code = evhttp_find_header ( param , " code " ) ;
if ( ! code )
{
* errmsg = safe_asprintf ( " Error: Didn't receive a code from Spotify " ) ;
return - 1 ;
}
DPRINTF ( E_DBG , L_SPOTIFY , " Received OAuth code: %s \n " , code ) ;
ret = spotifywebapi_token_get ( code , redirect_uri , & user , & err ) ;
if ( ret < 0 )
{
* errmsg = safe_asprintf ( " Error: %s " , err ) ;
return - 1 ;
}
// Trigger scan after successful access to spotifywebapi
spotifywebapi_fullrescan ( ) ;
listener_notify ( LISTENER_SPOTIFY ) ;
return 0 ;
}
2017-02-05 07:56:12 -05:00
static int
2017-12-27 12:51:11 -05:00
tokens_get ( struct keyval * kv , const char * * err )
2017-02-05 07:56:12 -05:00
{
struct http_client_ctx ctx ;
char * param ;
char * body ;
json_object * haystack ;
const char * tmp ;
int ret ;
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 ;
}
DPRINTF ( E_DBG , L_SPOTIFY , " Token reply: %s \n " , body ) ;
haystack = json_tokener_parse ( body ) ;
if ( ! haystack )
{
* err = " JSON parser returned an error " ;
ret = - 1 ;
goto out_free_input_body ;
}
free ( spotify_access_token ) ;
spotify_access_token = NULL ;
tmp = jparse_str_from_obj ( haystack , " access_token " ) ;
if ( tmp )
spotify_access_token = strdup ( tmp ) ;
tmp = jparse_str_from_obj ( haystack , " refresh_token " ) ;
if ( tmp )
{
free ( spotify_refresh_token ) ;
spotify_refresh_token = strdup ( tmp ) ;
}
expires_in = jparse_int_from_obj ( haystack , " expires_in " ) ;
if ( expires_in = = 0 )
expires_in = 3600 ;
jparse_free ( haystack ) ;
if ( ! spotify_access_token )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Could not find access token in reply: %s \n " , body ) ;
* err = " Could not find access token in Spotify reply (see log) " ;
ret = - 1 ;
goto out_free_input_body ;
}
token_requested = time ( NULL ) ;
if ( spotify_refresh_token )
2017-11-19 15:14:41 -05:00
db_admin_set ( DB_ADMIN_SPOTIFY_REFRESH_TOKEN , spotify_refresh_token ) ;
2017-02-05 07:56:12 -05:00
2017-12-27 12:51:11 -05:00
request_user_info ( ) ;
2017-02-05 07:56:12 -05:00
ret = 0 ;
out_free_input_body :
evbuffer_free ( ctx . input_body ) ;
free ( param ) ;
out_clear_kv :
return ret ;
}
2018-03-02 13:24:35 -05:00
static int
2017-08-19 02:36:43 -04:00
spotifywebapi_token_get ( const char * code , const char * redirect_uri , char * * user , const char * * err )
2017-02-05 07:56:12 -05:00
{
struct keyval kv ;
int ret ;
* err = " " ;
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 ;
}
else
2017-12-27 12:51:11 -05:00
ret = tokens_get ( & kv , err ) ;
2017-02-05 07:56:12 -05:00
2017-12-27 12:51:11 -05:00
if ( user & & ret = = 0 )
{
* user = safe_strdup ( spotify_user ) ;
}
2017-02-05 07:56:12 -05:00
keyval_clear ( & kv ) ;
return ret ;
}
2018-03-02 13:24:35 -05:00
static int
2017-08-19 02:36:43 -04:00
spotifywebapi_token_refresh ( char * * user )
2017-02-05 07:56:12 -05:00
{
struct keyval kv ;
2017-12-27 12:51:11 -05:00
char * refresh_token ;
2017-02-05 07:56:12 -05:00
const char * err ;
int ret ;
2018-03-02 13:24:35 -05:00
memset ( & kv , 0 , sizeof ( struct keyval ) ) ;
refresh_token = NULL ;
CHECK_ERR ( L_SPOTIFY , pthread_mutex_lock ( & token_lck ) ) ;
2017-02-05 07:56:12 -05:00
if ( token_requested & & difftime ( time ( NULL ) , token_requested ) < expires_in )
{
DPRINTF ( E_DBG , L_SPOTIFY , " Spotify token still valid \n " ) ;
2018-03-02 13:24:35 -05:00
CHECK_ERR ( L_SPOTIFY , pthread_mutex_unlock ( & token_lck ) ) ;
2017-02-05 07:56:12 -05:00
return 0 ;
}
2017-11-19 15:14:41 -05:00
refresh_token = db_admin_get ( DB_ADMIN_SPOTIFY_REFRESH_TOKEN ) ;
2017-02-05 07:56:12 -05:00
if ( ! refresh_token )
{
DPRINTF ( E_LOG , L_SPOTIFY , " No spotify refresh token found \n " ) ;
2018-03-02 13:24:35 -05:00
ret = - 1 ;
goto out ;
2017-02-05 07:56:12 -05:00
}
DPRINTF ( E_DBG , L_SPOTIFY , " Spotify refresh-token: '%s' \n " , refresh_token ) ;
ret = ( ( keyval_add ( & kv , " grant_type " , " refresh_token " ) = = 0 ) & &
( keyval_add ( & kv , " client_id " , spotify_client_id ) = = 0 ) & &
( keyval_add ( & kv , " client_secret " , spotify_client_secret ) = = 0 ) & &
( keyval_add ( & kv , " refresh_token " , refresh_token ) = = 0 ) ) ;
if ( ! ret )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Add parameters to keyval failed " ) ;
2017-12-27 12:51:11 -05:00
ret = - 1 ;
2017-02-05 07:56:12 -05:00
}
2017-12-27 12:51:11 -05:00
else
ret = tokens_get ( & kv , & err ) ;
2017-02-05 07:56:12 -05:00
2017-12-27 12:51:11 -05:00
if ( user & & ret = = 0 )
{
* user = safe_strdup ( spotify_user ) ;
}
2018-03-02 13:24:35 -05:00
out :
2017-02-05 07:56:12 -05:00
free ( refresh_token ) ;
keyval_clear ( & kv ) ;
2018-03-02 13:24:35 -05:00
CHECK_ERR ( L_SPOTIFY , pthread_mutex_unlock ( & token_lck ) ) ;
return ret ;
}
/*
* Returns the directory id for / spotify : / < artist > / < album > , if the directory ( or the parent
* directories ) does not yet exist , they will be created .
* If an error occured the return value is - 1.
*
* @ return directory id for the given artist / album directory
*/
static int
prepare_directories ( const char * artist , const char * album )
{
int dir_id ;
char virtual_path [ PATH_MAX ] ;
int ret ;
ret = snprintf ( virtual_path , sizeof ( virtual_path ) , " /spotify:/%s " , artist ) ;
if ( ( ret < 0 ) | | ( ret > = sizeof ( virtual_path ) ) )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Virtual path exceeds PATH_MAX (/spotify:/%s) \n " , artist ) ;
return - 1 ;
}
dir_id = db_directory_addorupdate ( virtual_path , 0 , DIR_SPOTIFY ) ;
if ( dir_id < = 0 )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Could not add or update directory '%s' \n " , virtual_path ) ;
return - 1 ;
}
ret = snprintf ( virtual_path , sizeof ( virtual_path ) , " /spotify:/%s/%s " , artist , album ) ;
if ( ( ret < 0 ) | | ( ret > = sizeof ( virtual_path ) ) )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Virtual path exceeds PATH_MAX (/spotify:/%s/%s) \n " , artist , album ) ;
return - 1 ;
}
dir_id = db_directory_addorupdate ( virtual_path , 0 , dir_id ) ;
if ( dir_id < = 0 )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Could not add or update directory '%s' \n " , virtual_path ) ;
return - 1 ;
}
return dir_id ;
}
static int
spotify_cleanup_files ( void )
{
struct query_params qp ;
char * path ;
int ret ;
memset ( & qp , 0 , sizeof ( struct query_params ) ) ;
qp . type = Q_BROWSE_PATH ;
qp . sort = S_NONE ;
qp . filter = " f.path LIKE 'spotify:%%' AND NOT f.path IN (SELECT filepath FROM playlistitems) " ;
ret = db_query_start ( & qp ) ;
if ( ret < 0 )
{
db_query_end ( & qp ) ;
return - 1 ;
}
while ( ( ( ret = db_query_fetch_string ( & qp , & path ) ) = = 0 ) & & ( path ) )
{
cache_artwork_delete_by_path ( path ) ;
}
db_query_end ( & qp ) ;
db_spotify_files_delete ( ) ;
return 0 ;
}
static int
playlist_remove ( const char * uri )
{
struct playlist_info * pli ;
int plid ;
pli = db_pl_fetch_bypath ( uri ) ;
if ( ! pli )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Playlist '%s' not found, can't delete \n " , uri ) ;
return - 1 ;
}
DPRINTF ( E_LOG , L_SPOTIFY , " Removing playlist '%s' (%s) \n " , pli - > title , uri ) ;
plid = pli - > id ;
free_pli ( pli , 0 ) ;
db_spotify_pl_delete ( plid ) ;
spotify_cleanup_files ( ) ;
return 0 ;
}
static void
map_track_to_mfi ( struct media_file_info * mfi , const struct spotify_track * track , const struct spotify_album * album , const char * pl_name )
{
char virtual_path [ PATH_MAX ] ;
mfi - > title = safe_strdup ( track - > name ) ;
mfi - > artist = safe_strdup ( track - > artist ) ;
mfi - > disc = track - > disc_number ;
mfi - > song_length = track - > duration_ms ;
mfi - > track = track - > track_number ;
mfi - > data_kind = DATA_KIND_SPOTIFY ;
mfi - > media_kind = MEDIA_KIND_MUSIC ;
mfi - > artwork = ARTWORK_SPOTIFY ;
mfi - > type = strdup ( " spotify " ) ;
mfi - > codectype = strdup ( " wav " ) ;
mfi - > description = strdup ( " Spotify audio " ) ;
mfi - > path = strdup ( track - > uri ) ;
mfi - > fname = strdup ( track - > uri ) ;
if ( album )
{
mfi - > album_artist = safe_strdup ( album - > artist ) ;
mfi - > album = safe_strdup ( album - > name ) ;
mfi - > genre = safe_strdup ( album - > genre ) ;
mfi - > compilation = album - > is_compilation ;
mfi - > year = album - > release_year ;
mfi - > time_modified = album - > mtime ;
}
else
{
mfi - > album_artist = safe_strdup ( track - > album_artist ) ;
if ( cfg_getbool ( cfg_getsec ( cfg , " spotify " ) , " album_override " ) & & pl_name )
mfi - > album = safe_strdup ( pl_name ) ;
else
mfi - > album = safe_strdup ( track - > album ) ;
if ( cfg_getbool ( cfg_getsec ( cfg , " spotify " ) , " artist_override " ) & & pl_name )
mfi - > compilation = true ;
else
mfi - > compilation = track - > is_compilation ;
mfi - > time_modified = time ( NULL ) ;
}
snprintf ( virtual_path , PATH_MAX , " /spotify:/%s/%s/%s " , mfi - > album_artist , mfi - > album , mfi - > title ) ;
mfi - > virtual_path = strdup ( virtual_path ) ;
}
static void
webapi_track_save ( struct spotify_track * track , struct spotify_album * album , const char * pl_name , int dir_id )
{
struct media_file_info mfi ;
int ret ;
if ( track - > linked_from_uri )
DPRINTF ( E_DBG , L_SPOTIFY , " Track '%s' (%s) linked from %s \n " , track - > name , track - > uri , track - > linked_from_uri ) ;
ret = db_file_ping_bypath ( track - > uri , track - > mtime ) ;
if ( ret = = 0 )
{
DPRINTF ( E_DBG , L_SPOTIFY , " Track '%s' (%s) is new or modified (mtime is % " PRIi64 " ) \n " , track - > name , track - > uri , ( int64_t ) track - > mtime ) ;
memset ( & mfi , 0 , sizeof ( struct media_file_info ) ) ;
mfi . id = db_file_id_bypath ( track - > uri ) ;
mfi . directory_id = dir_id ;
map_track_to_mfi ( & mfi , track , album , pl_name ) ;
library_add_media ( & mfi ) ;
free_mfi ( & mfi , 1 ) ;
}
spotify_uri_register ( track - > uri ) ;
if ( album )
cache_artwork_ping ( track - > uri , album - > mtime , 0 ) ;
else
cache_artwork_ping ( track - > uri , 1 , 0 ) ;
}
/* Thread: library */
static int
scan_saved_albums ( )
{
struct spotify_request request ;
struct spotify_album album ;
struct spotify_track track ;
json_object * jsontracks ;
int track_count ;
int dir_id ;
int i ;
int count ;
int ret ;
count = 0 ;
memset ( & request , 0 , sizeof ( struct spotify_request ) ) ;
while ( 0 = = spotifywebapi_request_next ( & request , spotify_albums_uri , false ) )
{
while ( 0 = = spotifywebapi_saved_albums_fetch ( & request , & jsontracks , & track_count , & album ) )
{
DPRINTF ( E_DBG , L_SPOTIFY , " Got saved album: '%s' - '%s' (%s) - track-count: %d \n " ,
album . artist , album . name , album . uri , track_count ) ;
db_transaction_begin ( ) ;
dir_id = prepare_directories ( album . artist , album . name ) ;
ret = 0 ;
for ( i = 0 ; i < track_count & & ret = = 0 ; i + + )
{
ret = spotifywebapi_album_track_fetch ( jsontracks , i , & track ) ;
if ( ret < 0 | | ! track . uri )
continue ;
webapi_track_save ( & track , & album , NULL , dir_id ) ;
if ( spotify_saved_plid )
db_pl_add_item_bypath ( spotify_saved_plid , track . uri ) ;
}
db_transaction_end ( ) ;
count + + ;
if ( count > = request . total | | ( count % 10 = = 0 ) )
DPRINTF ( E_LOG , L_SPOTIFY , " Scanned %d of %d saved albums \n " , count , request . total ) ;
}
}
spotifywebapi_request_end ( & request ) ;
return 0 ;
}
/* Thread: library */
static int
scan_playlisttracks ( struct spotify_playlist * playlist , int plid )
{
struct spotify_request request ;
struct spotify_track track ;
int dir_id ;
memset ( & request , 0 , sizeof ( struct spotify_request ) ) ;
while ( 0 = = spotifywebapi_request_next ( & request , playlist - > tracks_href , true ) )
{
db_transaction_begin ( ) ;
while ( 0 = = spotifywebapi_playlisttracks_fetch ( & request , & track ) )
{
if ( ! track . uri | | ! track . is_playable )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Track not available for playback: '%s' - '%s' (%s) (restrictions: %s) \n " , track . artist , track . name , track . uri , track . restrictions ) ;
continue ;
}
dir_id = prepare_directories ( track . album_artist , track . album ) ;
webapi_track_save ( & track , NULL , playlist - > name , dir_id ) ;
db_pl_add_item_bypath ( plid , track . uri ) ;
}
db_transaction_end ( ) ;
}
spotifywebapi_request_end ( & request ) ;
return 0 ;
}
/* Thread: library */
static int
scan_playlists ( )
{
struct spotify_request request ;
struct spotify_playlist playlist ;
char virtual_path [ PATH_MAX ] ;
int plid ;
int count ;
int trackcount ;
count = 0 ;
trackcount = 0 ;
memset ( & request , 0 , sizeof ( struct spotify_request ) ) ;
while ( 0 = = spotifywebapi_request_next ( & request , spotify_playlists_uri , false ) )
{
while ( 0 = = spotifywebapi_playlists_fetch ( & request , & playlist ) )
{
DPRINTF ( E_DBG , L_SPOTIFY , " Got playlist: '%s' with %d tracks (%s) \n " , playlist . name , playlist . tracks_count , playlist . uri ) ;
if ( ! playlist . uri | | ! playlist . name | | playlist . tracks_count = = 0 )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Ignoring playlist '%s' with %d tracks (%s) \n " , playlist . name , playlist . tracks_count , playlist . uri ) ;
continue ;
}
if ( playlist . owner )
{
snprintf ( virtual_path , PATH_MAX , " /spotify:/%s (%s) " , playlist . name , playlist . owner ) ;
}
else
{
snprintf ( virtual_path , PATH_MAX , " /spotify:/%s " , playlist . name ) ;
}
db_transaction_begin ( ) ;
plid = library_add_playlist_info ( playlist . uri , playlist . name , virtual_path , PL_PLAIN , spotify_base_plid , DIR_SPOTIFY ) ;
db_transaction_end ( ) ;
if ( plid > 0 )
scan_playlisttracks ( & playlist , plid ) ;
else
DPRINTF ( E_LOG , L_SPOTIFY , " Error adding playlist: '%s' (%s) \n " , playlist . name , playlist . uri ) ;
count + + ;
trackcount + = playlist . tracks_count ;
DPRINTF ( E_LOG , L_SPOTIFY , " Scanned %d of %d saved playlists (%d tracks) \n " , count , request . total , trackcount ) ;
}
}
spotifywebapi_request_end ( & request ) ;
return 0 ;
}
/* Thread: library */
static int
scan_playlist ( const char * uri )
{
struct spotify_request request ;
struct spotify_playlist playlist ;
char virtual_path [ PATH_MAX ] ;
int plid ;
memset ( & request , 0 , sizeof ( struct spotify_request ) ) ;
memset ( & playlist , 0 , sizeof ( struct spotify_playlist ) ) ;
if ( 0 = = spotifywebapi_playlist_start ( & request , uri , & playlist ) )
{
if ( ! playlist . uri )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Got playlist with missing uri for path:: '%s' \n " , uri ) ;
}
else
{
DPRINTF ( E_LOG , L_SPOTIFY , " Saving playlist '%s' with %d tracks (%s) \n " , playlist . name , playlist . tracks_count , playlist . uri ) ;
if ( playlist . owner )
{
snprintf ( virtual_path , PATH_MAX , " /spotify:/%s (%s) " , playlist . name , playlist . owner ) ;
}
else
{
snprintf ( virtual_path , PATH_MAX , " /spotify:/%s " , playlist . name ) ;
}
db_transaction_begin ( ) ;
plid = library_add_playlist_info ( playlist . uri , playlist . name , virtual_path , PL_PLAIN , spotify_base_plid , DIR_SPOTIFY ) ;
db_transaction_end ( ) ;
if ( plid > 0 )
scan_playlisttracks ( & playlist , plid ) ;
else
DPRINTF ( E_LOG , L_SPOTIFY , " Error adding playlist: '%s' (%s) \n " , playlist . name , playlist . uri ) ;
}
}
spotifywebapi_request_end ( & request ) ;
return 0 ;
}
static void
create_saved_tracks_playlist ( )
{
spotify_saved_plid = library_add_playlist_info ( " spotify:savedtracks " , " Spotify Saved " , " /spotify:/Spotify Saved " , PL_PLAIN , spotify_base_plid , DIR_SPOTIFY ) ;
if ( spotify_saved_plid < = 0 )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Error adding playlist for saved tracks \n " ) ;
spotify_saved_plid = 0 ;
}
}
/*
* Add or update playlist folder for all spotify playlists ( if enabled in config )
*/
static void
create_base_playlist ( )
{
cfg_t * spotify_cfg ;
int ret ;
spotify_base_plid = 0 ;
spotify_cfg = cfg_getsec ( cfg , " spotify " ) ;
if ( ! cfg_getbool ( spotify_cfg , " base_playlist_disable " ) )
{
ret = library_add_playlist_info ( " spotify:playlistfolder " , " Spotify " , NULL , PL_FOLDER , 0 , 0 ) ;
if ( ret < 0 )
DPRINTF ( E_LOG , L_SPOTIFY , " Error adding base playlist \n " ) ;
else
spotify_base_plid = ret ;
}
}
static void
scan ( )
{
if ( token_valid ( ) & & ! scanning )
{
scanning = true ;
db_directory_enable_bypath ( " /spotify: " ) ;
create_base_playlist ( ) ;
create_saved_tracks_playlist ( ) ;
scan_saved_albums ( ) ;
scan_playlists ( ) ;
scanning = false ;
}
else
{
DPRINTF ( E_DBG , L_SPOTIFY , " No valid web api token or scan already in progress, rescan ignored \n " ) ;
}
}
/* Thread: library */
static int
initscan ( )
{
int ret ;
/* Refresh access token for the spotify webapi */
ret = spotifywebapi_token_refresh ( NULL ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Spotify webapi token refresh failed. "
" In order to use the web api, authorize forked-daapd to access "
" your saved tracks by visiting http://forked-daapd.local:3689 \n " ) ;
db_spotify_purge ( ) ;
return 0 ;
}
spotify_saved_plid = 0 ;
/*
* Login to spotify needs to be done before scanning tracks from the web api .
* ( Scanned tracks need to be registered with libspotify for playback )
*/
ret = spotify_relogin ( ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_SPOTIFY , " libspotify-login failed. In order to use Spotify, "
" provide valid credentials for libspotify by visiting http://forked-daapd.local:3689 \n " ) ;
db_spotify_purge ( ) ;
return 0 ;
}
/*
* Scan saved tracks from the web api
*/
scan ( ) ;
return 0 ;
}
/* Thread: library */
static int
rescan ( )
{
scan ( ) ;
return 0 ;
}
/* Thread: library */
static int
fullrescan ( )
{
db_spotify_purge ( ) ;
scan ( ) ;
return 0 ;
}
/* Thread: library */
static enum command_state
webapi_fullrescan ( void * arg , int * ret )
{
* ret = fullrescan ( ) ;
return COMMAND_END ;
}
/* Thread: library */
static enum command_state
webapi_rescan ( void * arg , int * ret )
{
* ret = rescan ( ) ;
return COMMAND_END ;
}
/* Thread: library */
static enum command_state
webapi_pl_save ( void * arg , int * ret )
{
const char * uri = arg ;
* ret = scan_playlist ( uri ) ;
return COMMAND_END ;
}
/* Thread: library */
static enum command_state
webapi_pl_remove ( void * arg , int * ret )
{
const char * uri = arg ;
* ret = playlist_remove ( uri ) ;
return COMMAND_END ;
}
void
spotifywebapi_fullrescan ( void )
{
library_exec_async ( webapi_fullrescan , NULL ) ;
}
void
spotifywebapi_rescan ( void )
{
library_exec_async ( webapi_rescan , NULL ) ;
}
void
spotifywebapi_pl_save ( const char * uri )
{
if ( scanning | | ! token_valid ( ) )
{
DPRINTF ( E_DBG , L_SPOTIFY , " Scanning spotify saved tracks still in progress, ignoring update trigger for single playlist '%s' \n " , uri ) ;
return ;
}
library_exec_async ( webapi_pl_save , strdup ( uri ) ) ;
}
void
spotifywebapi_pl_remove ( const char * uri )
{
if ( scanning | | ! token_valid ( ) )
{
DPRINTF ( E_DBG , L_SPOTIFY , " Scanning spotify saved tracks still in progress, ignoring remove trigger for single playlist '%s' \n " , uri ) ;
return ;
}
library_exec_async ( webapi_pl_remove , strdup ( uri ) ) ;
}
void
spotifywebapi_status_info_get ( struct spotifywebapi_status_info * info )
{
memset ( info , 0 , sizeof ( struct spotifywebapi_status_info ) ) ;
CHECK_ERR ( L_SPOTIFY , pthread_mutex_lock ( & token_lck ) ) ;
info - > token_valid = token_valid ( ) ;
if ( spotify_user )
{
memcpy ( info - > user , spotify_user , ( sizeof ( info - > user ) - 1 ) ) ;
}
CHECK_ERR ( L_SPOTIFY , pthread_mutex_unlock ( & token_lck ) ) ;
}
static int
spotifywebapi_init ( )
{
int ret ;
CHECK_ERR ( L_SPOTIFY , mutex_init ( & token_lck ) ) ;
ret = spotify_init ( ) ;
2017-02-05 07:56:12 -05:00
return ret ;
}
2018-03-02 13:24:35 -05:00
static void
spotifywebapi_deinit ( )
{
CHECK_ERR ( L_SPOTIFY , pthread_mutex_destroy ( & token_lck ) ) ;
spotify_deinit ( ) ;
}
struct library_source spotifyscanner =
{
. name = " spotifyscanner " ,
. disabled = 0 ,
. init = spotifywebapi_init ,
. deinit = spotifywebapi_deinit ,
. rescan = rescan ,
. initscan = initscan ,
. fullrescan = fullrescan ,
} ;