[spotify] Scan saved albums and playlist using the spotify web api

This commit is contained in:
chme 2017-01-01 11:06:00 +01:00
parent 1672b67040
commit 0bea83cafa
5 changed files with 247 additions and 43 deletions

View File

@ -1415,7 +1415,7 @@ cache_daap_threshold(void)
* @return 0 if successful, -1 if an error occurred
*/
void
cache_artwork_ping(char *path, time_t mtime, int del)
cache_artwork_ping(const char *path, time_t mtime, int del)
{
struct cache_arg *cmdarg;

View File

@ -31,7 +31,7 @@ cache_daap_threshold(void);
#define CACHE_ARTWORK_INDIVIDUAL 1
void
cache_artwork_ping(char *path, time_t mtime, int del);
cache_artwork_ping(const char *path, time_t mtime, int del);
int
cache_artwork_delete_by_path(char *path);

View File

@ -156,8 +156,8 @@ static void *g_libhandle;
static enum spotify_state g_state;
// 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 telling us if access to the web api was granted
static bool spotify_access_token_valid;
// Audio fifo
static audio_fifo_t *g_audio_fifo;
@ -707,7 +707,7 @@ spotify_track_save(int plid, sp_track *track, const char *pltitle, int time_adde
// DPRINTF(E_DBG, L_SPOTIFY, "Saving track '%s': '%s' by %s (%s)\n", url, mfi.title, mfi.artist, mfi.album);
library_process_media(url, time(NULL), 0, DATA_KIND_SPOTIFY, 0, 0, &mfi, dir_id);
library_process_media(url, time(NULL), 0, DATA_KIND_SPOTIFY, 0, false, &mfi, dir_id);
free_mfi(&mfi, 1);
@ -910,7 +910,7 @@ spotify_playlist_save(sp_playlist *pl)
// 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)
uri_register(void *arg, int *retval)
{
sp_link *link;
sp_track *track;
@ -963,7 +963,8 @@ static void playlist_update_in_progress(sp_playlist *pl, bool done, void *userda
{
DPRINTF(E_DBG, L_SPOTIFY, "Playlist update (status %d): %s\n", done, fptr_sp_playlist_name(pl));
spotify_playlist_save(pl);
if (!spotify_access_token_valid) // TODO Trigger update of library on pl change
spotify_playlist_save(pl);
}
}
@ -971,7 +972,8 @@ static void playlist_metadata_updated(sp_playlist *pl, void *userdata)
{
DPRINTF(E_DBG, L_SPOTIFY, "Playlist metadata updated: %s\n", fptr_sp_playlist_name(pl));
spotify_playlist_save(pl);
if (!spotify_access_token_valid) // TODO Trigger update of library on pl change
spotify_playlist_save(pl);
}
/**
@ -1001,7 +1003,8 @@ static void playlist_added(sp_playlistcontainer *pc, sp_playlist *pl,
fptr_sp_playlist_add_callbacks(pl, &pl_callbacks, NULL);
spotify_playlist_save(pl);
if (!spotify_access_token_valid) // TODO Trigger update of library on pl change
spotify_playlist_save(pl);
}
/**
@ -1027,6 +1030,9 @@ playlist_removed(sp_playlistcontainer *pc, sp_playlist *pl, int position, void *
fptr_sp_playlist_remove_callbacks(pl, &pl_callbacks, NULL);
if (spotify_access_token_valid) // TODO Trigger update of library on pl change
return;
link = fptr_sp_link_create_from_playlist(pl);
if (!link)
{
@ -1545,11 +1551,8 @@ artwork_get(void *arg, int *retval)
static void
logged_in(sp_session *sess, sp_error error)
{
cfg_t *spotify_cfg;
sp_playlist *pl;
sp_playlistcontainer *pc;
struct playlist_info pli;
int ret;
int i;
if (SP_ERROR_OK != error)
@ -1565,24 +1568,6 @@ logged_in(sp_session *sess, sp_error error)
pl = fptr_sp_session_starred_create(sess);
fptr_sp_playlist_add_callbacks(pl, &pl_callbacks, NULL);
spotify_cfg = cfg_getsec(cfg, "spotify");
if (! cfg_getbool(spotify_cfg, "base_playlist_disable"))
{
memset(&pli, 0, sizeof(struct playlist_info));
pli.title = "Spotify";
pli.type = PL_FOLDER;
pli.path = "spotify:playlistfolder";
ret = db_pl_add(&pli, &spotify_base_plid);
if (ret < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Error adding base playlist\n");
return;
}
}
else
spotify_base_plid = 0;
pc = fptr_sp_session_playlistcontainer(sess);
fptr_sp_playlistcontainer_add_callbacks(pc, &pc_callbacks, NULL);
@ -2005,6 +1990,9 @@ spotify_oauth_callback(struct evbuffer *evbuf, struct evkeyvalq *param, const ch
return;
}
// Received a valid access token
spotify_access_token_valid = true;
// TODO Trigger init-scan after successful access to spotifywebapi
evbuffer_add_printf(evbuf, "ok, all done</p>\n");
@ -2012,6 +2000,15 @@ spotify_oauth_callback(struct evbuffer *evbuf, struct evkeyvalq *param, const ch
return;
}
static void
spotify_uri_register(const char *uri)
{
char *tmp;
tmp = strdup(uri);
commands_exec_async(cmdbase, uri_register, tmp);
}
/* Thread: library */
void
spotify_login(char *path)
@ -2055,9 +2052,6 @@ spotify_login(char *path)
if (path)
{
db_spotify_purge();
spotify_saved_plid = 0;
ret = spotify_file_read(path, &username, &password);
if (ret < 0)
return;
@ -2068,9 +2062,6 @@ spotify_login(char *path)
}
else
{
db_spotify_purge();
spotify_saved_plid = 0;
err = fptr_sp_session_relogin(g_sess);
}
@ -2081,11 +2072,212 @@ spotify_login(char *path)
}
}
static void
map_track_to_mfi(const struct spotify_track *track, struct media_file_info* mfi)
{
mfi->title = safe_strdup(track->name);
mfi->album = safe_strdup(track->album);
mfi->artist = safe_strdup(track->artist);
mfi->album_artist = safe_strdup(track->album_artist);
mfi->disc = track->disc_number;
mfi->song_length = track->duration_ms;
mfi->track = track->track_number;
mfi->compilation = track->is_compilation;
}
static void
map_album_to_mfi(const struct spotify_album *album, struct media_file_info* mfi)
{
mfi->album = safe_strdup(album->name);
mfi->album_artist = safe_strdup(album->artist);
mfi->genre = safe_strdup(album->genre);
mfi->compilation = album->is_compilation;
}
/* Thread: library */
static int
scan_saved_albums()
{
struct spotify_request request;
json_object *jsontracks;
int track_count;
struct spotify_album album;
struct spotify_track track;
struct media_file_info mfi;
int dir_id;
int i;
int ret;
db_transaction_begin();
memset(&request, 0, sizeof(struct spotify_request));
while (0 == spotifywebapi_request_next(&request, SPOTIFY_WEBAPI_SAVED_ALBUMS))
{
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);
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)
{
memset(&mfi, 0, sizeof(struct media_file_info));
map_track_to_mfi(&track, &mfi);
map_album_to_mfi(&album, &mfi);
library_process_media(track.uri, album.mtime, 0, DATA_KIND_SPOTIFY, 0, album.is_compilation, &mfi, dir_id);
spotify_uri_register(track.uri);
cache_artwork_ping(track.uri, album.mtime, 0);
free_mfi(&mfi, 1);
}
}
}
}
spotifywebapi_request_end(&request);
db_transaction_end();
return 0;
}
/* Thread: library */
static int
scan_playlisttracks(struct spotify_playlist *playlist, int plid)
{
struct spotify_request request;
struct spotify_track track;
struct media_file_info mfi;
int dir_id;
memset(&request, 0, sizeof(struct spotify_request));
while (0 == spotifywebapi_request_next(&request, playlist->tracks_href))
{
// DPRINTF(E_DBG, L_SPOTIFY, "Playlist tracks\n%s\n", request.response_body);
while (0 == spotifywebapi_playlisttracks_fetch(&request, &track))
{
DPRINTF(E_DBG, L_SPOTIFY, "Got playlist track: '%s' (%s) \n", track.name, track.uri);
if (track.uri)
{
dir_id = prepare_directories(track.album_artist, track.album);
memset(&mfi, 0, sizeof(struct media_file_info));
map_track_to_mfi(&track, &mfi);
library_process_media(track.uri, 1 /* TODO passing one prevents overwriting existing entries */, 0, DATA_KIND_SPOTIFY, 0, track.is_compilation, &mfi, dir_id);
spotify_uri_register(track.uri);
cache_artwork_ping(track.uri, 1, 0);
free_mfi(&mfi, 1);
db_pl_add_item_bypath(plid, track.uri);
}
}
}
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;
db_transaction_begin();
memset(&request, 0, sizeof(struct spotify_request));
while (0 == spotifywebapi_request_next(&request, SPOTIFY_WEBAPI_SAVED_PLAYLISTS))
{
while (0 == spotifywebapi_playlists_fetch(&request, &playlist))
{
DPRINTF(E_DBG, L_SPOTIFY, "Got playlist: '%s' (%s) \n", playlist.name, 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);
}
plid = library_add_playlist_info(playlist.uri, playlist.name, virtual_path, PL_PLAIN, spotify_base_plid, DIR_SPOTIFY);
if (plid)
scan_playlisttracks(&playlist, plid);
}
}
spotifywebapi_request_end(&request);
db_transaction_end();
return 0;
}
/* Thread: library */
static int
spotify_initscan()
{
cfg_t *spotify_cfg;
int ret;
/* Refresh access token for the spotify webapi */
spotify_access_token_valid = (0 == spotifywebapi_token_refresh());
if (!spotify_access_token_valid)
{
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/oauth\n");
db_spotify_purge();
}
/*
* Add playlist folder for all spotify playlists
*/
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;
}
/*
* Login to spotify needs to be done before scanning tracks from the web api.
* (Scanned tracks need to be registered with libspotify for playback)
*/
spotify_login(NULL);
/*
* Scan saved tracks from the web api
*/
if (spotify_access_token_valid)
{
scan_saved_albums();
scan_playlists();
}
return 0;
}
@ -2112,6 +2304,8 @@ spotify_init(void)
sp_error err;
int ret;
spotify_access_token_valid = false;
/* Initialize libspotify */
g_libhandle = dlopen("libspotify.so", RTLD_LAZY);
if (!g_libhandle)

