2014-03-11 18:20:29 -04:00
/*
2017-10-01 17:48:00 -04:00
* Copyright ( C ) 2016 - 17 Espen Jürgensen < espenjurgensen @ gmail . com >
2014-03-11 18:20:29 -04:00
*
* 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
*/
# ifdef HAVE_CONFIG_H
# include <config.h>
# endif
# include <stdio.h>
# include <stdlib.h>
2016-12-31 01:28:18 -05:00
# include <stdbool.h>
2014-03-11 18:20:29 -04:00
# include <stdint.h>
2014-03-23 16:45:06 -04:00
# include <fcntl.h>
2014-03-11 18:20:29 -04:00
# include <unistd.h>
# include <string.h>
# include <time.h>
# include <errno.h>
# include <sys/param.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <sys/ioctl.h>
# include <sys/queue.h>
# include <pthread.h>
2016-05-05 13:39:15 -04:00
# ifdef HAVE_PTHREAD_NP_H
2016-03-05 07:19:55 -05:00
# include <pthread_np.h>
# endif
2014-03-11 18:20:29 -04:00
# include <dlfcn.h>
# include <libspotify/api.h>
2017-01-06 03:44:18 -05:00
# include <json.h>
2016-11-05 08:44:23 -04:00
2014-03-11 18:20:29 -04:00
# include "spotify.h"
2017-01-01 02:32:05 -05:00
# include "spotify_webapi.h"
2014-03-11 18:20:29 -04:00
# include "logger.h"
2016-11-05 08:44:23 -04:00
# include "misc.h"
# include "http.h"
2014-03-11 18:20:29 -04:00
# include "conffile.h"
2016-01-16 11:02:03 -05:00
# include "cache.h"
2016-05-08 03:54:58 -04:00
# include "commands.h"
2016-12-31 01:28:18 -05:00
# include "library.h"
2016-12-28 18:39:23 -05:00
# include "input.h"
2017-08-19 02:36:43 -04:00
# include "listener.h"
2014-03-11 18:20:29 -04:00
2016-11-05 08:44:23 -04:00
/* TODO for the web api:
2016-11-13 14:28:29 -05:00
* - map " added_at " to time_added
2016-11-05 08:44:23 -04:00
* - what to do about the lack of push ?
2016-11-13 14:28:29 -05:00
* - use the web api more , implement proper init
*/
/* A few words on our reloading sequence of saved tracks
*
* 1. libspotify will not tell us about the user ' s saved tracks when loading
* so we keep track of them with the special playlist spotify : savedtracks .
* 2. spotify_login will copy all paths in spotify : savedtracks to a temporary
* spotify_reload_list before all Spotify items in the database get purged .
* 3. when the connection to Spotify is established after login , we register
* all the paths with libspotify , and we also add them back to the
* spotify : savedtracks playlist - however , that ' s just for the
* playlistsitems table . Adding the items to the files table is done when
* libspotify calls back with metadata - see spotify_pending_process ( ) .
* 4. if the user reloads saved tracks , we first clear all items in the
* playlist , then add those back that are returned from the web api , and
* then use our normal cleanup of stray files to tidy db and cache .
2016-11-05 08:44:23 -04:00
*/
2014-03-11 18:20:29 -04:00
2016-11-05 08:44:23 -04:00
// How long to wait for artwork (in sec) before giving up
2016-01-08 18:36:30 -05:00
# define SPOTIFY_ARTWORK_TIMEOUT 3
2016-11-05 08:44:23 -04:00
// An upper limit on sequential requests to Spotify's web api
// - each request will return 50 objects (tracks)
# define SPOTIFY_WEB_REQUESTS_MAX 20
2014-11-11 05:59:40 -05:00
2014-03-11 18:20:29 -04:00
/* --- Types --- */
enum spotify_state
2014-03-23 16:45:06 -04:00
{
SPOTIFY_STATE_INACTIVE ,
SPOTIFY_STATE_WAIT ,
SPOTIFY_STATE_PLAYING ,
SPOTIFY_STATE_PAUSED ,
SPOTIFY_STATE_STOPPING ,
SPOTIFY_STATE_STOPPED ,
} ;
2014-04-04 17:14:43 -04:00
struct artwork_get_param
{
struct evbuffer * evbuf ;
char * path ;
int max_w ;
int max_h ;
2016-01-08 18:36:30 -05:00
sp_image * image ;
2016-01-07 16:25:25 -05:00
pthread_mutex_t mutex ;
pthread_cond_t cond ;
2016-01-08 18:36:30 -05:00
int is_loaded ;
2016-01-07 16:25:25 -05:00
} ;
2014-03-23 16:45:06 -04:00
/* --- Globals --- */
// Spotify thread
static pthread_t tid_spotify ;
2014-12-28 15:08:52 -05:00
// Used to make sure no login is attempted before the logout cb from Spotify
static pthread_mutex_t login_lck ;
static pthread_cond_t login_cond ;
2014-03-23 16:45:06 -04:00
// Event base, pipes and events
struct event_base * evbase_spotify ;
static int g_notify_pipe [ 2 ] ;
static struct event * g_notifyev ;
2016-05-08 03:54:58 -04:00
static struct commands_base * cmdbase ;
2016-11-05 08:44:23 -04:00
// The session handle
2014-03-23 16:45:06 -04:00
static sp_session * g_sess ;
2016-11-05 08:44:23 -04:00
// The library handle
2014-03-23 16:45:06 -04:00
static void * g_libhandle ;
2016-11-05 08:44:23 -04:00
// The state telling us what the thread is currently doing
2014-03-23 16:45:06 -04:00
static enum spotify_state g_state ;
2016-11-13 14:28:29 -05:00
// The base playlist id for all Spotify playlists in the db
static int spotify_base_plid ;
2017-01-01 05:06:00 -05:00
// Flag telling us if access to the web api was granted
static bool spotify_access_token_valid ;
2016-11-13 14:28:29 -05:00
// The base playlist id for Spotify saved tracks in the db
static int spotify_saved_plid ;
2017-01-05 16:09:19 -05:00
// Flag to avoid triggering playlist change events while the (re)scan is running
static bool scanning ;
2014-03-23 16:45:06 -04:00
2017-08-19 02:36:43 -04:00
static pthread_mutex_t status_lck ;
static struct spotify_status_info spotify_status_info ;
2017-01-15 17:20:53 -05:00
// Timeout timespec
static struct timespec spotify_artwork_timeout = { SPOTIFY_ARTWORK_TIMEOUT , 0 } ;
2016-12-28 18:39:23 -05:00
// Audio buffer
static struct evbuffer * spotify_audio_buffer ;
2014-03-11 18:20:29 -04:00
/**
* The application key is specific to forked - daapd , and allows Spotify
* to produce statistics on how their service is used .
*/
const uint8_t g_appkey [ ] = {
0x01 , 0xC6 , 0x9D , 0x18 , 0xA4 , 0xF7 , 0x79 , 0x12 , 0x43 , 0x55 , 0x0F , 0xAD , 0xBF , 0x23 , 0x23 , 0x10 ,
0x2E , 0x51 , 0x46 , 0x8F , 0x06 , 0x3D , 0xEE , 0xC3 , 0xF0 , 0x2A , 0x5D , 0x8E , 0x72 , 0x35 , 0xD1 , 0x21 ,
0x44 , 0xE3 , 0x19 , 0x80 , 0xED , 0xD5 , 0xAD , 0xE6 , 0xE1 , 0xDD , 0xBE , 0xCB , 0xA9 , 0x84 , 0xBD , 0xC2 ,
0xAF , 0xB1 , 0xF2 , 0xD5 , 0x87 , 0xFC , 0x35 , 0xD6 , 0x1C , 0x5F , 0x5B , 0x76 , 0x38 , 0x1D , 0x6E , 0x49 ,
0x6D , 0x85 , 0x15 , 0xCD , 0x38 , 0x14 , 0xD6 , 0xB8 , 0xFE , 0x05 , 0x0A , 0xAC , 0x9B , 0x31 , 0xD1 , 0xC0 ,
0xAF , 0x16 , 0x78 , 0x48 , 0x49 , 0x27 , 0x41 , 0xCA , 0xAF , 0x07 , 0xEC , 0x10 , 0x5D , 0x19 , 0x43 , 0x2E ,
0x84 , 0xEB , 0x43 , 0x5D , 0x4B , 0xBF , 0xD0 , 0x5C , 0xDF , 0x3D , 0x12 , 0x6D , 0x1C , 0x76 , 0x4E , 0x9F ,
0xBF , 0x14 , 0xC9 , 0x46 , 0x95 , 0x99 , 0x32 , 0x6A , 0xC2 , 0xF1 , 0x89 , 0xA4 , 0xB3 , 0xF3 , 0xA0 , 0xEB ,
0xDA , 0x84 , 0x67 , 0x27 , 0x07 , 0x1F , 0xF6 , 0x19 , 0xAC , 0xF1 , 0xB8 , 0xB6 , 0xCF , 0xAB , 0xF8 , 0x0A ,
0xEE , 0x4D , 0xAC , 0xC2 , 0x39 , 0x63 , 0x50 , 0x13 , 0x7B , 0x51 , 0x3A , 0x50 , 0xE0 , 0x03 , 0x6E , 0xB7 ,
0x17 , 0xEE , 0x58 , 0xCE , 0xF8 , 0x15 , 0x3C , 0x70 , 0xDE , 0xE6 , 0xEB , 0xE6 , 0xD4 , 0x2C , 0x27 , 0xB9 ,
0xCA , 0x15 , 0xCE , 0x2E , 0x31 , 0x54 , 0xF5 , 0x0A , 0x98 , 0x8D , 0x78 , 0xE5 , 0xB6 , 0xF8 , 0xE4 , 0x62 ,
0x43 , 0xAA , 0x37 , 0x93 , 0xFF , 0xE3 , 0xAB , 0x17 , 0xC5 , 0x81 , 0x4F , 0xFD , 0xF1 , 0x84 , 0xE1 , 0x8A ,
0x99 , 0xB0 , 0x1D , 0x85 , 0x80 , 0xA2 , 0x49 , 0x35 , 0x8D , 0xDD , 0xBC , 0x74 , 0x0B , 0xBA , 0x33 , 0x5B ,
0xD5 , 0x7A , 0xB9 , 0x2F , 0x9B , 0x24 , 0xA5 , 0xAB , 0xF6 , 0x1E , 0xE3 , 0xA3 , 0xA8 , 0x0D , 0x1E , 0x48 ,
0xF7 , 0xDB , 0xE2 , 0x54 , 0x65 , 0x43 , 0xA6 , 0xD3 , 0x3F , 0x2C , 0x9B , 0x13 , 0x9A , 0xBE , 0x0F , 0x4D ,
0x51 , 0xC3 , 0x73 , 0xA5 , 0xFE , 0xFC , 0x93 , 0x12 , 0xEF , 0x9C , 0x4D , 0x68 , 0xE3 , 0xDA , 0x52 , 0x67 ,
0x28 , 0x41 , 0x17 , 0x22 , 0x3E , 0x33 , 0xB0 , 0x3A , 0xFB , 0x44 , 0xB0 , 0x2E , 0xA6 , 0xD2 , 0x95 , 0xC0 ,
0x9A , 0xBA , 0x32 , 0xA3 , 0xC5 , 0xFE , 0x86 , 0x5D , 0xC8 , 0xBB , 0xB5 , 0xDE , 0x92 , 0x8C , 0x7D , 0xE4 ,
0x03 , 0xD4 , 0xF9 , 0xAE , 0x41 , 0xE3 , 0xBD , 0x35 , 0x4B , 0x94 , 0x27 , 0xE0 , 0x12 , 0x21 , 0x46 , 0xE9 ,
0x09 ,
} ;
// This section defines and assigns function pointers to the libspotify functions
// The arguments and return values must be in sync with the spotify api
// Please scroll through the ugliness which follows
typedef const char * ( * fptr_sp_error_message_t ) ( sp_error error ) ;
typedef sp_error ( * fptr_sp_session_create_t ) ( const sp_session_config * config , sp_session * * sess ) ;
typedef sp_error ( * fptr_sp_session_release_t ) ( sp_session * sess ) ;
typedef sp_error ( * fptr_sp_session_login_t ) ( sp_session * session , const char * username , const char * password , bool remember_me , const char * blob ) ;
typedef sp_error ( * fptr_sp_session_relogin_t ) ( sp_session * session ) ;
typedef sp_error ( * fptr_sp_session_logout_t ) ( sp_session * session ) ;
typedef sp_error ( * fptr_sp_session_process_events_t ) ( sp_session * session , int * next_timeout ) ;
2015-03-07 13:31:24 -05:00
typedef sp_playlist * ( * fptr_sp_session_starred_create_t ) ( sp_session * session ) ;
2014-03-11 18:20:29 -04:00
typedef sp_playlistcontainer * ( * fptr_sp_session_playlistcontainer_t ) ( sp_session * session ) ;
typedef sp_error ( * fptr_sp_session_player_load_t ) ( sp_session * session , sp_track * track ) ;
typedef sp_error ( * fptr_sp_session_player_unload_t ) ( sp_session * session ) ;
typedef sp_error ( * fptr_sp_session_player_play_t ) ( sp_session * session , bool play ) ;
typedef sp_error ( * fptr_sp_session_player_seek_t ) ( sp_session * session , int offset ) ;
2014-03-23 16:45:06 -04:00
typedef sp_connectionstate ( * fptr_sp_session_connectionstate_t ) ( sp_session * session ) ;
2014-03-29 17:26:46 -04:00
typedef sp_error ( * fptr_sp_session_preferred_bitrate_t ) ( sp_session * session , sp_bitrate bitrate ) ;
2017-08-21 15:19:33 -04:00
typedef const char * ( * fptr_sp_session_user_name_t ) ( sp_session * session ) ;
2014-03-11 18:20:29 -04:00
typedef sp_error ( * fptr_sp_playlistcontainer_add_callbacks_t ) ( sp_playlistcontainer * pc , sp_playlistcontainer_callbacks * callbacks , void * userdata ) ;
typedef int ( * fptr_sp_playlistcontainer_num_playlists_t ) ( sp_playlistcontainer * pc ) ;
typedef sp_playlist * ( * fptr_sp_playlistcontainer_playlist_t ) ( sp_playlistcontainer * pc , int index ) ;
typedef sp_error ( * fptr_sp_playlist_add_callbacks_t ) ( sp_playlist * playlist , sp_playlist_callbacks * callbacks , void * userdata ) ;
typedef const char * ( * fptr_sp_playlist_name_t ) ( sp_playlist * playlist ) ;
typedef sp_error ( * fptr_sp_playlist_remove_callbacks_t ) ( sp_playlist * playlist , sp_playlist_callbacks * callbacks , void * userdata ) ;
typedef int ( * fptr_sp_playlist_num_tracks_t ) ( sp_playlist * playlist ) ;
typedef sp_track * ( * fptr_sp_playlist_track_t ) ( sp_playlist * playlist , int index ) ;
typedef bool ( * fptr_sp_playlist_is_loaded_t ) ( sp_playlist * playlist ) ;
2015-05-29 13:47:53 -04:00
typedef int ( * fptr_sp_playlist_track_create_time_t ) ( sp_playlist * playlist , int index ) ;
2016-03-12 01:39:26 -05:00
typedef sp_user * ( * fptr_sp_playlist_owner_t ) ( sp_playlist * playlist ) ;
2014-03-11 18:20:29 -04:00
typedef sp_error ( * fptr_sp_track_error_t ) ( sp_track * track ) ;
typedef bool ( * fptr_sp_track_is_loaded_t ) ( sp_track * track ) ;
typedef const char * ( * fptr_sp_track_name_t ) ( sp_track * track ) ;
typedef int ( * fptr_sp_track_duration_t ) ( sp_track * track ) ;
typedef int ( * fptr_sp_track_index_t ) ( sp_track * track ) ;
typedef int ( * fptr_sp_track_disc_t ) ( sp_track * track ) ;
typedef sp_album * ( * fptr_sp_track_album_t ) ( sp_track * track ) ;
2014-04-03 14:44:36 -04:00
typedef sp_track_availability ( * fptr_sp_track_get_availability_t ) ( sp_session * session , sp_track * track ) ;
2015-03-07 12:30:32 -05:00
typedef bool ( * fptr_sp_track_is_starred_t ) ( sp_session * session , sp_track * track ) ;
2014-03-11 18:20:29 -04:00
typedef sp_link * ( * fptr_sp_link_create_from_playlist_t ) ( sp_playlist * playlist ) ;
typedef sp_link * ( * fptr_sp_link_create_from_track_t ) ( sp_track * track , int offset ) ;
typedef sp_link * ( * fptr_sp_link_create_from_string_t ) ( const char * link ) ;
typedef int ( * fptr_sp_link_as_string_t ) ( sp_link * link , char * buffer , int buffer_size ) ;
typedef sp_track * ( * fptr_sp_link_as_track_t ) ( sp_link * link ) ;
typedef sp_error ( * fptr_sp_link_release_t ) ( sp_link * link ) ;
typedef const char * ( * fptr_sp_album_name_t ) ( sp_album * album ) ;
typedef sp_artist * ( * fptr_sp_album_artist_t ) ( sp_album * album ) ;
typedef int ( * fptr_sp_album_year_t ) ( sp_album * album ) ;
typedef sp_albumtype ( * fptr_sp_album_type_t ) ( sp_album * album ) ;
2014-04-04 17:14:43 -04:00
typedef const byte * ( * fptr_sp_album_cover_t ) ( sp_album * album , sp_image_size size ) ;
2014-03-11 18:20:29 -04:00
typedef const char * ( * fptr_sp_artist_name_t ) ( sp_artist * artist ) ;
2014-04-04 17:14:43 -04:00
typedef sp_image * ( * fptr_sp_image_create_t ) ( sp_session * session , const byte image_id [ 20 ] ) ;
typedef bool ( * fptr_sp_image_is_loaded_t ) ( sp_image * image ) ;
typedef sp_error ( * fptr_sp_image_error_t ) ( sp_image * image ) ;
typedef sp_imageformat ( * fptr_sp_image_format_t ) ( sp_image * image ) ;
typedef const void * ( * fptr_sp_image_data_t ) ( sp_image * image , size_t * data_size ) ;
typedef sp_error ( * fptr_sp_image_release_t ) ( sp_image * image ) ;
2016-01-07 16:25:25 -05:00
typedef sp_error ( * fptr_sp_image_add_load_callback_t ) ( sp_image * image , image_loaded_cb * callback , void * userdata ) ;
typedef sp_error ( * fptr_sp_image_remove_load_callback_t ) ( sp_image * image , image_loaded_cb * callback , void * userdata ) ;
2014-04-04 17:14:43 -04:00
2016-03-12 01:39:26 -05:00
typedef const char * ( * fptr_sp_user_display_name_t ) ( sp_user * user ) ;
typedef const char * ( * fptr_sp_user_canonical_name_t ) ( sp_user * user ) ;
2014-03-11 18:20:29 -04:00
/* Define actual function pointers */
fptr_sp_error_message_t fptr_sp_error_message ;
fptr_sp_session_create_t fptr_sp_session_create ;
fptr_sp_session_release_t fptr_sp_session_release ;
fptr_sp_session_login_t fptr_sp_session_login ;
fptr_sp_session_relogin_t fptr_sp_session_relogin ;
fptr_sp_session_logout_t fptr_sp_session_logout ;
2015-03-07 13:31:24 -05:00
fptr_sp_session_starred_create_t fptr_sp_session_starred_create ;
2014-03-11 18:20:29 -04:00
fptr_sp_session_playlistcontainer_t fptr_sp_session_playlistcontainer ;
fptr_sp_session_process_events_t fptr_sp_session_process_events ;
fptr_sp_session_player_load_t fptr_sp_session_player_load ;
fptr_sp_session_player_unload_t fptr_sp_session_player_unload ;
fptr_sp_session_player_play_t fptr_sp_session_player_play ;
fptr_sp_session_player_seek_t fptr_sp_session_player_seek ;
2014-03-23 16:45:06 -04:00
fptr_sp_session_connectionstate_t fptr_sp_session_connectionstate ;
2014-03-29 17:26:46 -04:00
fptr_sp_session_preferred_bitrate_t fptr_sp_session_preferred_bitrate ;
2017-08-21 15:19:33 -04:00
fptr_sp_session_user_name_t fptr_sp_session_user_name ;
2014-03-11 18:20:29 -04:00
fptr_sp_playlistcontainer_add_callbacks_t fptr_sp_playlistcontainer_add_callbacks ;
fptr_sp_playlistcontainer_num_playlists_t fptr_sp_playlistcontainer_num_playlists ;
fptr_sp_playlistcontainer_playlist_t fptr_sp_playlistcontainer_playlist ;
fptr_sp_playlist_add_callbacks_t fptr_sp_playlist_add_callbacks ;
fptr_sp_playlist_name_t fptr_sp_playlist_name ;
fptr_sp_playlist_remove_callbacks_t fptr_sp_playlist_remove_callbacks ;
fptr_sp_playlist_num_tracks_t fptr_sp_playlist_num_tracks ;
fptr_sp_playlist_track_t fptr_sp_playlist_track ;
fptr_sp_playlist_is_loaded_t fptr_sp_playlist_is_loaded ;
2015-05-29 13:47:53 -04:00
fptr_sp_playlist_track_create_time_t fptr_sp_playlist_track_create_time ;
2016-03-12 01:39:26 -05:00
fptr_sp_playlist_owner_t fptr_sp_playlist_owner ;
2014-03-11 18:20:29 -04:00
fptr_sp_track_error_t fptr_sp_track_error ;
fptr_sp_track_is_loaded_t fptr_sp_track_is_loaded ;
fptr_sp_track_name_t fptr_sp_track_name ;
fptr_sp_track_duration_t fptr_sp_track_duration ;
fptr_sp_track_index_t fptr_sp_track_index ;
fptr_sp_track_disc_t fptr_sp_track_disc ;
fptr_sp_track_album_t fptr_sp_track_album ;
2014-04-03 14:44:36 -04:00
fptr_sp_track_get_availability_t fptr_sp_track_get_availability ;
2015-03-07 12:30:32 -05:00
fptr_sp_track_is_starred_t fptr_sp_track_is_starred ;
2014-03-11 18:20:29 -04:00
fptr_sp_link_create_from_playlist_t fptr_sp_link_create_from_playlist ;
fptr_sp_link_create_from_track_t fptr_sp_link_create_from_track ;
fptr_sp_link_create_from_string_t fptr_sp_link_create_from_string ;
fptr_sp_link_as_string_t fptr_sp_link_as_string ;
fptr_sp_link_as_track_t fptr_sp_link_as_track ;
fptr_sp_link_release_t fptr_sp_link_release ;
fptr_sp_album_name_t fptr_sp_album_name ;
fptr_sp_album_artist_t fptr_sp_album_artist ;
fptr_sp_album_year_t fptr_sp_album_year ;
fptr_sp_album_type_t fptr_sp_album_type ;
2014-04-04 17:14:43 -04:00
fptr_sp_album_cover_t fptr_sp_album_cover ;
2014-03-11 18:20:29 -04:00
fptr_sp_artist_name_t fptr_sp_artist_name ;
2014-04-04 17:14:43 -04:00
fptr_sp_image_create_t fptr_sp_image_create ;
fptr_sp_image_is_loaded_t fptr_sp_image_is_loaded ;
fptr_sp_image_error_t fptr_sp_image_error ;
fptr_sp_image_format_t fptr_sp_image_format ;
fptr_sp_image_data_t fptr_sp_image_data ;
fptr_sp_image_release_t fptr_sp_image_release ;
2016-01-07 16:25:25 -05:00
fptr_sp_image_add_load_callback_t fptr_sp_image_add_load_callback ;
fptr_sp_image_remove_load_callback_t fptr_sp_image_remove_load_callback ;
2014-04-04 17:14:43 -04:00
2016-03-12 01:39:26 -05:00
fptr_sp_user_display_name_t fptr_sp_user_display_name ;
fptr_sp_user_canonical_name_t fptr_sp_user_canonical_name ;
2014-03-11 18:20:29 -04:00
/* Assign function pointers to libspotify symbol */
static int
fptr_assign_all ( )
{
void * h ;
char * err ;
int ret ;
h = g_libhandle ;
// The following is non-ISO compliant
ret = ( fptr_sp_error_message = dlsym ( h , " sp_error_message " ) )
& & ( fptr_sp_session_create = dlsym ( h , " sp_session_create " ) )
& & ( fptr_sp_session_release = dlsym ( h , " sp_session_release " ) )
& & ( fptr_sp_session_login = dlsym ( h , " sp_session_login " ) )
& & ( fptr_sp_session_relogin = dlsym ( h , " sp_session_relogin " ) )
& & ( fptr_sp_session_logout = dlsym ( h , " sp_session_logout " ) )
& & ( fptr_sp_session_playlistcontainer = dlsym ( h , " sp_session_playlistcontainer " ) )
& & ( fptr_sp_session_process_events = dlsym ( h , " sp_session_process_events " ) )
& & ( fptr_sp_session_player_load = dlsym ( h , " sp_session_player_load " ) )
& & ( fptr_sp_session_player_unload = dlsym ( h , " sp_session_player_unload " ) )
& & ( fptr_sp_session_player_play = dlsym ( h , " sp_session_player_play " ) )
& & ( fptr_sp_session_player_seek = dlsym ( h , " sp_session_player_seek " ) )
2014-03-23 16:45:06 -04:00
& & ( fptr_sp_session_connectionstate = dlsym ( h , " sp_session_connectionstate " ) )
2014-03-29 17:26:46 -04:00
& & ( fptr_sp_session_preferred_bitrate = dlsym ( h , " sp_session_preferred_bitrate " ) )
2017-08-21 15:19:33 -04:00
& & ( fptr_sp_session_user_name = dlsym ( h , " sp_session_user_name " ) )
2014-03-11 18:20:29 -04:00
& & ( fptr_sp_playlistcontainer_add_callbacks = dlsym ( h , " sp_playlistcontainer_add_callbacks " ) )
& & ( fptr_sp_playlistcontainer_num_playlists = dlsym ( h , " sp_playlistcontainer_num_playlists " ) )
2015-03-07 13:31:24 -05:00
& & ( fptr_sp_session_starred_create = dlsym ( h , " sp_session_starred_create " ) )
2014-03-11 18:20:29 -04:00
& & ( fptr_sp_playlistcontainer_playlist = dlsym ( h , " sp_playlistcontainer_playlist " ) )
& & ( fptr_sp_playlist_add_callbacks = dlsym ( h , " sp_playlist_add_callbacks " ) )
& & ( fptr_sp_playlist_name = dlsym ( h , " sp_playlist_name " ) )
& & ( fptr_sp_playlist_remove_callbacks = dlsym ( h , " sp_playlist_remove_callbacks " ) )
& & ( fptr_sp_playlist_num_tracks = dlsym ( h , " sp_playlist_num_tracks " ) )
& & ( fptr_sp_playlist_track = dlsym ( h , " sp_playlist_track " ) )
& & ( fptr_sp_playlist_is_loaded = dlsym ( h , " sp_playlist_is_loaded " ) )
2015-05-29 13:47:53 -04:00
& & ( fptr_sp_playlist_track_create_time = dlsym ( h , " sp_playlist_track_create_time " ) )
2016-03-12 01:39:26 -05:00
& & ( fptr_sp_playlist_owner = dlsym ( h , " sp_playlist_owner " ) )
2014-03-11 18:20:29 -04:00
& & ( fptr_sp_track_error = dlsym ( h , " sp_track_error " ) )
& & ( fptr_sp_track_is_loaded = dlsym ( h , " sp_track_is_loaded " ) )
& & ( fptr_sp_track_name = dlsym ( h , " sp_track_name " ) )
& & ( fptr_sp_track_duration = dlsym ( h , " sp_track_duration " ) )
& & ( fptr_sp_track_index = dlsym ( h , " sp_track_index " ) )
& & ( fptr_sp_track_disc = dlsym ( h , " sp_track_disc " ) )
& & ( fptr_sp_track_album = dlsym ( h , " sp_track_album " ) )
2014-04-03 14:44:36 -04:00
& & ( fptr_sp_track_get_availability = dlsym ( h , " sp_track_get_availability " ) )
2015-03-07 12:30:32 -05:00
& & ( fptr_sp_track_is_starred = dlsym ( h , " sp_track_is_starred " ) )
2014-03-11 18:20:29 -04:00
& & ( fptr_sp_link_create_from_playlist = dlsym ( h , " sp_link_create_from_playlist " ) )
& & ( fptr_sp_link_create_from_track = dlsym ( h , " sp_link_create_from_track " ) )
& & ( fptr_sp_link_create_from_string = dlsym ( h , " sp_link_create_from_string " ) )
& & ( fptr_sp_link_as_string = dlsym ( h , " sp_link_as_string " ) )
& & ( fptr_sp_link_as_track = dlsym ( h , " sp_link_as_track " ) )
& & ( fptr_sp_link_release = dlsym ( h , " sp_link_release " ) )
& & ( fptr_sp_album_name = dlsym ( h , " sp_album_name " ) )
& & ( fptr_sp_album_artist = dlsym ( h , " sp_album_artist " ) )
& & ( fptr_sp_album_year = dlsym ( h , " sp_album_year " ) )
& & ( fptr_sp_album_type = dlsym ( h , " sp_album_type " ) )
2014-04-04 17:14:43 -04:00
& & ( fptr_sp_album_cover = dlsym ( h , " sp_album_cover " ) )
2014-03-11 18:20:29 -04:00
& & ( fptr_sp_artist_name = dlsym ( h , " sp_artist_name " ) )
2014-04-04 17:14:43 -04:00
& & ( fptr_sp_image_create = dlsym ( h , " sp_image_create " ) )
& & ( fptr_sp_image_is_loaded = dlsym ( h , " sp_image_is_loaded " ) )
& & ( fptr_sp_image_error = dlsym ( h , " sp_image_error " ) )
& & ( fptr_sp_image_format = dlsym ( h , " sp_image_format " ) )
& & ( fptr_sp_image_data = dlsym ( h , " sp_image_data " ) )
& & ( fptr_sp_image_release = dlsym ( h , " sp_image_release " ) )
2016-01-07 16:25:25 -05:00
& & ( fptr_sp_image_add_load_callback = dlsym ( h , " sp_image_add_load_callback " ) )
& & ( fptr_sp_image_remove_load_callback = dlsym ( h , " sp_image_remove_load_callback " ) )
2016-03-12 01:39:26 -05:00
& & ( fptr_sp_user_display_name = dlsym ( h , " sp_user_display_name " ) )
& & ( fptr_sp_user_canonical_name = dlsym ( h , " sp_user_canonical_name " ) )
2014-03-11 18:20:29 -04:00
;
err = dlerror ( ) ;
if ( ret & & ! err )
return ret ;
else if ( err )
DPRINTF ( E_LOG , L_SPOTIFY , " Assignment error (%d): %s \n " , ret , err ) ;
else
DPRINTF ( E_LOG , L_SPOTIFY , " Unknown assignment error (%d) \n " , ret ) ;
return - 1 ;
}
// End of ugly part
2017-01-04 14:27:55 -05:00
static enum command_state
2017-01-05 16:09:19 -05:00
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 ) ;
2017-03-11 03:44:54 -05:00
static void
create_base_playlist ( ) ;
2017-01-04 14:27:55 -05:00
2016-11-05 08:44:23 -04:00
2014-03-11 18:20:29 -04:00
/* -------------------------- PLAYLIST HELPERS ------------------------- */
2014-03-23 16:45:06 -04:00
/* Should only be called from within the spotify thread */
2014-03-11 18:20:29 -04:00
2016-12-31 10:56:35 -05:00
/*
* 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 ;
}
2016-01-16 11:02:03 -05:00
static int
2016-11-13 14:28:29 -05:00
spotify_cleanup_files ( void )
2016-01-16 11:02:03 -05:00
{
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 ;
}
2016-11-05 08:44:23 -04:00
// Registers a track with libspotify, which will make it start loading the track
// metadata. When that is done metadata_updated() is called (but we won't be
// told which track it was...). Note that this function will result in a ref
// count on the sp_link, which the caller must decrease with sp_link_release.
static enum command_state
2017-01-01 05:06:00 -05:00
uri_register ( void * arg , int * retval )
2016-11-05 08:44:23 -04:00
{
sp_link * link ;
sp_track * track ;
char * uri = arg ;
if ( SP_CONNECTION_STATE_LOGGED_IN ! = fptr_sp_session_connectionstate ( g_sess ) )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Can't register music, not connected and logged in to Spotify \n " ) ;
* retval = - 1 ;
return COMMAND_END ;
}
link = fptr_sp_link_create_from_string ( uri ) ;
if ( ! link )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Invalid Spotify link: '%s' \n " , uri ) ;
* retval = - 1 ;
return COMMAND_END ;
}
track = fptr_sp_link_as_track ( link ) ;
if ( ! track )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Invalid Spotify track: '%s' \n " , uri ) ;
* retval = - 1 ;
return COMMAND_END ;
}
* retval = 0 ;
return COMMAND_END ;
}
2017-01-05 16:09:19 -05:00
static void
webapi_playlist_updated ( sp_playlist * pl )
2016-11-05 08:44:23 -04:00
{
2017-01-05 16:09:19 -05:00
sp_link * link ;
char url [ 1024 ] ;
int ret ;
2016-11-05 08:44:23 -04:00
2017-01-05 16:09:19 -05:00
if ( ! scanning )
2016-11-05 08:44:23 -04:00
{
2017-01-05 16:09:19 -05:00
// 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 ;
}
2016-11-05 08:44:23 -04:00
2017-01-05 16:09:19 -05:00
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 ) ;
2016-11-13 14:28:29 -05:00
2017-01-05 16:09:19 -05:00
library_exec_async ( webapi_pl_save , strdup ( url ) ) ;
2016-11-05 08:44:23 -04:00
}
}
2014-03-11 18:20:29 -04:00
/* -------------------------- PLAYLIST CALLBACKS ------------------------- */
/**
* Called when a playlist is updating or is done updating
*
* This is called before and after a series of changes are applied to the
* playlist . It allows e . g . the user interface to defer updating until the
* entire operation is complete .
*
* @ param [ in ] pl Playlist object
* @ param [ in ] done True iff the update is completed
* @ param [ in ] userdata Userdata passed to sp_playlist_add_callbacks ( )
*/
static void playlist_update_in_progress ( sp_playlist * pl , bool done , void * userdata )
2016-11-13 14:28:29 -05:00
{
2014-03-11 18:20:29 -04:00
if ( done )
{
2014-03-18 04:57:49 -04:00
DPRINTF ( E_DBG , L_SPOTIFY , " Playlist update (status %d): %s \n " , done , fptr_sp_playlist_name ( pl ) ) ;
2016-11-13 14:28:29 -05:00
2017-01-05 16:09:19 -05:00
if ( spotify_access_token_valid )
2017-01-18 14:28:56 -05:00
{
2017-01-05 16:09:19 -05:00
webapi_playlist_updated ( pl ) ;
}
2014-03-11 18:20:29 -04:00
}
2016-11-13 14:28:29 -05:00
}
2016-11-05 08:44:23 -04:00
2014-03-11 18:20:29 -04:00
static void playlist_metadata_updated ( sp_playlist * pl , void * userdata )
2016-11-05 08:44:23 -04:00
{
2014-03-11 18:20:29 -04:00
DPRINTF ( E_DBG , L_SPOTIFY , " Playlist metadata updated: %s \n " , fptr_sp_playlist_name ( pl ) ) ;
2016-11-05 08:44:23 -04:00
2017-01-05 16:09:19 -05:00
if ( spotify_access_token_valid )
{
//TODO Update disabled to prevent multiple triggering of updates e. g. on adding a playlist
//webapi_playlist_updated(pl);
}
2014-03-11 18:20:29 -04:00
}
2016-11-05 08:44:23 -04:00
2014-03-11 18:20:29 -04:00
/**
* The callbacks we are interested in for individual playlists .
*/
static sp_playlist_callbacks pl_callbacks = {
. playlist_update_in_progress = & playlist_update_in_progress ,
. playlist_metadata_updated = & playlist_metadata_updated ,
} ;
2016-11-05 08:44:23 -04:00
2014-03-11 18:20:29 -04:00
/* -------------------- PLAYLIST CONTAINER CALLBACKS --------------------- */
/**
* Callback from libspotify , telling us a playlist was added to the playlist container .
*
* We add our playlist callbacks to the newly added playlist .
*
* @ param pc The playlist container handle
* @ param pl The playlist handle
* @ param position Index of the added playlist
* @ param userdata The opaque pointer
*/
static void playlist_added ( sp_playlistcontainer * pc , sp_playlist * pl ,
int position , void * userdata )
2016-11-05 08:44:23 -04:00
{
2014-03-11 18:20:29 -04:00
DPRINTF ( E_INFO , L_SPOTIFY , " Playlist added: %s (%d tracks) \n " , fptr_sp_playlist_name ( pl ) , fptr_sp_playlist_num_tracks ( pl ) ) ;
fptr_sp_playlist_add_callbacks ( pl , & pl_callbacks , NULL ) ;
2016-11-05 08:44:23 -04:00
2017-01-05 16:09:19 -05:00
if ( spotify_access_token_valid )
2016-11-05 08:44:23 -04:00
{
2017-01-05 16:09:19 -05:00
webapi_playlist_updated ( pl ) ;
2016-11-05 08:44:23 -04:00
}
2017-01-05 16:09:19 -05:00
}
2016-11-05 08:44:23 -04:00
2017-01-05 16:09:19 -05:00
static int
playlist_remove ( const char * uri )
{
struct playlist_info * pli ;
int plid ;
2016-11-05 08:44:23 -04:00
2017-01-05 16:09:19 -05:00
pli = db_pl_fetch_bypath ( uri ) ;
2016-11-05 08:44:23 -04:00
2017-01-05 16:09:19 -05:00
if ( ! pli )
2016-11-05 08:44:23 -04:00
{
2017-01-13 12:28:36 -05:00
DPRINTF ( E_LOG , L_SPOTIFY , " Playlist '%s' not found, can't delete \n " , uri ) ;
2017-01-05 16:09:19 -05:00
return - 1 ;
2016-11-05 08:44:23 -04:00
}
2017-01-13 12:28:36 -05:00
DPRINTF ( E_LOG , L_SPOTIFY , " Removing playlist '%s' (%s) \n " , pli - > title , uri ) ;
2016-11-05 08:44:23 -04:00
2017-01-05 16:09:19 -05:00
plid = pli - > id ;
2016-11-05 08:44:23 -04:00
2017-01-05 16:09:19 -05:00
free_pli ( pli , 0 ) ;
db_spotify_pl_delete ( plid ) ;
spotify_cleanup_files ( ) ;
return 0 ;
2016-11-05 08:44:23 -04:00
}
2014-03-11 18:20:29 -04:00
/**
* Callback from libspotify , telling us a playlist was removed from the playlist container .
*
* This is the place to remove our playlist callbacks .
*
* @ param pc The playlist container handle
* @ param pl The playlist handle
* @ param position Index of the removed playlist
* @ param userdata The opaque pointer
*/
static void
playlist_removed ( sp_playlistcontainer * pc , sp_playlist * pl , int position , void * userdata )
2016-11-05 08:44:23 -04:00
{
2014-03-11 18:20:29 -04:00
sp_link * link ;
char url [ 1024 ] ;
2016-11-05 08:44:23 -04:00
int ret ;
2014-03-11 18:20:29 -04:00
DPRINTF ( E_INFO , L_SPOTIFY , " Playlist removed: %s \n " , fptr_sp_playlist_name ( pl ) ) ;
fptr_sp_playlist_remove_callbacks ( pl , & pl_callbacks , NULL ) ;
2016-11-05 08:44:23 -04:00
2017-10-22 06:05:13 -04:00
if ( spotify_access_token_valid & & ! scanning )
2016-11-05 08:44:23 -04:00
{
2017-10-22 06:05:13 -04:00
link = fptr_sp_link_create_from_playlist ( pl ) ;
if ( ! link )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Could not find link for deleted playlist \n " ) ;
return ;
}
2016-11-05 08:44:23 -04:00
2017-10-22 06:05:13 -04:00
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 ) ;
2016-11-05 08:44:23 -04:00
2017-01-05 16:09:19 -05:00
// Run playlist remove in the library thread
2017-10-22 06:05:13 -04:00
library_exec_async ( webapi_pl_remove , strdup ( url ) ) ;
2016-11-05 08:44:23 -04:00
}
2014-03-11 18:20:29 -04:00
}
/**
* Callback from libspotify , telling us the rootlist is fully synchronized
*
* @ param pc The playlist container handle
* @ param userdata The opaque pointer
*/
static void
container_loaded ( sp_playlistcontainer * pc , void * userdata )
{
int num ;
num = fptr_sp_playlistcontainer_num_playlists ( pc ) ;
DPRINTF ( E_INFO , L_SPOTIFY , " Rootlist synchronized (%d playlists) \n " , num ) ;
}
/**
* The playlist container callbacks
*/
static sp_playlistcontainer_callbacks pc_callbacks = {
. playlist_added = & playlist_added ,
. playlist_removed = & playlist_removed ,
. container_loaded = & container_loaded ,
} ;
2014-03-23 16:45:06 -04:00
/* --------------------- INTERNAL PLAYBACK AND AUDIO ----------------------- */
/* Should only be called from within the spotify thread */
2016-05-08 03:54:58 -04:00
static enum command_state
playback_setup ( void * arg , int * retval )
2014-03-23 16:45:06 -04:00
{
2016-05-08 03:54:58 -04:00
sp_link * link ;
2014-03-23 16:45:06 -04:00
sp_track * track ;
sp_error err ;
2014-03-11 18:20:29 -04:00
2015-09-12 01:45:31 -04:00
DPRINTF ( E_DBG , L_SPOTIFY , " Setting up for playback \n " ) ;
2014-03-11 18:20:29 -04:00
2016-05-08 03:54:58 -04:00
link = ( sp_link * ) arg ;
2014-03-23 16:45:06 -04:00
if ( SP_CONNECTION_STATE_LOGGED_IN ! = fptr_sp_session_connectionstate ( g_sess ) )
2014-03-11 18:20:29 -04:00
{
2014-03-23 16:45:06 -04:00
DPRINTF ( E_LOG , L_SPOTIFY , " Can't play music, not connected and logged in to Spotify \n " ) ;
2016-05-08 03:54:58 -04:00
* retval = - 1 ;
return COMMAND_END ;
2014-03-11 18:20:29 -04:00
}
2016-05-08 03:54:58 -04:00
if ( ! link )
2014-03-11 18:20:29 -04:00
{
2014-03-23 16:45:06 -04:00
DPRINTF ( E_LOG , L_SPOTIFY , " Playback setup failed, no Spotify link \n " ) ;
2016-05-08 03:54:58 -04:00
* retval = - 1 ;
return COMMAND_END ;
2014-03-11 18:20:29 -04:00
}
2016-05-08 03:54:58 -04:00
track = fptr_sp_link_as_track ( link ) ;
2014-03-23 16:45:06 -04:00
if ( ! track )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Playback setup failed, invalid Spotify track \n " ) ;
2016-05-08 03:54:58 -04:00
* retval = - 1 ;
return COMMAND_END ;
2014-03-23 16:45:06 -04:00
}
2016-11-05 08:44:23 -04:00
2014-03-23 16:45:06 -04:00
err = fptr_sp_session_player_load ( g_sess , track ) ;
if ( SP_ERROR_OK ! = err )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Playback setup failed: %s \n " , fptr_sp_error_message ( err ) ) ;
2016-05-08 03:54:58 -04:00
* retval = - 1 ;
return COMMAND_END ;
2014-03-23 16:45:06 -04:00
}
2014-03-11 18:20:29 -04:00
2016-05-08 03:54:58 -04:00
* retval = 0 ;
return COMMAND_END ;
2015-09-12 01:45:31 -04:00
}
2016-05-08 03:54:58 -04:00
static enum command_state
playback_play ( void * arg , int * retval )
2015-09-12 01:45:31 -04:00
{
sp_error err ;
DPRINTF ( E_DBG , L_SPOTIFY , " Starting playback \n " ) ;
2014-03-23 16:45:06 -04:00
err = fptr_sp_session_player_play ( g_sess , 1 ) ;
if ( SP_ERROR_OK ! = err )
2014-03-11 18:20:29 -04:00
{
2014-03-23 16:45:06 -04:00
DPRINTF ( E_LOG , L_SPOTIFY , " Playback failed: %s \n " , fptr_sp_error_message ( err ) ) ;
2016-05-08 03:54:58 -04:00
* retval = - 1 ;
return COMMAND_END ;
2014-03-23 16:45:06 -04:00
}
2014-03-11 18:20:29 -04:00
2014-03-23 16:45:06 -04:00
g_state = SPOTIFY_STATE_PLAYING ;
2014-03-11 18:20:29 -04:00
2016-05-08 03:54:58 -04:00
* retval = 0 ;
return COMMAND_END ;
2014-03-23 16:45:06 -04:00
}
2014-03-11 18:20:29 -04:00
2016-05-08 03:54:58 -04:00
static enum command_state
playback_pause ( void * arg , int * retval )
2014-03-23 16:45:06 -04:00
{
sp_error err ;
2014-03-11 18:20:29 -04:00
2014-03-23 16:45:06 -04:00
DPRINTF ( E_DBG , L_SPOTIFY , " Pausing playback \n " ) ;
2014-03-11 18:20:29 -04:00
2014-03-23 16:45:06 -04:00
err = fptr_sp_session_player_play ( g_sess , 0 ) ;
DPRINTF ( E_DBG , L_SPOTIFY , " Playback paused \n " ) ;
2014-03-11 18:20:29 -04:00
2014-03-23 16:45:06 -04:00
if ( SP_ERROR_OK ! = err )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Playback pause failed: %s \n " , fptr_sp_error_message ( err ) ) ;
2016-05-08 03:54:58 -04:00
* retval = - 1 ;
return COMMAND_END ;
2014-03-23 16:45:06 -04:00
}
2014-03-11 18:20:29 -04:00
2014-03-23 16:45:06 -04:00
g_state = SPOTIFY_STATE_PAUSED ;
2014-03-11 18:20:29 -04:00
2016-05-08 03:54:58 -04:00
* retval = 0 ;
return COMMAND_END ;
2014-03-11 18:20:29 -04:00
}
2016-05-08 03:54:58 -04:00
static enum command_state
playback_stop ( void * arg , int * retval )
2014-03-23 16:45:06 -04:00
{
sp_error err ;
DPRINTF ( E_DBG , L_SPOTIFY , " Stopping playback \n " ) ;
err = fptr_sp_session_player_unload ( g_sess ) ;
if ( SP_ERROR_OK ! = err )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Playback stop failed: %s \n " , fptr_sp_error_message ( err ) ) ;
2016-05-08 03:54:58 -04:00
* retval = - 1 ;
return COMMAND_END ;
2014-03-23 16:45:06 -04:00
}
g_state = SPOTIFY_STATE_STOPPED ;
2017-01-30 16:50:02 -05:00
evbuffer_drain ( spotify_audio_buffer , evbuffer_get_length ( spotify_audio_buffer ) ) ;
2016-05-08 03:54:58 -04:00
* retval = 0 ;
return COMMAND_END ;
2014-03-23 16:45:06 -04:00
}
2016-05-08 03:54:58 -04:00
static enum command_state
playback_seek ( void * arg , int * retval )
2014-03-23 16:45:06 -04:00
{
2016-05-08 03:54:58 -04:00
int seek_ms ;
2014-03-23 16:45:06 -04:00
sp_error err ;
DPRINTF ( E_DBG , L_SPOTIFY , " Playback seek \n " ) ;
2016-05-08 03:54:58 -04:00
seek_ms = * ( ( int * ) arg ) ;
err = fptr_sp_session_player_seek ( g_sess , seek_ms ) ;
2014-03-23 16:45:06 -04:00
if ( SP_ERROR_OK ! = err )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Could not seek: %s \n " , fptr_sp_error_message ( err ) ) ;
2016-05-08 03:54:58 -04:00
* retval = - 1 ;
return COMMAND_END ;
2014-03-23 16:45:06 -04:00
}
2016-05-08 03:54:58 -04:00
* retval = 0 ;
return COMMAND_END ;
2014-03-23 16:45:06 -04:00
}
2016-05-08 03:54:58 -04:00
static enum command_state
playback_eot ( void * arg , int * retval )
2014-03-23 16:45:06 -04:00
{
sp_error err ;
DPRINTF ( E_DBG , L_SPOTIFY , " Playback end of track \n " ) ;
err = fptr_sp_session_player_unload ( g_sess ) ;
if ( SP_ERROR_OK ! = err )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Playback end of track failed: %s \n " , fptr_sp_error_message ( err ) ) ;
2016-08-06 00:26:38 -04:00
* retval = - 1 ;
return COMMAND_END ;
2014-03-23 16:45:06 -04:00
}
g_state = SPOTIFY_STATE_STOPPING ;
2016-12-28 18:39:23 -05:00
// TODO 1) This will block for a while, but perhaps ok?
input_write ( spotify_audio_buffer , INPUT_FLAG_EOF ) ;
2014-03-23 16:45:06 -04:00
2016-12-28 18:39:23 -05:00
* retval = 0 ;
2016-05-08 03:54:58 -04:00
return COMMAND_END ;
2014-03-23 16:45:06 -04:00
}
2016-01-07 16:25:25 -05:00
static void
artwork_loaded_cb ( sp_image * image , void * userdata )
{
2016-05-08 03:54:58 -04:00
struct artwork_get_param * artwork ;
2017-01-13 17:32:59 -05:00
2016-05-08 03:54:58 -04:00
artwork = userdata ;
2017-01-13 17:32:59 -05:00
2017-01-21 10:11:20 -05:00
CHECK_ERR ( L_SPOTIFY , pthread_mutex_lock ( & artwork - > mutex ) ) ;
2016-01-08 18:36:30 -05:00
2016-05-08 03:54:58 -04:00
artwork - > is_loaded = 1 ;
2016-01-08 18:36:30 -05:00
2017-01-21 10:11:20 -05:00
CHECK_ERR ( L_SPOTIFY , pthread_cond_signal ( & artwork - > cond ) ) ;
CHECK_ERR ( L_SPOTIFY , pthread_mutex_unlock ( & artwork - > mutex ) ) ;
2016-01-08 18:36:30 -05:00
}
2016-05-08 03:54:58 -04:00
static enum command_state
artwork_get_bh ( void * arg , int * retval )
2016-01-08 18:36:30 -05:00
{
2016-05-08 03:54:58 -04:00
struct artwork_get_param * artwork ;
2016-01-08 18:36:30 -05:00
sp_imageformat imageformat ;
2016-01-07 16:25:25 -05:00
sp_error err ;
2016-01-08 18:36:30 -05:00
const void * data ;
2017-10-28 18:36:56 -04:00
char * path ;
2016-01-08 18:36:30 -05:00
size_t data_size ;
int ret ;
2016-01-07 16:25:25 -05:00
2016-05-08 03:54:58 -04:00
artwork = arg ;
sp_image * image = artwork - > image ;
2017-10-28 18:36:56 -04:00
path = artwork - > path ;
2016-01-07 16:25:25 -05:00
2017-02-07 17:23:39 -05:00
fptr_sp_image_remove_load_callback ( image , artwork_loaded_cb , artwork ) ;
2016-01-07 16:25:25 -05:00
err = fptr_sp_image_error ( image ) ;
if ( err ! = SP_ERROR_OK )
{
2016-01-08 18:36:30 -05:00
DPRINTF ( E_WARN , L_SPOTIFY , " Getting artwork (%s) failed, Spotify error: %s \n " , path , fptr_sp_error_message ( err ) ) ;
goto fail ;
2016-01-07 16:25:25 -05:00
}
2016-01-08 18:36:30 -05:00
if ( ! fptr_sp_image_is_loaded ( image ) )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Load callback returned, but no image? Possible bug: %s \n " , path ) ;
goto fail ;
}
imageformat = fptr_sp_image_format ( image ) ;
if ( imageformat ! = SP_IMAGE_FORMAT_JPEG )
{
DPRINTF ( E_WARN , L_SPOTIFY , " Getting artwork failed, invalid image format from Spotify: %s \n " , path ) ;
goto fail ;
}
2017-10-28 18:36:56 -04:00
data_size = 0 ;
2016-01-08 18:36:30 -05:00
data = fptr_sp_image_data ( image , & data_size ) ;
2017-10-28 18:36:56 -04:00
if ( ! data )
2016-01-08 18:36:30 -05:00
{
DPRINTF ( E_LOG , L_SPOTIFY , " Getting artwork failed, no image data from Spotify: %s \n " , path ) ;
goto fail ;
}
2017-10-28 18:36:56 -04:00
if ( ( data_size < 200 ) | | ( data_size > 20000000 ) )
{
// Sometimes we get strange data size even though fptr_sp_image_data returns success
DPRINTF ( E_LOG , L_SPOTIFY , " Skipping artwork, data size is weird (%zu) \n " , data_size ) ;
goto fail ;
}
2016-05-08 03:54:58 -04:00
ret = evbuffer_expand ( artwork - > evbuf , data_size ) ;
2016-01-08 18:36:30 -05:00
if ( ret < 0 )
{
2017-10-28 18:36:56 -04:00
DPRINTF ( E_LOG , L_SPOTIFY , " Out of memory for artwork (data size requested was %zu) \n " , data_size ) ;
2016-01-08 18:36:30 -05:00
goto fail ;
}
2016-05-08 03:54:58 -04:00
ret = evbuffer_add ( artwork - > evbuf , data , data_size ) ;
2016-01-08 18:36:30 -05:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Could not add Spotify image to event buffer \n " ) ;
goto fail ;
}
DPRINTF ( E_DBG , L_SPOTIFY , " Spotify artwork loaded ok \n " ) ;
fptr_sp_image_release ( image ) ;
2016-05-08 03:54:58 -04:00
* retval = 0 ;
return COMMAND_END ;
2016-01-08 18:36:30 -05:00
fail :
fptr_sp_image_release ( image ) ;
2016-05-08 03:54:58 -04:00
* retval = - 1 ;
return COMMAND_END ;
2016-01-07 16:25:25 -05:00
}
2016-05-08 03:54:58 -04:00
static enum command_state
artwork_get ( void * arg , int * retval )
2014-04-04 17:14:43 -04:00
{
2016-05-08 03:54:58 -04:00
struct artwork_get_param * artwork ;
2014-04-04 17:14:43 -04:00
char * path ;
sp_link * link ;
sp_track * track ;
sp_album * album ;
const byte * image_id ;
sp_image * image ;
sp_image_size image_size ;
sp_error err ;
2016-05-08 03:54:58 -04:00
artwork = arg ;
path = artwork - > path ;
2014-04-04 17:14:43 -04:00
// Now begins: path -> link -> track -> album -> image_id -> image -> format -> data
link = fptr_sp_link_create_from_string ( path ) ;
if ( ! link )
{
DPRINTF ( E_WARN , L_SPOTIFY , " Getting artwork failed, invalid Spotify link: %s \n " , path ) ;
goto level1_exit ;
}
track = fptr_sp_link_as_track ( link ) ;
if ( ! track )
{
DPRINTF ( E_WARN , L_SPOTIFY , " Getting artwork failed, invalid Spotify track: %s \n " , path ) ;
goto level2_exit ;
}
album = fptr_sp_track_album ( track ) ;
if ( ! album )
{
DPRINTF ( E_WARN , L_SPOTIFY , " Getting artwork failed, invalid Spotify album: %s \n " , path ) ;
goto level2_exit ;
}
2014-11-11 05:37:03 -05:00
// Get an image at least the same size as requested
2014-04-04 17:14:43 -04:00
image_size = SP_IMAGE_SIZE_SMALL ; // 64x64
2016-05-08 03:54:58 -04:00
if ( ( artwork - > max_w > 64 ) | | ( artwork - > max_h > 64 ) )
2014-04-04 17:14:43 -04:00
image_size = SP_IMAGE_SIZE_NORMAL ; // 300x300
2016-05-08 03:54:58 -04:00
if ( ( artwork - > max_w > 300 ) | | ( artwork - > max_h > 300 ) )
2014-04-04 17:14:43 -04:00
image_size = SP_IMAGE_SIZE_LARGE ; // 640x640
image_id = fptr_sp_album_cover ( album , image_size ) ;
if ( ! image_id )
{
DPRINTF ( E_DBG , L_SPOTIFY , " Getting artwork failed, no Spotify image id: %s \n " , path ) ;
goto level2_exit ;
}
image = fptr_sp_image_create ( g_sess , image_id ) ;
if ( ! image )
{
DPRINTF ( E_DBG , L_SPOTIFY , " Getting artwork failed, no Spotify image: %s \n " , path ) ;
goto level2_exit ;
}
2016-01-08 18:36:30 -05:00
fptr_sp_link_release ( link ) ;
2014-04-04 17:14:43 -04:00
2016-05-08 03:54:58 -04:00
artwork - > image = image ;
artwork - > is_loaded = fptr_sp_image_is_loaded ( image ) ;
2014-04-04 17:14:43 -04:00
2016-01-08 18:36:30 -05:00
/* If the image is ready we can return it straight away, otherwise we will
* let the calling thread wait , since the Spotify thread should not wait
*/
2016-05-08 03:54:58 -04:00
if ( artwork - > is_loaded )
return artwork_get_bh ( artwork , retval ) ;
2014-04-04 17:14:43 -04:00
2016-01-08 18:36:30 -05:00
DPRINTF ( E_SPAM , L_SPOTIFY , " Will wait for Spotify to call artwork_loaded_cb \n " ) ;
2014-04-04 17:14:43 -04:00
2016-01-08 18:36:30 -05:00
/* Async - we will return to spotify_artwork_get which will wait for callback */
2016-05-08 03:54:58 -04:00
err = fptr_sp_image_add_load_callback ( image , artwork_loaded_cb , artwork ) ;
2016-01-08 18:36:30 -05:00
if ( err ! = SP_ERROR_OK )
2014-04-04 17:14:43 -04:00
{
2016-01-08 18:36:30 -05:00
DPRINTF ( E_WARN , L_SPOTIFY , " Adding artwork cb failed, Spotify error: %s \n " , fptr_sp_error_message ( err ) ) ;
2016-05-08 03:54:58 -04:00
* retval = - 1 ;
return COMMAND_END ;
2014-04-04 17:14:43 -04:00
}
2016-05-08 03:54:58 -04:00
* retval = 0 ;
return COMMAND_END ;
2014-04-04 17:14:43 -04:00
level2_exit :
fptr_sp_link_release ( link ) ;
level1_exit :
2016-05-08 03:54:58 -04:00
* retval = - 1 ;
return COMMAND_END ;
2014-04-04 17:14:43 -04:00
}
2014-03-23 16:45:06 -04:00
/* --------------------------- SESSION CALLBACKS ------------------------- */
/**
* This callback is called when an attempt to login has succeeded or failed .
*
* @ sa sp_session_callbacks # logged_in
*/
static void
logged_in ( sp_session * sess , sp_error error )
{
sp_playlist * pl ;
sp_playlistcontainer * pc ;
int i ;
if ( SP_ERROR_OK ! = error )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Login failed: %s \n " , fptr_sp_error_message ( error ) ) ;
2017-08-19 02:36:43 -04:00
CHECK_ERR ( L_REMOTE , pthread_mutex_lock ( & status_lck ) ) ;
spotify_status_info . libspotify_logged_in = false ;
spotify_status_info . libspotify_user [ 0 ] = ' \0 ' ;
CHECK_ERR ( L_REMOTE , pthread_mutex_unlock ( & status_lck ) ) ;
listener_notify ( LISTENER_SPOTIFY ) ;
2014-03-23 16:45:06 -04:00
return ;
}
2016-11-13 14:28:29 -05:00
DPRINTF ( E_LOG , L_SPOTIFY , " Login to Spotify succeeded, reloading playlists \n " ) ;
2014-03-23 16:45:06 -04:00
2017-03-11 03:44:54 -05:00
if ( ! spotify_access_token_valid )
create_base_playlist ( ) ;
2016-01-30 01:27:20 -05:00
db_directory_enable_bypath ( " /spotify: " ) ;
2015-03-07 13:31:24 -05:00
pl = fptr_sp_session_starred_create ( sess ) ;
fptr_sp_playlist_add_callbacks ( pl , & pl_callbacks , NULL ) ;
2014-03-23 16:45:06 -04:00
pc = fptr_sp_session_playlistcontainer ( sess ) ;
fptr_sp_playlistcontainer_add_callbacks ( pc , & pc_callbacks , NULL ) ;
DPRINTF ( E_DBG , L_SPOTIFY , " Found %d playlists \n " , fptr_sp_playlistcontainer_num_playlists ( pc ) ) ;
for ( i = 0 ; i < fptr_sp_playlistcontainer_num_playlists ( pc ) ; i + + )
{
pl = fptr_sp_playlistcontainer_playlist ( pc , i ) ;
fptr_sp_playlist_add_callbacks ( pl , & pl_callbacks , NULL ) ;
}
2017-08-19 02:36:43 -04:00
CHECK_ERR ( L_REMOTE , pthread_mutex_lock ( & status_lck ) ) ;
spotify_status_info . libspotify_logged_in = true ;
snprintf ( spotify_status_info . libspotify_user , sizeof ( spotify_status_info . libspotify_user ) , " %s " , fptr_sp_session_user_name ( sess ) ) ;
spotify_status_info . libspotify_user [ sizeof ( spotify_status_info . libspotify_user ) - 1 ] = ' \0 ' ;
CHECK_ERR ( L_REMOTE , pthread_mutex_unlock ( & status_lck ) ) ;
listener_notify ( LISTENER_SPOTIFY ) ;
2014-03-23 16:45:06 -04:00
}
2014-12-28 15:08:52 -05:00
/**
* Called when logout has been processed .
* Either called explicitly if you initialize a logout operation , or implicitly
* if there is a permanent connection error
*
* @ sa sp_session_callbacks # logged_out
*/
static void
logged_out ( sp_session * sess )
{
DPRINTF ( E_INFO , L_SPOTIFY , " Logout complete \n " ) ;
2017-08-19 02:36:43 -04:00
CHECK_ERR ( L_REMOTE , pthread_mutex_lock ( & status_lck ) ) ;
spotify_status_info . libspotify_logged_in = false ;
spotify_status_info . libspotify_user [ 0 ] = ' \0 ' ;
CHECK_ERR ( L_REMOTE , pthread_mutex_unlock ( & status_lck ) ) ;
2017-01-21 10:11:20 -05:00
CHECK_ERR ( L_SPOTIFY , pthread_mutex_lock ( & login_lck ) ) ;
2014-12-28 15:08:52 -05:00
2017-01-21 10:11:20 -05:00
CHECK_ERR ( L_SPOTIFY , pthread_cond_signal ( & login_cond ) ) ;
CHECK_ERR ( L_SPOTIFY , pthread_mutex_unlock ( & login_lck ) ) ;
2017-08-19 02:36:43 -04:00
listener_notify ( LISTENER_SPOTIFY ) ;
2014-12-28 15:08:52 -05:00
}
2014-03-23 16:45:06 -04:00
/**
* This callback is used from libspotify whenever there is PCM data available .
*
* @ sa sp_session_callbacks # music_delivery
*/
static int music_delivery ( sp_session * sess , const sp_audioformat * format ,
const void * frames , int num_frames )
2014-03-11 18:20:29 -04:00
{
2016-12-28 18:39:23 -05:00
size_t size ;
int ret ;
2014-03-23 16:45:06 -04:00
/* No support for resampling right now */
if ( ( format - > sample_rate ! = 44100 ) | | ( format - > channels ! = 2 ) )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Got music with unsupported samplerate or channels, stopping playback \n " ) ;
spotify_playback_stop_nonblock ( ) ;
return num_frames ;
}
2016-12-28 18:39:23 -05:00
// Audio discontinuity, e.g. seek
2014-03-23 16:45:06 -04:00
if ( num_frames = = 0 )
{
2016-12-28 18:39:23 -05:00
evbuffer_drain ( spotify_audio_buffer , evbuffer_get_length ( spotify_audio_buffer ) ) ;
2014-03-23 16:45:06 -04:00
return 0 ;
}
2016-12-28 18:39:23 -05:00
size = num_frames * sizeof ( int16_t ) * format - > channels ;
2014-03-23 16:45:06 -04:00
2016-12-28 18:39:23 -05:00
ret = evbuffer_add ( spotify_audio_buffer , frames , size ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Out of memory adding audio to buffer \n " ) ;
return num_frames ;
}
2014-03-23 16:45:06 -04:00
2016-12-28 18:39:23 -05:00
// The input buffer only accepts writing when it is approaching depletion, and
// because we use NONBLOCK it will just return if this is not the case. So in
// most cases no actual write is made and spotify_audio_buffer will just grow.
input_write ( spotify_audio_buffer , INPUT_FLAG_NONBLOCK ) ;
2014-03-23 16:45:06 -04:00
return num_frames ;
2014-03-11 18:20:29 -04:00
}
/**
2014-03-23 16:45:06 -04:00
* This callback is called from an internal libspotify thread to ask us to
* reiterate the main loop . This must not block .
2014-03-11 18:20:29 -04:00
*
2014-03-23 16:45:06 -04:00
* @ sa sp_session_callbacks # notify_main_thread
2014-03-11 18:20:29 -04:00
*/
2014-03-23 16:45:06 -04:00
static void
notify_main_thread ( sp_session * sess )
{
int dummy = 42 ;
int ret ;
ret = write ( g_notify_pipe [ 1 ] , & dummy , sizeof ( dummy ) ) ;
if ( ret ! = sizeof ( dummy ) )
DPRINTF ( E_LOG , L_SPOTIFY , " Could not write to notify fd: %s \n " , strerror ( errno ) ) ;
}
/**
* Called whenever metadata has been updated
*
* If you have metadata cached outside of libspotify , you should purge
* your caches and fetch new versions .
*
* @ param [ in ] session Session
*/
static void metadata_updated ( sp_session * session )
{
DPRINTF ( E_DBG , L_SPOTIFY , " Session metadata updated \n " ) ;
}
/* Misc connection error callbacks */
2014-03-11 18:20:29 -04:00
static void play_token_lost ( sp_session * sess )
{
2014-03-23 16:45:06 -04:00
DPRINTF ( E_LOG , L_SPOTIFY , " Music interrupted - some other session is playing on the account \n " ) ;
spotify_playback_stop_nonblock ( ) ;
}
static void connectionstate_updated ( sp_session * session )
{
if ( SP_CONNECTION_STATE_LOGGED_IN = = fptr_sp_session_connectionstate ( session ) )
{
2016-11-13 14:28:29 -05:00
DPRINTF ( E_LOG , L_SPOTIFY , " Connection to Spotify (re)established, reloading saved tracks \n " ) ;
2014-03-23 16:45:06 -04:00
}
2014-06-22 16:27:52 -04:00
else if ( g_state = = SPOTIFY_STATE_PLAYING )
2014-03-23 16:45:06 -04:00
{
DPRINTF ( E_LOG , L_SPOTIFY , " Music interrupted - connection error or logged out \n " ) ;
spotify_playback_stop_nonblock ( ) ;
}
2014-03-11 18:20:29 -04:00
}
/**
* This callback is used from libspotify when the current track has ended
*
* @ sa sp_session_callbacks # end_of_track
*/
static void end_of_track ( sp_session * sess )
{
2014-03-18 04:57:49 -04:00
DPRINTF ( E_DBG , L_SPOTIFY , " End of track \n " ) ;
2014-03-23 16:45:06 -04:00
2016-05-08 03:54:58 -04:00
commands_exec_async ( cmdbase , playback_eot , NULL ) ;
2014-03-11 18:20:29 -04:00
}
/**
* The session callbacks
*/
static sp_session_callbacks session_callbacks = {
. logged_in = & logged_in ,
2014-12-28 15:08:52 -05:00
. logged_out = & logged_out ,
2014-03-23 16:45:06 -04:00
. connectionstate_updated = & connectionstate_updated ,
2014-03-11 18:20:29 -04:00
. notify_main_thread = & notify_main_thread ,
. music_delivery = & music_delivery ,
. metadata_updated = & metadata_updated ,
. play_token_lost = & play_token_lost ,
. log_message = NULL ,
. end_of_track = & end_of_track ,
} ;
/**
* The session configuration .
*/
static sp_session_config spconfig = {
. api_version = SPOTIFY_API_VERSION ,
. cache_location = NULL ,
. settings_location = NULL ,
. application_key = g_appkey ,
. application_key_size = sizeof ( g_appkey ) ,
. user_agent = " forked-daapd " ,
. callbacks = & session_callbacks ,
NULL ,
} ;
2014-03-23 16:45:06 -04:00
/* ------------------------------- MAIN LOOP ------------------------------- */
/* Thread: spotify */
2014-03-11 18:20:29 -04:00
2014-03-23 16:45:06 -04:00
static void *
spotify ( void * arg )
2014-03-11 18:20:29 -04:00
{
2014-03-23 16:45:06 -04:00
int ret ;
2014-03-11 18:20:29 -04:00
2014-03-23 16:45:06 -04:00
DPRINTF ( E_DBG , L_SPOTIFY , " Main loop initiating \n " ) ;
2014-03-11 18:20:29 -04:00
2014-03-23 16:45:06 -04:00
ret = db_perthread_init ( ) ;
if ( ret < 0 )
2014-03-11 18:20:29 -04:00
{
2014-03-23 16:45:06 -04:00
DPRINTF ( E_LOG , L_SPOTIFY , " Error: DB init failed \n " ) ;
pthread_exit ( NULL ) ;
2014-03-11 18:20:29 -04:00
}
2014-03-23 16:45:06 -04:00
g_state = SPOTIFY_STATE_WAIT ;
2014-03-11 18:20:29 -04:00
2014-03-23 16:45:06 -04:00
event_base_dispatch ( evbase_spotify ) ;
2014-03-11 18:20:29 -04:00
2014-03-23 16:45:06 -04:00
if ( g_state ! = SPOTIFY_STATE_INACTIVE )
2014-03-11 18:20:29 -04:00
{
2014-03-23 16:45:06 -04:00
DPRINTF ( E_LOG , L_SPOTIFY , " Spotify event loop terminated ahead of time! \n " ) ;
g_state = SPOTIFY_STATE_INACTIVE ;
2014-03-11 18:20:29 -04:00
}
2014-03-23 16:45:06 -04:00
db_perthread_deinit ( ) ;
DPRINTF ( E_DBG , L_SPOTIFY , " Main loop terminating \n " ) ;
pthread_exit ( NULL ) ;
2014-03-11 18:20:29 -04:00
}
2014-03-23 16:45:06 -04:00
static void
2016-05-23 00:41:43 -04:00
exit_cb ( )
2014-03-11 18:20:29 -04:00
{
2014-03-23 16:45:06 -04:00
fptr_sp_session_player_unload ( g_sess ) ;
fptr_sp_session_logout ( g_sess ) ;
2016-06-11 04:00:23 -04:00
g_state = SPOTIFY_STATE_INACTIVE ;
2014-03-11 18:20:29 -04:00
}
2014-03-23 16:45:06 -04:00
/* Process events when timeout expires or triggered by libspotify's notify_main_thread */
static void
notify_cb ( int fd , short what , void * arg )
{
struct timeval tv ;
int next_timeout ;
int dummy ;
int ret ;
2014-03-11 18:20:29 -04:00
2014-03-23 16:45:06 -04:00
if ( what & EV_READ )
{
ret = read ( g_notify_pipe [ 0 ] , & dummy , sizeof ( dummy ) ) ;
if ( ret ! = sizeof ( dummy ) )
DPRINTF ( E_LOG , L_SPOTIFY , " Error reading from notify pipe \n " ) ;
2014-03-11 18:20:29 -04:00
}
2014-03-23 16:45:06 -04:00
do
{
fptr_sp_session_process_events ( g_sess , & next_timeout ) ;
}
while ( next_timeout = = 0 ) ;
2014-03-11 18:20:29 -04:00
2014-03-23 16:45:06 -04:00
tv . tv_sec = next_timeout / 1000 ;
tv . tv_usec = ( next_timeout % 1000 ) * 1000 ;
2014-03-11 18:20:29 -04:00
2014-03-23 16:45:06 -04:00
event_add ( g_notifyev , & tv ) ;
2014-03-11 18:20:29 -04:00
}
2014-03-23 16:45:06 -04:00
/* ---------------------------- Our Spotify API --------------------------- */
2014-03-11 18:20:29 -04:00
/* Thread: player */
int
2016-10-26 13:37:41 -04:00
spotify_playback_setup ( const char * path )
2014-03-11 18:20:29 -04:00
{
sp_link * link ;
2015-09-12 01:45:31 -04:00
DPRINTF ( E_DBG , L_SPOTIFY , " Playback setup request \n " ) ;
2014-03-11 18:20:29 -04:00
2016-10-26 13:37:41 -04:00
link = fptr_sp_link_create_from_string ( path ) ;
2014-03-11 18:20:29 -04:00
if ( ! link )
{
2016-10-26 13:37:41 -04:00
DPRINTF ( E_LOG , L_SPOTIFY , " Playback setup failed, invalid Spotify link: %s \n " , path ) ;
2014-03-11 18:20:29 -04:00
return - 1 ;
}
2016-05-08 03:54:58 -04:00
return commands_exec_sync ( cmdbase , playback_setup , NULL , link ) ;
2014-03-23 16:45:06 -04:00
}
2014-03-11 18:20:29 -04:00
2015-09-12 01:45:31 -04:00
int
spotify_playback_play ( )
{
DPRINTF ( E_DBG , L_SPOTIFY , " Playback request \n " ) ;
2016-05-08 03:54:58 -04:00
return commands_exec_sync ( cmdbase , playback_play , NULL , NULL ) ;
2015-09-12 01:45:31 -04:00
}
int
spotify_playback_pause ( )
{
DPRINTF ( E_DBG , L_SPOTIFY , " Pause request \n " ) ;
2016-05-08 03:54:58 -04:00
return commands_exec_sync ( cmdbase , playback_pause , NULL , NULL ) ;
2015-09-12 01:45:31 -04:00
}
2014-03-23 16:45:06 -04:00
/* Thread: libspotify */
void
spotify_playback_pause_nonblock ( void )
{
DPRINTF ( E_DBG , L_SPOTIFY , " Nonblock pause request \n " ) ;
2016-05-08 03:54:58 -04:00
commands_exec_async ( cmdbase , playback_pause , NULL ) ;
2014-03-11 18:20:29 -04:00
}
2014-03-23 16:45:06 -04:00
/* Thread: player and libspotify */
2014-03-11 18:20:29 -04:00
int
spotify_playback_stop ( void )
{
DPRINTF ( E_DBG , L_SPOTIFY , " Stop request \n " ) ;
2016-05-08 03:54:58 -04:00
return commands_exec_sync ( cmdbase , playback_stop , NULL , NULL ) ;
2014-03-11 18:20:29 -04:00
}
2014-11-11 05:59:40 -05:00
/* Thread: player and libspotify */
2014-03-23 16:45:06 -04:00
void
spotify_playback_stop_nonblock ( void )
2014-03-11 18:20:29 -04:00
{
2014-03-23 16:45:06 -04:00
DPRINTF ( E_DBG , L_SPOTIFY , " Nonblock stop request \n " ) ;
2016-05-08 03:54:58 -04:00
commands_exec_async ( cmdbase , playback_stop , NULL ) ;
2014-03-11 18:20:29 -04:00
}
/* Thread: player */
int
2014-03-23 16:45:06 -04:00
spotify_playback_seek ( int ms )
2014-03-11 18:20:29 -04:00
{
int ret ;
2016-05-08 03:54:58 -04:00
ret = commands_exec_sync ( cmdbase , playback_seek , NULL , & ms ) ;
2014-03-17 17:25:47 -04:00
2014-03-23 16:45:06 -04:00
if ( ret = = 0 )
return ms ;
else
return - 1 ;
}
2014-03-17 17:25:47 -04:00
2016-01-08 18:36:30 -05:00
/* Thread: httpd (artwork) and worker */
2014-04-04 17:14:43 -04:00
int
spotify_artwork_get ( struct evbuffer * evbuf , char * path , int max_w , int max_h )
{
2016-05-08 03:54:58 -04:00
struct artwork_get_param artwork ;
2016-01-08 18:36:30 -05:00
struct timespec ts ;
2014-04-04 17:14:43 -04:00
int ret ;
2016-05-08 03:54:58 -04:00
artwork . evbuf = evbuf ;
artwork . path = path ;
artwork . max_w = max_w ;
artwork . max_h = max_h ;
2014-04-04 17:14:43 -04:00
2017-01-21 10:11:20 -05:00
CHECK_ERR ( L_SPOTIFY , mutex_init ( & artwork . mutex ) ) ;
CHECK_ERR ( L_SPOTIFY , pthread_cond_init ( & artwork . cond , NULL ) ) ;
2014-03-11 18:20:29 -04:00
2016-05-08 03:54:58 -04:00
ret = commands_exec_sync ( cmdbase , artwork_get , NULL , & artwork ) ;
2017-01-13 17:32:59 -05:00
2016-01-08 18:36:30 -05:00
// Artwork was not ready, wait for callback from libspotify
if ( ret = = 0 )
{
2017-01-21 10:11:20 -05:00
CHECK_ERR ( L_SPOTIFY , pthread_mutex_lock ( & artwork . mutex ) ) ;
2017-01-15 17:20:53 -05:00
ts = timespec_reltoabs ( spotify_artwork_timeout ) ;
2017-02-07 17:23:39 -05:00
while ( ( ! artwork . is_loaded ) & & ( ret ! = ETIMEDOUT ) )
CHECK_ERR_EXCEPT ( L_SPOTIFY , pthread_cond_timedwait ( & artwork . cond , & artwork . mutex , & ts ) , ret , ETIMEDOUT ) ;
if ( ret = = ETIMEDOUT )
DPRINTF ( E_LOG , L_SPOTIFY , " Timeout waiting for artwork from Spotify \n " ) ;
2017-01-21 10:11:20 -05:00
CHECK_ERR ( L_SPOTIFY , pthread_mutex_unlock ( & artwork . mutex ) ) ;
2016-01-08 18:36:30 -05:00
2016-05-08 03:54:58 -04:00
ret = commands_exec_sync ( cmdbase , artwork_get_bh , NULL , & artwork ) ;
2016-01-08 18:36:30 -05:00
}
2017-01-13 17:32:59 -05:00
2014-03-23 16:45:06 -04:00
return ret ;
2014-03-11 18:20:29 -04:00
}
2016-11-05 08:44:23 -04:00
/* Thread: httpd */
void
spotify_oauth_interface ( struct evbuffer * evbuf , const char * redirect_uri )
2014-03-11 18:20:29 -04:00
{
2017-01-01 02:32:05 -05:00
char * uri ;
2014-03-11 18:20:29 -04:00
2017-01-01 02:32:05 -05:00
uri = spotifywebapi_oauth_uri_get ( redirect_uri ) ;
if ( ! uri )
2014-03-11 18:20:29 -04:00
{
2016-11-05 08:44:23 -04:00
DPRINTF ( E_LOG , L_SPOTIFY , " Cannot display Spotify oath interface (http_form_uriencode() failed) \n " ) ;
2017-01-01 02:32:05 -05:00
return ;
2014-03-11 18:20:29 -04:00
}
2017-01-01 02:32:05 -05:00
evbuffer_add_printf ( evbuf , " <a href= \" %s \" >Click here to authorize forked-daapd with Spotify</a> \n " , uri ) ;
2014-03-11 18:20:29 -04:00
2017-01-01 02:32:05 -05:00
free ( uri ) ;
2016-11-05 08:44:23 -04:00
}
2014-03-11 18:20:29 -04:00
2016-11-05 08:44:23 -04:00
/* Thread: httpd */
2017-08-28 15:14:35 -04:00
int
spotify_oauth_callback ( struct evkeyvalq * param , const char * redirect_uri , char * * errmsg )
2016-11-05 08:44:23 -04:00
{
const char * code ;
2017-01-21 11:51:51 -05:00
const char * err ;
2017-08-19 02:36:43 -04:00
char * user = NULL ;
2016-11-05 08:44:23 -04:00
int ret ;
2014-03-11 18:20:29 -04:00
2017-08-28 15:14:35 -04:00
* errmsg = NULL ;
2016-11-05 08:44:23 -04:00
code = evhttp_find_header ( param , " code " ) ;
if ( ! code )
{
2017-08-28 15:14:35 -04:00
* errmsg = safe_asprintf ( " Error: Didn't receive a code from Spotify " ) ;
return - 1 ;
2014-03-11 18:20:29 -04:00
}
2016-11-05 08:44:23 -04:00
DPRINTF ( E_DBG , L_SPOTIFY , " Received OAuth code: %s \n " , code ) ;
2014-03-11 18:20:29 -04:00
2017-08-19 02:36:43 -04:00
ret = spotifywebapi_token_get ( code , redirect_uri , & user , & err ) ;
2016-11-05 08:44:23 -04:00
if ( ret < 0 )
2014-03-11 18:20:29 -04:00
{
2017-08-28 15:14:35 -04:00
* errmsg = safe_asprintf ( " Error: %s " , err ) ;
return - 1 ;
2014-03-11 18:20:29 -04:00
}
2017-01-01 05:06:00 -05:00
// Received a valid access token
spotify_access_token_valid = true ;
2014-03-11 18:20:29 -04:00
2017-08-19 02:36:43 -04:00
CHECK_ERR ( L_REMOTE , pthread_mutex_lock ( & status_lck ) ) ;
spotify_status_info . webapi_token_valid = spotify_access_token_valid ;
if ( user )
{
snprintf ( spotify_status_info . webapi_user , sizeof ( spotify_status_info . webapi_user ) , " %s " , user ) ;
spotify_status_info . webapi_user [ sizeof ( spotify_status_info . webapi_user ) - 1 ] = ' \0 ' ;
free ( user ) ;
}
CHECK_ERR ( L_REMOTE , pthread_mutex_unlock ( & status_lck ) ) ;
2017-01-04 14:27:55 -05:00
// Trigger scan after successful access to spotifywebapi
2017-01-05 16:09:19 -05:00
library_exec_async ( webapi_scan , NULL ) ;
2014-06-10 16:49:44 -04:00
2017-08-19 02:36:43 -04:00
listener_notify ( LISTENER_SPOTIFY ) ;
2017-08-28 15:14:35 -04:00
return 0 ;
2014-06-10 16:49:44 -04:00
}
2017-01-01 05:06:00 -05:00
static void
spotify_uri_register ( const char * uri )
{
char * tmp ;
tmp = strdup ( uri ) ;
commands_exec_async ( cmdbase , uri_register , tmp ) ;
}
2017-08-19 02:36:43 -04:00
void
spotify_status_info_get ( struct spotify_status_info * info )
{
CHECK_ERR ( L_REMOTE , pthread_mutex_lock ( & status_lck ) ) ;
memcpy ( info , & spotify_status_info , sizeof ( struct spotify_status_info ) ) ;
CHECK_ERR ( L_REMOTE , pthread_mutex_unlock ( & status_lck ) ) ;
}
2017-08-27 01:20:50 -04:00
/* Thread: library, httpd */
int
spotify_login_user ( const char * user , const char * password , char * * errmsg )
2014-06-10 16:49:44 -04:00
{
2014-12-28 15:08:52 -05:00
sp_error err ;
2014-06-10 16:49:44 -04:00
if ( ! g_sess )
{
2015-01-28 13:59:38 -05:00
if ( ! g_libhandle )
2017-08-27 01:20:50 -04:00
{
DPRINTF ( E_LOG , L_SPOTIFY , " Can't login! - could not find libspotify \n " ) ;
if ( errmsg )
* errmsg = safe_asprintf ( " Could not find libspotify " ) ;
}
2015-01-28 13:59:38 -05:00
else
2017-08-27 01:20:50 -04:00
{
DPRINTF ( E_LOG , L_SPOTIFY , " Can't login! - no valid Spotify session \n " ) ;
if ( errmsg )
* errmsg = safe_asprintf ( " No valid Spotify session " ) ;
}
2015-01-28 13:59:38 -05:00
2017-08-27 01:20:50 -04:00
return - 1 ;
2014-03-11 18:20:29 -04:00
}
2014-12-28 15:08:52 -05:00
if ( SP_CONNECTION_STATE_LOGGED_IN = = fptr_sp_session_connectionstate ( g_sess ) )
2014-03-11 18:20:29 -04:00
{
2017-01-21 10:11:20 -05:00
CHECK_ERR ( L_SPOTIFY , pthread_mutex_lock ( & login_lck ) ) ;
2014-03-11 18:20:29 -04:00
2014-12-28 15:08:52 -05:00
DPRINTF ( E_LOG , L_SPOTIFY , " Logging out of Spotify (current state is %d) \n " , g_state ) ;
2014-03-23 16:45:06 -04:00
2014-12-28 15:08:52 -05:00
fptr_sp_session_player_unload ( g_sess ) ;
err = fptr_sp_session_logout ( g_sess ) ;
if ( SP_ERROR_OK ! = err )
2014-03-23 16:45:06 -04:00
{
2014-12-28 15:08:52 -05:00
DPRINTF ( E_LOG , L_SPOTIFY , " Could not logout of Spotify: %s \n " , fptr_sp_error_message ( err ) ) ;
2017-08-27 01:20:50 -04:00
if ( errmsg )
* errmsg = safe_asprintf ( " Could not logout of Spotify: %s " , fptr_sp_error_message ( err ) ) ;
2017-01-21 10:11:20 -05:00
CHECK_ERR ( L_SPOTIFY , pthread_mutex_unlock ( & login_lck ) ) ;
2017-08-27 01:20:50 -04:00
return - 1 ;
2014-03-23 16:45:06 -04:00
}
2014-12-28 15:08:52 -05:00
2017-01-21 10:11:20 -05:00
CHECK_ERR ( L_SPOTIFY , pthread_cond_wait ( & login_cond , & login_lck ) ) ;
CHECK_ERR ( L_SPOTIFY , pthread_mutex_unlock ( & login_lck ) ) ;
2014-03-11 18:20:29 -04:00
}
2017-08-27 01:20:50 -04:00
if ( user & & password )
2014-06-10 16:49:44 -04:00
{
2017-08-27 01:20:50 -04:00
DPRINTF ( E_LOG , L_SPOTIFY , " Spotify credentials file OK, logging in with username %s \n " , user ) ;
err = fptr_sp_session_login ( g_sess , user , password , 1 , NULL ) ;
2014-06-10 16:49:44 -04:00
}
else
{
2017-06-14 17:49:18 -04:00
DPRINTF ( E_INFO , L_SPOTIFY , " Relogin to Spotify \n " ) ;
2014-06-10 16:49:44 -04:00
err = fptr_sp_session_relogin ( g_sess ) ;
}
2014-03-11 18:20:29 -04:00
if ( SP_ERROR_OK ! = err )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Could not login into Spotify: %s \n " , fptr_sp_error_message ( err ) ) ;
2017-08-27 01:20:50 -04:00
if ( errmsg )
* errmsg = safe_asprintf ( " Could not login into Spotify: %s " , fptr_sp_error_message ( err ) ) ;
return - 1 ;
2014-03-11 18:20:29 -04:00
}
2017-08-27 01:20:50 -04:00
return 0 ;
}
/* Thread: library */
void
spotify_login ( char * * arglist )
{
if ( arglist )
spotify_login_user ( arglist [ 0 ] , arglist [ 1 ] , NULL ) ;
else
spotify_login_user ( NULL , NULL , NULL ) ;
2014-03-11 18:20:29 -04:00
}
2017-01-01 05:06:00 -05:00
static void
2017-10-01 17:48:00 -04:00
map_track_to_mfi ( struct media_file_info * mfi , const struct spotify_track * track , const struct spotify_album * album , const char * pl_name )
2017-01-01 05:06:00 -05:00
{
2017-10-01 17:48:00 -04:00
char virtual_path [ PATH_MAX ] ;
2017-01-01 05:06:00 -05:00
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 ;
2017-01-07 01:22:40 -05:00
2017-10-01 18:04:05 -04:00
mfi - > data_kind = DATA_KIND_SPOTIFY ;
mfi - > media_kind = MEDIA_KIND_MUSIC ;
2017-01-07 01:22:40 -05:00
mfi - > artwork = ARTWORK_SPOTIFY ;
mfi - > type = strdup ( " spotify " ) ;
mfi - > codectype = strdup ( " wav " ) ;
mfi - > description = strdup ( " Spotify audio " ) ;
2017-01-29 04:01:05 -05:00
mfi - > path = strdup ( track - > uri ) ;
mfi - > fname = strdup ( track - > uri ) ;
2017-10-01 17:48:00 -04:00
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 ) ;
mfi - > album = cfg_getbool ( cfg_getsec ( cfg , " spotify " ) , " album_override " ) ? safe_strdup ( pl_name ) : safe_strdup ( track - > album ) ;
mfi - > compilation = cfg_getbool ( cfg_getsec ( cfg , " spotify " ) , " artist_override " ) ? true : 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 ) ;
2017-01-01 05:06:00 -05:00
}
static void
2017-10-01 17:48:00 -04:00
webapi_track_save ( struct spotify_track * track , struct spotify_album * album , const char * pl_name , int dir_id )
2017-01-01 05:06:00 -05:00
{
2017-10-01 17:48:00 -04:00
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 ) ;
2017-01-01 05:06:00 -05:00
}
2017-10-01 17:48:00 -04:00
2017-01-01 05:06:00 -05:00
/* Thread: library */
static int
scan_saved_albums ( )
{
struct spotify_request request ;
struct spotify_album album ;
struct spotify_track track ;
2017-10-01 17:48:00 -04:00
json_object * jsontracks ;
int track_count ;
2017-01-01 05:06:00 -05:00
int dir_id ;
int i ;
2017-01-07 01:22:40 -05:00
int count ;
2017-01-01 05:06:00 -05:00
int ret ;
2017-01-07 01:22:40 -05:00
count = 0 ;
2017-01-01 05:06:00 -05:00
memset ( & request , 0 , sizeof ( struct spotify_request ) ) ;
2017-02-05 07:56:12 -05:00
while ( 0 = = spotifywebapi_request_next ( & request , SPOTIFY_WEBAPI_SAVED_ALBUMS , false ) )
2017-01-01 05:06:00 -05:00
{
while ( 0 = = spotifywebapi_saved_albums_fetch ( & request , & jsontracks , & track_count , & album ) )
2016-11-13 14:28:29 -05:00
{
2017-01-01 05:06:00 -05:00
DPRINTF ( E_DBG , L_SPOTIFY , " Got saved album: '%s' - '%s' (%s) - track-count: %d \n " ,
album . artist , album . name , album . uri , track_count ) ;
2017-01-06 05:08:47 -05:00
db_transaction_begin ( ) ;
2017-01-01 05:06:00 -05:00
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 ) ;
2017-10-01 17:48:00 -04:00
if ( ret < 0 | | ! track . uri )
continue ;
2017-01-29 04:01:05 -05:00
2017-10-01 17:48:00 -04:00
webapi_track_save ( & track , & album , NULL , dir_id ) ;
if ( spotify_saved_plid )
db_pl_add_item_bypath ( spotify_saved_plid , track . uri ) ;
2017-01-01 05:06:00 -05:00
}
2017-01-06 05:08:47 -05:00
db_transaction_end ( ) ;
2017-01-07 01:22:40 -05:00
count + + ;
if ( count > = request . total | | ( count % 10 = = 0 ) )
DPRINTF ( E_LOG , L_SPOTIFY , " Scanned %d of %d saved albums \n " , count , request . total ) ;
2016-11-13 14:28:29 -05:00
}
2017-01-01 05:06:00 -05:00
}
2016-11-13 14:28:29 -05:00
2017-01-01 05:06:00 -05:00
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 ) ) ;
2017-02-05 07:56:12 -05:00
while ( 0 = = spotifywebapi_request_next ( & request , playlist - > tracks_href , true ) )
2017-01-01 05:06:00 -05:00
{
2017-01-08 01:56:41 -05:00
db_transaction_begin ( ) ;
2017-01-01 05:06:00 -05:00
while ( 0 = = spotifywebapi_playlisttracks_fetch ( & request , & track ) )
{
2017-10-01 17:48:00 -04:00
if ( ! track . uri | | ! track . is_playable )
2017-02-05 07:56:12 -05:00
{
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 ;
}
2017-10-01 17:48:00 -04:00
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 ) ;
2017-01-01 05:06:00 -05:00
}
2017-01-08 01:56:41 -05:00
db_transaction_end ( ) ;
2017-01-01 05:06:00 -05:00
}
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 ;
2017-01-07 01:22:40 -05:00
int count ;
2017-01-08 01:56:41 -05:00
int trackcount ;
2017-01-01 05:06:00 -05:00
2017-01-07 01:22:40 -05:00
count = 0 ;
2017-01-08 01:56:41 -05:00
trackcount = 0 ;
2017-01-01 05:06:00 -05:00
memset ( & request , 0 , sizeof ( struct spotify_request ) ) ;
2017-02-05 07:56:12 -05:00
while ( 0 = = spotifywebapi_request_next ( & request , SPOTIFY_WEBAPI_SAVED_PLAYLISTS , false ) )
2017-01-01 05:06:00 -05:00
{
while ( 0 = = spotifywebapi_playlists_fetch ( & request , & playlist ) )
{
2017-01-07 01:22:40 -05:00
DPRINTF ( E_DBG , L_SPOTIFY , " Got playlist: '%s' with %d tracks (%s) \n " , playlist . name , playlist . tracks_count , playlist . uri ) ;
2017-01-01 05:06:00 -05:00
2017-01-13 12:28:36 -05:00
if ( ! playlist . uri | | ! playlist . name | | playlist . tracks_count = = 0 )
{
2017-01-18 14:32:02 -05:00
DPRINTF ( E_LOG , L_SPOTIFY , " Ignoring playlist '%s' with %d tracks (%s) \n " , playlist . name , playlist . tracks_count , playlist . uri ) ;
2017-01-13 12:28:36 -05:00
continue ;
}
2017-01-01 05:06:00 -05:00
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 ) ;
}
2017-01-08 01:56:41 -05:00
db_transaction_begin ( ) ;
2017-01-01 05:06:00 -05:00
plid = library_add_playlist_info ( playlist . uri , playlist . name , virtual_path , PL_PLAIN , spotify_base_plid , DIR_SPOTIFY ) ;
2017-01-08 01:56:41 -05:00
db_transaction_end ( ) ;
2017-01-01 05:06:00 -05:00
2017-01-05 16:09:19 -05:00
if ( plid > 0 )
2017-01-01 05:06:00 -05:00
scan_playlisttracks ( & playlist , plid ) ;
2017-01-05 16:09:19 -05:00
else
DPRINTF ( E_LOG , L_SPOTIFY , " Error adding playlist: '%s' (%s) \n " , playlist . name , playlist . uri ) ;
2017-01-06 05:08:47 -05:00
2017-01-07 01:22:40 -05:00
count + + ;
2017-01-08 01:56:41 -05:00
trackcount + = playlist . tracks_count ;
DPRINTF ( E_LOG , L_SPOTIFY , " Scanned %d of %d saved playlists (%d tracks) \n " , count , request . total , trackcount ) ;
2017-01-01 05:06:00 -05:00
}
}
spotifywebapi_request_end ( & request ) ;
return 0 ;
}
2017-01-05 16:09:19 -05:00
/* 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 ) ) ;
if ( 0 = = spotifywebapi_playlist_start ( & request , uri , & playlist ) )
{
2017-01-06 10:33:04 -05:00
if ( ! playlist . uri )
2017-01-05 16:09:19 -05:00
{
2017-01-06 10:33:04 -05:00
DPRINTF ( E_LOG , L_SPOTIFY , " Got playlist with missing uri for path:: '%s' \n " , uri ) ;
2017-01-05 16:09:19 -05:00
}
else
{
2017-01-13 12:28:36 -05:00
DPRINTF ( E_LOG , L_SPOTIFY , " Saving playlist '%s' with %d tracks (%s) \n " , playlist . name , playlist . tracks_count , playlist . uri ) ;
2017-01-05 16:09:19 -05:00
2017-01-06 10:33:04 -05:00
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 ) ;
}
2017-01-08 01:56:41 -05:00
db_transaction_begin ( ) ;
2017-01-06 10:33:04 -05:00
plid = library_add_playlist_info ( playlist . uri , playlist . name , virtual_path , PL_PLAIN , spotify_base_plid , DIR_SPOTIFY ) ;
2017-01-08 01:56:41 -05:00
db_transaction_end ( ) ;
2017-01-06 05:08:47 -05:00
2017-01-06 10:33:04 -05:00
if ( plid > 0 )
scan_playlisttracks ( & playlist , plid ) ;
else
DPRINTF ( E_LOG , L_SPOTIFY , " Error adding playlist: '%s' (%s) \n " , playlist . name , playlist . uri ) ;
}
2017-01-05 16:09:19 -05:00
}
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 " ) ;
2016-11-13 14:28:29 -05:00
spotify_saved_plid = 0 ;
2017-01-05 16:09:19 -05:00
}
}
2016-11-13 14:28:29 -05:00
2017-03-11 03:44:54 -05:00
/*
* Add or update playlist folder for all spotify playlists ( if enabled in config )
*/
2017-01-06 05:08:47 -05:00
static void
create_base_playlist ( )
2016-12-31 01:28:18 -05:00
{
2017-01-01 05:06:00 -05:00
cfg_t * spotify_cfg ;
int ret ;
2017-01-06 05:08:47 -05:00
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 ;
2014-06-10 16:49:44 -04:00
}
2017-01-06 05:08:47 -05:00
}
2014-03-11 18:20:29 -04:00
2017-01-06 05:08:47 -05:00
/* Thread: library */
static int
initscan ( )
{
2017-08-19 02:36:43 -04:00
char * user = NULL ;
2017-01-05 16:09:19 -05:00
scanning = true ;
2017-01-01 05:06:00 -05:00
/* Refresh access token for the spotify webapi */
2017-08-19 02:36:43 -04:00
spotify_access_token_valid = ( 0 = = spotifywebapi_token_refresh ( & user ) ) ;
2017-01-01 05:06:00 -05:00
if ( ! spotify_access_token_valid )
2014-03-11 18:20:29 -04:00
{
2017-01-01 05:06:00 -05:00
DPRINTF ( E_LOG , L_SPOTIFY , " Spotify webapi token refresh failed. "
" In order to use the web api, authorize forked-daapd to access "
2017-10-01 17:48:00 -04:00
" your saved tracks by visiting http://forked-daapd.local:3689 \n " ) ;
2017-01-01 05:06:00 -05:00
db_spotify_purge ( ) ;
}
2017-08-19 02:36:43 -04:00
CHECK_ERR ( L_REMOTE , pthread_mutex_lock ( & status_lck ) ) ;
spotify_status_info . webapi_token_valid = spotify_access_token_valid ;
if ( user )
{
snprintf ( spotify_status_info . webapi_user , sizeof ( spotify_status_info . webapi_user ) , " %s " , user ) ;
spotify_status_info . webapi_user [ sizeof ( spotify_status_info . webapi_user ) - 1 ] = ' \0 ' ;
free ( user ) ;
}
CHECK_ERR ( L_REMOTE , pthread_mutex_unlock ( & status_lck ) ) ;
2017-01-01 05:06:00 -05:00
2017-01-05 16:09:19 -05:00
spotify_saved_plid = 0 ;
2017-01-01 05:06:00 -05:00
/*
* Login to spotify needs to be done before scanning tracks from the web api .
* ( Scanned tracks need to be registered with libspotify for playback )
*/
2016-12-31 01:28:18 -05:00
spotify_login ( NULL ) ;
2017-01-01 05:06:00 -05:00
/*
* Scan saved tracks from the web api
*/
if ( spotify_access_token_valid )
{
2017-03-11 03:44:54 -05:00
create_base_playlist ( ) ;
2017-01-05 16:09:19 -05:00
create_saved_tracks_playlist ( ) ;
2017-01-01 05:06:00 -05:00
scan_saved_albums ( ) ;
scan_playlists ( ) ;
2014-03-11 18:20:29 -04:00
}
2017-01-01 05:06:00 -05:00
2017-01-05 16:09:19 -05:00
scanning = false ;
2016-12-31 01:28:18 -05:00
return 0 ;
}
/* Thread: library */
static int
2017-01-01 10:51:21 -05:00
rescan ( )
2016-12-31 01:28:18 -05:00
{
2017-10-22 06:05:13 -04:00
if ( ! spotify_access_token_valid )
{
DPRINTF ( E_DBG , L_SPOTIFY , " No valid web api token, ignoring rescan \n " ) ;
return 0 ;
}
2017-01-05 16:09:19 -05:00
scanning = true ;
2017-01-06 05:08:47 -05:00
create_base_playlist ( ) ;
2017-10-22 06:05:13 -04:00
create_saved_tracks_playlist ( ) ;
scan_saved_albums ( ) ;
scan_playlists ( ) ;
2017-01-01 10:51:21 -05:00
2017-01-05 16:09:19 -05:00
scanning = false ;
2016-12-31 01:28:18 -05:00
return 0 ;
}
/* Thread: library */
static int
2017-01-01 10:51:21 -05:00
fullrescan ( )
2016-12-31 01:28:18 -05:00
{
2017-10-22 06:05:13 -04:00
if ( ! spotify_access_token_valid )
{
DPRINTF ( E_DBG , L_SPOTIFY , " No valid web api token, ignoring fullrescan \n " ) ;
return 0 ;
}
2017-01-06 05:08:47 -05:00
scanning = true ;
create_base_playlist ( ) ;
2017-10-22 06:05:13 -04:00
create_saved_tracks_playlist ( ) ;
scan_saved_albums ( ) ;
scan_playlists ( ) ;
2017-01-06 05:08:47 -05:00
scanning = false ;
2016-12-31 01:28:18 -05:00
return 0 ;
}
2017-01-05 16:09:19 -05:00
/* Thread: library */
2017-01-04 14:27:55 -05:00
static enum command_state
2017-01-05 16:09:19 -05:00
webapi_scan ( void * arg , int * ret )
2017-01-04 14:27:55 -05:00
{
2017-03-04 11:24:42 -05:00
db_spotify_purge ( ) ;
2017-01-04 14:27:55 -05:00
* ret = rescan ( ) ;
return COMMAND_END ;
}
2017-01-05 16:09:19 -05:00
/* 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 ;
2014-03-11 18:20:29 -04:00
}
/* Thread: main */
int
spotify_init ( void )
{
2014-03-29 17:26:46 -04:00
cfg_t * spotify_cfg ;
2014-03-11 18:20:29 -04:00
sp_session * sp ;
sp_error err ;
int ret ;
2017-01-01 05:06:00 -05:00
spotify_access_token_valid = false ;
2017-01-05 16:09:19 -05:00
scanning = false ;
2017-01-01 05:06:00 -05:00
2014-03-11 18:20:29 -04:00
/* Initialize libspotify */
g_libhandle = dlopen ( " libspotify.so " , RTLD_LAZY ) ;
if ( ! g_libhandle )
{
DPRINTF ( E_INFO , L_SPOTIFY , " libspotify.so not installed or not found \n " ) ;
2014-03-23 16:45:06 -04:00
goto libspotify_fail ;
2014-03-11 18:20:29 -04:00
}
ret = fptr_assign_all ( ) ;
if ( ret < 0 )
2014-03-23 16:45:06 -04:00
goto assign_fail ;
2014-03-11 18:20:29 -04:00
2016-03-17 17:20:16 -04:00
# ifdef HAVE_PIPE2
2014-03-23 16:45:06 -04:00
ret = pipe2 ( g_notify_pipe , O_CLOEXEC ) ;
2016-03-17 17:20:16 -04:00
# else
ret = pipe ( g_notify_pipe ) ;
# endif
2014-03-23 16:45:06 -04:00
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Could not notify command pipe: %s \n " , strerror ( errno ) ) ;
goto notify_fail ;
}
evbase_spotify = event_base_new ( ) ;
if ( ! evbase_spotify )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Could not create an event base \n " ) ;
goto evbase_fail ;
}
g_notifyev = event_new ( evbase_spotify , g_notify_pipe [ 0 ] , EV_READ | EV_TIMEOUT , notify_cb , NULL ) ;
if ( ! g_notifyev )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Could not create notify event \n " ) ;
goto evnew_fail ;
}
event_add ( g_notifyev , NULL ) ;
2016-05-23 00:41:43 -04:00
cmdbase = commands_base_new ( evbase_spotify , exit_cb ) ;
2016-05-08 03:54:58 -04:00
if ( ! cmdbase )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Could not create command base \n " ) ;
goto cmd_fail ;
}
2014-03-23 16:45:06 -04:00
DPRINTF ( E_INFO , L_SPOTIFY , " Spotify session init \n " ) ;
2014-03-11 18:20:29 -04:00
2014-03-29 17:26:46 -04:00
spotify_cfg = cfg_getsec ( cfg , " spotify " ) ;
spconfig . settings_location = cfg_getstr ( spotify_cfg , " settings_dir " ) ;
spconfig . cache_location = cfg_getstr ( spotify_cfg , " cache_dir " ) ;
2014-03-11 18:20:29 -04:00
DPRINTF ( E_DBG , L_SPOTIFY , " Creating Spotify session \n " ) ;
err = fptr_sp_session_create ( & spconfig , & sp ) ;
if ( SP_ERROR_OK ! = err )
{
DPRINTF ( E_LOG , L_SPOTIFY , " Could not create Spotify session: %s \n " , fptr_sp_error_message ( err ) ) ;
2014-03-23 16:45:06 -04:00
goto session_fail ;
2014-03-11 18:20:29 -04:00
}
g_sess = sp ;
2014-03-23 16:45:06 -04:00
g_state = SPOTIFY_STATE_INACTIVE ;
2014-03-11 18:20:29 -04:00
2014-03-29 17:26:46 -04:00
switch ( cfg_getint ( spotify_cfg , " bitrate " ) )
{
case 1 :
fptr_sp_session_preferred_bitrate ( g_sess , SP_BITRATE_96k ) ;
break ;
case 2 :
fptr_sp_session_preferred_bitrate ( g_sess , SP_BITRATE_160k ) ;
break ;
case 3 :
fptr_sp_session_preferred_bitrate ( g_sess , SP_BITRATE_320k ) ;
break ;
}
2017-08-19 02:36:43 -04:00
CHECK_ERR ( L_REMOTE , pthread_mutex_lock ( & status_lck ) ) ;
spotify_status_info . libspotify_installed = true ;
CHECK_ERR ( L_REMOTE , pthread_mutex_unlock ( & status_lck ) ) ;
2016-12-28 18:39:23 -05:00
spotify_audio_buffer = evbuffer_new ( ) ;
2014-03-11 18:20:29 -04:00
2017-01-30 16:50:02 -05:00
CHECK_ERR ( L_SPOTIFY , evbuffer_enable_locking ( spotify_audio_buffer , NULL ) ) ;
2017-01-21 10:11:20 -05:00
CHECK_ERR ( L_SPOTIFY , mutex_init ( & login_lck ) ) ;
CHECK_ERR ( L_SPOTIFY , pthread_cond_init ( & login_cond , NULL ) ) ;
2014-12-28 15:08:52 -05:00
/* Spawn thread */
ret = pthread_create ( & tid_spotify , NULL , spotify , NULL ) ;
if ( ret < 0 )
{
DPRINTF ( E_FATAL , L_SPOTIFY , " Could not spawn Spotify thread: %s \n " , strerror ( errno ) ) ;
goto thread_fail ;
}
2016-05-05 13:39:15 -04:00
# if defined(HAVE_PTHREAD_SETNAME_NP)
2016-03-05 07:19:55 -05:00
pthread_setname_np ( tid_spotify , " spotify " ) ;
2016-05-05 13:39:15 -04:00
# elif defined(HAVE_PTHREAD_SET_NAME_NP)
2016-03-05 07:19:55 -05:00
pthread_set_name_np ( tid_spotify , " spotify " ) ;
# endif
2014-03-11 18:20:29 -04:00
DPRINTF ( E_DBG , L_SPOTIFY , " Spotify init complete \n " ) ;
return 0 ;
2014-03-23 16:45:06 -04:00
2014-12-28 15:08:52 -05:00
thread_fail :
2017-01-21 10:11:20 -05:00
CHECK_ERR ( L_SPOTIFY , pthread_cond_destroy ( & login_cond ) ) ;
CHECK_ERR ( L_SPOTIFY , pthread_mutex_destroy ( & login_lck ) ) ;
2014-12-28 15:08:52 -05:00
2016-12-28 18:39:23 -05:00
evbuffer_free ( spotify_audio_buffer ) ;
2014-12-28 15:08:52 -05:00
2014-03-23 16:45:06 -04:00
fptr_sp_session_release ( g_sess ) ;
g_sess = NULL ;
session_fail :
2016-05-08 03:54:58 -04:00
cmd_fail :
2014-03-23 16:45:06 -04:00
evnew_fail :
2016-05-08 03:54:58 -04:00
commands_base_free ( cmdbase ) ;
2014-03-23 16:45:06 -04:00
event_base_free ( evbase_spotify ) ;
evbase_spotify = NULL ;
evbase_fail :
close ( g_notify_pipe [ 0 ] ) ;
close ( g_notify_pipe [ 1 ] ) ;
notify_fail :
assign_fail :
dlclose ( g_libhandle ) ;
g_libhandle = NULL ;
libspotify_fail :
return - 1 ;
2014-03-11 18:20:29 -04:00
}
void
spotify_deinit ( void )
{
int ret ;
2014-03-23 16:45:06 -04:00
if ( ! g_libhandle )
2014-03-11 18:20:29 -04:00
return ;
/* Send exit signal to thread (if active) */
if ( g_state ! = SPOTIFY_STATE_INACTIVE )
{
2016-06-11 04:00:23 -04:00
commands_base_destroy ( cmdbase ) ;
2016-05-23 00:41:43 -04:00
g_state = SPOTIFY_STATE_INACTIVE ;
2014-03-11 18:20:29 -04:00
ret = pthread_join ( tid_spotify , NULL ) ;
if ( ret ! = 0 )
{
DPRINTF ( E_FATAL , L_SPOTIFY , " Could not join Spotify thread: %s \n " , strerror ( errno ) ) ;
return ;
}
}
2014-03-23 16:45:06 -04:00
/* Release session */
2014-03-11 18:20:29 -04:00
fptr_sp_session_release ( g_sess ) ;
2014-03-23 16:45:06 -04:00
/* Free event base (should free events too) */
event_base_free ( evbase_spotify ) ;
2014-03-11 18:20:29 -04:00
2016-06-11 04:00:23 -04:00
/* Close pipes */
2014-03-23 16:45:06 -04:00
close ( g_notify_pipe [ 0 ] ) ;
close ( g_notify_pipe [ 1 ] ) ;
2014-03-11 18:20:29 -04:00
2014-12-28 15:08:52 -05:00
/* Destroy locks */
2017-01-21 10:11:20 -05:00
CHECK_ERR ( L_SPOTIFY , pthread_cond_destroy ( & login_cond ) ) ;
CHECK_ERR ( L_SPOTIFY , pthread_mutex_destroy ( & login_lck ) ) ;
2014-12-28 15:08:52 -05:00
2016-12-28 18:39:23 -05:00
/* Free audio buffer */
evbuffer_free ( spotify_audio_buffer ) ;
2014-03-11 18:20:29 -04:00
/* Release libspotify handle */
2014-03-23 16:45:06 -04:00
dlclose ( g_libhandle ) ;
2014-03-11 18:20:29 -04:00
}
2016-12-31 01:28:18 -05:00
struct library_source spotifyscanner =
{
. name = " spotifyscanner " ,
. disabled = 0 ,
. init = spotify_init ,
. deinit = spotify_deinit ,
2017-01-01 10:51:21 -05:00
. rescan = rescan ,
. initscan = initscan ,
. fullrescan = fullrescan ,
2016-12-31 01:28:18 -05:00
} ;