[spotify] Rescan of single playlists if update trigger received from

libspotify (Readds "spotify:savedtracks" playlist to avoid deletion of
saved tracks)
This commit is contained in:
chme 2017-01-05 22:09:19 +01:00
parent adac1d3b5f
commit 99945fa576
3 changed files with 280 additions and 31 deletions

View File

@ -158,6 +158,11 @@ static enum spotify_state g_state;
static int spotify_base_plid;
// Flag telling us if access to the web api was granted
static bool spotify_access_token_valid;
// 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;
// Audio fifo
static audio_fifo_t *g_audio_fifo;
@ -414,7 +419,11 @@ fptr_assign_all()
static enum command_state
scan_webapi(void *arg, int *ret);
webapi_scan(void *arg, int *ret);
static enum command_state
webapi_pl_save(void *arg, int *ret);
static enum command_state
webapi_pl_remove(void *arg, int *ret);
/* ------------------------------- MISC HELPERS ---------------------------- */
@ -947,6 +956,33 @@ uri_register(void *arg, int *retval)
return COMMAND_END;
}
static void
webapi_playlist_updated(sp_playlist *pl)
{
sp_link *link;
char url[1024];
int ret;
if (!scanning)
{
// Run playlist save in the library thread
link = fptr_sp_link_create_from_playlist(pl);
if (!link)
{
DPRINTF(E_LOG, L_SPOTIFY, "Could not create link for playlist: '%s'\n", fptr_sp_playlist_name(pl));
return;
}
ret = fptr_sp_link_as_string(link, url, sizeof(url));
if (ret == sizeof(url))
{
DPRINTF(E_DBG, L_SPOTIFY, "Spotify link truncated: %s\n", url);
}
fptr_sp_link_release(link);
library_exec_async(webapi_pl_save, strdup(url));
}
}
/* -------------------------- PLAYLIST CALLBACKS ------------------------- */
/**
@ -966,8 +1002,14 @@ 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));
if (!spotify_access_token_valid) // TODO Trigger update of library on pl change
spotify_playlist_save(pl);
if (spotify_access_token_valid)
{
webapi_playlist_updated(pl);
}
else
{
spotify_playlist_save(pl);
}
}
}
@ -975,8 +1017,15 @@ 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));
if (!spotify_access_token_valid) // TODO Trigger update of library on pl change
spotify_playlist_save(pl);
if (spotify_access_token_valid)
{
//TODO Update disabled to prevent multiple triggering of updates e. g. on adding a playlist
//webapi_playlist_updated(pl);
}
else
{
spotify_playlist_save(pl);
}
}
/**
@ -1006,8 +1055,37 @@ static void playlist_added(sp_playlistcontainer *pc, sp_playlist *pl,
fptr_sp_playlist_add_callbacks(pl, &pl_callbacks, NULL);
if (!spotify_access_token_valid) // TODO Trigger update of library on pl change
spotify_playlist_save(pl);
if (spotify_access_token_valid)
{
webapi_playlist_updated(pl);
}
else
{
spotify_playlist_save(pl);
}
}
static int
playlist_remove(const char *uri)
{
struct playlist_info *pli;
int plid;
pli = db_pl_fetch_bypath(uri);
if (!pli)
{
DPRINTF(E_DBG, L_SPOTIFY, "Playlist %s not found, can't delete\n", uri);
return -1;
}
plid = pli->id;
free_pli(pli, 0);
db_spotify_pl_delete(plid);
spotify_cleanup_files();
return 0;
}
/**
@ -1023,19 +1101,14 @@ static void playlist_added(sp_playlistcontainer *pc, sp_playlist *pl,
static void
playlist_removed(sp_playlistcontainer *pc, sp_playlist *pl, int position, void *userdata)
{
struct playlist_info *pli;
sp_link *link;
char url[1024];
int plid;
int ret;
DPRINTF(E_INFO, L_SPOTIFY, "Playlist removed: %s\n", fptr_sp_playlist_name(pl));
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)
{
@ -1050,21 +1123,16 @@ playlist_removed(sp_playlistcontainer *pc, sp_playlist *pl, int position, void *
}
fptr_sp_link_release(link);
pli = db_pl_fetch_bypath(url);
if (!pli)
if (spotify_access_token_valid)
{
DPRINTF(E_DBG, L_SPOTIFY, "Playlist %s not found, can't delete\n", url);
return;
// Run playlist remove in the library thread
if (!scanning)
library_exec_async(webapi_pl_remove, strdup(url));
}
else
{
playlist_remove(url);
}
plid = pli->id;
free_pli(pli, 0);
db_spotify_pl_delete(plid);
spotify_cleanup_files();
}
/**
@ -1997,7 +2065,7 @@ spotify_oauth_callback(struct evbuffer *evbuf, struct evkeyvalq *param, const ch
spotify_access_token_valid = true;
// Trigger scan after successful access to spotifywebapi
library_exec_async(scan_webapi, NULL);
library_exec_async(webapi_scan, NULL);
evbuffer_add_printf(evbuf, "ok, all done</p>\n");
@ -2140,6 +2208,9 @@ scan_saved_albums()
cache_artwork_ping(track.uri, album.mtime, 0);
free_mfi(&mfi, 1);
if (spotify_saved_plid)
db_pl_add_item_bypath(spotify_saved_plid, track.uri);
}
}
}
@ -2224,8 +2295,10 @@ scan_playlists()
plid = library_add_playlist_info(playlist.uri, playlist.name, virtual_path, PL_PLAIN, spotify_base_plid, DIR_SPOTIFY);
if (plid)
if (plid > 0)
scan_playlisttracks(&playlist, plid);
else
DPRINTF(E_LOG, L_SPOTIFY, "Error adding playlist: '%s' (%s) \n", playlist.name, playlist.uri);
}
}
@ -2235,6 +2308,58 @@ scan_playlists()
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;
db_transaction_begin();
memset(&request, 0, sizeof(struct spotify_request));
if (0 == spotifywebapi_playlist_start(&request, uri, &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 > 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);
db_transaction_end();
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;
}
}
/* Thread: library */
static int
initscan()
@ -2242,6 +2367,8 @@ initscan()
cfg_t *spotify_cfg;
int ret;
scanning = true;
/* Refresh access token for the spotify webapi */
spotify_access_token_valid = (0 == spotifywebapi_token_refresh());
if (!spotify_access_token_valid)
@ -2257,6 +2384,7 @@ initscan()
* Add playlist folder for all spotify playlists
*/
spotify_base_plid = 0;
spotify_saved_plid = 0;
spotify_cfg = cfg_getsec(cfg, "spotify");
if (! cfg_getbool(spotify_cfg, "base_playlist_disable"))
{
@ -2278,10 +2406,13 @@ initscan()
*/
if (spotify_access_token_valid)
{
create_saved_tracks_playlist();
scan_saved_albums();
scan_playlists();
}
scanning = false;
return 0;
}
@ -2289,11 +2420,14 @@ initscan()
static int
rescan()
{
scanning = true;
/*
* Scan saved tracks from the web api
*/
if (spotify_access_token_valid)
{
create_saved_tracks_playlist();
scan_saved_albums();
scan_playlists();
}
@ -2306,6 +2440,8 @@ rescan()
db_transaction_end();
}
scanning = false;
return 0;
}
@ -2316,14 +2452,34 @@ fullrescan()
return 0;
}
/* Thread: httpd */
/* Thread: library */
static enum command_state
scan_webapi(void *arg, int *ret)
webapi_scan(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;
}
/* Thread: main */
int
spotify_init(void)
@ -2334,6 +2490,7 @@ spotify_init(void)
int ret;
spotify_access_token_valid = false;
scanning = false;
/* Initialize libspotify */
g_libhandle = dlopen("libspotify.so", RTLD_LAZY);