View File

@ -392,14 +392,14 @@ spotifywebapi_request_uri(struct spotify_request *request, const char *uri)
snprintf(bearer_token, sizeof(bearer_token), "Bearer %s", spotify_access_token);
if (keyval_add(request->ctx->output_headers, "Authorization", bearer_token) < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Add bearer_token to keyval failed");
DPRINTF(E_LOG, L_SPOTIFY, "Add bearer_token to keyval failed\n");
return -1;
}
ret = http_client_request(request->ctx);
if (ret < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Request for saved tracks/albums failed");
DPRINTF(E_LOG, L_SPOTIFY, "Request for saved tracks/albums failed\n");
return -1;
}
@ -409,10 +409,12 @@ spotifywebapi_request_uri(struct spotify_request *request, const char *uri)
request->response_body = (char *) evbuffer_pullup(request->ctx->input_body, -1);
if (!request->response_body || (strlen(request->response_body) == 0))
{
DPRINTF(E_LOG, L_SPOTIFY, "Request for saved tracks/albums failed, response was empty");
DPRINTF(E_LOG, L_SPOTIFY, "Request for saved tracks/albums failed, response was empty\n");
return -1;
}
//DPRINTF(E_DBG, L_SPOTIFY, "Wep api response for '%s'\n%s\n", uri, request->response_body);
request->haystack = json_tokener_parse(request->response_body);
if (!request->haystack)
{
@ -498,6 +500,8 @@ track_metadata(json_object* jsontrack, struct spotify_track* track)
track->artist = jparse_artist_from_obj(jsonartists);
}
track->disc_number = jparse_int_from_obj(jsontrack, "disc_number");
track->album_type = jparse_str_from_obj(jsonalbum, "album_type");
track->is_compilation = (track->album_type && 0 == strcmp(track->album_type, "compilation"));
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");
@ -586,9 +590,13 @@ album_metadata(json_object *jsonalbum, struct spotify_album *album)
album->id = jparse_str_from_obj(jsonalbum, "id");
album->album_type = jparse_str_from_obj(jsonalbum, "album_type");
album->genre = jparse_str_from_obj(jsonalbum, "genre"); // FIXME Genre is an array of strings ('genres'), but it seems to be always empty
album->is_compilation = (album->album_type && 0 == strcmp(album->album_type, "compilation"));
album->label = jparse_str_from_obj(jsonalbum, "label");
album->release_date = jparse_str_from_obj(jsonalbum, "release_date");
// 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");
}
int

View File

@ -22,15 +22,14 @@
#include <event2/event.h>
#include "http.h"
#ifdef HAVE_JSON_C_OLD
# include <json/json.h>
#else
# include <json-c/json.h>
#endif
#include <stdbool.h>
#include "http.h"
#define SPOTIFY_WEBAPI_SAVED_TRACKS "https://api.spotify.com/v1/me/tracks?limit=50"
#define SPOTIFY_WEBAPI_TRACKS "https://api.spotify.com/v1/tracks/"
@ -43,6 +42,7 @@ struct spotify_album
time_t mtime;
const char *album_type;
bool is_compilation;
const char *artist;
const char *genre;
const char *id;
@ -61,6 +61,8 @@ struct spotify_track
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;