View File

@ -50,6 +50,7 @@ static const char *spotify_client_id = "0e684a5422384114a8ae7ac020f01789";
static const char *spotify_client_secret = "232af95f39014c9ba218285a5c11a239";
static const char *spotify_auth_uri = "https://accounts.spotify.com/authorize";
static const char *spotify_token_uri = "https://accounts.spotify.com/api/token";
static const char *spotify_playlist_uri = "https://api.spotify.com/v1/users/%s/playlists/%s";
/*--------------------- HELPERS FOR SPOTIFY WEB API -------------------------*/
@ -690,18 +691,67 @@ spotifywebapi_playlists_fetch(struct spotify_request *request, struct spotify_pl
if (request->index >= request->count)
{
DPRINTF(E_DBG, L_SPOTIFY, "All playlists processed\n");
return -1;
}
jsonplaylist = json_object_array_get_idx(request->items, request->index);
if (!jsonplaylist)
{
DPRINTF(E_LOG, L_SPOTIFY, "Error fetching playlist at index '%d'\n", request->index);
return -1;
}
playlist_metadata(jsonplaylist, playlist);
request->index++;
return 0;
}
/*
* 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;
}
int
spotifywebapi_playlisttracks_fetch(struct spotify_request *request, struct spotify_track *track)
{
@ -731,3 +781,44 @@ spotifywebapi_playlisttracks_fetch(struct spotify_request *request, struct spoti
return 0;
}
int
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;
}
ret = spotifywebapi_request_uri(request, uri);
if (ret < 0)
{
free(owner);
free(id);
return -1;
}
request->haystack = json_tokener_parse(request->response_body);
playlist_metadata(request->haystack, playlist);
free(owner);
free(id);
return 0;
}

View File

@ -121,6 +121,7 @@ int
spotifywebapi_playlists_fetch(struct spotify_request *request, struct spotify_playlist* playlist);
int
spotifywebapi_playlisttracks_fetch(struct spotify_request *request, struct spotify_track *track);
int
spotifywebapi_playlist_start(struct spotify_request *request, const char *path, struct spotify_playlist *playlist);
#endif /* SRC_SPOTIFY_WEBAPI_H_ */