From 97971f7c4b8575031ae5573e057ae77001d87e59 Mon Sep 17 00:00:00 2001 From: Skip Hansen Date: Wed, 27 Nov 2024 10:29:53 -0800 Subject: [PATCH 1/7] Added support for building using specified location for ffmpeg libs by setting LIBAV. --- Makefile | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 8c647fb80..ca4ecd75c 100644 --- a/Makefile +++ b/Makefile @@ -46,8 +46,14 @@ LIBPIANO_OBJ:=${LIBPIANO_SRC:.c=.o} LIBPIANO_RELOBJ:=${LIBPIANO_SRC:.c=.lo} LIBPIANO_INCLUDE:=${LIBPIANO_DIR} -LIBAV_CFLAGS:=$(shell $(PKG_CONFIG) --cflags libavcodec libavformat libavutil libavfilter) -LIBAV_LDFLAGS:=$(shell $(PKG_CONFIG) --libs libavcodec libavformat libavutil libavfilter) +LIBAV = /home/skip/open_src/audio/ffmpeg + +ifneq (${LIBAV},) +PKG_CONFIG_LIBDIR=PKG_CONFIG_LIBDIR=${LIBAV}/lib/pkgconfig +endif + +LIBAV_CFLAGS:=$(shell ${PKG_CONFIG_LIBDIR} $(PKG_CONFIG) --cflags libavcodec libavformat libavutil libavfilter) +LIBAV_LDFLAGS:=$(shell ${PKG_CONFIG_LIBDIR} $(PKG_CONFIG) --libs libavcodec libavformat libavutil libavfilter) LIBCURL_CFLAGS:=$(shell $(PKG_CONFIG) --cflags libcurl) LIBCURL_LDFLAGS:=$(shell $(PKG_CONFIG) --libs libcurl) @@ -150,4 +156,10 @@ uninstall: ${DESTDIR}/${LIBDIR}/libpiano.a \ ${DESTDIR}/${INCDIR}/piano.h -.PHONY: install install-libpiano uninstall test debug all +.PHONY: install install-libpiano uninstall test debug all make_debug + +make_debug: + @echo "LIBAV: '${LIBAV}'" + @echo "PKG_CONFIG_LIBDIR: '${PKG_CONFIG_LIBDIR}'" + @echo "LIBAV_CFLAGS: '${LIBAV_CFLAGS}'" + @echo "LIBAV_LDFLAGS: '${LIBAV_LDFLAGS}'" From cd9787612a8b3dde500800add0ea860e01d158e9 Mon Sep 17 00:00:00 2001 From: Skip Hansen Date: Wed, 8 Jan 2025 07:05:38 -0800 Subject: [PATCH 2/7] Added support for podcasts, playlists, albums and tracks (#656). --- Makefile | 12 +- src/debug.h | 1 + src/libpiano/debug_log.c | 30 ++ src/libpiano/debug_log.h | 21 ++ src/libpiano/piano.h | 59 +++- src/libpiano/piano_private.h | 8 + src/libpiano/request.c | 156 ++++++++- src/libpiano/response.c | 607 ++++++++++++++++++++++++++++++++++- src/main.c | 286 ++++++++++++++++- src/main.h | 6 + src/settings.c | 2 +- src/settings.h | 7 +- src/ui.c | 112 ++++++- src/ui.h | 1 + src/ui_act.c | 204 +++++++++++- src/ui_act.h | 2 + src/ui_dispatch.c | 81 ++++- src/ui_dispatch.h | 50 ++- 18 files changed, 1553 insertions(+), 92 deletions(-) create mode 100755 src/libpiano/debug_log.c create mode 100755 src/libpiano/debug_log.h diff --git a/Makefile b/Makefile index ca4ecd75c..81c66e941 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,12 @@ LIBDIR:=${PREFIX}/lib INCDIR:=${PREFIX}/include MANDIR:=${PREFIX}/share/man DYNLINK:=0 -CFLAGS?=-O2 -DNDEBUG + +ifeq ($(MAKECMDGOALS),debug) + CFLAGS?=-O0 -g -DDEBUG +else + CFLAGS?=-O2 -DNDEBUG +endif ifeq (${CC},cc) OS := $(shell uname) @@ -41,7 +46,8 @@ LIBPIANO_SRC:=\ ${LIBPIANO_DIR}/piano.c \ ${LIBPIANO_DIR}/request.c \ ${LIBPIANO_DIR}/response.c \ - ${LIBPIANO_DIR}/list.c + ${LIBPIANO_DIR}/list.c \ + ${LIBPIANO_DIR}/debug_log.c LIBPIANO_OBJ:=${LIBPIANO_SRC:.c=.o} LIBPIANO_RELOBJ:=${LIBPIANO_SRC:.c=.lo} LIBPIANO_INCLUDE:=${LIBPIANO_DIR} @@ -128,6 +134,8 @@ clean: all: pianobar +debug: pianobar + ifeq (${DYNLINK},1) install: pianobar install-libpiano else diff --git a/src/debug.h b/src/debug.h index e2e356b08..6000fab2d 100644 --- a/src/debug.h +++ b/src/debug.h @@ -25,6 +25,7 @@ THE SOFTWARE. #include "config.h" #include +#include "debug_log.h" #ifdef HAVE_DEBUGLOG #include diff --git a/src/libpiano/debug_log.c b/src/libpiano/debug_log.c new file mode 100755 index 000000000..445e56af2 --- /dev/null +++ b/src/libpiano/debug_log.c @@ -0,0 +1,30 @@ +#include +#include +#include + +#include "debug_log.h" + +static ErrMsgCallback_t gPianoPrintErrMsgCB; + +void PianoRegisterErrMsgCallback(ErrMsgCallback_t Arg) +{ + gPianoPrintErrMsgCB = Arg; +} + +void PianoPrintErrMsg(const char *format, ...) +{ + va_list fmtargs; + + assert (format != NULL); + va_start (fmtargs, format); + + if(gPianoPrintErrMsgCB != NULL) { + gPianoPrintErrMsgCB(format,fmtargs); + } + else { + va_start (fmtargs, format); + vprintf (format, fmtargs); + va_end (fmtargs); + } +} + diff --git a/src/libpiano/debug_log.h b/src/libpiano/debug_log.h new file mode 100755 index 000000000..c58a17218 --- /dev/null +++ b/src/libpiano/debug_log.h @@ -0,0 +1,21 @@ +#ifndef _DEBUG_LOG_H_ +#define _DEBUG_LOG_H_ +#include + +typedef void (*ErrMsgCallback_t) (const char *fmt,va_list fmtargs); +// typedef void (ErrMsgCallback_t) (const char *format); + +void PianoRegisterErrMsgCallback(ErrMsgCallback_t); +void PianoPrintErrMsg(const char *format,...) __attribute__((format(printf, 1, 2))); + +#define ELOG(format, ... ) PianoPrintErrMsg("%s#%d: " format, __FUNCTION__,__LINE__,## __VA_ARGS__) +#ifdef DEBUG + #define LOG(format, ... ) printf("%s: " format,__FUNCTION__,## __VA_ARGS__) + #define LOG_RAW(format, ... ) printf(format,## __VA_ARGS__) +#else + #define LOG(format, ... ) + #define LOG_RAW(format, ... ) +#endif + +#endif + diff --git a/src/libpiano/piano.h b/src/libpiano/piano.h index f7360aae1..0eff7833d 100644 --- a/src/libpiano/piano.h +++ b/src/libpiano/piano.h @@ -50,17 +50,32 @@ typedef struct PianoListHead { typedef struct PianoUserInfo { char *listenerId; char *authToken; + bool IsSubscriber; + bool IsPremiumUser; + int PlayListCount; + int StationCount; + int AlbumCount; + int TrackCount; + int PodcastCount; } PianoUserInfo_t; -typedef struct PianoStation { - PianoListHead_t head; - char isCreator; - char isQuickMix; - char useQuickMix; /* station will be included in quickmix */ - char *name; - char *id; - char *seedId; -} PianoStation_t; +typedef enum { + PIANO_TYPE_NONE = 0, + PIANO_TYPE_STATION = 1, + PIANO_TYPE_PODCAST = 2, + PIANO_TYPE_PLAYLIST = 3, + PIANO_TYPE_ALBUM = 4, + PIANO_TYPE_TRACK = 5, + PIANO_TYPE_LAST +} PianoStationType_t; + +typedef enum { + PIANO_MODE_STATION = 0, + PIANO_MODE_PODCAST = 1, + PIANO_MODE_PLAYLIST = 2, + PIANO_MODE_ALBUM = 3, + PIANO_MODE_ALL = 4 +} PianoMode_t; typedef enum { PIANO_RATE_NONE = 0, @@ -102,6 +117,24 @@ typedef struct PianoSong { PianoAudioFormat_t audioFormat; } PianoSong_t; +typedef struct PianoStation { + PianoListHead_t head; + char isCreator; + char isQuickMix; + char useQuickMix; /* station will be included in quickmix */ + char *name; + char *id; + char *seedId; + PianoStationType_t stationType; + PianoSong_t *theSong; +} PianoStation_t; + +typedef struct { + PianoStation_t *station; + PianoSong_t *playList; + bool bGetAll; +} PianoRequestDataGetEpisodes_t; + /* currently only used for search results */ typedef struct PianoArtist { PianoListHead_t head; @@ -188,6 +221,14 @@ typedef enum { PIANO_REQUEST_CHANGE_SETTINGS = 24, PIANO_REQUEST_GET_STATION_MODES = 25, PIANO_REQUEST_SET_STATION_MODE = 26, + PIANO_REQUEST_GET_PLAYLISTS = 27, + PIANO_REQUEST_GET_TRACKS = 28, + PIANO_REQUEST_GET_PLAYBACK_INFO = 29, + PIANO_REQUEST_GET_ITEMS = 30, + PIANO_REQUEST_GET_USER_PROFILE = 31, + PIANO_REQUEST_ANNOTATE_OBJECTS = 32, + PIANO_REQUEST_REMOVE_ITEM = 33, + PIANO_REQUEST_GET_EPISODES = 34, } PianoRequestType_t; typedef struct PianoRequest { diff --git a/src/libpiano/piano_private.h b/src/libpiano/piano_private.h index ffc14c820..a5d954acc 100644 --- a/src/libpiano/piano_private.h +++ b/src/libpiano/piano_private.h @@ -25,6 +25,14 @@ THE SOFTWARE. #include "piano.h" +#ifdef DEBUG + #define LOG(format, ... ) printf("%s: " format,__FUNCTION__,## __VA_ARGS__) + #define LOG_RAW(format, ... ) printf(format,## __VA_ARGS__) +#else + #define LOG(format, ... ) + #define LOG_RAW(format, ... ) +#endif + void PianoDestroyStation (PianoStation_t *station); void PianoDestroyUserInfo (PianoUserInfo_t *user); diff --git a/src/libpiano/request.c b/src/libpiano/request.c index 69e49a145..b966f53af 100644 --- a/src/libpiano/request.c +++ b/src/libpiano/request.c @@ -30,7 +30,9 @@ THE SOFTWARE. #include #include "piano.h" +#include "piano_private.h" #include "crypt.h" +#include "debug.h" /* prepare piano request (initializes request type, urlpath and postData) * @param piano handle @@ -95,6 +97,8 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, json_object_new_string (ph->partner.authToken)); json_object_object_add (j, "syncTime", json_object_new_int (timestamp)); + json_object_object_add (j, "returnIsSubscriber", + json_object_new_boolean (true)); CURL * const curl = curl_easy_init (); urlencAuthToken = curl_easy_escape (curl, @@ -501,8 +505,158 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, goto cleanup; break; } - } + case PIANO_REQUEST_GET_PLAYLISTS: { + /* get stations, user must be authenticated */ + assert (ph->user.listenerId != NULL); + req->secure = true; + json_object *a = json_object_new_object (); + json_object_object_add(a,"listenerId",json_object_new_string(ph->user.listenerId)); + json_object_object_add(a,"offset",json_object_new_int(0)); + json_object_object_add(a,"limit",json_object_new_int(100)); + json_object_object_add(a,"annotationLimit",json_object_new_int(100)); + json_object_object_add(j,"request",a); + json_object_object_add(j,"deviceId",json_object_new_string("1880")); + method = "collections.v7.getSortedPlaylists"; + break; + } + + case PIANO_REQUEST_GET_TRACKS: { + assert (ph->user.listenerId != NULL); + PianoRequestDataGetPlaylist_t *reqData = req->data; + + assert (reqData != NULL); + assert (reqData->station != NULL); + assert (reqData->station->id != NULL); + + req->secure = true; + switch(reqData->station->stationType) { + case PIANO_TYPE_PLAYLIST: { + json_object *a = json_object_new_object (); + json_object_object_add(a,"pandoraId",json_object_new_string(reqData->station->id)); + json_object_object_add(a,"limit",json_object_new_int(100)); + json_object_object_add(a,"annotationLimit",json_object_new_int(100)); + json_object_object_add(a,"bypassPrivacyRu1les",json_object_new_boolean(true)); + json_object_object_add(j,"request",a); + json_object_object_add(j,"deviceId",json_object_new_string("1880")); + method = "playlists.v7.getTracks"; + break; + } + + case PIANO_TYPE_ALBUM: { + json_object *a = json_object_new_array(); + json_object_array_add(a,json_object_new_string(reqData->station->id)); + json_object_object_add(j,"pandoraIds",a); + json_object_object_add(j,"annotateAlbumTracks",json_object_new_boolean(true)); + method = "catalog.v4.annotateObjects"; + break; + } + + default: + LOG("Invalid stationType 0x%x\n",ph->stations->stationType); + break; + } + break; + } + + case PIANO_REQUEST_GET_PLAYBACK_INFO: { + PianoRequestDataGetPlaylist_t *reqData = req->data; + + assert (reqData != NULL); + assert (reqData->station != NULL); + assert (reqData->station->id != NULL); + assert (reqData->station->stationType != PIANO_TYPE_STATION); + + req->secure = true; + json_object_object_add (j, "pandoraId", + json_object_new_string (reqData->retPlaylist->trackToken)); + json_object_object_add (j, "sourcePandoraId", + json_object_new_string (reqData->retPlaylist->seedId)); + json_object_object_add (j, "includeAudioToken", + json_object_new_boolean (true)); + json_object_object_add (j, "deviceCode",json_object_new_string ("")); + method = "onDemand.getAudioPlaybackInfo"; + break; + } + case PIANO_REQUEST_GET_USER_PROFILE: { + req->secure = true; + json_object *a = json_object_new_object (); + json_object_object_add(a,"limit",json_object_new_int(10)); + json_object_object_add(a,"annotationLimit",json_object_new_int(10)); + json_object_object_add(a,"profileOwner", + json_object_new_string(ph->user.listenerId)); + json_object_object_add(j,"request",a); + method = "profile.v1.getFullProfile"; + break; + } + + case PIANO_REQUEST_GET_ITEMS: { + req->secure = true; + + json_object *a = json_object_new_object (); + json_object_object_add(j,"request",a); + #if 0 + // test ability to continue after hitting limit + json_object_object_add(a,"limit",json_object_new_int(4)); + json_object_object_add(a,"cursor",json_object_new_string("g6FjzwAFdTr1OqMYoXbAoXSWokFSolRSolBMolBDokFMolBF")); + #endif + json_object_object_add(j,"deviceId",json_object_new_string("1880")); + method = "collections.v7.getItems"; + break; + } + + case PIANO_REQUEST_ANNOTATE_OBJECTS: { + json_object *a = json_object_new_array(); + PianoStation_t *station = ph->stations; + req->secure = true; + + while(station != NULL) { + assert(station->id != NULL); + if(station->name == NULL) { + switch(station->stationType) { + case PIANO_TYPE_PODCAST: + case PIANO_TYPE_ALBUM: + case PIANO_TYPE_TRACK: + json_object_array_add(a,json_object_new_string(station->id)); + break; + } + } + station = (PianoStation_t *) station->head.next; + } + json_object_object_add(j,"pandoraIds",a); + json_object_object_add(j,"annotateAlbumTracks", + json_object_new_boolean(false)); + method = "catalog.v4.annotateObjects"; + break; + } + + case PIANO_REQUEST_REMOVE_ITEM: { + /* delete item */ + PianoStation_t *station = req->data; + assert (station != NULL); + + req->secure = true; + json_object *a = json_object_new_object (); + json_object_object_add(a,"pandoraId",json_object_new_string(station->id)); + json_object_object_add(j,"request",a); + method = "collections.v7.removeItem"; + break; + } + + case PIANO_REQUEST_GET_EPISODES: { + PianoRequestDataGetEpisodes_t *reqData = req->data; + PianoStation_t *station = reqData->station; + assert (station != NULL); + + req->secure = true; + json_object_object_add(j,"annotationLimit",json_object_new_int(20)); + json_object_object_add(j,"catalogVersion",json_object_new_int(4)); + json_object_object_add(j,"pandoraId",json_object_new_string(station->id)); + json_object_object_add(j,"sortingOrder",json_object_new_string("")); + method = "aesop.v1.getDetails"; + break; + } + } /* standard parameter */ if (method != NULL) { char *urlencAuthToken; diff --git a/src/libpiano/response.c b/src/libpiano/response.c index 0be887281..e34449589 100644 --- a/src/libpiano/response.c +++ b/src/libpiano/response.c @@ -33,6 +33,15 @@ THE SOFTWARE. #include "piano_private.h" #include "crypt.h" +static const char *qualityMap[] = { + "", "lowQuality", "mediumQuality","highQuality" +}; + +static const char *formatMap[] = { + "", "aacplus", "mp3" +}; + +static const char *imageHost = "https://content-images.p-cdn.com/"; static char *PianoJsonStrdup (json_object *j, const char *key) { assert (j != NULL); assert (key != NULL); @@ -57,6 +66,69 @@ static bool getBoolDefault (json_object * const j, const char * const key, const } } +static const char *PianoJsonGetStr(json_object *j, const char *key) { + assert (j != NULL); + assert (key != NULL); + + json_object *v; + if (json_object_object_get_ex (j, key, &v)) { + return json_object_get_string (v); + } else { + return NULL; + } +} + +static int getInt(json_object * const j, const char * const key) { + assert (j != NULL); + assert (key != NULL); + + json_object *v; + if (json_object_object_get_ex (j, key, &v)) { + return json_object_get_int(v); + } else { + return 0; + } +} + +static char *getCoverArt(struct json_object *Val) +{ + char artUrl[120]; + json_object *v = NULL; + char *Ret = NULL; + + if (json_pointer_get(Val, "/icon/artUrl", &v)) { + LOG("Couldn't get artUrl\n"); + } + else { + assert (v != NULL); + snprintf(artUrl,sizeof(artUrl),"%s%s", + imageHost,json_object_get_string(v)); + Ret = strdup(artUrl); + } + + return Ret; +} + +static int getBool(json_object * const j, const char * const key) { + assert (j != NULL); + assert (key != NULL); + + json_object *v; + if (json_pointer_get(j, key, &v)) { + return -1; + } else { + return json_object_get_boolean (v) ? 1 : 0; + } +} + +static void PianoJsonParsePlaylist(json_object *j, PianoStation_t *s) +{ + s->name = PianoJsonStrdup (j, "name"); + s->id = PianoJsonStrdup (j, "pandoraId"); + s->isCreator = false; + s->isQuickMix = false; +} + static void PianoJsonParseStation (json_object *j, PianoStation_t *s) { s->name = PianoJsonStrdup (j, "stationName"); s->id = PianoJsonStrdup (j, "stationToken"); @@ -191,6 +263,8 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { ph->user.listenerId = PianoJsonStrdup (result, "userId"); ph->user.authToken = PianoJsonStrdup (result, "userAuthToken"); + ph->user.IsSubscriber = getBoolDefault (result, + "isSubscriber", false); break; } break; @@ -213,6 +287,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { return PIANO_RET_OUT_OF_MEMORY; } + tmpStation->stationType = PIANO_TYPE_STATION; PianoJsonParseStation (s, tmpStation); @@ -241,6 +316,173 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { break; } + case PIANO_REQUEST_GET_PLAYLISTS: { + /* get playlists */ + assert (req->responseData != NULL); + + json_object *playlists; + + if (!json_object_object_get_ex (result, "items", &playlists)) { + break; + } + + for (int i = 0; i < json_object_array_length (playlists); i++) { + PianoStation_t *tmpStation; + json_object *s = json_object_array_get_idx (playlists, i); + + if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + tmpStation->stationType = PIANO_TYPE_PLAYLIST; + + PianoJsonParsePlaylist(s, tmpStation); + + /* start new linked list or append */ + ph->stations = PianoListAppendP (ph->stations, tmpStation); + } + break; + } + + // Get album or playlist tracks + case PIANO_REQUEST_GET_TRACKS: { + assert (req->responseData != NULL); + + PianoRequestDataGetPlaylist_t *reqData = req->data; + PianoSong_t *playlist = NULL; + PianoSong_t *FullPlaylist = NULL; + + assert (result != NULL); + assert (req->responseData != NULL); + assert (reqData != NULL); + + switch(reqData->station->stationType) { + case PIANO_TYPE_PLAYLIST: { + json_object *tracks = NULL; + json_object *annotations = NULL; + if (!json_object_object_get_ex (result, "tracks", &tracks)) { + break; + } + assert (tracks!= NULL); + LOG("got tracks\n"); + if (!json_object_object_get_ex (result, "annotations", &annotations)) { + break; + } + assert (annotations != NULL); + LOG("got annotations\n"); + + for (int i = 0; i < json_object_array_length (tracks); i++) { + json_object *s = json_object_array_get_idx (tracks, i); + json_object *trackInfo = NULL; + PianoSong_t *song; + + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + song->seedId = PianoJsonStrdup(result, "pandoraId"); + song->trackToken = PianoJsonStrdup (s, "trackPandoraId"); + assert (song->trackToken != NULL); + + LOG("track %d: %s\n",i + 1,song->trackToken); + + if (!json_object_object_get_ex (annotations, song->trackToken, &trackInfo)) { + break; + } + assert (trackInfo!= NULL); + song->artist = PianoJsonStrdup(trackInfo, "artistName"); + song->album = PianoJsonStrdup(trackInfo, "albumName"); + song->title = PianoJsonStrdup(trackInfo, "name"); + song->fileGain = 0.0; + song->length = getInt(trackInfo, "duration"); + song->coverArt = getCoverArt(trackInfo); + playlist = PianoListAppendP (playlist, song); + } + break; + } + + case PIANO_TYPE_ALBUM: { + char trackTitle[120]; + int totalTracks = 0; + int trackNumber; + + // Count tracks + json_object_object_foreach(result,Key1,Val1) { + if(Key1[0] != 'T' || Key1[1] != 'R') { + continue; + } + totalTracks++; + } + + json_object_object_foreach(result,Key,Val) { + if(Key[0] != 'T' || Key[1] != 'R') { + continue; + } + LOG("got track %s: ",Key); + trackNumber = getInt(Val,"trackNumber"); + snprintf(trackTitle,sizeof(trackTitle), + totalTracks > 9 ? "%02d %s" : "%d %s", + trackNumber,PianoJsonGetStr(Val,"name")); + LOG_RAW("%s\n",trackTitle); + if(getBool(Val,"/rightsInfo/hasInteractive") != 1) { + LOG(" ignored\n"); + LOG(" hasInteractive: %d\n",getBool(Val,"/rightsInfo/hasInteractive")); + LOG(" hasOffline: %d\n",getBool(Val,"/rightsInfo/hasOffline")); + LOG(" hasNonInteractive: %d\n",getBool(Val,"/rightsInfo/hasNonInteractive")); + LOG(" hasStatutory: %d\n",getBool(Val,"/rightsInfo/hasStatutory")); + LOG(" hasRadioRights: %d\n",getBool(Val,"/rightsInfo/hasRadioRights")); + continue; + } + PianoSong_t *song; + + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + song->stationId = strdup(reqData->station->id); + song->title = strdup(trackTitle); + song->trackToken = strdup(Key); + song->seedId = PianoJsonStrdup(Val,"albumId"); + song->artist = PianoJsonStrdup(Val,"artistName"); + song->album = PianoJsonStrdup(Val,"albumName"); + song->fileGain = 0.0; + song->length = getInt(Val, "duration"); + song->coverArt = getCoverArt(Val); + // Add to playlist in track order + + if(playlist == NULL) { + playlist = song; + } + else { + PianoSong_t *lastSong = (PianoSong_t *) &playlist; + PianoSong_t *nextSong = playlist; + do { + int nextSongTrackNum; + if(nextSong == NULL) { + lastSong->head.next = (struct PianoListHead *) song; + break; + } + sscanf(nextSong->title,"%d",&nextSongTrackNum); + if(nextSongTrackNum > trackNumber) { + lastSong->head.next = (struct PianoListHead *) song; + song->head.next = (struct PianoListHead *) nextSong; + break; + } + lastSong = nextSong; + nextSong = (PianoSong_t *) nextSong->head.next; + } while(true); + } + } + break; + } + + default: + LOG("Invalid stationType 0x%x\n",ph->stations->stationType); + break; + } + reqData->retPlaylist = playlist; + break; + } + case PIANO_REQUEST_GET_PLAYLIST: { /* get playlist, usually four songs */ PianoRequestDataGetPlaylist_t *reqData = req->data; @@ -351,6 +593,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { break; } + case PIANO_REQUEST_REMOVE_ITEM: case PIANO_REQUEST_DELETE_STATION: { /* delete station from server and station list */ PianoStation_t *station = req->data; @@ -712,8 +955,370 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { } break; } - } + case PIANO_REQUEST_GET_PLAYBACK_INFO: { + PianoRequestDataGetPlaylist_t *reqData = req->data; + PianoSong_t *song = reqData->retPlaylist; + + assert (req->responseData != NULL); + assert (reqData != NULL); + assert (reqData->quality < sizeof (qualityMap)/sizeof (*qualityMap)); + assert (song != NULL); + + json_object *audioUrlMap = NULL; + if (!json_object_object_get_ex (result, "audioUrlMap", &audioUrlMap)) { + break; + } + assert (audioUrlMap != NULL); + + const char *quality = qualityMap[reqData->quality]; + json_object *umap; + + json_object *jsonEncoding = NULL; + if (json_object_object_get_ex (audioUrlMap, quality, &umap)) { + assert (umap != NULL); + if (json_object_object_get_ex (umap, "encoding", &jsonEncoding)) { + assert (jsonEncoding != NULL); + const char *encoding = json_object_get_string (jsonEncoding); + assert (encoding != NULL); + for (size_t k = 0; k < sizeof (formatMap)/sizeof (*formatMap); k++) { + if (strcmp (formatMap[k], encoding) == 0) { + song->audioFormat = k; + break; + } + } + song->audioUrl = PianoJsonStrdup (umap, "audioUrl"); + } + } + + if(song->audioUrl == NULL) { + /* requested quality is not available */ + LOG("quality %s not found in audioUrlMap\n",quality); + ret = PIANO_RET_QUALITY_UNAVAILABLE; + PianoDestroyPlaylist (reqData->retPlaylist); + goto cleanup; + } + break; + } + + case PIANO_REQUEST_GET_USER_PROFILE: { + assert (req->responseData != NULL); + + json_object *stations; + json_object *annotations = NULL; + ph->user.IsPremiumUser = getBoolDefault (result,"isPremiumUser", false); + LOG("IsPremiumUser: %d\n",ph->user.IsPremiumUser); + + if (!json_object_object_get_ex (result, "annotations", &annotations)) { + break; + } + assert (annotations != NULL); + int Annotations = 0; + json_object_object_foreach(annotations,Key,Val) { + const char *type = PianoJsonGetStr(Val,"type"); + + Annotations++; + if(strcmp(type,"PL") == 0) { + ph->user.PlayListCount++; + } + else if(strcmp(type,"ST") == 0) { + ph->user.StationCount++; + } + else if(strcmp(type,"AL") == 0) { + ph->user.AlbumCount++; + } + else if(strcmp(type,"TR") == 0) { + ph->user.TrackCount++; + } + else if(strcmp(type,"LI") == 0) { + } + else if(strcmp(type,"AR") == 0) { + } + else { + LOG("type %s ignored\n",type); + } + } + LOG("Found: %d annotations:\n",Annotations); + LOG(" PlayLists: %d:\n",ph->user.PlayListCount); + LOG(" Stations: %d:\n",ph->user.StationCount); + LOG(" Albums: %d:\n",ph->user.AlbumCount); + LOG(" Tracks: %d:\n",ph->user.TrackCount); + break; + } + + case PIANO_REQUEST_GET_ITEMS: { + assert (req->responseData != NULL); + json_object *items = NULL; + if (!json_object_object_get_ex (result, "items", &items)) { + break; + } + assert (items != NULL); + for (int i = 0; i < json_object_array_length (items); i++) { + json_object *s = json_object_array_get_idx (items, i); + const char *type = PianoJsonGetStr(s,"pandoraType"); + PianoSong_t *song; + PianoStationType_t stationType = PIANO_TYPE_NONE; + + if(strcmp(type,"PL") == 0 || strcmp(type,"ST") == 0) { + // Playlists and stations handled elsewhere + } + else if(strcmp(type,"AL") == 0) { + stationType = PIANO_TYPE_ALBUM; + } + else if(strcmp(type,"TR") == 0) { + stationType = PIANO_TYPE_TRACK; + } + else if(strcmp(type,"PC") == 0) { + stationType = PIANO_TYPE_PODCAST; + ph->user.PodcastCount++; + } + else { + LOG("type %s ignored\n",type); + } + + if(stationType != PIANO_TYPE_NONE) { + PianoStation_t *tmpStation; + if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + tmpStation->stationType = stationType; + tmpStation->id = PianoJsonStrdup (s, "pandoraId"); + /* start new linked list or append */ + ph->stations = PianoListAppendP (ph->stations, tmpStation); + } + else { + LOG("type %s ignored\n",type); + } + } + + if(ph->user.PodcastCount) { + LOG("Found %d podcast stations\n",ph->user.PodcastCount); + } + break; + } + + case PIANO_REQUEST_ANNOTATE_OBJECTS: { + assert (req->responseData != NULL); + assert (req->data != NULL); + PianoStation_t *station = ph->stations; + PianoRequestDataGetPlaylist_t *reqData = req->data; + + while(station != NULL) { + assert(station->id != NULL); + switch(station->stationType) { + case PIANO_TYPE_PODCAST: { + json_object_object_foreach(result,Key,Val) { + if(strcmp(Key,station->id) == 0) { + PianoSong_t *song = station->theSong; + assert (song == NULL); + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + station->name = PianoJsonStrdup(Val,"name"); + station->seedId = PianoJsonStrdup(Val,"latestEpisodeId"); + station->theSong = song; + song->album = strdup(station->name); + song->coverArt = getCoverArt(Val); // podcast coverArt + LOG("podcast coverart %s\n",song->coverArt); + break; + } + } + if(station->name == NULL) { + LOG("Couldn't find station %s\n",station->id); + } + break; + } + + case PIANO_TYPE_ALBUM: { + json_object_object_foreach(result,Key,Val) { + if(strcmp(Key,station->id) == 0) { + char Temp[120]; + snprintf(Temp,sizeof(Temp),"%s - %s", + PianoJsonGetStr(Val,"artistName"), + PianoJsonGetStr(Val,"name")); + station->name = strdup(Temp); + station->seedId = PianoJsonStrdup(Val,"pandoraId"); + break; + } + } + if(station->name == NULL) { + LOG("Couldn't find station %s\n",station->id); + } + break; + } + + case PIANO_TYPE_TRACK: { + json_object_object_foreach(result,Key,Val) { + if(strcmp(Key,station->id) == 0) { + station->name = PianoJsonStrdup(Val,"name"); + station->seedId = PianoJsonStrdup(Val,"albumId"); + + PianoSong_t *song = station->theSong; + assert (song == NULL); + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + station->theSong = song; + song->artist = PianoJsonStrdup(Val, "artistName"); + song->album = PianoJsonStrdup(Val, "albumName"); + song->title = PianoJsonStrdup(Val, "name"); + song->length = getInt(Val, "duration"); + song->coverArt = getCoverArt(Val); + song->fileGain = 0.0; + break; + } + } + if(station->name == NULL) { + LOG("Couldn't find station %s\n",station->id); + } + break; + } + } + station = (PianoStation_t *) station->head.next; + } + break; + } + + case PIANO_REQUEST_GET_EPISODES: { + assert (req->responseData != NULL); + assert (req->data != NULL); + PianoRequestDataGetEpisodes_t *reqData = req->data; + PianoStation_t *station = reqData->station; + PianoSong_t *playlist = NULL; + int Added = 0; + PianoSong_t *song; + + json_object *annotations = NULL; + if (json_pointer_get(result, "/details/annotations", &annotations)) { + break; + } + json_object_object_foreach(annotations,Key,Val) { + if(Key[0] != 'P' || Key[1] != 'E') { + // not episode, ignore it + continue; + } + const char *EpisodeTitle = PianoJsonGetStr(Val,"name"); + + if(EpisodeTitle == NULL) { + LOG("Couldn't get title of episode\n"); + continue; + } + const char *Id = PianoJsonGetStr(Val,"podcastId"); + if(Id == NULL) { + LOG("Couldn't get podcastId\n"); + continue; + } + + if(strcmp(station->id,Id) != 0) { + LOG("Episode not for selected podcast (%s != %s)\n", + station->id,Id); + continue; + } + + const char *State = PianoJsonGetStr(Val,"contentState"); + if(State == NULL) { + LOG("Couldn't get contentState\n"); + continue; + } + + if(strcmp(State,"AVAILABLE") != 0) { + if(strlen(EpisodeTitle) > 0) { + LOG(" ignored %s\n",EpisodeTitle); + LOG(" contentState: %s\n",State); + } + continue; + } + if(getBool(Val,"/rightsInfo/hasInteractive") != 1) { + LOG(" ignored %s\n",EpisodeTitle); + LOG(" hasInteractive: %d\n",getBool(Val,"/rightsInfo/hasInteractive")); + LOG(" hasOffline: %d\n",getBool(Val,"/rightsInfo/hasOffline")); + LOG(" hasNonInteractive: %d\n",getBool(Val,"/rightsInfo/hasNonInteractive")); + LOG(" hasStatutory: %d\n",getBool(Val,"/rightsInfo/hasStatutory")); + LOG(" hasRadioRights: %d\n",getBool(Val,"/rightsInfo/hasRadioRights")); + continue; + } + + int Month; + int Day; + int Year; + char trackTitle[120]; + const char *Released = PianoJsonGetStr(Val,"releaseDate"); + if(Released == NULL) { + LOG("Couldn't get releaseDate\n"); + continue; + } + if(sscanf(Released,"%d-%d-%d",&Year,&Month,&Day) != 3) { + LOG("Couldn't convert releaseDate %s\n",Released); + continue; + } + const char *Title = PianoJsonGetStr(Val,"name"); + if(Title == NULL) { + LOG("Couldn't get episode title\n"); + continue; + } + const char *trackToken = PianoJsonGetStr(Val, "pandoraId"); + if(trackToken == NULL) { + LOG("Couldn't get trackToken\n"); + continue; + } + + snprintf(trackTitle,sizeof(trackTitle),"%02d/%02d: %s", + Month,Day,Title); + LOG("Got %s\n",trackTitle); + + if(!reqData->bGetAll) { + // just getting name of the current episode + song = reqData->playList; + if(strcmp(trackToken,song->trackToken) != 0) { + LOG("Ignoring %s, not current episode\n",trackTitle); + continue; + } + song->title = strdup(trackTitle); + LOG("Added name of current episode\n"); + break; + } + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + song->title = strdup(trackTitle); + song->trackToken = strdup(trackToken); + song->length = getInt(Val, "duration"); + // Save release date for sorting + song->fileGain = ((Year - 1900) * 10000) + (Month * 100) + Day; + // Add to playlist in release data order + PianoSong_t *thisSong = playlist; + PianoSong_t *lastSong = (PianoSong_t *) &playlist; + do { + if(thisSong == NULL) { + lastSong->head.next = (struct PianoListHead *) song; + // LOG("Added to end of list\n"); + break; + } + // LOG("Comparing to %s\n",thisSong->title); + if(thisSong->fileGain > song->fileGain) { + lastSong->head.next = (struct PianoListHead *) song; + song->head.next = (struct PianoListHead *) thisSong; + // LOG("Added before\n"); + break; + } + lastSong = thisSong; + thisSong = (PianoSong_t *) thisSong->head.next; + } while(true); + Added++; + } + reqData->playList = playlist; + LOG("Added %d episodes:\n",Added); +#if 0 + song = playlist; + while(song != NULL) { + LOG(" %s\n",song->title); + song = (PianoSong_t *) song->head.next; + } +#endif + break; + } + } cleanup: json_object_put (j); diff --git a/src/main.c b/src/main.c index c726b28bf..a3eab1778 100644 --- a/src/main.c +++ b/src/main.c @@ -56,6 +56,7 @@ THE SOFTWARE. #include "ui.h" #include "ui_dispatch.h" #include "ui_readline.h" +#include "debug_log.h" /* authenticate user */ @@ -178,6 +179,53 @@ static bool BarMainGetStations (BarApp_t *app) { return ret; } +static bool BarMainGetUserProfile(BarApp_t *app) { + PianoReturn_t pRet; + CURLcode wRet; + bool ret; + + BarUiMsg (&app->settings, MSG_INFO, "Get user profile ... "); + ret = BarUiPianoCall (app, PIANO_REQUEST_GET_USER_PROFILE, NULL, &pRet, &wRet); + return ret; +} + +static bool BarMainGetAllStations (BarApp_t *app) { + PianoReturn_t pRet; + CURLcode wRet; + bool ret; + PianoRequestDataGetPlaylist_t reqData; + reqData.station = app->nextStation; + reqData.quality = app->settings.audioQuality; + reqData.retPlaylist = NULL; + + do { + ret = BarMainGetStations (app); + if(!ret) { + break; + } + BarUiMsg (&app->settings, MSG_INFO, "Get items ... "); + ret = BarUiPianoCall (app, PIANO_REQUEST_GET_ITEMS, &reqData, &pRet, &wRet); + if(!ret) { + break; + } + BarUiMsg (&app->settings, MSG_INFO, "Annotate Objects ... "); + ret = BarUiPianoCall (app, PIANO_REQUEST_ANNOTATE_OBJECTS, &reqData, &pRet, &wRet); + if(!ret) { + break; + } + if(app->ph.user.IsPremiumUser) { + BarUiMsg (&app->settings, MSG_INFO, "Get Playlists ... "); + ret = BarUiPianoCall (app, PIANO_REQUEST_GET_PLAYLISTS,NULL, &pRet, &wRet); + if(!ret) { + break; + } + } + BarUiStartEventCmd (&app->settings, "usergetstations", NULL, NULL, &app->player, + app->ph.stations, pRet, wRet); + + } while (false); + return ret; +} /* get initial station from autostart setting or user input */ static void BarMainGetInitialStation (BarApp_t *app) { @@ -214,21 +262,108 @@ static void BarMainGetPlaylist (BarApp_t *app) { PianoReturn_t pRet; CURLcode wRet; PianoRequestDataGetPlaylist_t reqData; - reqData.station = app->nextStation; + PianoStation_t *station = app->nextStation; + assert(station != NULL); + + memset(&reqData,0,sizeof(reqData)); + reqData.station = station; reqData.quality = app->settings.audioQuality; + app->stationStarted = true; - BarUiMsg (&app->settings, MSG_INFO, "Receiving new playlist... "); - if (!BarUiPianoCall (app, PIANO_REQUEST_GET_PLAYLIST, - &reqData, &pRet, &wRet)) { - app->nextStation = NULL; - } else { - app->playlist = reqData.retPlaylist; - if (app->playlist == NULL) { - BarUiMsg (&app->settings, MSG_INFO, "No tracks left.\n"); + LOG("stationType %s\n",StationType2Str(station->stationType)); + + switch(station->stationType) { + case PIANO_TYPE_STATION: + BarUiMsg (&app->settings, MSG_INFO, "Receiving new playlist... "); + if (!BarUiPianoCall (app, PIANO_REQUEST_GET_PLAYLIST, + &reqData, &pRet, &wRet)) { + app->nextStation = NULL; + } else { + app->playlist = reqData.retPlaylist; + if (app->playlist == NULL) { + BarUiMsg (&app->settings, MSG_INFO, "No tracks left.\n"); + app->nextStation = NULL; + } + } + app->curStation = app->nextStation; + break; + + case PIANO_TYPE_PLAYLIST: + BarUiMsg (&app->settings, MSG_INFO, "Get tracks ... "); + if (!BarUiPianoCall (app, PIANO_REQUEST_GET_TRACKS, + &reqData, &pRet, &wRet)) { + app->nextStation = NULL; + } else { + app->playlist = reqData.retPlaylist; + app->FullPlaylist = CopyPlaylist(app->playlist); + + if (app->playlist == NULL) { + BarUiMsg (&app->settings, MSG_INFO, "No tracks left.\n"); + app->nextStation = NULL; + } + } + app->curStation = app->nextStation; + break; + + case PIANO_TYPE_TRACK: + assert(station->theSong != NULL); + reqData.retPlaylist = CopySong(station->theSong); + assert(reqData.retPlaylist != NULL); + reqData.retPlaylist->trackToken = strdup(station->id); + reqData.retPlaylist->seedId = strdup(station->seedId); + + BarUiMsg (&app->settings, MSG_INFO, "Get playback info ... "); + if (BarUiPianoCall (app, PIANO_REQUEST_GET_PLAYBACK_INFO, + &reqData, &pRet, &wRet)) { + app->playlist = reqData.retPlaylist; + } + else { + ELOG("REQUEST_GET_PLAYBACK_INFO failed\n"); + } + app->curStation = app->nextStation; + break; + + case PIANO_TYPE_PODCAST: + PianoSong_t *song = station->theSong; + station->theSong = NULL; + assert (song != NULL); + song->trackToken = strdup(station->seedId); + song->seedId = strdup(station->id); + app->playlist = song; + app->curStation = app->nextStation; app->nextStation = NULL; - } + if(song->title == NULL) { + // Get name of episode + PianoRequestDataGetEpisodes_t reqData1; + reqData1.station = app->curStation; + reqData1.playList = song; + reqData1.bGetAll = false; + BarUiMsg (&app->settings, MSG_INFO, "Get episodes ... "); + if (!BarUiPianoCall (app, PIANO_REQUEST_GET_EPISODES, + &reqData1, &pRet, &wRet)) { + app->curStation = NULL; + ELOG("Internal error\n"); + break; + } + } + break; + + case PIANO_TYPE_ALBUM: + BarUiMsg (&app->settings, MSG_INFO, "Get tracks ... "); + if (!BarUiPianoCall (app, PIANO_REQUEST_GET_TRACKS, + &reqData, &pRet, &wRet)) { + app->nextStation = NULL; + } else { + app->playlist = reqData.retPlaylist; + app->FullPlaylist = CopyPlaylist(app->playlist); + if (app->playlist == NULL) { + BarUiMsg (&app->settings, MSG_INFO, "No tracks left.\n"); + app->nextStation = NULL; + } + } + app->curStation = app->nextStation; + break; } - app->curStation = app->nextStation; BarUiStartEventCmd (&app->settings, "stationfetchplaylist", app->curStation, app->playlist, &app->player, app->ph.stations, pRet, wRet); @@ -243,14 +378,34 @@ static void BarMainStartPlayback (BarApp_t *app, pthread_t *playerThread) { const PianoSong_t * const curSong = app->playlist; assert (curSong != NULL); + app->stationStarted = true; BarUiPrintSong (&app->settings, curSong, app->curStation->isQuickMix ? PianoFindStationById (app->ph.stations, curSong->stationId) : NULL); + if(app->curStation->stationType != PIANO_TYPE_STATION && + curSong->audioUrl == NULL) + { + PianoRequestDataGetPlaylist_t reqData; + PianoReturn_t pRet; + CURLcode wRet; + + reqData.station = app->curStation; + reqData.quality = app->settings.audioQuality; + reqData.retPlaylist = app->playlist; + + BarUiMsg (&app->settings, MSG_INFO, "Get playback info ... "); + BarUiPianoCall (app, PIANO_REQUEST_GET_PLAYBACK_INFO, + &reqData, &pRet, &wRet); + } + static const char httpPrefix[] = "http://"; + static const char httpsPrefix[] = "https://"; /* avoid playing local files */ - if (curSong->audioUrl == NULL || - strncmp (curSong->audioUrl, httpPrefix, strlen (httpPrefix)) != 0) { + if (curSong->audioUrl == NULL + || (strncmp (curSong->audioUrl, httpPrefix, strlen (httpPrefix)) != 0 + && strncmp (curSong->audioUrl, httpsPrefix, strlen (httpsPrefix))) != 0) + { BarUiMsg (&app->settings, MSG_ERR, "Invalid song url.\n"); } else { player_t * const player = &app->player; @@ -357,7 +512,11 @@ static void BarMainLoop (BarApp_t *app) { return; } - if (!BarMainGetStations (app)) { + if(app->ph.user.IsSubscriber) { + BarMainGetUserProfile (app); + } + + if (!BarMainGetAllStations (app)) { return; } @@ -386,9 +545,29 @@ static void BarMainLoop (BarApp_t *app) { } if (app->playlist == NULL && app->nextStation != NULL && !app->doQuit) { if (app->nextStation != app->curStation) { + app->stationStarted = false; BarUiPrintStation (&app->settings, app->nextStation); } - BarMainGetPlaylist (app); + switch(app->nextStation->stationType) { + case PIANO_TYPE_STATION: + case PIANO_TYPE_PLAYLIST: + // when these types finish playing just start them over + BarMainGetPlaylist (app); + break; + + case PIANO_TYPE_ALBUM: + case PIANO_TYPE_PODCAST: + case PIANO_TYPE_TRACK: + if(app->stationStarted) { + // when these types finish playing prompt user to select a new "station" + app->nextStation = NULL; + BarUiActSelectStation( app, NULL,NULL,BAR_DC_UNDEFINED); + } + else { + BarMainGetPlaylist (app); + } + break; + } } /* song ready to play */ if (app->playlist != NULL) { @@ -427,6 +606,82 @@ static void BarMainSetupSigaction () { sigaction (SIGINT, &act, NULL); } +const char *StationType2Str(PianoStationType_t Type) +{ + const char *Ret = "Invalid station type"; + const char *StationTypeStrings[] = { + "station", // PIANO_TYPE_NONE + "station", // PIANO_TYPE_STATION + "podcast", // PIANO_TYPE_PODCAST + "playlist", // PIANO_TYPE_PLAYLIST + "album", // PIANO_TYPE_ALBUM + "track", // PIANO_TYPE_TRACK + }; + if(Type <= PIANO_TYPE_TRACK) { + Ret = StationTypeStrings[Type]; + } + return Ret; +} + +PianoSong_t *CopySong(PianoSong_t *song) +{ + PianoSong_t *Ret = calloc(1, sizeof (PianoSong_t)); + + if(Ret != NULL) { + if(song->artist != NULL) { + Ret->artist = strdup(song->artist); + } + if(song->stationId != NULL) { + Ret->stationId = strdup(song->stationId); + } + if(song->album != NULL) { + Ret->album = strdup(song->album); + } + if(song->audioUrl != NULL) { + Ret->audioUrl = strdup(song->audioUrl); + } + if(song->coverArt != NULL) { + Ret->coverArt = strdup(song->coverArt); + } + if(song->musicId != NULL) { + Ret->musicId = strdup(song->musicId); + } + if(song->title != NULL) { + Ret->title = strdup(song->title); + } + if(song->seedId != NULL) { + Ret->seedId = strdup(song->seedId); + } + if(song->feedbackId != NULL) { + Ret->feedbackId = strdup(song->feedbackId); + } + if(song->detailUrl != NULL) { + Ret->detailUrl = strdup(song->detailUrl); + } + if(song->trackToken != NULL) { + Ret->trackToken = strdup(song->trackToken); + } + Ret->fileGain = song->fileGain; + Ret->length = song->length; + Ret->rating = song->rating; + Ret->audioFormat = song->audioFormat; + } + + return Ret; +} + +PianoSong_t *CopyPlaylist(PianoSong_t *song) +{ + PianoSong_t *Ret = NULL; + + while(song != NULL) { + PianoSong_t *NewSong = CopySong(song); + Ret = PianoListAppend(&Ret->head,&NewSong->head); + song = (PianoSong_t *) song->head.next; + } + return Ret; +} + int main (int argc, char **argv) { static BarApp_t app; @@ -513,6 +768,7 @@ int main (int argc, char **argv) { PianoDestroy (&app.ph); PianoDestroyPlaylist (app.songHistory); PianoDestroyPlaylist (app.playlist); + PianoDestroyPlaylist (app.FullPlaylist); curl_easy_cleanup (app.http); curl_global_cleanup (); BarPlayerDestroy (&app.player); diff --git a/src/main.h b/src/main.h index 7dc0ac8df..a5b6559b7 100644 --- a/src/main.h +++ b/src/main.h @@ -45,8 +45,14 @@ typedef struct { sig_atomic_t doQuit; BarReadlineFds_t input; unsigned int playerErrors; + PianoStationType_t Filter; + char stationStarted; + PianoSong_t *FullPlaylist; } BarApp_t; #include extern sig_atomic_t *interrupted; +const char *StationType2Str(PianoStationType_t Type); +PianoSong_t *CopySong(PianoSong_t *song); +PianoSong_t *CopyPlaylist(PianoSong_t *song); diff --git a/src/settings.c b/src/settings.c index 5859b827b..df11c4829 100644 --- a/src/settings.c +++ b/src/settings.c @@ -174,7 +174,7 @@ void BarSettingsRead (BarSettings_t *settings) { settings->tiredIcon = strdup (" zZ"); settings->atIcon = strdup (" @ "); settings->npSongFormat = strdup ("\"%t\" by \"%a\" on \"%l\"%r%@%s"); - settings->npStationFormat = strdup ("Station \"%n\" (%i)"); + settings->npStationFormat = strdup ("%s \"%n\" (%i)"); settings->listSongFormat = strdup ("%i) %a - %t%r"); settings->timeFormat = strdup ("%s%r/%t"); settings->rpcHost = strdup (PIANO_RPC_HOST); diff --git a/src/settings.h b/src/settings.h index 2e5a37824..499811da4 100644 --- a/src/settings.h +++ b/src/settings.h @@ -59,8 +59,11 @@ typedef enum { BAR_KS_PAUSE = 27, BAR_KS_VOLRESET = 28, BAR_KS_SETTINGS = 29, - /* insert new shortcuts _before_ this element and increase its value */ - BAR_KS_COUNT = 30, + BAR_KS_MODE = 30, + BAR_KS_GOTO = 31, + + /* insert new shortcuts _before_ this element and increase its value */ + BAR_KS_COUNT = 32, } BarKeyShortcutId_t; #define BAR_KS_DISABLED '\x00' diff --git a/src/ui.c b/src/ui.c index 0c7386ad3..1cb366018 100644 --- a/src/ui.c +++ b/src/ui.c @@ -467,6 +467,7 @@ PianoStation_t *BarUiSelectStation (BarApp_t *app, PianoStation_t *stations, PianoStation_t **sortedStations = NULL, *retStation = NULL; size_t stationCount, i, lastDisplayed, displayCount; char buf[100]; + PianoStationType_t Filter = app->Filter; if (stations == NULL) { BarUiMsg (&app->settings, MSG_ERR, "No station available.\n"); @@ -484,11 +485,37 @@ PianoStation_t *BarUiSelectStation (BarApp_t *app, PianoStation_t *stations, for (i = 0; i < stationCount; i++) { const PianoStation_t *currStation = sortedStations[i]; /* filter stations */ - if (BarStrCaseStr (currStation->name, buf) != NULL) { - BarUiMsg (&app->settings, MSG_LIST, "%2zi) %c%c%c %s\n", i, + if (BarStrCaseStr (currStation->name, buf) != NULL && + (Filter == PIANO_TYPE_NONE || Filter == currStation->stationType)) + { + char StationTypeChar = ' '; + switch(currStation->stationType) { + case PIANO_TYPE_STATION: + if(!currStation->isCreator) { + StationTypeChar = 'S'; + } + break; + + case PIANO_TYPE_PLAYLIST: + StationTypeChar = 'P'; + break; + + case PIANO_TYPE_PODCAST: + StationTypeChar = 'C'; + break; + + case PIANO_TYPE_ALBUM: + StationTypeChar = 'A'; + break; + + case PIANO_TYPE_TRACK: + StationTypeChar = 'T'; + break; + } + BarUiMsg (&app->settings, MSG_LIST, "%2zi) %c%c%c %s\n", displayCount, currStation->useQuickMix ? 'q' : ' ', currStation->isQuickMix ? 'Q' : ' ', - !currStation->isCreator ? 'S' : ' ', + StationTypeChar, currStation->name); ++displayCount; lastDisplayed = i; @@ -508,8 +535,22 @@ PianoStation_t *BarUiSelectStation (BarApp_t *app, PianoStation_t *stations, if (isnumeric (buf)) { unsigned long selected = strtoul (buf, NULL, 0); - if (selected < stationCount) { - retStation = sortedStations[selected]; + if (selected < displayCount) { + memset (buf, 0, sizeof (buf)); + displayCount = 0; + for (i = 0; i < stationCount; i++) { + PianoStation_t *currStation = sortedStations[i]; + /* filter stations */ + if (BarStrCaseStr (currStation->name, buf) != NULL && + (Filter == PIANO_TYPE_NONE || Filter == currStation->stationType)) + { + if(displayCount == selected) { + retStation = currStation; + break; + } + ++displayCount; + } + } } } @@ -746,12 +787,20 @@ static void BarUiAppendNewline (char *s, size_t maxlen) { void BarUiPrintStation (const BarSettings_t *settings, PianoStation_t *station) { char outstr[512]; - const char *vals[] = {station->name, station->id}; - - BarUiCustomFormat (outstr, sizeof (outstr), settings->npStationFormat, - "ni", vals); - BarUiAppendNewline (outstr, sizeof (outstr)); - BarUiMsg (settings, MSG_PLAYING, "%s", outstr); + char StationTypeStr[32]; + const char *vals[] = {StationTypeStr, station->name, station->id}; + strcpy(StationTypeStr,StationType2Str(station->stationType)); + StationTypeStr[0] = toupper(StationTypeStr[0]); + + switch(station->stationType) { + case PIANO_TYPE_STATION: + case PIANO_TYPE_PODCAST: + BarUiCustomFormat (outstr, sizeof (outstr), settings->npStationFormat, + "sni", vals); + BarUiAppendNewline (outstr, sizeof (outstr)); + BarUiMsg (settings, MSG_PLAYING, "%s", outstr); + break; + } } static const char *ratingToIcon (const BarSettings_t * const settings, @@ -785,8 +834,14 @@ void BarUiPrintSong (const BarSettings_t *settings, station != NULL ? station->name : "", song->detailUrl}; - BarUiCustomFormat (outstr, sizeof (outstr), settings->npSongFormat, - "talr@su", vals); + if(song->seedId != NULL && strncmp(song->seedId,"PC:",3) == 0) { + // "Song" is a podcast + strcpy(outstr,song->title); + } + else { + BarUiCustomFormat (outstr, sizeof (outstr), settings->npSongFormat, + "talr@su", vals); + } BarUiAppendNewline (outstr, sizeof (outstr)); BarUiMsg (settings, MSG_PLAYING, "%s", outstr); } @@ -887,6 +942,37 @@ static void BarUiEventcmdPrintSong (FILE * restrict stream, ); } +/* let user pick one station + * @param app handle + * @param stations that should be listed + * @param prompt string + * @param called if input was not a number + * @param auto-select if only one station remains after filtering + * @return pointer to selected station or NULL + */ +void BarUiSelectFilter(BarApp_t *app) +{ + char buf[100]; + const char *FilterTypes[] = { + "All","Stations","Podcasts","Playlists","Albums","Tracks",NULL + }; + + for(int i = 0; FilterTypes[i] != NULL; i++) { + BarUiMsg (&app->settings, MSG_LIST, "%i) %s\n", i,FilterTypes[i]); + if(i == PIANO_TYPE_PODCAST && !app->ph.user.IsSubscriber){ + // Must be a subscriber for Playlists, etc + break; + } + } + BarUiMsg (&app->settings, MSG_QUESTION, "%s", "Select filter: "); + + if (!BarReadlineStr (buf, sizeof (buf), &app->input, BAR_RL_DEFAULT) == 0 + && isnumeric (buf)) + { + app->Filter = atoi(buf); + } +} + /* Excute external event handler * @param settings containing the cmdline * @param event type diff --git a/src/ui.h b/src/ui.h index 33d2a76df..de51ebf72 100644 --- a/src/ui.h +++ b/src/ui.h @@ -55,4 +55,5 @@ bool BarUiPianoCall (BarApp_t * const, const PianoRequestType_t, void BarUiHistoryPrepend (BarApp_t *app, PianoSong_t *song); void BarUiCustomFormat (char *dest, size_t destSize, const char *format, const char *formatChars, const char **formatVals); +void BarUiSelectFilter(BarApp_t *app); diff --git a/src/ui_act.c b/src/ui_act.c index fa5c43bc6..740d3c142 100644 --- a/src/ui_act.c +++ b/src/ui_act.c @@ -34,6 +34,7 @@ THE SOFTWARE. #include "ui.h" #include "ui_readline.h" #include "ui_dispatch.h" +#include "debug_log.h" /* standard eventcmd call */ @@ -90,7 +91,7 @@ BarUiActCallback(BarUiActHelp) { BarUiMsg (&app->settings, MSG_NONE, "\r"); for (size_t i = 0; i < BAR_KS_COUNT; i++) { if (dispatchActions[i].helpText != NULL && - (context & dispatchActions[i].context) == dispatchActions[i].context && + BarUiContextMatch(context,dispatchActions[i].context,NULL) && app->settings.keys[i] != BAR_KS_DISABLED) { BarUiMsg (&app->settings, MSG_LIST, "%c %s\n", app->settings.keys[i], dispatchActions[i].helpText); @@ -225,12 +226,17 @@ BarUiActCallback(BarUiActAddSharedStation) { } static void drainPlaylist (BarApp_t * const app) { + app->stationStarted = false; BarUiDoSkipSong (&app->player); if (app->playlist != NULL) { /* drain playlist */ PianoDestroyPlaylist (PianoListNextP (app->playlist)); app->playlist->head.next = NULL; } + if(app->FullPlaylist != NULL) { + PianoDestroyPlaylist (app->FullPlaylist); + app->FullPlaylist = NULL; + } } /* delete current station @@ -238,23 +244,52 @@ static void drainPlaylist (BarApp_t * const app) { BarUiActCallback(BarUiActDeleteStation) { PianoReturn_t pRet; CURLcode wRet; - + bool bDeleted; + PianoRequestType_t requestType = 0; + char Temp[32]; + const char *StationTypeStr; assert (selStation != NULL); + StationTypeStr = StationType2Str(selStation->stationType); + + switch(selStation->stationType) { + case PIANO_TYPE_STATION: + requestType = PIANO_REQUEST_DELETE_STATION; + break; + + case PIANO_TYPE_PLAYLIST: + case PIANO_TYPE_ALBUM: + case PIANO_TYPE_TRACK: + requestType = PIANO_REQUEST_REMOVE_ITEM; + break; + + default: + BarUiMsg (&app->settings, MSG_ERR, "Delete not implemented for %ss.\n", + StationTypeStr); + break; + } - BarUiMsg (&app->settings, MSG_QUESTION, "Really delete \"%s\"? [yN] ", - selStation->name); - if (BarReadlineYesNo (false, &app->input)) { - BarUiMsg (&app->settings, MSG_INFO, "Deleting station... "); - if (BarUiActDefaultPianoCall (PIANO_REQUEST_DELETE_STATION, - selStation) && selStation == app->curStation) { - drainPlaylist (app); - app->nextStation = NULL; - /* XXX: usually we shoudn’t touch cur*, but DELETE_STATION destroys - * station struct */ - app->curStation = NULL; - selStation = NULL; + if(requestType) { + BarUiMsg (&app->settings, MSG_QUESTION, "Really delete the %s \"%s\"? [yN] ", + StationTypeStr,selStation->name); + if (BarReadlineYesNo (false, &app->input)) { + BarUiMsg (&app->settings, MSG_INFO, "Deleting %s... ",StationTypeStr); + if (BarUiActDefaultPianoCall (requestType,selStation) + && selStation == app->curStation) + { + drainPlaylist (app); + if(app->FullPlaylist != NULL) { + PianoDestroyPlaylist (app->FullPlaylist); + app->FullPlaylist = NULL; + } + app->nextStation = NULL; + /* XXX: usually we shoudn’t touch cur*, but DELETE_STATION destroys + * station struct */ + app->curStation = NULL; + selStation = NULL; + } + snprintf(Temp,sizeof(Temp),"%sdelete",StationTypeStr); + BarUiActDefaultEventcmd (Temp); } - BarUiActDefaultEventcmd ("stationdelete"); } } @@ -481,12 +516,18 @@ BarUiActCallback(BarUiActRenameStation) { /* play another station */ BarUiActCallback(BarUiActSelectStation) { + char prompt[32]; + snprintf(prompt,sizeof(prompt),"Select %s: ",StationType2Str(app->Filter)); PianoStation_t *newStation = BarUiSelectStation (app, app->ph.stations, - "Select station: ", NULL, app->settings.autoselect); + prompt, NULL, app->settings.autoselect); if (newStation != NULL) { app->nextStation = newStation; drainPlaylist (app); } + if(app->FullPlaylist != NULL) { + PianoDestroyPlaylist (app->FullPlaylist); + app->FullPlaylist = NULL; + } } /* ban song for 1 month @@ -927,6 +968,10 @@ BarUiActCallback(BarUiActManageStation) { PIANO_REQUEST_SET_STATION_MODE, &subReqDataSet)) { drainPlaylist (app); } + if(app->FullPlaylist != NULL) { + PianoDestroyPlaylist (app->FullPlaylist); + app->FullPlaylist = NULL; + } BarUiActDefaultEventcmd ("stationsetmode"); break; } @@ -938,3 +983,130 @@ BarUiActCallback(BarUiActManageStation) { PianoDestroyStationInfo (&reqData.info); } + +/* set station list filter + */ +BarUiActCallback(BarUiActFilter) { + BarUiSelectFilter(app); +} + +/* skip to specific track in a playlist or album */ +BarUiActCallback(BarUiActGotoSong) { + assert (selStation != NULL); + PianoRequestType_t requestType = 0; + PianoSong_t *song = NULL; + + switch(selStation->stationType) { + case PIANO_TYPE_ALBUM: { + song = app->FullPlaylist; + int i; + while(song != NULL) { + BarUiMsg (&app->settings, MSG_LIST, "%s\n", song->title); + song = (PianoSong_t *) song->head.next; + } + BarUiMsg (&app->settings, MSG_QUESTION, "Select track: "); + if (BarReadlineInt (&i, &app->input) == 0) { + return; + } + song = app->FullPlaylist; + while(song != NULL) { + int TrackNumber; + sscanf(song->title,"%d",&TrackNumber); + if(i == TrackNumber) { + LOG("Selected %s\n",song->title); + break; + } + song = (PianoSong_t *) song->head.next; + } + break; + } + + case PIANO_TYPE_PLAYLIST: { + song = app->FullPlaylist; + int i = 1; + while(song != NULL) { + BarUiMsg (&app->settings, MSG_LIST, "%d) %s\n", i ,song->title); + song = (PianoSong_t *) song->head.next; + i++; + } + BarUiMsg (&app->settings, MSG_QUESTION, "Select song: "); + if (BarReadlineInt (&i, &app->input) == 0) { + return; + } + + song = app->FullPlaylist; + int j = 1; + while(song != NULL) { + if(i == j++) { + break; + } + song = (PianoSong_t *) song->head.next; + } + break; + } + + case PIANO_TYPE_PODCAST: { + PianoReturn_t pRet; + CURLcode wRet; + PianoRequestDataGetEpisodes_t reqData; + int i = 1; + + reqData.station = selStation; + reqData.playList = NULL; + reqData.bGetAll = true; + + if (BarUiActDefaultPianoCall(PIANO_REQUEST_GET_EPISODES,&reqData) ) { + song = reqData.playList; + while(song != NULL) { + BarUiMsg (&app->settings, MSG_LIST, "%2d) %s\n", i ,song->title); + song = (PianoSong_t *) song->head.next; + i++; + } + BarUiMsg (&app->settings, MSG_QUESTION, "Select episode: "); + if (BarReadlineInt (&i, &app->input) != 0) { + song = reqData.playList; + int j = 1; + while(song != NULL) { + if(i == j++) { + break; + } + song = (PianoSong_t *) song->head.next; + } + if(song != NULL) { + PianoSong_t *NewSong = CopySong(app->playlist); + LOG("Selected '%s'\n",song->title); + + assert(NewSong->trackToken != NULL); + assert(NewSong->title != NULL); + free(NewSong->trackToken); + NewSong->trackToken = strdup(song->trackToken); + free(NewSong->title); + NewSong->title = strdup(song->title); + NewSong->fileGain = 0.0; + song = NewSong; + } + } + } + PianoDestroyPlaylist(reqData.playList); + break; + } + + case PIANO_TYPE_STATION: + case PIANO_TYPE_TRACK: + default: + BarUiMsg (&app->settings, MSG_ERR, "GoTo not implemented for %ss.\n", + StationType2Str(selStation->stationType)); + break; + } + + if(song != NULL) { + BarUiDoSkipSong (&app->player); + assert(app->playlist != NULL); + PianoSong_t *PlayTail = (PianoSong_t *) app->playlist->head.next; + + if (PlayTail != NULL) { + PianoDestroyPlaylist (PianoListNextP (PlayTail)); + } + app->playlist->head.next = (PianoListHead_t *) CopyPlaylist(song); + } +} diff --git a/src/ui_act.h b/src/ui_act.h index fb4457b08..b2c947d8c 100644 --- a/src/ui_act.h +++ b/src/ui_act.h @@ -61,4 +61,6 @@ BarUiActCallback(BarUiActVolUp); BarUiActCallback(BarUiActManageStation); BarUiActCallback(BarUiActVolReset); BarUiActCallback(BarUiActSettings); +BarUiActCallback(BarUiActFilter); +BarUiActCallback(BarUiActGotoSong); diff --git a/src/ui_dispatch.c b/src/ui_dispatch.c index 93c77c869..2c0e24a1b 100644 --- a/src/ui_dispatch.c +++ b/src/ui_dispatch.c @@ -26,6 +26,7 @@ THE SOFTWARE. #include "ui_dispatch.h" #include "settings.h" #include "ui.h" +#include "debug_log.h" /* handle global keyboard shortcuts * @return BAR_KS_* if action was performed or BAR_KS_COUNT on error/if no @@ -37,9 +38,27 @@ BarKeyShortcutId_t BarUiDispatch (BarApp_t *app, const char key, PianoStation_t assert (app != NULL); assert (sizeof (app->settings.keys) / sizeof (*app->settings.keys) == sizeof (dispatchActions) / sizeof (*dispatchActions)); + BarUiDispatchContext_t AllowedStationTypes = context & BAR_DC_ALL_STATION_TYPES; + BarUiDispatchContext_t TempContext; + BarKeyShortcutId_t Ret = BAR_KS_COUNT; if (selStation != NULL) { + const BarUiDispatchContext_t StationTypes[] = { + BAR_DC_UNDEFINED, + BAR_DC_STATION_TYPE_STATION, + BAR_DC_STATION_TYPE_PODCAST, + BAR_DC_STATION_TYPE_PLAYLIST, + BAR_DC_STATION_TYPE_ALBUM, + BAR_DC_STATION_TYPE_TRACK + }; context |= BAR_DC_STATION; + PianoStationType_t stationType = selStation->stationType; + if(stationType >= PIANO_TYPE_NONE && stationType < PIANO_TYPE_LAST) { + context |= StationTypes[stationType]; + } + else { + ELOG("Internal error: stationType 0x%x\n",stationType); + } } if (selSong != NULL) { context |= BAR_DC_SONG; @@ -47,25 +66,55 @@ BarKeyShortcutId_t BarUiDispatch (BarApp_t *app, const char key, PianoStation_t for (size_t i = 0; i < BAR_KS_COUNT; i++) { if (app->settings.keys[i] != BAR_KS_DISABLED && - app->settings.keys[i] == key) { - if ((dispatchActions[i].context & context) == dispatchActions[i].context) { - assert (dispatchActions[i].function != NULL); - - dispatchActions[i].function (app, selStation, selSong, - context); - return i; - } else if (verbose) { - if (dispatchActions[i].context & BAR_DC_SONG) { - BarUiMsg (&app->settings, MSG_ERR, "No song playing.\n"); - } else if (dispatchActions[i].context & BAR_DC_STATION) { - BarUiMsg (&app->settings, MSG_ERR, "No station selected.\n"); - } else { - assert (0); + app->settings.keys[i] == key) + { + const char *ErrMsg = NULL; + if(!BarUiContextMatch(context,dispatchActions[i].context,&ErrMsg)) { + if(verbose && ErrMsg != NULL) { + BarUiMsg (&app->settings, MSG_ERR, "%s",ErrMsg); } - return BAR_KS_COUNT; + break; } + assert (dispatchActions[i].function != NULL); + dispatchActions[i].function (app, selStation, selSong,context); + Ret = i; + break; } } - return BAR_KS_COUNT; + return Ret; +} + +bool BarUiContextMatch( + BarUiDispatchContext_t Have, + BarUiDispatchContext_t Need, + const char **ErrMsg) +{ + BarUiDispatchContext_t NeedStation = Need & BAR_DC_ALL_STATION_TYPES; + BarUiDispatchContext_t HaveStation = Have & BAR_DC_ALL_STATION_TYPES; + bool Ret = false; + + Need &= ~BAR_DC_ALL_STATION_TYPES; + Have &= ~BAR_DC_ALL_STATION_TYPES; + + do { + if(NeedStation != 0 && (NeedStation & HaveStation) == 0) { + // Station type reqirement not met + break; + } + if(Need != BAR_DC_UNDEFINED && (Need & Have) != Need) { + break; + if(ErrMsg != NULL) { + if((Need & BAR_DC_SONG) && !(Have & BAR_DC_SONG)) { + *ErrMsg = "No song playing.\n"; + } + else if((Need & BAR_DC_STATION) && !(Have & BAR_DC_STATION)) { + *ErrMsg = "No station selected.\n"; + } + } + } + Ret = true; + } while(false); + + return Ret; } diff --git a/src/ui_dispatch.h b/src/ui_dispatch.h index 72de887fe..5606a9095 100644 --- a/src/ui_dispatch.h +++ b/src/ui_dispatch.h @@ -29,8 +29,19 @@ typedef enum { BAR_DC_GLOBAL = 1, /* top-level action */ BAR_DC_STATION = 2, /* station selected */ BAR_DC_SONG = 4, /* song selected */ + BAR_DC_STATION_TYPE_STATION = 8, + BAR_DC_STATION_TYPE_PLAYLIST = 0x10, + BAR_DC_STATION_TYPE_PODCAST = 0x20, + BAR_DC_STATION_TYPE_ALBUM = 0x40, + BAR_DC_STATION_TYPE_TRACK = 0x80, } BarUiDispatchContext_t; +#define BAR_DC_ALL_STATION_TYPES (BAR_DC_STATION_TYPE_STATION \ + | BAR_DC_STATION_TYPE_PLAYLIST \ + | BAR_DC_STATION_TYPE_PODCAST \ + | BAR_DC_STATION_TYPE_ALBUM \ + | BAR_DC_STATION_TYPE_TRACK) + #include "settings.h" #include "main.h" @@ -50,16 +61,18 @@ typedef struct { /* see settings.h */ static const BarUiDispatchAction_t dispatchActions[BAR_KS_COUNT] = { {'?', BAR_DC_UNDEFINED, BarUiActHelp, NULL, "act_help"}, - {'+', BAR_DC_SONG, BarUiActLoveSong, "love song", - "act_songlove"}, - {'-', BAR_DC_SONG, BarUiActBanSong, "ban song", "act_songban"}, - {'a', BAR_DC_STATION, BarUiActAddMusic, "add music to station", - "act_stationaddmusic"}, + {'+', BAR_DC_STATION_TYPE_STATION | BAR_DC_SONG, + BarUiActLoveSong, "love song","act_songlove"}, + {'-', BAR_DC_STATION_TYPE_STATION | BAR_DC_SONG, + BarUiActBanSong, "ban song", "act_songban"}, + {'a', BAR_DC_STATION_TYPE_STATION | BAR_DC_STATION, + BarUiActAddMusic, "add music to station","act_stationaddmusic"}, {'c', BAR_DC_GLOBAL, BarUiActCreateStation, "create new station", "act_stationcreate"}, - {'d', BAR_DC_STATION, BarUiActDeleteStation, "delete station", - "act_stationdelete"}, - {'e', BAR_DC_SONG, BarUiActExplain, "explain why this song is played", + {'d', BAR_DC_ALL_STATION_TYPES | BAR_DC_STATION, + BarUiActDeleteStation, "delete station","act_stationdelete"}, + {'e', BAR_DC_STATION_TYPE_STATION | BAR_DC_SONG, + BarUiActExplain, "explain why this song is played", "act_songexplain"}, {'g', BAR_DC_GLOBAL, BarUiActStationFromGenre, "add genre station", "act_stationaddbygenre"}, @@ -73,16 +86,16 @@ static const BarUiDispatchAction_t dispatchActions[BAR_KS_COUNT] = { {'p', BAR_DC_GLOBAL | BAR_DC_STATION, BarUiActTogglePause, "pause/resume playback", "act_songpausetoggle"}, {'q', BAR_DC_GLOBAL, BarUiActQuit, "quit", "act_quit"}, - {'r', BAR_DC_STATION, BarUiActRenameStation, "rename station", - "act_stationrename"}, + {'r', BAR_DC_STATION_TYPE_STATION | BAR_DC_STATION, + BarUiActRenameStation, "rename station","act_stationrename"}, {'s', BAR_DC_GLOBAL, BarUiActSelectStation, "change station", "act_stationchange"}, - {'t', BAR_DC_SONG, BarUiActTempBanSong, "tired (ban song for 1 month)", - "act_songtired"}, + {'t', BAR_DC_STATION_TYPE_STATION | BAR_DC_SONG, + BarUiActTempBanSong, "tired (ban song for 1 month)", "act_songtired"}, {'u', BAR_DC_GLOBAL | BAR_DC_STATION, BarUiActPrintUpcoming, "upcoming songs", "act_upcoming"}, - {'x', BAR_DC_STATION, BarUiActSelectQuickMix, "select quickmix stations", - "act_stationselectquickmix"}, + {'x', BAR_DC_STATION_TYPE_STATION | BAR_DC_STATION, + BarUiActSelectQuickMix, "select quickmix stations","act_stationselectquickmix"}, {'$', BAR_DC_SONG, BarUiActDebug, NULL, "act_debug"}, {'b', BAR_DC_SONG, BarUiActBookmark, "bookmark song/artist", "act_bookmark"}, @@ -90,8 +103,8 @@ static const BarUiDispatchAction_t dispatchActions[BAR_KS_COUNT] = { "act_voldown"}, {')', BAR_DC_GLOBAL, BarUiActVolUp, "increase volume", "act_volup"}, - {'=', BAR_DC_STATION, BarUiActManageStation, "manage station seeds/feedback/mode", - "act_managestation"}, + {'=', BAR_DC_STATION_TYPE_STATION | BAR_DC_STATION, BarUiActManageStation, + "manage station seeds/feedback/mode","act_managestation"}, {' ', BAR_DC_GLOBAL | BAR_DC_STATION, BarUiActTogglePause, NULL, "act_songpausetoggle2"}, {'v', BAR_DC_SONG, BarUiActCreateStationFromSong, @@ -104,6 +117,10 @@ static const BarUiDispatchAction_t dispatchActions[BAR_KS_COUNT] = { "act_volreset"}, {'!', BAR_DC_GLOBAL, BarUiActSettings, "change settings", "act_settings"}, + {'f', BAR_DC_GLOBAL, BarUiActFilter, "filter station list settings", + "act_filter"}, + {'G', BAR_DC_STATION_TYPE_ALBUM | BAR_DC_STATION_TYPE_PLAYLIST | BAR_DC_STATION_TYPE_PODCAST | BAR_DC_SONG, + BarUiActGotoSong, "goto song", "act_gotosong"}, }; #include @@ -112,4 +129,5 @@ static const BarUiDispatchAction_t dispatchActions[BAR_KS_COUNT] = { BarKeyShortcutId_t BarUiDispatch (BarApp_t *, const char, PianoStation_t *, PianoSong_t *, const bool, BarUiDispatchContext_t); +bool BarUiContextMatch(BarUiDispatchContext_t Have,BarUiDispatchContext_t Need,const char **ErrMsg); From d16a9148ab545a8ea824d2ae149c87c952374450 Mon Sep 17 00:00:00 2001 From: Skip Hansen Date: Tue, 1 Jul 2025 11:36:29 -0400 Subject: [PATCH 3/7] Removed LIBAV init. --- Makefile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Makefile b/Makefile index 81c66e941..b070c596e 100644 --- a/Makefile +++ b/Makefile @@ -52,8 +52,6 @@ LIBPIANO_OBJ:=${LIBPIANO_SRC:.c=.o} LIBPIANO_RELOBJ:=${LIBPIANO_SRC:.c=.lo} LIBPIANO_INCLUDE:=${LIBPIANO_DIR} -LIBAV = /home/skip/open_src/audio/ffmpeg - ifneq (${LIBAV},) PKG_CONFIG_LIBDIR=PKG_CONFIG_LIBDIR=${LIBAV}/lib/pkgconfig endif From f9a77727c510c916fda715e8dc88fe80007e6e8d Mon Sep 17 00:00:00 2001 From: Skip Hansen Date: Tue, 1 Jul 2025 11:56:44 -0400 Subject: [PATCH 4/7] Verify that pandoraType is found in GetItems response (review comment). --- src/libpiano/response.c | 2495 ++++++++++++++++++++------------------- 1 file changed, 1248 insertions(+), 1247 deletions(-) diff --git a/src/libpiano/response.c b/src/libpiano/response.c index e34449589..070bd3bbc 100644 --- a/src/libpiano/response.c +++ b/src/libpiano/response.c @@ -1,6 +1,6 @@ /* Copyright (c) 2008-2017 - Lars-Dominik Braun + Lars-Dominik Braun Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -34,1294 +34,1295 @@ THE SOFTWARE. #include "crypt.h" static const char *qualityMap[] = { - "", "lowQuality", "mediumQuality","highQuality" + "", "lowQuality", "mediumQuality","highQuality" }; static const char *formatMap[] = { - "", "aacplus", "mp3" + "", "aacplus", "mp3" }; static const char *imageHost = "https://content-images.p-cdn.com/"; static char *PianoJsonStrdup (json_object *j, const char *key) { - assert (j != NULL); - assert (key != NULL); - - json_object *v; - if (json_object_object_get_ex (j, key, &v)) { - return strdup (json_object_get_string (v)); - } else { - return NULL; - } + assert (j != NULL); + assert (key != NULL); + + json_object *v; + if (json_object_object_get_ex (j, key, &v)) { + return strdup (json_object_get_string (v)); + } else { + return NULL; + } } static bool getBoolDefault (json_object * const j, const char * const key, const bool def) { - assert (j != NULL); - assert (key != NULL); - - json_object *v; - if (json_object_object_get_ex (j, key, &v)) { - return json_object_get_boolean (v); - } else { - return def; - } + assert (j != NULL); + assert (key != NULL); + + json_object *v; + if (json_object_object_get_ex (j, key, &v)) { + return json_object_get_boolean (v); + } else { + return def; + } } static const char *PianoJsonGetStr(json_object *j, const char *key) { - assert (j != NULL); - assert (key != NULL); - - json_object *v; - if (json_object_object_get_ex (j, key, &v)) { - return json_object_get_string (v); - } else { - return NULL; - } + assert (j != NULL); + assert (key != NULL); + + json_object *v; + if (json_object_object_get_ex (j, key, &v)) { + return json_object_get_string (v); + } else { + return NULL; + } } static int getInt(json_object * const j, const char * const key) { - assert (j != NULL); - assert (key != NULL); - - json_object *v; - if (json_object_object_get_ex (j, key, &v)) { - return json_object_get_int(v); - } else { - return 0; - } + assert (j != NULL); + assert (key != NULL); + + json_object *v; + if (json_object_object_get_ex (j, key, &v)) { + return json_object_get_int(v); + } else { + return 0; + } } static char *getCoverArt(struct json_object *Val) { - char artUrl[120]; - json_object *v = NULL; - char *Ret = NULL; - - if (json_pointer_get(Val, "/icon/artUrl", &v)) { - LOG("Couldn't get artUrl\n"); - } - else { - assert (v != NULL); - snprintf(artUrl,sizeof(artUrl),"%s%s", - imageHost,json_object_get_string(v)); - Ret = strdup(artUrl); - } - - return Ret; + char artUrl[120]; + json_object *v = NULL; + char *Ret = NULL; + + if (json_pointer_get(Val, "/icon/artUrl", &v)) { + LOG("Couldn't get artUrl\n"); + } + else { + assert (v != NULL); + snprintf(artUrl,sizeof(artUrl),"%s%s", + imageHost,json_object_get_string(v)); + Ret = strdup(artUrl); + } + + return Ret; } static int getBool(json_object * const j, const char * const key) { - assert (j != NULL); - assert (key != NULL); - - json_object *v; - if (json_pointer_get(j, key, &v)) { - return -1; - } else { - return json_object_get_boolean (v) ? 1 : 0; - } + assert (j != NULL); + assert (key != NULL); + + json_object *v; + if (json_pointer_get(j, key, &v)) { + return -1; + } else { + return json_object_get_boolean (v) ? 1 : 0; + } } static void PianoJsonParsePlaylist(json_object *j, PianoStation_t *s) { - s->name = PianoJsonStrdup (j, "name"); - s->id = PianoJsonStrdup (j, "pandoraId"); - s->isCreator = false; - s->isQuickMix = false; + s->name = PianoJsonStrdup (j, "name"); + s->id = PianoJsonStrdup (j, "pandoraId"); + s->isCreator = false; + s->isQuickMix = false; } static void PianoJsonParseStation (json_object *j, PianoStation_t *s) { - s->name = PianoJsonStrdup (j, "stationName"); - s->id = PianoJsonStrdup (j, "stationToken"); - s->isCreator = !getBoolDefault (j, "isShared", !false); - s->isQuickMix = getBoolDefault (j, "isQuickMix", false); + s->name = PianoJsonStrdup (j, "stationName"); + s->id = PianoJsonStrdup (j, "stationToken"); + s->isCreator = !getBoolDefault (j, "isShared", !false); + s->isQuickMix = getBoolDefault (j, "isQuickMix", false); } -/* concat strings - * @param destination - * @param source string - * @param destination size +/* concat strings + * @param destination + * @param source string + * @param destination size */ static void PianoStrpcat (char * restrict dest, const char * restrict src, - size_t len) { - /* skip to end of string */ - while (*dest != '\0' && len > 1) { - ++dest; - --len; - } - - /* append until source exhausted or destination full */ - while (*src != '\0' && len > 1) { - *dest = *src; - ++dest; - ++src; - --len; - } - - *dest = '\0'; + size_t len) { + /* skip to end of string */ + while (*dest != '\0' && len > 1) { + ++dest; + --len; + } + + /* append until source exhausted or destination full */ + while (*src != '\0' && len > 1) { + *dest = *src; + ++dest; + ++src; + --len; + } + + *dest = '\0'; } -/* parse xml response and update data structures/return new data structure - * @param piano handle - * @param initialized request (expects responseData to be a NUL-terminated - * string) +/* parse xml response and update data structures/return new data structure + * @param piano handle + * @param initialized request (expects responseData to be a NUL-terminated + * string) */ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { - PianoReturn_t ret = PIANO_RET_OK; - - assert (ph != NULL); - assert (req != NULL); - - json_object * const j = json_tokener_parse (req->responseData); - - json_object *status; - if (!json_object_object_get_ex (j, "stat", &status)) { - ret = PIANO_RET_INVALID_RESPONSE; - goto cleanup; - } - - /* error handling */ - if (strcmp (json_object_get_string (status), "ok") != 0) { - json_object *code; - if (!json_object_object_get_ex (j, "code", &code)) { - ret = PIANO_RET_INVALID_RESPONSE; - } else { - ret = json_object_get_int (code)+PIANO_RET_OFFSET; - - if (ret == PIANO_RET_P_INVALID_PARTNER_LOGIN && - req->type == PIANO_REQUEST_LOGIN) { - PianoRequestDataLogin_t *reqData = req->data; - if (reqData->step == 1) { - /* return value is ambiguous, as both, partnerLogin and - * userLogin return INVALID_PARTNER_LOGIN. Fix that to provide - * better error messages. */ - ret = PIANO_RET_INVALID_LOGIN; - } - } - } - - goto cleanup; - } - - json_object *result = NULL; - /* missing for some request types */ - json_object_object_get_ex (j, "result", &result); - - switch (req->type) { - case PIANO_REQUEST_LOGIN: { - /* authenticate user */ - PianoRequestDataLogin_t *reqData = req->data; - - assert (req->responseData != NULL); - assert (reqData != NULL); - - switch (reqData->step) { - case 0: { - /* decrypt timestamp */ - json_object *jsonTimestamp; - if (!json_object_object_get_ex (result, "syncTime", &jsonTimestamp)) { - ret = PIANO_RET_INVALID_RESPONSE; - break; - } - assert (jsonTimestamp != NULL); - const char * const cryptedTimestamp = json_object_get_string (jsonTimestamp); - assert (cryptedTimestamp != NULL); - const time_t realTimestamp = time (NULL); - char *decryptedTimestamp = NULL; - size_t decryptedSize; - - ret = PIANO_RET_ERR; - if ((decryptedTimestamp = PianoDecryptString (ph->partner.in, - cryptedTimestamp, &decryptedSize)) != NULL && - decryptedSize > 4) { - /* skip four bytes garbage(?) at beginning */ - const unsigned long timestamp = strtoul ( - decryptedTimestamp+4, NULL, 0); - ph->timeOffset = (long int) realTimestamp - - (long int) timestamp; - ret = PIANO_RET_CONTINUE_REQUEST; - } - free (decryptedTimestamp); - /* get auth token */ - ph->partner.authToken = PianoJsonStrdup (result, - "partnerAuthToken"); - json_object *partnerId; - if (!json_object_object_get_ex (result, "partnerId", &partnerId)) { - ret = PIANO_RET_INVALID_RESPONSE; - break; - } - ph->partner.id = json_object_get_int (partnerId); - ++reqData->step; - break; - } - - case 1: - /* information exists when reauthenticating, destroy to - * avoid memleak */ - if (ph->user.listenerId != NULL) { - PianoDestroyUserInfo (&ph->user); - } - ph->user.listenerId = PianoJsonStrdup (result, "userId"); - ph->user.authToken = PianoJsonStrdup (result, - "userAuthToken"); - ph->user.IsSubscriber = getBoolDefault (result, - "isSubscriber", false); - break; - } - break; - } - - case PIANO_REQUEST_GET_STATIONS: { - /* get stations */ - assert (req->responseData != NULL); - - json_object *stations, *mix = NULL; - - if (!json_object_object_get_ex (result, "stations", &stations)) { - break; - } - - for (unsigned int i = 0; i < json_object_array_length (stations); i++) { - PianoStation_t *tmpStation; - json_object *s = json_object_array_get_idx (stations, i); - - if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - tmpStation->stationType = PIANO_TYPE_STATION; - - PianoJsonParseStation (s, tmpStation); - - if (tmpStation->isQuickMix) { - /* fix flags on other stations later */ - json_object_object_get_ex (s, "quickMixStationIds", &mix); - } - - /* start new linked list or append */ - ph->stations = PianoListAppendP (ph->stations, tmpStation); - } - - /* fix quickmix flags */ - if (mix != NULL) { - PianoStation_t *curStation = ph->stations; - PianoListForeachP (curStation) { - for (unsigned int i = 0; i < json_object_array_length (mix); i++) { - json_object *id = json_object_array_get_idx (mix, i); - if (strcmp (json_object_get_string (id), - curStation->id) == 0) { - curStation->useQuickMix = true; - } - } - } - } - break; - } - - case PIANO_REQUEST_GET_PLAYLISTS: { - /* get playlists */ - assert (req->responseData != NULL); - - json_object *playlists; - - if (!json_object_object_get_ex (result, "items", &playlists)) { - break; - } - - for (int i = 0; i < json_object_array_length (playlists); i++) { - PianoStation_t *tmpStation; - json_object *s = json_object_array_get_idx (playlists, i); - - if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - tmpStation->stationType = PIANO_TYPE_PLAYLIST; - - PianoJsonParsePlaylist(s, tmpStation); - - /* start new linked list or append */ - ph->stations = PianoListAppendP (ph->stations, tmpStation); - } - break; - } - - // Get album or playlist tracks - case PIANO_REQUEST_GET_TRACKS: { - assert (req->responseData != NULL); - - PianoRequestDataGetPlaylist_t *reqData = req->data; - PianoSong_t *playlist = NULL; - PianoSong_t *FullPlaylist = NULL; - - assert (result != NULL); - assert (req->responseData != NULL); - assert (reqData != NULL); - - switch(reqData->station->stationType) { - case PIANO_TYPE_PLAYLIST: { - json_object *tracks = NULL; - json_object *annotations = NULL; - if (!json_object_object_get_ex (result, "tracks", &tracks)) { - break; - } - assert (tracks!= NULL); - LOG("got tracks\n"); - if (!json_object_object_get_ex (result, "annotations", &annotations)) { - break; - } - assert (annotations != NULL); - LOG("got annotations\n"); - - for (int i = 0; i < json_object_array_length (tracks); i++) { - json_object *s = json_object_array_get_idx (tracks, i); - json_object *trackInfo = NULL; - PianoSong_t *song; - - if ((song = calloc (1, sizeof (*song))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - song->seedId = PianoJsonStrdup(result, "pandoraId"); - song->trackToken = PianoJsonStrdup (s, "trackPandoraId"); - assert (song->trackToken != NULL); - - LOG("track %d: %s\n",i + 1,song->trackToken); - - if (!json_object_object_get_ex (annotations, song->trackToken, &trackInfo)) { - break; - } - assert (trackInfo!= NULL); - song->artist = PianoJsonStrdup(trackInfo, "artistName"); - song->album = PianoJsonStrdup(trackInfo, "albumName"); - song->title = PianoJsonStrdup(trackInfo, "name"); - song->fileGain = 0.0; - song->length = getInt(trackInfo, "duration"); - song->coverArt = getCoverArt(trackInfo); - playlist = PianoListAppendP (playlist, song); - } - break; - } - - case PIANO_TYPE_ALBUM: { - char trackTitle[120]; - int totalTracks = 0; - int trackNumber; - - // Count tracks - json_object_object_foreach(result,Key1,Val1) { - if(Key1[0] != 'T' || Key1[1] != 'R') { - continue; - } - totalTracks++; - } - - json_object_object_foreach(result,Key,Val) { - if(Key[0] != 'T' || Key[1] != 'R') { - continue; - } - LOG("got track %s: ",Key); - trackNumber = getInt(Val,"trackNumber"); - snprintf(trackTitle,sizeof(trackTitle), - totalTracks > 9 ? "%02d %s" : "%d %s", - trackNumber,PianoJsonGetStr(Val,"name")); - LOG_RAW("%s\n",trackTitle); - if(getBool(Val,"/rightsInfo/hasInteractive") != 1) { - LOG(" ignored\n"); - LOG(" hasInteractive: %d\n",getBool(Val,"/rightsInfo/hasInteractive")); - LOG(" hasOffline: %d\n",getBool(Val,"/rightsInfo/hasOffline")); - LOG(" hasNonInteractive: %d\n",getBool(Val,"/rightsInfo/hasNonInteractive")); - LOG(" hasStatutory: %d\n",getBool(Val,"/rightsInfo/hasStatutory")); - LOG(" hasRadioRights: %d\n",getBool(Val,"/rightsInfo/hasRadioRights")); - continue; - } - PianoSong_t *song; - - if ((song = calloc (1, sizeof (*song))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - song->stationId = strdup(reqData->station->id); - song->title = strdup(trackTitle); - song->trackToken = strdup(Key); - song->seedId = PianoJsonStrdup(Val,"albumId"); - song->artist = PianoJsonStrdup(Val,"artistName"); - song->album = PianoJsonStrdup(Val,"albumName"); - song->fileGain = 0.0; - song->length = getInt(Val, "duration"); - song->coverArt = getCoverArt(Val); - // Add to playlist in track order - - if(playlist == NULL) { - playlist = song; - } - else { - PianoSong_t *lastSong = (PianoSong_t *) &playlist; - PianoSong_t *nextSong = playlist; - do { - int nextSongTrackNum; - if(nextSong == NULL) { - lastSong->head.next = (struct PianoListHead *) song; - break; - } - sscanf(nextSong->title,"%d",&nextSongTrackNum); - if(nextSongTrackNum > trackNumber) { - lastSong->head.next = (struct PianoListHead *) song; - song->head.next = (struct PianoListHead *) nextSong; - break; - } - lastSong = nextSong; - nextSong = (PianoSong_t *) nextSong->head.next; - } while(true); - } - } - break; - } - - default: - LOG("Invalid stationType 0x%x\n",ph->stations->stationType); - break; - } - reqData->retPlaylist = playlist; - break; - } - - case PIANO_REQUEST_GET_PLAYLIST: { - /* get playlist, usually four songs */ - PianoRequestDataGetPlaylist_t *reqData = req->data; - PianoSong_t *playlist = NULL; - - assert (req->responseData != NULL); - assert (reqData != NULL); - assert (reqData->quality != PIANO_AQ_UNKNOWN); - - json_object *items = NULL; - if (!json_object_object_get_ex (result, "items", &items)) { - break; - } - assert (items != NULL); - - for (unsigned int i = 0; i < json_object_array_length (items); i++) { - json_object *s = json_object_array_get_idx (items, i); - PianoSong_t *song; - - if ((song = calloc (1, sizeof (*song))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - if (!json_object_object_get_ex (s, "artistName", NULL)) { - free (song); - continue; - } - - /* get audio url based on selected quality */ - static const char *qualityMap[] = {"", "lowQuality", "mediumQuality", - "highQuality"}; - assert (reqData->quality < sizeof (qualityMap)/sizeof (*qualityMap)); - static const char *formatMap[] = {"", "aacplus", "mp3"}; - - json_object *umap; - if (json_object_object_get_ex (s, "audioUrlMap", &umap)) { - assert (umap != NULL); - json_object *jsonEncoding, *qmap; - if (json_object_object_get_ex (umap, qualityMap[reqData->quality], &qmap) && - json_object_object_get_ex (qmap, "encoding", &jsonEncoding)) { - assert (qmap != NULL); - const char *encoding = json_object_get_string (jsonEncoding); - assert (encoding != NULL); - for (size_t k = 0; k < sizeof (formatMap)/sizeof (*formatMap); k++) { - if (strcmp (formatMap[k], encoding) == 0) { - song->audioFormat = k; - break; - } - } - song->audioUrl = PianoJsonStrdup (qmap, "audioUrl"); - } else { - /* requested quality is not available */ - ret = PIANO_RET_QUALITY_UNAVAILABLE; - free (song); - PianoDestroyPlaylist (playlist); - goto cleanup; - } - } - - json_object *v; - song->artist = PianoJsonStrdup (s, "artistName"); - song->album = PianoJsonStrdup (s, "albumName"); - song->title = PianoJsonStrdup (s, "songName"); - song->trackToken = PianoJsonStrdup (s, "trackToken"); - song->stationId = PianoJsonStrdup (s, "stationId"); - song->coverArt = PianoJsonStrdup (s, "albumArtUrl"); - song->detailUrl = PianoJsonStrdup (s, "songDetailUrl"); - song->fileGain = json_object_object_get_ex (s, "trackGain", &v) ? - json_object_get_double (v) : 0.0; - song->length = json_object_object_get_ex (s, "trackLength", &v) ? - json_object_get_int (v) : 0; - switch (json_object_object_get_ex (s, "songRating", &v) ? - json_object_get_int (v) : 0) { - case 1: - song->rating = PIANO_RATE_LOVE; - break; - } - - playlist = PianoListAppendP (playlist, song); - } - - reqData->retPlaylist = playlist; - break; - } - - case PIANO_REQUEST_RATE_SONG: { - /* love/ban song */ - PianoRequestDataRateSong_t *reqData = req->data; - reqData->song->rating = reqData->rating; - break; - } - - case PIANO_REQUEST_ADD_FEEDBACK: - /* never ever use this directly, low-level call */ - assert (0); - break; - - case PIANO_REQUEST_RENAME_STATION: { - /* rename station and update PianoStation_t structure */ - PianoRequestDataRenameStation_t *reqData = req->data; - - assert (reqData != NULL); - assert (reqData->station != NULL); - assert (reqData->newName != NULL); - - free (reqData->station->name); - reqData->station->name = strdup (reqData->newName); - break; - } - - case PIANO_REQUEST_REMOVE_ITEM: - case PIANO_REQUEST_DELETE_STATION: { - /* delete station from server and station list */ - PianoStation_t *station = req->data; - - assert (station != NULL); - - ph->stations = PianoListDeleteP (ph->stations, station); - PianoDestroyStation (station); - free (station); - break; - } - - case PIANO_REQUEST_SEARCH: { - /* search artist/song */ - PianoRequestDataSearch_t *reqData = req->data; - PianoSearchResult_t *searchResult; - - assert (req->responseData != NULL); - assert (reqData != NULL); - - searchResult = &reqData->searchResult; - memset (searchResult, 0, sizeof (*searchResult)); - - /* get artists */ - json_object *artists; - if (json_object_object_get_ex (result, "artists", &artists)) { - for (unsigned int i = 0; i < json_object_array_length (artists); i++) { - json_object *a = json_object_array_get_idx (artists, i); - PianoArtist_t *artist; - - if ((artist = calloc (1, sizeof (*artist))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - artist->name = PianoJsonStrdup (a, "artistName"); - artist->musicId = PianoJsonStrdup (a, "musicToken"); - - searchResult->artists = - PianoListAppendP (searchResult->artists, artist); - } - } - - /* get songs */ - json_object *songs; - if (json_object_object_get_ex (result, "songs", &songs)) { - for (unsigned int i = 0; i < json_object_array_length (songs); i++) { - json_object *s = json_object_array_get_idx (songs, i); - PianoSong_t *song; - - if ((song = calloc (1, sizeof (*song))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - song->title = PianoJsonStrdup (s, "songName"); - song->artist = PianoJsonStrdup (s, "artistName"); - song->musicId = PianoJsonStrdup (s, "musicToken"); - - searchResult->songs = - PianoListAppendP (searchResult->songs, song); - } - } - break; - } - - case PIANO_REQUEST_CREATE_STATION: { - /* create station, insert new station into station list on success */ - PianoStation_t *tmpStation; - - if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - PianoJsonParseStation (result, tmpStation); - - PianoStation_t *search = PianoFindStationById (ph->stations, - tmpStation->id); - if (search != NULL) { - ph->stations = PianoListDeleteP (ph->stations, search); - PianoDestroyStation (search); - free (search); - } - ph->stations = PianoListAppendP (ph->stations, tmpStation); - break; - } - - case PIANO_REQUEST_ADD_TIRED_SONG: { - PianoSong_t * const song = req->data; - song->rating = PIANO_RATE_TIRED; - break; - } - - case PIANO_REQUEST_ADD_SEED: - case PIANO_REQUEST_SET_QUICKMIX: - case PIANO_REQUEST_BOOKMARK_SONG: - case PIANO_REQUEST_BOOKMARK_ARTIST: - case PIANO_REQUEST_DELETE_FEEDBACK: - case PIANO_REQUEST_DELETE_SEED: - case PIANO_REQUEST_CHANGE_SETTINGS: - /* response unused */ - break; - - case PIANO_REQUEST_GET_GENRE_STATIONS: { - /* get genre stations */ - json_object *categories; - if (json_object_object_get_ex (result, "categories", &categories)) { - for (unsigned int i = 0; i < json_object_array_length (categories); i++) { - json_object *c = json_object_array_get_idx (categories, i); - PianoGenreCategory_t *tmpGenreCategory; - - if ((tmpGenreCategory = calloc (1, - sizeof (*tmpGenreCategory))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - tmpGenreCategory->name = PianoJsonStrdup (c, - "categoryName"); - - /* get genre subnodes */ - json_object *stations; - if (json_object_object_get_ex (c, "stations", &stations)) { - for (unsigned int k = 0; - k < json_object_array_length (stations); k++) { - json_object *s = - json_object_array_get_idx (stations, k); - PianoGenre_t *tmpGenre; - - if ((tmpGenre = calloc (1, - sizeof (*tmpGenre))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - /* get genre attributes */ - tmpGenre->name = PianoJsonStrdup (s, - "stationName"); - tmpGenre->musicId = PianoJsonStrdup (s, - "stationToken"); - - tmpGenreCategory->genres = - PianoListAppendP (tmpGenreCategory->genres, - tmpGenre); - } - } - - ph->genreStations = PianoListAppendP (ph->genreStations, - tmpGenreCategory); - } - } - break; - } - - case PIANO_REQUEST_TRANSFORM_STATION: { - /* transform shared station into private and update isCreator flag */ - PianoStation_t *station = req->data; - - assert (req->responseData != NULL); - assert (station != NULL); - - station->isCreator = 1; - break; - } - - case PIANO_REQUEST_EXPLAIN: { - /* explain why song was selected */ - PianoRequestDataExplain_t *reqData = req->data; - const size_t strSize = 768; - - assert (reqData != NULL); - - json_object *explanations; - if (json_object_object_get_ex (result, "explanations", &explanations) && - json_object_array_length (explanations) > 0) { - reqData->retExplain = malloc (strSize * - sizeof (*reqData->retExplain)); - strncpy (reqData->retExplain, "We're playing this track " - "because it features ", strSize); - for (unsigned int i = 0; i < json_object_array_length (explanations); i++) { - json_object *e = json_object_array_get_idx (explanations, - i); - json_object *f; - if (!json_object_object_get_ex (e, "focusTraitName", &f)) { - continue; - } - const char *s = json_object_get_string (f); - PianoStrpcat (reqData->retExplain, s, strSize); - if (i < json_object_array_length (explanations)-2) { - PianoStrpcat (reqData->retExplain, ", ", strSize); - } else if (i == json_object_array_length (explanations)-2) { - PianoStrpcat (reqData->retExplain, " and ", strSize); - } else { - PianoStrpcat (reqData->retExplain, ".", strSize); - } - } - } - break; - } - - case PIANO_REQUEST_GET_SETTINGS: { - PianoSettings_t * const settings = req->data; - - assert (settings != NULL); - - settings->explicitContentFilter = getBoolDefault (result, - "isExplicitContentFilterEnabled", false); - settings->username = PianoJsonStrdup (result, "username"); - break; - } - - case PIANO_REQUEST_GET_STATION_INFO: { - /* get station information (seeds and feedback) */ - PianoRequestDataGetStationInfo_t *reqData = req->data; - PianoStationInfo_t *info; - - assert (reqData != NULL); - - info = &reqData->info; - assert (info != NULL); - - /* parse music seeds */ - json_object *music; - if (json_object_object_get_ex (result, "music", &music)) { - /* songs */ - json_object *songs; - if (json_object_object_get_ex (music, "songs", &songs)) { - for (unsigned int i = 0; i < json_object_array_length (songs); i++) { - json_object *s = json_object_array_get_idx (songs, i); - PianoSong_t *seedSong; - - seedSong = calloc (1, sizeof (*seedSong)); - if (seedSong == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - seedSong->title = PianoJsonStrdup (s, "songName"); - seedSong->artist = PianoJsonStrdup (s, "artistName"); - seedSong->seedId = PianoJsonStrdup (s, "seedId"); - - info->songSeeds = PianoListAppendP (info->songSeeds, - seedSong); - } - } - - /* artists */ - json_object *artists; - if (json_object_object_get_ex (music, "artists", &artists)) { - for (unsigned int i = 0; i < json_object_array_length (artists); i++) { - json_object *a = json_object_array_get_idx (artists, i); - PianoArtist_t *seedArtist; - - seedArtist = calloc (1, sizeof (*seedArtist)); - if (seedArtist == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - seedArtist->name = PianoJsonStrdup (a, "artistName"); - seedArtist->seedId = PianoJsonStrdup (a, "seedId"); - - info->artistSeeds = - PianoListAppendP (info->artistSeeds, seedArtist); - } - } - } - - /* parse feedback */ - json_object *feedback; - if (json_object_object_get_ex (result, "feedback", &feedback)) { - static const char * const keys[] = {"thumbsUp", "thumbsDown"}; - for (size_t i = 0; i < sizeof (keys)/sizeof (*keys); i++) { - json_object *val; - if (!json_object_object_get_ex (feedback, keys[i], &val)) { - continue; - } - assert (json_object_is_type (val, json_type_array)); - for (unsigned int i = 0; i < json_object_array_length (val); i++) { - json_object *s = json_object_array_get_idx (val, i); - PianoSong_t *feedbackSong; - - feedbackSong = calloc (1, sizeof (*feedbackSong)); - if (feedbackSong == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - feedbackSong->title = PianoJsonStrdup (s, "songName"); - feedbackSong->artist = PianoJsonStrdup (s, - "artistName"); - feedbackSong->feedbackId = PianoJsonStrdup (s, - "feedbackId"); - feedbackSong->rating = getBoolDefault (s, "isPositive", - false) ? PIANO_RATE_LOVE : PIANO_RATE_BAN; - - json_object *v; - feedbackSong->length = - json_object_object_get_ex (s, "trackLength", &v) ? - json_object_get_int (v) : 0; - - info->feedback = PianoListAppendP (info->feedback, - feedbackSong); - } - } - } - break; - } - - case PIANO_REQUEST_GET_STATION_MODES: { - PianoRequestDataGetStationModes_t *reqData = req->data; - assert (reqData != NULL); - - int active = -1; - - json_object *activeMode; - if (json_object_object_get_ex (result, "currentModeId", &activeMode)) { - active = json_object_get_int (activeMode); - } - - json_object *availableModes; - if (json_object_object_get_ex (result, "availableModes", &availableModes)) { - for (unsigned int i = 0; i < json_object_array_length (availableModes); i++) { - json_object *val = json_object_array_get_idx (availableModes, i); - - assert (json_object_is_type (val, json_type_object)); - - PianoStationMode_t *mode; - if ((mode = calloc (1, sizeof (*mode))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - json_object *modeId; - if (json_object_object_get_ex (val, "modeId", &modeId)) { - mode->id = json_object_get_int (modeId); - mode->name = PianoJsonStrdup (val, "modeName"); - mode->description = PianoJsonStrdup (val, "modeDescription"); - mode->isAlgorithmic = getBoolDefault (val, "isAlgorithmicMode", - false); - mode->isTakeover = getBoolDefault (val, "isTakeoverMode", - false); - mode->active = active == mode->id; - } - - reqData->retModes = PianoListAppendP (reqData->retModes, - mode); - } - } - break; - } - - case PIANO_REQUEST_SET_STATION_MODE: { - PianoRequestDataSetStationMode_t *reqData = req->data; - assert (reqData != NULL); - - int active = -1; - - json_object *activeMode; - if (json_object_object_get_ex (result, "currentModeId", &activeMode)) { - active = json_object_get_int (activeMode); - } - - if (active != reqData->id) { - /* this did not work */ - return PIANO_RET_ERR; - } - break; - } - - case PIANO_REQUEST_GET_PLAYBACK_INFO: { - PianoRequestDataGetPlaylist_t *reqData = req->data; - PianoSong_t *song = reqData->retPlaylist; - - assert (req->responseData != NULL); - assert (reqData != NULL); - assert (reqData->quality < sizeof (qualityMap)/sizeof (*qualityMap)); - assert (song != NULL); - - json_object *audioUrlMap = NULL; - if (!json_object_object_get_ex (result, "audioUrlMap", &audioUrlMap)) { - break; - } - assert (audioUrlMap != NULL); - - const char *quality = qualityMap[reqData->quality]; - json_object *umap; - - json_object *jsonEncoding = NULL; - if (json_object_object_get_ex (audioUrlMap, quality, &umap)) { - assert (umap != NULL); - if (json_object_object_get_ex (umap, "encoding", &jsonEncoding)) { - assert (jsonEncoding != NULL); - const char *encoding = json_object_get_string (jsonEncoding); - assert (encoding != NULL); - for (size_t k = 0; k < sizeof (formatMap)/sizeof (*formatMap); k++) { - if (strcmp (formatMap[k], encoding) == 0) { - song->audioFormat = k; - break; - } - } - song->audioUrl = PianoJsonStrdup (umap, "audioUrl"); - } - } - - if(song->audioUrl == NULL) { - /* requested quality is not available */ - LOG("quality %s not found in audioUrlMap\n",quality); - ret = PIANO_RET_QUALITY_UNAVAILABLE; - PianoDestroyPlaylist (reqData->retPlaylist); - goto cleanup; - } - break; - } - - case PIANO_REQUEST_GET_USER_PROFILE: { - assert (req->responseData != NULL); - - json_object *stations; - json_object *annotations = NULL; - ph->user.IsPremiumUser = getBoolDefault (result,"isPremiumUser", false); - LOG("IsPremiumUser: %d\n",ph->user.IsPremiumUser); - - if (!json_object_object_get_ex (result, "annotations", &annotations)) { - break; - } - assert (annotations != NULL); - int Annotations = 0; - json_object_object_foreach(annotations,Key,Val) { - const char *type = PianoJsonGetStr(Val,"type"); - - Annotations++; - if(strcmp(type,"PL") == 0) { - ph->user.PlayListCount++; - } - else if(strcmp(type,"ST") == 0) { - ph->user.StationCount++; - } - else if(strcmp(type,"AL") == 0) { - ph->user.AlbumCount++; - } - else if(strcmp(type,"TR") == 0) { - ph->user.TrackCount++; - } - else if(strcmp(type,"LI") == 0) { - } - else if(strcmp(type,"AR") == 0) { - } - else { - LOG("type %s ignored\n",type); - } - } - LOG("Found: %d annotations:\n",Annotations); - LOG(" PlayLists: %d:\n",ph->user.PlayListCount); - LOG(" Stations: %d:\n",ph->user.StationCount); - LOG(" Albums: %d:\n",ph->user.AlbumCount); - LOG(" Tracks: %d:\n",ph->user.TrackCount); - break; - } - - case PIANO_REQUEST_GET_ITEMS: { - assert (req->responseData != NULL); - json_object *items = NULL; - if (!json_object_object_get_ex (result, "items", &items)) { - break; - } - assert (items != NULL); - for (int i = 0; i < json_object_array_length (items); i++) { - json_object *s = json_object_array_get_idx (items, i); - const char *type = PianoJsonGetStr(s,"pandoraType"); - PianoSong_t *song; - PianoStationType_t stationType = PIANO_TYPE_NONE; - - if(strcmp(type,"PL") == 0 || strcmp(type,"ST") == 0) { - // Playlists and stations handled elsewhere - } - else if(strcmp(type,"AL") == 0) { - stationType = PIANO_TYPE_ALBUM; - } - else if(strcmp(type,"TR") == 0) { - stationType = PIANO_TYPE_TRACK; - } - else if(strcmp(type,"PC") == 0) { - stationType = PIANO_TYPE_PODCAST; - ph->user.PodcastCount++; - } - else { - LOG("type %s ignored\n",type); - } - - if(stationType != PIANO_TYPE_NONE) { - PianoStation_t *tmpStation; - if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - tmpStation->stationType = stationType; - tmpStation->id = PianoJsonStrdup (s, "pandoraId"); - /* start new linked list or append */ - ph->stations = PianoListAppendP (ph->stations, tmpStation); - } - else { - LOG("type %s ignored\n",type); - } - } - - if(ph->user.PodcastCount) { - LOG("Found %d podcast stations\n",ph->user.PodcastCount); - } - break; - } - - case PIANO_REQUEST_ANNOTATE_OBJECTS: { - assert (req->responseData != NULL); - assert (req->data != NULL); - PianoStation_t *station = ph->stations; - PianoRequestDataGetPlaylist_t *reqData = req->data; - - while(station != NULL) { - assert(station->id != NULL); - switch(station->stationType) { - case PIANO_TYPE_PODCAST: { - json_object_object_foreach(result,Key,Val) { - if(strcmp(Key,station->id) == 0) { - PianoSong_t *song = station->theSong; - assert (song == NULL); - if ((song = calloc (1, sizeof (*song))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - station->name = PianoJsonStrdup(Val,"name"); - station->seedId = PianoJsonStrdup(Val,"latestEpisodeId"); - station->theSong = song; - song->album = strdup(station->name); - song->coverArt = getCoverArt(Val); // podcast coverArt - LOG("podcast coverart %s\n",song->coverArt); - break; - } - } - if(station->name == NULL) { - LOG("Couldn't find station %s\n",station->id); - } - break; - } - - case PIANO_TYPE_ALBUM: { - json_object_object_foreach(result,Key,Val) { - if(strcmp(Key,station->id) == 0) { - char Temp[120]; - snprintf(Temp,sizeof(Temp),"%s - %s", - PianoJsonGetStr(Val,"artistName"), - PianoJsonGetStr(Val,"name")); - station->name = strdup(Temp); - station->seedId = PianoJsonStrdup(Val,"pandoraId"); - break; - } - } - if(station->name == NULL) { - LOG("Couldn't find station %s\n",station->id); - } - break; - } - - case PIANO_TYPE_TRACK: { - json_object_object_foreach(result,Key,Val) { - if(strcmp(Key,station->id) == 0) { - station->name = PianoJsonStrdup(Val,"name"); - station->seedId = PianoJsonStrdup(Val,"albumId"); - - PianoSong_t *song = station->theSong; - assert (song == NULL); - if ((song = calloc (1, sizeof (*song))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - station->theSong = song; - song->artist = PianoJsonStrdup(Val, "artistName"); - song->album = PianoJsonStrdup(Val, "albumName"); - song->title = PianoJsonStrdup(Val, "name"); - song->length = getInt(Val, "duration"); - song->coverArt = getCoverArt(Val); - song->fileGain = 0.0; - break; - } - } - if(station->name == NULL) { - LOG("Couldn't find station %s\n",station->id); - } - break; - } - } - station = (PianoStation_t *) station->head.next; - } - break; - } - - case PIANO_REQUEST_GET_EPISODES: { - assert (req->responseData != NULL); - assert (req->data != NULL); - PianoRequestDataGetEpisodes_t *reqData = req->data; - PianoStation_t *station = reqData->station; - PianoSong_t *playlist = NULL; - int Added = 0; - PianoSong_t *song; - - json_object *annotations = NULL; - if (json_pointer_get(result, "/details/annotations", &annotations)) { - break; - } - json_object_object_foreach(annotations,Key,Val) { - if(Key[0] != 'P' || Key[1] != 'E') { - // not episode, ignore it - continue; - } - const char *EpisodeTitle = PianoJsonGetStr(Val,"name"); - - if(EpisodeTitle == NULL) { - LOG("Couldn't get title of episode\n"); - continue; - } - const char *Id = PianoJsonGetStr(Val,"podcastId"); - if(Id == NULL) { - LOG("Couldn't get podcastId\n"); - continue; - } - - if(strcmp(station->id,Id) != 0) { - LOG("Episode not for selected podcast (%s != %s)\n", - station->id,Id); - continue; - } - - const char *State = PianoJsonGetStr(Val,"contentState"); - if(State == NULL) { - LOG("Couldn't get contentState\n"); - continue; - } - - if(strcmp(State,"AVAILABLE") != 0) { - if(strlen(EpisodeTitle) > 0) { - LOG(" ignored %s\n",EpisodeTitle); - LOG(" contentState: %s\n",State); - } - continue; - } - if(getBool(Val,"/rightsInfo/hasInteractive") != 1) { - LOG(" ignored %s\n",EpisodeTitle); - LOG(" hasInteractive: %d\n",getBool(Val,"/rightsInfo/hasInteractive")); - LOG(" hasOffline: %d\n",getBool(Val,"/rightsInfo/hasOffline")); - LOG(" hasNonInteractive: %d\n",getBool(Val,"/rightsInfo/hasNonInteractive")); - LOG(" hasStatutory: %d\n",getBool(Val,"/rightsInfo/hasStatutory")); - LOG(" hasRadioRights: %d\n",getBool(Val,"/rightsInfo/hasRadioRights")); - continue; - } - - int Month; - int Day; - int Year; - char trackTitle[120]; - const char *Released = PianoJsonGetStr(Val,"releaseDate"); - if(Released == NULL) { - LOG("Couldn't get releaseDate\n"); - continue; - } - if(sscanf(Released,"%d-%d-%d",&Year,&Month,&Day) != 3) { - LOG("Couldn't convert releaseDate %s\n",Released); - continue; - } - const char *Title = PianoJsonGetStr(Val,"name"); - if(Title == NULL) { - LOG("Couldn't get episode title\n"); - continue; - } - const char *trackToken = PianoJsonGetStr(Val, "pandoraId"); - if(trackToken == NULL) { - LOG("Couldn't get trackToken\n"); - continue; - } - - snprintf(trackTitle,sizeof(trackTitle),"%02d/%02d: %s", - Month,Day,Title); - LOG("Got %s\n",trackTitle); - - if(!reqData->bGetAll) { - // just getting name of the current episode - song = reqData->playList; - if(strcmp(trackToken,song->trackToken) != 0) { - LOG("Ignoring %s, not current episode\n",trackTitle); - continue; - } - song->title = strdup(trackTitle); - LOG("Added name of current episode\n"); - break; - } - if ((song = calloc (1, sizeof (*song))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - song->title = strdup(trackTitle); - song->trackToken = strdup(trackToken); - song->length = getInt(Val, "duration"); - // Save release date for sorting - song->fileGain = ((Year - 1900) * 10000) + (Month * 100) + Day; - // Add to playlist in release data order - PianoSong_t *thisSong = playlist; - PianoSong_t *lastSong = (PianoSong_t *) &playlist; - do { - if(thisSong == NULL) { - lastSong->head.next = (struct PianoListHead *) song; - // LOG("Added to end of list\n"); - break; - } - // LOG("Comparing to %s\n",thisSong->title); - if(thisSong->fileGain > song->fileGain) { - lastSong->head.next = (struct PianoListHead *) song; - song->head.next = (struct PianoListHead *) thisSong; - // LOG("Added before\n"); - break; - } - lastSong = thisSong; - thisSong = (PianoSong_t *) thisSong->head.next; - } while(true); - Added++; - } - reqData->playList = playlist; - LOG("Added %d episodes:\n",Added); + PianoReturn_t ret = PIANO_RET_OK; + + assert (ph != NULL); + assert (req != NULL); + + json_object * const j = json_tokener_parse (req->responseData); + + json_object *status; + if (!json_object_object_get_ex (j, "stat", &status)) { + ret = PIANO_RET_INVALID_RESPONSE; + goto cleanup; + } + + /* error handling */ + if (strcmp (json_object_get_string (status), "ok") != 0) { + json_object *code; + if (!json_object_object_get_ex (j, "code", &code)) { + ret = PIANO_RET_INVALID_RESPONSE; + } else { + ret = json_object_get_int (code)+PIANO_RET_OFFSET; + + if (ret == PIANO_RET_P_INVALID_PARTNER_LOGIN && + req->type == PIANO_REQUEST_LOGIN) { + PianoRequestDataLogin_t *reqData = req->data; + if (reqData->step == 1) { + /* return value is ambiguous, as both, partnerLogin and + * userLogin return INVALID_PARTNER_LOGIN. Fix that to provide + * better error messages. */ + ret = PIANO_RET_INVALID_LOGIN; + } + } + } + + goto cleanup; + } + + json_object *result = NULL; + /* missing for some request types */ + json_object_object_get_ex (j, "result", &result); + + switch (req->type) { + case PIANO_REQUEST_LOGIN: { + /* authenticate user */ + PianoRequestDataLogin_t *reqData = req->data; + + assert (req->responseData != NULL); + assert (reqData != NULL); + + switch (reqData->step) { + case 0: { + /* decrypt timestamp */ + json_object *jsonTimestamp; + if (!json_object_object_get_ex (result, "syncTime", &jsonTimestamp)) { + ret = PIANO_RET_INVALID_RESPONSE; + break; + } + assert (jsonTimestamp != NULL); + const char * const cryptedTimestamp = json_object_get_string (jsonTimestamp); + assert (cryptedTimestamp != NULL); + const time_t realTimestamp = time (NULL); + char *decryptedTimestamp = NULL; + size_t decryptedSize; + + ret = PIANO_RET_ERR; + if ((decryptedTimestamp = PianoDecryptString (ph->partner.in, + cryptedTimestamp, &decryptedSize)) != NULL && + decryptedSize > 4) { + /* skip four bytes garbage(?) at beginning */ + const unsigned long timestamp = strtoul ( + decryptedTimestamp+4, NULL, 0); + ph->timeOffset = (long int) realTimestamp - + (long int) timestamp; + ret = PIANO_RET_CONTINUE_REQUEST; + } + free (decryptedTimestamp); + /* get auth token */ + ph->partner.authToken = PianoJsonStrdup (result, + "partnerAuthToken"); + json_object *partnerId; + if (!json_object_object_get_ex (result, "partnerId", &partnerId)) { + ret = PIANO_RET_INVALID_RESPONSE; + break; + } + ph->partner.id = json_object_get_int (partnerId); + ++reqData->step; + break; + } + + case 1: + /* information exists when reauthenticating, destroy to + * avoid memleak */ + if (ph->user.listenerId != NULL) { + PianoDestroyUserInfo (&ph->user); + } + ph->user.listenerId = PianoJsonStrdup (result, "userId"); + ph->user.authToken = PianoJsonStrdup (result, + "userAuthToken"); + ph->user.IsSubscriber = getBoolDefault (result, + "isSubscriber", false); + break; + } + break; + } + + case PIANO_REQUEST_GET_STATIONS: { + /* get stations */ + assert (req->responseData != NULL); + + json_object *stations, *mix = NULL; + + if (!json_object_object_get_ex (result, "stations", &stations)) { + break; + } + + for (unsigned int i = 0; i < json_object_array_length (stations); i++) { + PianoStation_t *tmpStation; + json_object *s = json_object_array_get_idx (stations, i); + + if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + tmpStation->stationType = PIANO_TYPE_STATION; + + PianoJsonParseStation (s, tmpStation); + + if (tmpStation->isQuickMix) { + /* fix flags on other stations later */ + json_object_object_get_ex (s, "quickMixStationIds", &mix); + } + + /* start new linked list or append */ + ph->stations = PianoListAppendP (ph->stations, tmpStation); + } + + /* fix quickmix flags */ + if (mix != NULL) { + PianoStation_t *curStation = ph->stations; + PianoListForeachP (curStation) { + for (unsigned int i = 0; i < json_object_array_length (mix); i++) { + json_object *id = json_object_array_get_idx (mix, i); + if (strcmp (json_object_get_string (id), + curStation->id) == 0) { + curStation->useQuickMix = true; + } + } + } + } + break; + } + + case PIANO_REQUEST_GET_PLAYLISTS: { + /* get playlists */ + assert (req->responseData != NULL); + + json_object *playlists; + + if (!json_object_object_get_ex (result, "items", &playlists)) { + break; + } + + for (int i = 0; i < json_object_array_length (playlists); i++) { + PianoStation_t *tmpStation; + json_object *s = json_object_array_get_idx (playlists, i); + + if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + tmpStation->stationType = PIANO_TYPE_PLAYLIST; + + PianoJsonParsePlaylist(s, tmpStation); + + /* start new linked list or append */ + ph->stations = PianoListAppendP (ph->stations, tmpStation); + } + break; + } + + // Get album or playlist tracks + case PIANO_REQUEST_GET_TRACKS: { + assert (req->responseData != NULL); + + PianoRequestDataGetPlaylist_t *reqData = req->data; + PianoSong_t *playlist = NULL; + PianoSong_t *FullPlaylist = NULL; + + assert (result != NULL); + assert (req->responseData != NULL); + assert (reqData != NULL); + + switch(reqData->station->stationType) { + case PIANO_TYPE_PLAYLIST: { + json_object *tracks = NULL; + json_object *annotations = NULL; + if (!json_object_object_get_ex (result, "tracks", &tracks)) { + break; + } + assert (tracks!= NULL); + LOG("got tracks\n"); + if (!json_object_object_get_ex (result, "annotations", &annotations)) { + break; + } + assert (annotations != NULL); + LOG("got annotations\n"); + + for (int i = 0; i < json_object_array_length (tracks); i++) { + json_object *s = json_object_array_get_idx (tracks, i); + json_object *trackInfo = NULL; + PianoSong_t *song; + + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + song->seedId = PianoJsonStrdup(result, "pandoraId"); + song->trackToken = PianoJsonStrdup (s, "trackPandoraId"); + assert (song->trackToken != NULL); + + LOG("track %d: %s\n",i + 1,song->trackToken); + + if (!json_object_object_get_ex (annotations, song->trackToken, &trackInfo)) { + break; + } + assert (trackInfo!= NULL); + song->artist = PianoJsonStrdup(trackInfo, "artistName"); + song->album = PianoJsonStrdup(trackInfo, "albumName"); + song->title = PianoJsonStrdup(trackInfo, "name"); + song->fileGain = 0.0; + song->length = getInt(trackInfo, "duration"); + song->coverArt = getCoverArt(trackInfo); + playlist = PianoListAppendP (playlist, song); + } + break; + } + + case PIANO_TYPE_ALBUM: { + char trackTitle[120]; + int totalTracks = 0; + int trackNumber; + + // Count tracks + json_object_object_foreach(result,Key1,Val1) { + if(Key1[0] != 'T' || Key1[1] != 'R') { + continue; + } + totalTracks++; + } + + json_object_object_foreach(result,Key,Val) { + if(Key[0] != 'T' || Key[1] != 'R') { + continue; + } + LOG("got track %s: ",Key); + trackNumber = getInt(Val,"trackNumber"); + snprintf(trackTitle,sizeof(trackTitle), + totalTracks > 9 ? "%02d %s" : "%d %s", + trackNumber,PianoJsonGetStr(Val,"name")); + LOG_RAW("%s\n",trackTitle); + if(getBool(Val,"/rightsInfo/hasInteractive") != 1) { + LOG(" ignored\n"); + LOG(" hasInteractive: %d\n",getBool(Val,"/rightsInfo/hasInteractive")); + LOG(" hasOffline: %d\n",getBool(Val,"/rightsInfo/hasOffline")); + LOG(" hasNonInteractive: %d\n",getBool(Val,"/rightsInfo/hasNonInteractive")); + LOG(" hasStatutory: %d\n",getBool(Val,"/rightsInfo/hasStatutory")); + LOG(" hasRadioRights: %d\n",getBool(Val,"/rightsInfo/hasRadioRights")); + continue; + } + PianoSong_t *song; + + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + song->stationId = strdup(reqData->station->id); + song->title = strdup(trackTitle); + song->trackToken = strdup(Key); + song->seedId = PianoJsonStrdup(Val,"albumId"); + song->artist = PianoJsonStrdup(Val,"artistName"); + song->album = PianoJsonStrdup(Val,"albumName"); + song->fileGain = 0.0; + song->length = getInt(Val, "duration"); + song->coverArt = getCoverArt(Val); + // Add to playlist in track order + + if(playlist == NULL) { + playlist = song; + } + else { + PianoSong_t *lastSong = (PianoSong_t *) &playlist; + PianoSong_t *nextSong = playlist; + do { + int nextSongTrackNum; + if(nextSong == NULL) { + lastSong->head.next = (struct PianoListHead *) song; + break; + } + sscanf(nextSong->title,"%d",&nextSongTrackNum); + if(nextSongTrackNum > trackNumber) { + lastSong->head.next = (struct PianoListHead *) song; + song->head.next = (struct PianoListHead *) nextSong; + break; + } + lastSong = nextSong; + nextSong = (PianoSong_t *) nextSong->head.next; + } while(true); + } + } + break; + } + + default: + LOG("Invalid stationType 0x%x\n",ph->stations->stationType); + break; + } + reqData->retPlaylist = playlist; + break; + } + + case PIANO_REQUEST_GET_PLAYLIST: { + /* get playlist, usually four songs */ + PianoRequestDataGetPlaylist_t *reqData = req->data; + PianoSong_t *playlist = NULL; + + assert (req->responseData != NULL); + assert (reqData != NULL); + assert (reqData->quality != PIANO_AQ_UNKNOWN); + + json_object *items = NULL; + if (!json_object_object_get_ex (result, "items", &items)) { + break; + } + assert (items != NULL); + + for (unsigned int i = 0; i < json_object_array_length (items); i++) { + json_object *s = json_object_array_get_idx (items, i); + PianoSong_t *song; + + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + if (!json_object_object_get_ex (s, "artistName", NULL)) { + free (song); + continue; + } + + /* get audio url based on selected quality */ + static const char *qualityMap[] = {"", "lowQuality", "mediumQuality", + "highQuality"}; + assert (reqData->quality < sizeof (qualityMap)/sizeof (*qualityMap)); + static const char *formatMap[] = {"", "aacplus", "mp3"}; + + json_object *umap; + if (json_object_object_get_ex (s, "audioUrlMap", &umap)) { + assert (umap != NULL); + json_object *jsonEncoding, *qmap; + if (json_object_object_get_ex (umap, qualityMap[reqData->quality], &qmap) && + json_object_object_get_ex (qmap, "encoding", &jsonEncoding)) { + assert (qmap != NULL); + const char *encoding = json_object_get_string (jsonEncoding); + assert (encoding != NULL); + for (size_t k = 0; k < sizeof (formatMap)/sizeof (*formatMap); k++) { + if (strcmp (formatMap[k], encoding) == 0) { + song->audioFormat = k; + break; + } + } + song->audioUrl = PianoJsonStrdup (qmap, "audioUrl"); + } else { + /* requested quality is not available */ + ret = PIANO_RET_QUALITY_UNAVAILABLE; + free (song); + PianoDestroyPlaylist (playlist); + goto cleanup; + } + } + + json_object *v; + song->artist = PianoJsonStrdup (s, "artistName"); + song->album = PianoJsonStrdup (s, "albumName"); + song->title = PianoJsonStrdup (s, "songName"); + song->trackToken = PianoJsonStrdup (s, "trackToken"); + song->stationId = PianoJsonStrdup (s, "stationId"); + song->coverArt = PianoJsonStrdup (s, "albumArtUrl"); + song->detailUrl = PianoJsonStrdup (s, "songDetailUrl"); + song->fileGain = json_object_object_get_ex (s, "trackGain", &v) ? + json_object_get_double (v) : 0.0; + song->length = json_object_object_get_ex (s, "trackLength", &v) ? + json_object_get_int (v) : 0; + switch (json_object_object_get_ex (s, "songRating", &v) ? + json_object_get_int (v) : 0) { + case 1: + song->rating = PIANO_RATE_LOVE; + break; + } + + playlist = PianoListAppendP (playlist, song); + } + + reqData->retPlaylist = playlist; + break; + } + + case PIANO_REQUEST_RATE_SONG: { + /* love/ban song */ + PianoRequestDataRateSong_t *reqData = req->data; + reqData->song->rating = reqData->rating; + break; + } + + case PIANO_REQUEST_ADD_FEEDBACK: + /* never ever use this directly, low-level call */ + assert (0); + break; + + case PIANO_REQUEST_RENAME_STATION: { + /* rename station and update PianoStation_t structure */ + PianoRequestDataRenameStation_t *reqData = req->data; + + assert (reqData != NULL); + assert (reqData->station != NULL); + assert (reqData->newName != NULL); + + free (reqData->station->name); + reqData->station->name = strdup (reqData->newName); + break; + } + + case PIANO_REQUEST_REMOVE_ITEM: + case PIANO_REQUEST_DELETE_STATION: { + /* delete station from server and station list */ + PianoStation_t *station = req->data; + + assert (station != NULL); + + ph->stations = PianoListDeleteP (ph->stations, station); + PianoDestroyStation (station); + free (station); + break; + } + + case PIANO_REQUEST_SEARCH: { + /* search artist/song */ + PianoRequestDataSearch_t *reqData = req->data; + PianoSearchResult_t *searchResult; + + assert (req->responseData != NULL); + assert (reqData != NULL); + + searchResult = &reqData->searchResult; + memset (searchResult, 0, sizeof (*searchResult)); + + /* get artists */ + json_object *artists; + if (json_object_object_get_ex (result, "artists", &artists)) { + for (unsigned int i = 0; i < json_object_array_length (artists); i++) { + json_object *a = json_object_array_get_idx (artists, i); + PianoArtist_t *artist; + + if ((artist = calloc (1, sizeof (*artist))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + artist->name = PianoJsonStrdup (a, "artistName"); + artist->musicId = PianoJsonStrdup (a, "musicToken"); + + searchResult->artists = + PianoListAppendP (searchResult->artists, artist); + } + } + + /* get songs */ + json_object *songs; + if (json_object_object_get_ex (result, "songs", &songs)) { + for (unsigned int i = 0; i < json_object_array_length (songs); i++) { + json_object *s = json_object_array_get_idx (songs, i); + PianoSong_t *song; + + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + song->title = PianoJsonStrdup (s, "songName"); + song->artist = PianoJsonStrdup (s, "artistName"); + song->musicId = PianoJsonStrdup (s, "musicToken"); + + searchResult->songs = + PianoListAppendP (searchResult->songs, song); + } + } + break; + } + + case PIANO_REQUEST_CREATE_STATION: { + /* create station, insert new station into station list on success */ + PianoStation_t *tmpStation; + + if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + PianoJsonParseStation (result, tmpStation); + + PianoStation_t *search = PianoFindStationById (ph->stations, + tmpStation->id); + if (search != NULL) { + ph->stations = PianoListDeleteP (ph->stations, search); + PianoDestroyStation (search); + free (search); + } + ph->stations = PianoListAppendP (ph->stations, tmpStation); + break; + } + + case PIANO_REQUEST_ADD_TIRED_SONG: { + PianoSong_t * const song = req->data; + song->rating = PIANO_RATE_TIRED; + break; + } + + case PIANO_REQUEST_ADD_SEED: + case PIANO_REQUEST_SET_QUICKMIX: + case PIANO_REQUEST_BOOKMARK_SONG: + case PIANO_REQUEST_BOOKMARK_ARTIST: + case PIANO_REQUEST_DELETE_FEEDBACK: + case PIANO_REQUEST_DELETE_SEED: + case PIANO_REQUEST_CHANGE_SETTINGS: + /* response unused */ + break; + + case PIANO_REQUEST_GET_GENRE_STATIONS: { + /* get genre stations */ + json_object *categories; + if (json_object_object_get_ex (result, "categories", &categories)) { + for (unsigned int i = 0; i < json_object_array_length (categories); i++) { + json_object *c = json_object_array_get_idx (categories, i); + PianoGenreCategory_t *tmpGenreCategory; + + if ((tmpGenreCategory = calloc (1, + sizeof (*tmpGenreCategory))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + tmpGenreCategory->name = PianoJsonStrdup (c, + "categoryName"); + + /* get genre subnodes */ + json_object *stations; + if (json_object_object_get_ex (c, "stations", &stations)) { + for (unsigned int k = 0; + k < json_object_array_length (stations); k++) { + json_object *s = + json_object_array_get_idx (stations, k); + PianoGenre_t *tmpGenre; + + if ((tmpGenre = calloc (1, + sizeof (*tmpGenre))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + /* get genre attributes */ + tmpGenre->name = PianoJsonStrdup (s, + "stationName"); + tmpGenre->musicId = PianoJsonStrdup (s, + "stationToken"); + + tmpGenreCategory->genres = + PianoListAppendP (tmpGenreCategory->genres, + tmpGenre); + } + } + + ph->genreStations = PianoListAppendP (ph->genreStations, + tmpGenreCategory); + } + } + break; + } + + case PIANO_REQUEST_TRANSFORM_STATION: { + /* transform shared station into private and update isCreator flag */ + PianoStation_t *station = req->data; + + assert (req->responseData != NULL); + assert (station != NULL); + + station->isCreator = 1; + break; + } + + case PIANO_REQUEST_EXPLAIN: { + /* explain why song was selected */ + PianoRequestDataExplain_t *reqData = req->data; + const size_t strSize = 768; + + assert (reqData != NULL); + + json_object *explanations; + if (json_object_object_get_ex (result, "explanations", &explanations) && + json_object_array_length (explanations) > 0) { + reqData->retExplain = malloc (strSize * + sizeof (*reqData->retExplain)); + strncpy (reqData->retExplain, "We're playing this track " + "because it features ", strSize); + for (unsigned int i = 0; i < json_object_array_length (explanations); i++) { + json_object *e = json_object_array_get_idx (explanations, + i); + json_object *f; + if (!json_object_object_get_ex (e, "focusTraitName", &f)) { + continue; + } + const char *s = json_object_get_string (f); + PianoStrpcat (reqData->retExplain, s, strSize); + if (i < json_object_array_length (explanations)-2) { + PianoStrpcat (reqData->retExplain, ", ", strSize); + } else if (i == json_object_array_length (explanations)-2) { + PianoStrpcat (reqData->retExplain, " and ", strSize); + } else { + PianoStrpcat (reqData->retExplain, ".", strSize); + } + } + } + break; + } + + case PIANO_REQUEST_GET_SETTINGS: { + PianoSettings_t * const settings = req->data; + + assert (settings != NULL); + + settings->explicitContentFilter = getBoolDefault (result, + "isExplicitContentFilterEnabled", false); + settings->username = PianoJsonStrdup (result, "username"); + break; + } + + case PIANO_REQUEST_GET_STATION_INFO: { + /* get station information (seeds and feedback) */ + PianoRequestDataGetStationInfo_t *reqData = req->data; + PianoStationInfo_t *info; + + assert (reqData != NULL); + + info = &reqData->info; + assert (info != NULL); + + /* parse music seeds */ + json_object *music; + if (json_object_object_get_ex (result, "music", &music)) { + /* songs */ + json_object *songs; + if (json_object_object_get_ex (music, "songs", &songs)) { + for (unsigned int i = 0; i < json_object_array_length (songs); i++) { + json_object *s = json_object_array_get_idx (songs, i); + PianoSong_t *seedSong; + + seedSong = calloc (1, sizeof (*seedSong)); + if (seedSong == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + seedSong->title = PianoJsonStrdup (s, "songName"); + seedSong->artist = PianoJsonStrdup (s, "artistName"); + seedSong->seedId = PianoJsonStrdup (s, "seedId"); + + info->songSeeds = PianoListAppendP (info->songSeeds, + seedSong); + } + } + + /* artists */ + json_object *artists; + if (json_object_object_get_ex (music, "artists", &artists)) { + for (unsigned int i = 0; i < json_object_array_length (artists); i++) { + json_object *a = json_object_array_get_idx (artists, i); + PianoArtist_t *seedArtist; + + seedArtist = calloc (1, sizeof (*seedArtist)); + if (seedArtist == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + seedArtist->name = PianoJsonStrdup (a, "artistName"); + seedArtist->seedId = PianoJsonStrdup (a, "seedId"); + + info->artistSeeds = + PianoListAppendP (info->artistSeeds, seedArtist); + } + } + } + + /* parse feedback */ + json_object *feedback; + if (json_object_object_get_ex (result, "feedback", &feedback)) { + static const char * const keys[] = {"thumbsUp", "thumbsDown"}; + for (size_t i = 0; i < sizeof (keys)/sizeof (*keys); i++) { + json_object *val; + if (!json_object_object_get_ex (feedback, keys[i], &val)) { + continue; + } + assert (json_object_is_type (val, json_type_array)); + for (unsigned int i = 0; i < json_object_array_length (val); i++) { + json_object *s = json_object_array_get_idx (val, i); + PianoSong_t *feedbackSong; + + feedbackSong = calloc (1, sizeof (*feedbackSong)); + if (feedbackSong == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + feedbackSong->title = PianoJsonStrdup (s, "songName"); + feedbackSong->artist = PianoJsonStrdup (s, + "artistName"); + feedbackSong->feedbackId = PianoJsonStrdup (s, + "feedbackId"); + feedbackSong->rating = getBoolDefault (s, "isPositive", + false) ? PIANO_RATE_LOVE : PIANO_RATE_BAN; + + json_object *v; + feedbackSong->length = + json_object_object_get_ex (s, "trackLength", &v) ? + json_object_get_int (v) : 0; + + info->feedback = PianoListAppendP (info->feedback, + feedbackSong); + } + } + } + break; + } + + case PIANO_REQUEST_GET_STATION_MODES: { + PianoRequestDataGetStationModes_t *reqData = req->data; + assert (reqData != NULL); + + int active = -1; + + json_object *activeMode; + if (json_object_object_get_ex (result, "currentModeId", &activeMode)) { + active = json_object_get_int (activeMode); + } + + json_object *availableModes; + if (json_object_object_get_ex (result, "availableModes", &availableModes)) { + for (unsigned int i = 0; i < json_object_array_length (availableModes); i++) { + json_object *val = json_object_array_get_idx (availableModes, i); + + assert (json_object_is_type (val, json_type_object)); + + PianoStationMode_t *mode; + if ((mode = calloc (1, sizeof (*mode))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + json_object *modeId; + if (json_object_object_get_ex (val, "modeId", &modeId)) { + mode->id = json_object_get_int (modeId); + mode->name = PianoJsonStrdup (val, "modeName"); + mode->description = PianoJsonStrdup (val, "modeDescription"); + mode->isAlgorithmic = getBoolDefault (val, "isAlgorithmicMode", + false); + mode->isTakeover = getBoolDefault (val, "isTakeoverMode", + false); + mode->active = active == mode->id; + } + + reqData->retModes = PianoListAppendP (reqData->retModes, + mode); + } + } + break; + } + + case PIANO_REQUEST_SET_STATION_MODE: { + PianoRequestDataSetStationMode_t *reqData = req->data; + assert (reqData != NULL); + + int active = -1; + + json_object *activeMode; + if (json_object_object_get_ex (result, "currentModeId", &activeMode)) { + active = json_object_get_int (activeMode); + } + + if (active != reqData->id) { + /* this did not work */ + return PIANO_RET_ERR; + } + break; + } + + case PIANO_REQUEST_GET_PLAYBACK_INFO: { + PianoRequestDataGetPlaylist_t *reqData = req->data; + PianoSong_t *song = reqData->retPlaylist; + + assert (req->responseData != NULL); + assert (reqData != NULL); + assert (reqData->quality < sizeof (qualityMap)/sizeof (*qualityMap)); + assert (song != NULL); + + json_object *audioUrlMap = NULL; + if (!json_object_object_get_ex (result, "audioUrlMap", &audioUrlMap)) { + break; + } + assert (audioUrlMap != NULL); + + const char *quality = qualityMap[reqData->quality]; + json_object *umap; + + json_object *jsonEncoding = NULL; + if (json_object_object_get_ex (audioUrlMap, quality, &umap)) { + assert (umap != NULL); + if (json_object_object_get_ex (umap, "encoding", &jsonEncoding)) { + assert (jsonEncoding != NULL); + const char *encoding = json_object_get_string (jsonEncoding); + assert (encoding != NULL); + for (size_t k = 0; k < sizeof (formatMap)/sizeof (*formatMap); k++) { + if (strcmp (formatMap[k], encoding) == 0) { + song->audioFormat = k; + break; + } + } + song->audioUrl = PianoJsonStrdup (umap, "audioUrl"); + } + } + + if(song->audioUrl == NULL) { + /* requested quality is not available */ + LOG("quality %s not found in audioUrlMap\n",quality); + ret = PIANO_RET_QUALITY_UNAVAILABLE; + PianoDestroyPlaylist (reqData->retPlaylist); + goto cleanup; + } + break; + } + + case PIANO_REQUEST_GET_USER_PROFILE: { + assert (req->responseData != NULL); + + json_object *stations; + json_object *annotations = NULL; + ph->user.IsPremiumUser = getBoolDefault (result,"isPremiumUser", false); + LOG("IsPremiumUser: %d\n",ph->user.IsPremiumUser); + + if (!json_object_object_get_ex (result, "annotations", &annotations)) { + break; + } + assert (annotations != NULL); + int Annotations = 0; + json_object_object_foreach(annotations,Key,Val) { + const char *type = PianoJsonGetStr(Val,"type"); + + Annotations++; + if(strcmp(type,"PL") == 0) { + ph->user.PlayListCount++; + } + else if(strcmp(type,"ST") == 0) { + ph->user.StationCount++; + } + else if(strcmp(type,"AL") == 0) { + ph->user.AlbumCount++; + } + else if(strcmp(type,"TR") == 0) { + ph->user.TrackCount++; + } + else if(strcmp(type,"LI") == 0) { + } + else if(strcmp(type,"AR") == 0) { + } + else { + LOG("type %s ignored\n",type); + } + } + LOG("Found: %d annotations:\n",Annotations); + LOG(" PlayLists: %d:\n",ph->user.PlayListCount); + LOG(" Stations: %d:\n",ph->user.StationCount); + LOG(" Albums: %d:\n",ph->user.AlbumCount); + LOG(" Tracks: %d:\n",ph->user.TrackCount); + break; + } + + case PIANO_REQUEST_GET_ITEMS: { + assert (req->responseData != NULL); + json_object *items = NULL; + if (!json_object_object_get_ex (result, "items", &items)) { + break; + } + assert (items != NULL); + for (int i = 0; i < json_object_array_length (items); i++) { + json_object *s = json_object_array_get_idx (items, i); + const char *type = PianoJsonGetStr(s,"pandoraType"); + PianoSong_t *song; + PianoStationType_t stationType = PIANO_TYPE_NONE; + + assert(type != NULL); + if(strcmp(type,"PL") == 0 || strcmp(type,"ST") == 0) { + // Playlists and stations handled elsewhere + } + else if(strcmp(type,"AL") == 0) { + stationType = PIANO_TYPE_ALBUM; + } + else if(strcmp(type,"TR") == 0) { + stationType = PIANO_TYPE_TRACK; + } + else if(strcmp(type,"PC") == 0) { + stationType = PIANO_TYPE_PODCAST; + ph->user.PodcastCount++; + } + else { + LOG("type %s ignored\n",type); + } + + if(stationType != PIANO_TYPE_NONE) { + PianoStation_t *tmpStation; + if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + tmpStation->stationType = stationType; + tmpStation->id = PianoJsonStrdup (s, "pandoraId"); + /* start new linked list or append */ + ph->stations = PianoListAppendP (ph->stations, tmpStation); + } + else { + LOG("type %s ignored\n",type); + } + } + + if(ph->user.PodcastCount) { + LOG("Found %d podcast stations\n",ph->user.PodcastCount); + } + break; + } + + case PIANO_REQUEST_ANNOTATE_OBJECTS: { + assert (req->responseData != NULL); + assert (req->data != NULL); + PianoStation_t *station = ph->stations; + PianoRequestDataGetPlaylist_t *reqData = req->data; + + while(station != NULL) { + assert(station->id != NULL); + switch(station->stationType) { + case PIANO_TYPE_PODCAST: { + json_object_object_foreach(result,Key,Val) { + if(strcmp(Key,station->id) == 0) { + PianoSong_t *song = station->theSong; + assert (song == NULL); + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + station->name = PianoJsonStrdup(Val,"name"); + station->seedId = PianoJsonStrdup(Val,"latestEpisodeId"); + station->theSong = song; + song->album = strdup(station->name); + song->coverArt = getCoverArt(Val); // podcast coverArt + LOG("podcast coverart %s\n",song->coverArt); + break; + } + } + if(station->name == NULL) { + LOG("Couldn't find station %s\n",station->id); + } + break; + } + + case PIANO_TYPE_ALBUM: { + json_object_object_foreach(result,Key,Val) { + if(strcmp(Key,station->id) == 0) { + char Temp[120]; + snprintf(Temp,sizeof(Temp),"%s - %s", + PianoJsonGetStr(Val,"artistName"), + PianoJsonGetStr(Val,"name")); + station->name = strdup(Temp); + station->seedId = PianoJsonStrdup(Val,"pandoraId"); + break; + } + } + if(station->name == NULL) { + LOG("Couldn't find station %s\n",station->id); + } + break; + } + + case PIANO_TYPE_TRACK: { + json_object_object_foreach(result,Key,Val) { + if(strcmp(Key,station->id) == 0) { + station->name = PianoJsonStrdup(Val,"name"); + station->seedId = PianoJsonStrdup(Val,"albumId"); + + PianoSong_t *song = station->theSong; + assert (song == NULL); + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + station->theSong = song; + song->artist = PianoJsonStrdup(Val, "artistName"); + song->album = PianoJsonStrdup(Val, "albumName"); + song->title = PianoJsonStrdup(Val, "name"); + song->length = getInt(Val, "duration"); + song->coverArt = getCoverArt(Val); + song->fileGain = 0.0; + break; + } + } + if(station->name == NULL) { + LOG("Couldn't find station %s\n",station->id); + } + break; + } + } + station = (PianoStation_t *) station->head.next; + } + break; + } + + case PIANO_REQUEST_GET_EPISODES: { + assert (req->responseData != NULL); + assert (req->data != NULL); + PianoRequestDataGetEpisodes_t *reqData = req->data; + PianoStation_t *station = reqData->station; + PianoSong_t *playlist = NULL; + int Added = 0; + PianoSong_t *song; + + json_object *annotations = NULL; + if (json_pointer_get(result, "/details/annotations", &annotations)) { + break; + } + json_object_object_foreach(annotations,Key,Val) { + if(Key[0] != 'P' || Key[1] != 'E') { + // not episode, ignore it + continue; + } + const char *EpisodeTitle = PianoJsonGetStr(Val,"name"); + + if(EpisodeTitle == NULL) { + LOG("Couldn't get title of episode\n"); + continue; + } + const char *Id = PianoJsonGetStr(Val,"podcastId"); + if(Id == NULL) { + LOG("Couldn't get podcastId\n"); + continue; + } + + if(strcmp(station->id,Id) != 0) { + LOG("Episode not for selected podcast (%s != %s)\n", + station->id,Id); + continue; + } + + const char *State = PianoJsonGetStr(Val,"contentState"); + if(State == NULL) { + LOG("Couldn't get contentState\n"); + continue; + } + + if(strcmp(State,"AVAILABLE") != 0) { + if(strlen(EpisodeTitle) > 0) { + LOG(" ignored %s\n",EpisodeTitle); + LOG(" contentState: %s\n",State); + } + continue; + } + if(getBool(Val,"/rightsInfo/hasInteractive") != 1) { + LOG(" ignored %s\n",EpisodeTitle); + LOG(" hasInteractive: %d\n",getBool(Val,"/rightsInfo/hasInteractive")); + LOG(" hasOffline: %d\n",getBool(Val,"/rightsInfo/hasOffline")); + LOG(" hasNonInteractive: %d\n",getBool(Val,"/rightsInfo/hasNonInteractive")); + LOG(" hasStatutory: %d\n",getBool(Val,"/rightsInfo/hasStatutory")); + LOG(" hasRadioRights: %d\n",getBool(Val,"/rightsInfo/hasRadioRights")); + continue; + } + + int Month; + int Day; + int Year; + char trackTitle[120]; + const char *Released = PianoJsonGetStr(Val,"releaseDate"); + if(Released == NULL) { + LOG("Couldn't get releaseDate\n"); + continue; + } + if(sscanf(Released,"%d-%d-%d",&Year,&Month,&Day) != 3) { + LOG("Couldn't convert releaseDate %s\n",Released); + continue; + } + const char *Title = PianoJsonGetStr(Val,"name"); + if(Title == NULL) { + LOG("Couldn't get episode title\n"); + continue; + } + const char *trackToken = PianoJsonGetStr(Val, "pandoraId"); + if(trackToken == NULL) { + LOG("Couldn't get trackToken\n"); + continue; + } + + snprintf(trackTitle,sizeof(trackTitle),"%02d/%02d: %s", + Month,Day,Title); + LOG("Got %s\n",trackTitle); + + if(!reqData->bGetAll) { + // just getting name of the current episode + song = reqData->playList; + if(strcmp(trackToken,song->trackToken) != 0) { + LOG("Ignoring %s, not current episode\n",trackTitle); + continue; + } + song->title = strdup(trackTitle); + LOG("Added name of current episode\n"); + break; + } + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + song->title = strdup(trackTitle); + song->trackToken = strdup(trackToken); + song->length = getInt(Val, "duration"); + // Save release date for sorting + song->fileGain = ((Year - 1900) * 10000) + (Month * 100) + Day; + // Add to playlist in release data order + PianoSong_t *thisSong = playlist; + PianoSong_t *lastSong = (PianoSong_t *) &playlist; + do { + if(thisSong == NULL) { + lastSong->head.next = (struct PianoListHead *) song; + // LOG("Added to end of list\n"); + break; + } + // LOG("Comparing to %s\n",thisSong->title); + if(thisSong->fileGain > song->fileGain) { + lastSong->head.next = (struct PianoListHead *) song; + song->head.next = (struct PianoListHead *) thisSong; + // LOG("Added before\n"); + break; + } + lastSong = thisSong; + thisSong = (PianoSong_t *) thisSong->head.next; + } while(true); + Added++; + } + reqData->playList = playlist; + LOG("Added %d episodes:\n",Added); #if 0 - song = playlist; - while(song != NULL) { - LOG(" %s\n",song->title); - song = (PianoSong_t *) song->head.next; - } + song = playlist; + while(song != NULL) { + LOG(" %s\n",song->title); + song = (PianoSong_t *) song->head.next; + } #endif - break; - } + break; + } } cleanup: - json_object_put (j); + json_object_put (j); - return ret; + return ret; } From 73643baaf62560caa1b4ef1e30c2402041141e30 Mon Sep 17 00:00:00 2001 From: Skip Hansen Date: Tue, 1 Jul 2025 12:02:42 -0400 Subject: [PATCH 5/7] Added releaseDate field to PianoSong_t instead of misusing fileGain. --- src/libpiano/piano.h | 473 ++++++++++++++++++++-------------------- src/libpiano/response.c | 5 +- 2 files changed, 239 insertions(+), 239 deletions(-) diff --git a/src/libpiano/piano.h b/src/libpiano/piano.h index 0eff7833d..649cb8509 100644 --- a/src/libpiano/piano.h +++ b/src/libpiano/piano.h @@ -1,6 +1,6 @@ /* Copyright (c) 2008-2013 - Lars-Dominik Braun + Lars-Dominik Braun Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -44,344 +44,345 @@ THE SOFTWARE. #define PIANO_RPC_PATH "/services/json/?" typedef struct PianoListHead { - struct PianoListHead *next; + struct PianoListHead *next; } PianoListHead_t; typedef struct PianoUserInfo { - char *listenerId; - char *authToken; - bool IsSubscriber; - bool IsPremiumUser; - int PlayListCount; - int StationCount; - int AlbumCount; - int TrackCount; - int PodcastCount; + char *listenerId; + char *authToken; + bool IsSubscriber; + bool IsPremiumUser; + int PlayListCount; + int StationCount; + int AlbumCount; + int TrackCount; + int PodcastCount; } PianoUserInfo_t; typedef enum { - PIANO_TYPE_NONE = 0, - PIANO_TYPE_STATION = 1, - PIANO_TYPE_PODCAST = 2, - PIANO_TYPE_PLAYLIST = 3, - PIANO_TYPE_ALBUM = 4, - PIANO_TYPE_TRACK = 5, - PIANO_TYPE_LAST + PIANO_TYPE_NONE = 0, + PIANO_TYPE_STATION = 1, + PIANO_TYPE_PODCAST = 2, + PIANO_TYPE_PLAYLIST = 3, + PIANO_TYPE_ALBUM = 4, + PIANO_TYPE_TRACK = 5, + PIANO_TYPE_LAST } PianoStationType_t; typedef enum { - PIANO_MODE_STATION = 0, - PIANO_MODE_PODCAST = 1, - PIANO_MODE_PLAYLIST = 2, - PIANO_MODE_ALBUM = 3, - PIANO_MODE_ALL = 4 + PIANO_MODE_STATION = 0, + PIANO_MODE_PODCAST = 1, + PIANO_MODE_PLAYLIST = 2, + PIANO_MODE_ALBUM = 3, + PIANO_MODE_ALL = 4 } PianoMode_t; typedef enum { - PIANO_RATE_NONE = 0, - PIANO_RATE_LOVE = 1, - PIANO_RATE_BAN = 2, - PIANO_RATE_TIRED = 3, + PIANO_RATE_NONE = 0, + PIANO_RATE_LOVE = 1, + PIANO_RATE_BAN = 2, + PIANO_RATE_TIRED = 3, } PianoSongRating_t; /* UNKNOWN should be 0, because memset sets audio format to 0 */ typedef enum { - PIANO_AF_UNKNOWN = 0, - PIANO_AF_AACPLUS = 1, - PIANO_AF_MP3 = 2, + PIANO_AF_UNKNOWN = 0, + PIANO_AF_AACPLUS = 1, + PIANO_AF_MP3 = 2, } PianoAudioFormat_t; typedef enum { - PIANO_AQ_UNKNOWN = 0, - PIANO_AQ_LOW = 1, - PIANO_AQ_MEDIUM = 2, - PIANO_AQ_HIGH = 3, + PIANO_AQ_UNKNOWN = 0, + PIANO_AQ_LOW = 1, + PIANO_AQ_MEDIUM = 2, + PIANO_AQ_HIGH = 3, } PianoAudioQuality_t; typedef struct PianoSong { - PianoListHead_t head; - char *artist; - char *stationId; - char *album; - char *audioUrl; - char *coverArt; - char *musicId; - char *title; - char *seedId; - char *feedbackId; - char *detailUrl; - char *trackToken; - float fileGain; - unsigned int length; /* song length in seconds */ - PianoSongRating_t rating; - PianoAudioFormat_t audioFormat; + PianoListHead_t head; + char *artist; + char *stationId; + char *album; + char *audioUrl; + char *coverArt; + char *musicId; + char *title; + char *seedId; + char *feedbackId; + char *detailUrl; + char *trackToken; + float fileGain; + unsigned int length; /* song length in seconds */ + PianoSongRating_t rating; + PianoAudioFormat_t audioFormat; + unsigned int releaseDate; } PianoSong_t; typedef struct PianoStation { - PianoListHead_t head; - char isCreator; - char isQuickMix; - char useQuickMix; /* station will be included in quickmix */ - char *name; - char *id; - char *seedId; - PianoStationType_t stationType; - PianoSong_t *theSong; + PianoListHead_t head; + char isCreator; + char isQuickMix; + char useQuickMix; /* station will be included in quickmix */ + char *name; + char *id; + char *seedId; + PianoStationType_t stationType; + PianoSong_t *theSong; } PianoStation_t; typedef struct { - PianoStation_t *station; - PianoSong_t *playList; - bool bGetAll; + PianoStation_t *station; + PianoSong_t *playList; + bool bGetAll; } PianoRequestDataGetEpisodes_t; /* currently only used for search results */ typedef struct PianoArtist { - PianoListHead_t head; - char *name; - char *musicId; - char *seedId; - int score; + PianoListHead_t head; + char *name; + char *musicId; + char *seedId; + int score; } PianoArtist_t; typedef struct PianoGenre { - PianoListHead_t head; - char *name; - char *musicId; + PianoListHead_t head; + char *name; + char *musicId; } PianoGenre_t; typedef struct PianoGenreCategory { - PianoListHead_t head; - char *name; - PianoGenre_t *genres; + PianoListHead_t head; + char *name; + PianoGenre_t *genres; } PianoGenreCategory_t; typedef struct PianoPartner { - gcry_cipher_hd_t in, out; - char *authToken, *device, *user, *password; - unsigned int id; + gcry_cipher_hd_t in, out; + char *authToken, *device, *user, *password; + unsigned int id; } PianoPartner_t; typedef struct PianoHandle { - PianoUserInfo_t user; - /* linked lists */ - PianoStation_t *stations; - PianoGenreCategory_t *genreStations; - PianoPartner_t partner; - int timeOffset; + PianoUserInfo_t user; + /* linked lists */ + PianoStation_t *stations; + PianoGenreCategory_t *genreStations; + PianoPartner_t partner; + int timeOffset; } PianoHandle_t; typedef struct PianoSearchResult { - PianoSong_t *songs; - PianoArtist_t *artists; + PianoSong_t *songs; + PianoArtist_t *artists; } PianoSearchResult_t; typedef struct { - PianoSong_t *songSeeds; - PianoArtist_t *artistSeeds; - PianoStation_t *stationSeeds; - PianoSong_t *feedback; + PianoSong_t *songSeeds; + PianoArtist_t *artistSeeds; + PianoStation_t *stationSeeds; + PianoSong_t *feedback; } PianoStationInfo_t; typedef struct { - char *username; - bool explicitContentFilter; + char *username; + bool explicitContentFilter; } PianoSettings_t; typedef struct { - PianoListHead_t head; - char *name, *description; - bool isAlgorithmic, isTakeover, active; - int id; + PianoListHead_t head; + char *name, *description; + bool isAlgorithmic, isTakeover, active; + int id; } PianoStationMode_t; typedef enum { - /* 0 is reserved: memset (x, 0, sizeof (x)) */ - PIANO_REQUEST_LOGIN = 1, - PIANO_REQUEST_GET_STATIONS = 2, - PIANO_REQUEST_GET_PLAYLIST = 3, - PIANO_REQUEST_RATE_SONG = 4, - PIANO_REQUEST_ADD_FEEDBACK = 5, - PIANO_REQUEST_RENAME_STATION = 7, - PIANO_REQUEST_DELETE_STATION = 8, - PIANO_REQUEST_SEARCH = 9, - PIANO_REQUEST_CREATE_STATION = 10, - PIANO_REQUEST_ADD_SEED = 11, - PIANO_REQUEST_ADD_TIRED_SONG = 12, - PIANO_REQUEST_SET_QUICKMIX = 13, - PIANO_REQUEST_GET_GENRE_STATIONS = 14, - PIANO_REQUEST_TRANSFORM_STATION = 15, - PIANO_REQUEST_EXPLAIN = 16, - PIANO_REQUEST_BOOKMARK_SONG = 18, - PIANO_REQUEST_BOOKMARK_ARTIST = 19, - PIANO_REQUEST_GET_STATION_INFO = 20, - PIANO_REQUEST_DELETE_FEEDBACK = 21, - PIANO_REQUEST_DELETE_SEED = 22, - PIANO_REQUEST_GET_SETTINGS = 23, - PIANO_REQUEST_CHANGE_SETTINGS = 24, - PIANO_REQUEST_GET_STATION_MODES = 25, - PIANO_REQUEST_SET_STATION_MODE = 26, - PIANO_REQUEST_GET_PLAYLISTS = 27, - PIANO_REQUEST_GET_TRACKS = 28, - PIANO_REQUEST_GET_PLAYBACK_INFO = 29, - PIANO_REQUEST_GET_ITEMS = 30, - PIANO_REQUEST_GET_USER_PROFILE = 31, - PIANO_REQUEST_ANNOTATE_OBJECTS = 32, - PIANO_REQUEST_REMOVE_ITEM = 33, - PIANO_REQUEST_GET_EPISODES = 34, + /* 0 is reserved: memset (x, 0, sizeof (x)) */ + PIANO_REQUEST_LOGIN = 1, + PIANO_REQUEST_GET_STATIONS = 2, + PIANO_REQUEST_GET_PLAYLIST = 3, + PIANO_REQUEST_RATE_SONG = 4, + PIANO_REQUEST_ADD_FEEDBACK = 5, + PIANO_REQUEST_RENAME_STATION = 7, + PIANO_REQUEST_DELETE_STATION = 8, + PIANO_REQUEST_SEARCH = 9, + PIANO_REQUEST_CREATE_STATION = 10, + PIANO_REQUEST_ADD_SEED = 11, + PIANO_REQUEST_ADD_TIRED_SONG = 12, + PIANO_REQUEST_SET_QUICKMIX = 13, + PIANO_REQUEST_GET_GENRE_STATIONS = 14, + PIANO_REQUEST_TRANSFORM_STATION = 15, + PIANO_REQUEST_EXPLAIN = 16, + PIANO_REQUEST_BOOKMARK_SONG = 18, + PIANO_REQUEST_BOOKMARK_ARTIST = 19, + PIANO_REQUEST_GET_STATION_INFO = 20, + PIANO_REQUEST_DELETE_FEEDBACK = 21, + PIANO_REQUEST_DELETE_SEED = 22, + PIANO_REQUEST_GET_SETTINGS = 23, + PIANO_REQUEST_CHANGE_SETTINGS = 24, + PIANO_REQUEST_GET_STATION_MODES = 25, + PIANO_REQUEST_SET_STATION_MODE = 26, + PIANO_REQUEST_GET_PLAYLISTS = 27, + PIANO_REQUEST_GET_TRACKS = 28, + PIANO_REQUEST_GET_PLAYBACK_INFO = 29, + PIANO_REQUEST_GET_ITEMS = 30, + PIANO_REQUEST_GET_USER_PROFILE = 31, + PIANO_REQUEST_ANNOTATE_OBJECTS = 32, + PIANO_REQUEST_REMOVE_ITEM = 33, + PIANO_REQUEST_GET_EPISODES = 34, } PianoRequestType_t; typedef struct PianoRequest { - PianoRequestType_t type; - bool secure; - void *data; - char urlPath[1024]; - char *postData; - char *responseData; + PianoRequestType_t type; + bool secure; + void *data; + char urlPath[1024]; + char *postData; + char *responseData; } PianoRequest_t; /* request data structures */ typedef struct { - char *user; - char *password; - unsigned char step; + char *user; + char *password; + unsigned char step; } PianoRequestDataLogin_t; typedef struct { - PianoStation_t *station; - PianoAudioQuality_t quality; - PianoSong_t *retPlaylist; + PianoStation_t *station; + PianoAudioQuality_t quality; + PianoSong_t *retPlaylist; } PianoRequestDataGetPlaylist_t; typedef struct { - PianoSong_t *song; - PianoSongRating_t rating; + PianoSong_t *song; + PianoSongRating_t rating; } PianoRequestDataRateSong_t; typedef struct { - char *stationId; - char *trackToken; - PianoSongRating_t rating; + char *stationId; + char *trackToken; + PianoSongRating_t rating; } PianoRequestDataAddFeedback_t; typedef struct { - PianoStation_t *station; - char *newName; + PianoStation_t *station; + char *newName; } PianoRequestDataRenameStation_t; typedef struct { - char *searchStr; - PianoSearchResult_t searchResult; + char *searchStr; + PianoSearchResult_t searchResult; } PianoRequestDataSearch_t; typedef struct { - char *token; - enum { - PIANO_MUSICTYPE_INVALID = 0, - PIANO_MUSICTYPE_SONG, - PIANO_MUSICTYPE_ARTIST, - } type; + char *token; + enum { + PIANO_MUSICTYPE_INVALID = 0, + PIANO_MUSICTYPE_SONG, + PIANO_MUSICTYPE_ARTIST, + } type; } PianoRequestDataCreateStation_t; typedef struct { - PianoStation_t *station; - char *musicId; + PianoStation_t *station; + char *musicId; } PianoRequestDataAddSeed_t; typedef struct { - PianoSong_t *song; - char *retExplain; + PianoSong_t *song; + char *retExplain; } PianoRequestDataExplain_t; typedef struct { - PianoStation_t *station; - PianoStationInfo_t info; + PianoStation_t *station; + PianoStationInfo_t info; } PianoRequestDataGetStationInfo_t; typedef struct { - PianoSong_t *song; - PianoArtist_t *artist; - PianoStation_t *station; + PianoSong_t *song; + PianoArtist_t *artist; + PianoStation_t *station; } PianoRequestDataDeleteSeed_t; typedef enum { - PIANO_UNDEFINED = 0, - PIANO_FALSE = 1, - PIANO_TRUE = 2, + PIANO_UNDEFINED = 0, + PIANO_FALSE = 1, + PIANO_TRUE = 2, } PianoTristate_t; typedef struct { - char *currentUsername, *newUsername; - char *currentPassword, *newPassword; - PianoTristate_t explicitContentFilter; + char *currentUsername, *newUsername; + char *currentPassword, *newPassword; + PianoTristate_t explicitContentFilter; } PianoRequestDataChangeSettings_t; typedef struct { - PianoStation_t *station; - PianoStationMode_t *retModes; + PianoStation_t *station; + PianoStationMode_t *retModes; } PianoRequestDataGetStationModes_t; typedef struct { - PianoStation_t *station; - unsigned int id; + PianoStation_t *station; + unsigned int id; } PianoRequestDataSetStationMode_t; /* pandora error code offset */ #define PIANO_RET_OFFSET 1024 typedef enum { - PIANO_RET_ERR = 0, - PIANO_RET_OK = 1, - PIANO_RET_INVALID_RESPONSE = 2, - PIANO_RET_CONTINUE_REQUEST = 3, - PIANO_RET_OUT_OF_MEMORY = 4, - PIANO_RET_INVALID_LOGIN = 5, - PIANO_RET_QUALITY_UNAVAILABLE = 6, - PIANO_RET_GCRY_ERR = 7, - - /* pandora error codes */ - PIANO_RET_P_INTERNAL = PIANO_RET_OFFSET+0, - PIANO_RET_P_API_VERSION_NOT_SUPPORTED = PIANO_RET_OFFSET+11, - PIANO_RET_P_BIRTH_YEAR_INVALID = PIANO_RET_OFFSET+1025, - PIANO_RET_P_BIRTH_YEAR_TOO_YOUNG = PIANO_RET_OFFSET+1026, - PIANO_RET_P_CALL_NOT_ALLOWED = PIANO_RET_OFFSET+1008, - PIANO_RET_P_CERTIFICATE_REQUIRED = PIANO_RET_OFFSET+7, - PIANO_RET_P_COMPLIMENTARY_PERIOD_ALREADY_IN_USE = PIANO_RET_OFFSET+1007, - PIANO_RET_P_DAILY_TRIAL_LIMIT_REACHED = PIANO_RET_OFFSET+1035, - PIANO_RET_P_DEVICE_ALREADY_ASSOCIATED_TO_ACCOUNT = PIANO_RET_OFFSET+1014, - PIANO_RET_P_DEVICE_DISABLED = PIANO_RET_OFFSET+1034, - PIANO_RET_P_DEVICE_MODEL_INVALID = PIANO_RET_OFFSET+1023, - PIANO_RET_P_DEVICE_NOT_FOUND = PIANO_RET_OFFSET+1009, - PIANO_RET_P_EXPLICIT_PIN_INCORRECT = PIANO_RET_OFFSET+1018, - PIANO_RET_P_EXPLICIT_PIN_MALFORMED = PIANO_RET_OFFSET+1020, - PIANO_RET_P_INSUFFICIENT_CONNECTIVITY = PIANO_RET_OFFSET+13, - PIANO_RET_P_INVALID_AUTH_TOKEN = PIANO_RET_OFFSET+1001, - PIANO_RET_P_INVALID_COUNTRY_CODE = PIANO_RET_OFFSET+1027, - PIANO_RET_P_INVALID_GENDER = PIANO_RET_OFFSET+1027, - PIANO_RET_P_INVALID_PARTNER_LOGIN = PIANO_RET_OFFSET+1002, - PIANO_RET_P_INVALID_PASSWORD = PIANO_RET_OFFSET+1012, - PIANO_RET_P_INVALID_SPONSOR = PIANO_RET_OFFSET+1036, - PIANO_RET_P_INVALID_USERNAME = PIANO_RET_OFFSET+1011, - PIANO_RET_P_LICENSING_RESTRICTIONS = PIANO_RET_OFFSET+12, - PIANO_RET_P_MAINTENANCE_MODE = PIANO_RET_OFFSET+1, - PIANO_RET_P_MAX_STATIONS_REACHED = PIANO_RET_OFFSET+1005, - PIANO_RET_P_PARAMETER_MISSING = PIANO_RET_OFFSET+9, - PIANO_RET_P_PARAMETER_TYPE_MISMATCH = PIANO_RET_OFFSET+8, - PIANO_RET_P_PARAMETER_VALUE_INVALID = PIANO_RET_OFFSET+10, - PIANO_RET_P_PARTNER_NOT_AUTHORIZED = PIANO_RET_OFFSET+1010, - PIANO_RET_P_READ_ONLY_MODE = PIANO_RET_OFFSET+1000, - PIANO_RET_P_SECURE_PROTOCOL_REQUIRED = PIANO_RET_OFFSET+6, - PIANO_RET_P_STATION_DOES_NOT_EXIST = PIANO_RET_OFFSET+1006, - PIANO_RET_P_UPGRADE_DEVICE_MODEL_INVALID = PIANO_RET_OFFSET+1015, - PIANO_RET_P_URL_PARAM_MISSING_AUTH_TOKEN = PIANO_RET_OFFSET+3, - PIANO_RET_P_URL_PARAM_MISSING_METHOD = PIANO_RET_OFFSET+2, - PIANO_RET_P_URL_PARAM_MISSING_PARTNER_ID = PIANO_RET_OFFSET+4, - PIANO_RET_P_URL_PARAM_MISSING_USER_ID = PIANO_RET_OFFSET+5, - PIANO_RET_P_USERNAME_ALREADY_EXISTS = PIANO_RET_OFFSET+1013, - PIANO_RET_P_USER_ALREADY_USED_TRIAL = PIANO_RET_OFFSET+1037, - PIANO_RET_P_LISTENER_NOT_AUTHORIZED = PIANO_RET_OFFSET+1003, - PIANO_RET_P_USER_NOT_AUTHORIZED = PIANO_RET_OFFSET+1004, - PIANO_RET_P_ZIP_CODE_INVALID = PIANO_RET_OFFSET+1024, - PIANO_RET_P_RATE_LIMIT = PIANO_RET_OFFSET+1039, + PIANO_RET_ERR = 0, + PIANO_RET_OK = 1, + PIANO_RET_INVALID_RESPONSE = 2, + PIANO_RET_CONTINUE_REQUEST = 3, + PIANO_RET_OUT_OF_MEMORY = 4, + PIANO_RET_INVALID_LOGIN = 5, + PIANO_RET_QUALITY_UNAVAILABLE = 6, + PIANO_RET_GCRY_ERR = 7, + + /* pandora error codes */ + PIANO_RET_P_INTERNAL = PIANO_RET_OFFSET+0, + PIANO_RET_P_API_VERSION_NOT_SUPPORTED = PIANO_RET_OFFSET+11, + PIANO_RET_P_BIRTH_YEAR_INVALID = PIANO_RET_OFFSET+1025, + PIANO_RET_P_BIRTH_YEAR_TOO_YOUNG = PIANO_RET_OFFSET+1026, + PIANO_RET_P_CALL_NOT_ALLOWED = PIANO_RET_OFFSET+1008, + PIANO_RET_P_CERTIFICATE_REQUIRED = PIANO_RET_OFFSET+7, + PIANO_RET_P_COMPLIMENTARY_PERIOD_ALREADY_IN_USE = PIANO_RET_OFFSET+1007, + PIANO_RET_P_DAILY_TRIAL_LIMIT_REACHED = PIANO_RET_OFFSET+1035, + PIANO_RET_P_DEVICE_ALREADY_ASSOCIATED_TO_ACCOUNT = PIANO_RET_OFFSET+1014, + PIANO_RET_P_DEVICE_DISABLED = PIANO_RET_OFFSET+1034, + PIANO_RET_P_DEVICE_MODEL_INVALID = PIANO_RET_OFFSET+1023, + PIANO_RET_P_DEVICE_NOT_FOUND = PIANO_RET_OFFSET+1009, + PIANO_RET_P_EXPLICIT_PIN_INCORRECT = PIANO_RET_OFFSET+1018, + PIANO_RET_P_EXPLICIT_PIN_MALFORMED = PIANO_RET_OFFSET+1020, + PIANO_RET_P_INSUFFICIENT_CONNECTIVITY = PIANO_RET_OFFSET+13, + PIANO_RET_P_INVALID_AUTH_TOKEN = PIANO_RET_OFFSET+1001, + PIANO_RET_P_INVALID_COUNTRY_CODE = PIANO_RET_OFFSET+1027, + PIANO_RET_P_INVALID_GENDER = PIANO_RET_OFFSET+1027, + PIANO_RET_P_INVALID_PARTNER_LOGIN = PIANO_RET_OFFSET+1002, + PIANO_RET_P_INVALID_PASSWORD = PIANO_RET_OFFSET+1012, + PIANO_RET_P_INVALID_SPONSOR = PIANO_RET_OFFSET+1036, + PIANO_RET_P_INVALID_USERNAME = PIANO_RET_OFFSET+1011, + PIANO_RET_P_LICENSING_RESTRICTIONS = PIANO_RET_OFFSET+12, + PIANO_RET_P_MAINTENANCE_MODE = PIANO_RET_OFFSET+1, + PIANO_RET_P_MAX_STATIONS_REACHED = PIANO_RET_OFFSET+1005, + PIANO_RET_P_PARAMETER_MISSING = PIANO_RET_OFFSET+9, + PIANO_RET_P_PARAMETER_TYPE_MISMATCH = PIANO_RET_OFFSET+8, + PIANO_RET_P_PARAMETER_VALUE_INVALID = PIANO_RET_OFFSET+10, + PIANO_RET_P_PARTNER_NOT_AUTHORIZED = PIANO_RET_OFFSET+1010, + PIANO_RET_P_READ_ONLY_MODE = PIANO_RET_OFFSET+1000, + PIANO_RET_P_SECURE_PROTOCOL_REQUIRED = PIANO_RET_OFFSET+6, + PIANO_RET_P_STATION_DOES_NOT_EXIST = PIANO_RET_OFFSET+1006, + PIANO_RET_P_UPGRADE_DEVICE_MODEL_INVALID = PIANO_RET_OFFSET+1015, + PIANO_RET_P_URL_PARAM_MISSING_AUTH_TOKEN = PIANO_RET_OFFSET+3, + PIANO_RET_P_URL_PARAM_MISSING_METHOD = PIANO_RET_OFFSET+2, + PIANO_RET_P_URL_PARAM_MISSING_PARTNER_ID = PIANO_RET_OFFSET+4, + PIANO_RET_P_URL_PARAM_MISSING_USER_ID = PIANO_RET_OFFSET+5, + PIANO_RET_P_USERNAME_ALREADY_EXISTS = PIANO_RET_OFFSET+1013, + PIANO_RET_P_USER_ALREADY_USED_TRIAL = PIANO_RET_OFFSET+1037, + PIANO_RET_P_LISTENER_NOT_AUTHORIZED = PIANO_RET_OFFSET+1003, + PIANO_RET_P_USER_NOT_AUTHORIZED = PIANO_RET_OFFSET+1004, + PIANO_RET_P_ZIP_CODE_INVALID = PIANO_RET_OFFSET+1024, + PIANO_RET_P_RATE_LIMIT = PIANO_RET_OFFSET+1039, } PianoReturn_t; /* list stuff */ @@ -391,26 +392,26 @@ typedef enum { size_t PianoListCount (const PianoListHead_t * const l); #define PianoListCountP(l) PianoListCount(&(l)->head) void *PianoListAppend (PianoListHead_t * const l, PianoListHead_t * const e) - __attribute__ ((warn_unused_result)); + __attribute__ ((warn_unused_result)); #define PianoListAppendP(l,e) PianoListAppend(((l) == NULL) ? NULL : &(l)->head, \ - &(e)->head) + &(e)->head) void *PianoListDelete (PianoListHead_t * const l, PianoListHead_t * const e) - __attribute__ ((warn_unused_result)); + __attribute__ ((warn_unused_result)); #define PianoListDeleteP(l,e) PianoListDelete(((l) == NULL) ? NULL : &(l)->head, \ - &(e)->head) + &(e)->head) #define PianoListNextP(e) ((e) == NULL ? NULL : (void *) (e)->head.next) void *PianoListPrepend (PianoListHead_t * const l, PianoListHead_t * const e) - __attribute__ ((warn_unused_result)); + __attribute__ ((warn_unused_result)); #define PianoListPrependP(l,e) PianoListPrepend (((l) == NULL) ? NULL : &(l)->head, \ - &(e)->head) + &(e)->head) void *PianoListGet (PianoListHead_t * const l, const size_t n); #define PianoListGetP(l,n) PianoListGet (&(l)->head, n) #define PianoListForeachP(l) for (; (l) != NULL; (l) = (void *) (l)->head.next) /* memory management */ PianoReturn_t PianoInit (PianoHandle_t *, const char *, - const char *, const char *, const char *, - const char *); + const char *, const char *, const char *, + const char *); void PianoDestroy (PianoHandle_t *); void PianoDestroyPlaylist (PianoSong_t *); void PianoDestroySearchResult (PianoSearchResult_t *); @@ -419,12 +420,12 @@ void PianoDestroyStationMode (PianoStationMode_t * const); /* pandora rpc */ PianoReturn_t PianoRequest (PianoHandle_t *, PianoRequest_t *, - PianoRequestType_t); + PianoRequestType_t); PianoReturn_t PianoResponse (PianoHandle_t *, PianoRequest_t *); void PianoDestroyRequest (PianoRequest_t *); /* misc */ PianoStation_t *PianoFindStationById (PianoStation_t * const, - const char * const); + const char * const); const char *PianoErrorToStr (PianoReturn_t); diff --git a/src/libpiano/response.c b/src/libpiano/response.c index 070bd3bbc..f16559c6d 100644 --- a/src/libpiano/response.c +++ b/src/libpiano/response.c @@ -1286,7 +1286,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { song->trackToken = strdup(trackToken); song->length = getInt(Val, "duration"); // Save release date for sorting - song->fileGain = ((Year - 1900) * 10000) + (Month * 100) + Day; + song->releaseDate = ((Year - 1900) * 10000) + (Month * 100) + Day; // Add to playlist in release data order PianoSong_t *thisSong = playlist; PianoSong_t *lastSong = (PianoSong_t *) &playlist; @@ -1296,8 +1296,7 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { // LOG("Added to end of list\n"); break; } - // LOG("Comparing to %s\n",thisSong->title); - if(thisSong->fileGain > song->fileGain) { + if(thisSong->releaseDate > song->releaseDate) { lastSong->head.next = (struct PianoListHead *) song; song->head.next = (struct PianoListHead *) thisSong; // LOG("Added before\n"); From ba0ab9e950d1479853cff77d8524662c741ab188 Mon Sep 17 00:00:00 2001 From: Skip Hansen Date: Wed, 2 Jul 2025 16:23:37 -0400 Subject: [PATCH 6/7] Revert unintentional formating changes (tabs -> spaces). --- src/libpiano/piano.h | 474 ++++---- src/libpiano/response.c | 2495 ++++++++++++++++++++------------------- 2 files changed, 1485 insertions(+), 1484 deletions(-) diff --git a/src/libpiano/piano.h b/src/libpiano/piano.h index 649cb8509..b57298714 100644 --- a/src/libpiano/piano.h +++ b/src/libpiano/piano.h @@ -1,6 +1,6 @@ /* Copyright (c) 2008-2013 - Lars-Dominik Braun + Lars-Dominik Braun Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -44,345 +44,345 @@ THE SOFTWARE. #define PIANO_RPC_PATH "/services/json/?" typedef struct PianoListHead { - struct PianoListHead *next; + struct PianoListHead *next; } PianoListHead_t; typedef struct PianoUserInfo { - char *listenerId; - char *authToken; - bool IsSubscriber; - bool IsPremiumUser; - int PlayListCount; - int StationCount; - int AlbumCount; - int TrackCount; - int PodcastCount; + char *listenerId; + char *authToken; + bool IsSubscriber; + bool IsPremiumUser; + int PlayListCount; + int StationCount; + int AlbumCount; + int TrackCount; + int PodcastCount; } PianoUserInfo_t; typedef enum { - PIANO_TYPE_NONE = 0, - PIANO_TYPE_STATION = 1, - PIANO_TYPE_PODCAST = 2, - PIANO_TYPE_PLAYLIST = 3, - PIANO_TYPE_ALBUM = 4, - PIANO_TYPE_TRACK = 5, - PIANO_TYPE_LAST + PIANO_TYPE_NONE = 0, + PIANO_TYPE_STATION = 1, + PIANO_TYPE_PODCAST = 2, + PIANO_TYPE_PLAYLIST = 3, + PIANO_TYPE_ALBUM = 4, + PIANO_TYPE_TRACK = 5, + PIANO_TYPE_LAST } PianoStationType_t; typedef enum { - PIANO_MODE_STATION = 0, - PIANO_MODE_PODCAST = 1, - PIANO_MODE_PLAYLIST = 2, - PIANO_MODE_ALBUM = 3, - PIANO_MODE_ALL = 4 + PIANO_MODE_STATION = 0, + PIANO_MODE_PODCAST = 1, + PIANO_MODE_PLAYLIST = 2, + PIANO_MODE_ALBUM = 3, + PIANO_MODE_ALL = 4 } PianoMode_t; typedef enum { - PIANO_RATE_NONE = 0, - PIANO_RATE_LOVE = 1, - PIANO_RATE_BAN = 2, - PIANO_RATE_TIRED = 3, + PIANO_RATE_NONE = 0, + PIANO_RATE_LOVE = 1, + PIANO_RATE_BAN = 2, + PIANO_RATE_TIRED = 3, } PianoSongRating_t; /* UNKNOWN should be 0, because memset sets audio format to 0 */ typedef enum { - PIANO_AF_UNKNOWN = 0, - PIANO_AF_AACPLUS = 1, - PIANO_AF_MP3 = 2, + PIANO_AF_UNKNOWN = 0, + PIANO_AF_AACPLUS = 1, + PIANO_AF_MP3 = 2, } PianoAudioFormat_t; typedef enum { - PIANO_AQ_UNKNOWN = 0, - PIANO_AQ_LOW = 1, - PIANO_AQ_MEDIUM = 2, - PIANO_AQ_HIGH = 3, + PIANO_AQ_UNKNOWN = 0, + PIANO_AQ_LOW = 1, + PIANO_AQ_MEDIUM = 2, + PIANO_AQ_HIGH = 3, } PianoAudioQuality_t; typedef struct PianoSong { - PianoListHead_t head; - char *artist; - char *stationId; - char *album; - char *audioUrl; - char *coverArt; - char *musicId; - char *title; - char *seedId; - char *feedbackId; - char *detailUrl; - char *trackToken; - float fileGain; - unsigned int length; /* song length in seconds */ - PianoSongRating_t rating; - PianoAudioFormat_t audioFormat; - unsigned int releaseDate; + PianoListHead_t head; + char *artist; + char *stationId; + char *album; + char *audioUrl; + char *coverArt; + char *musicId; + char *title; + char *seedId; + char *feedbackId; + char *detailUrl; + char *trackToken; + float fileGain; + unsigned int length; /* song length in seconds */ + PianoSongRating_t rating; + PianoAudioFormat_t audioFormat; + unsigned int releaseDate; } PianoSong_t; typedef struct PianoStation { - PianoListHead_t head; - char isCreator; - char isQuickMix; - char useQuickMix; /* station will be included in quickmix */ - char *name; - char *id; - char *seedId; - PianoStationType_t stationType; - PianoSong_t *theSong; + PianoListHead_t head; + char isCreator; + char isQuickMix; + char useQuickMix; /* station will be included in quickmix */ + char *name; + char *id; + char *seedId; + PianoStationType_t stationType; + PianoSong_t *theSong; } PianoStation_t; typedef struct { - PianoStation_t *station; - PianoSong_t *playList; - bool bGetAll; + PianoStation_t *station; + PianoSong_t *playList; + bool bGetAll; } PianoRequestDataGetEpisodes_t; /* currently only used for search results */ typedef struct PianoArtist { - PianoListHead_t head; - char *name; - char *musicId; - char *seedId; - int score; + PianoListHead_t head; + char *name; + char *musicId; + char *seedId; + int score; } PianoArtist_t; typedef struct PianoGenre { - PianoListHead_t head; - char *name; - char *musicId; + PianoListHead_t head; + char *name; + char *musicId; } PianoGenre_t; typedef struct PianoGenreCategory { - PianoListHead_t head; - char *name; - PianoGenre_t *genres; + PianoListHead_t head; + char *name; + PianoGenre_t *genres; } PianoGenreCategory_t; typedef struct PianoPartner { - gcry_cipher_hd_t in, out; - char *authToken, *device, *user, *password; - unsigned int id; + gcry_cipher_hd_t in, out; + char *authToken, *device, *user, *password; + unsigned int id; } PianoPartner_t; typedef struct PianoHandle { - PianoUserInfo_t user; - /* linked lists */ - PianoStation_t *stations; - PianoGenreCategory_t *genreStations; - PianoPartner_t partner; - int timeOffset; + PianoUserInfo_t user; + /* linked lists */ + PianoStation_t *stations; + PianoGenreCategory_t *genreStations; + PianoPartner_t partner; + int timeOffset; } PianoHandle_t; typedef struct PianoSearchResult { - PianoSong_t *songs; - PianoArtist_t *artists; + PianoSong_t *songs; + PianoArtist_t *artists; } PianoSearchResult_t; typedef struct { - PianoSong_t *songSeeds; - PianoArtist_t *artistSeeds; - PianoStation_t *stationSeeds; - PianoSong_t *feedback; + PianoSong_t *songSeeds; + PianoArtist_t *artistSeeds; + PianoStation_t *stationSeeds; + PianoSong_t *feedback; } PianoStationInfo_t; typedef struct { - char *username; - bool explicitContentFilter; + char *username; + bool explicitContentFilter; } PianoSettings_t; typedef struct { - PianoListHead_t head; - char *name, *description; - bool isAlgorithmic, isTakeover, active; - int id; + PianoListHead_t head; + char *name, *description; + bool isAlgorithmic, isTakeover, active; + int id; } PianoStationMode_t; typedef enum { - /* 0 is reserved: memset (x, 0, sizeof (x)) */ - PIANO_REQUEST_LOGIN = 1, - PIANO_REQUEST_GET_STATIONS = 2, - PIANO_REQUEST_GET_PLAYLIST = 3, - PIANO_REQUEST_RATE_SONG = 4, - PIANO_REQUEST_ADD_FEEDBACK = 5, - PIANO_REQUEST_RENAME_STATION = 7, - PIANO_REQUEST_DELETE_STATION = 8, - PIANO_REQUEST_SEARCH = 9, - PIANO_REQUEST_CREATE_STATION = 10, - PIANO_REQUEST_ADD_SEED = 11, - PIANO_REQUEST_ADD_TIRED_SONG = 12, - PIANO_REQUEST_SET_QUICKMIX = 13, - PIANO_REQUEST_GET_GENRE_STATIONS = 14, - PIANO_REQUEST_TRANSFORM_STATION = 15, - PIANO_REQUEST_EXPLAIN = 16, - PIANO_REQUEST_BOOKMARK_SONG = 18, - PIANO_REQUEST_BOOKMARK_ARTIST = 19, - PIANO_REQUEST_GET_STATION_INFO = 20, - PIANO_REQUEST_DELETE_FEEDBACK = 21, - PIANO_REQUEST_DELETE_SEED = 22, - PIANO_REQUEST_GET_SETTINGS = 23, - PIANO_REQUEST_CHANGE_SETTINGS = 24, - PIANO_REQUEST_GET_STATION_MODES = 25, - PIANO_REQUEST_SET_STATION_MODE = 26, - PIANO_REQUEST_GET_PLAYLISTS = 27, - PIANO_REQUEST_GET_TRACKS = 28, - PIANO_REQUEST_GET_PLAYBACK_INFO = 29, - PIANO_REQUEST_GET_ITEMS = 30, - PIANO_REQUEST_GET_USER_PROFILE = 31, - PIANO_REQUEST_ANNOTATE_OBJECTS = 32, - PIANO_REQUEST_REMOVE_ITEM = 33, - PIANO_REQUEST_GET_EPISODES = 34, + /* 0 is reserved: memset (x, 0, sizeof (x)) */ + PIANO_REQUEST_LOGIN = 1, + PIANO_REQUEST_GET_STATIONS = 2, + PIANO_REQUEST_GET_PLAYLIST = 3, + PIANO_REQUEST_RATE_SONG = 4, + PIANO_REQUEST_ADD_FEEDBACK = 5, + PIANO_REQUEST_RENAME_STATION = 7, + PIANO_REQUEST_DELETE_STATION = 8, + PIANO_REQUEST_SEARCH = 9, + PIANO_REQUEST_CREATE_STATION = 10, + PIANO_REQUEST_ADD_SEED = 11, + PIANO_REQUEST_ADD_TIRED_SONG = 12, + PIANO_REQUEST_SET_QUICKMIX = 13, + PIANO_REQUEST_GET_GENRE_STATIONS = 14, + PIANO_REQUEST_TRANSFORM_STATION = 15, + PIANO_REQUEST_EXPLAIN = 16, + PIANO_REQUEST_BOOKMARK_SONG = 18, + PIANO_REQUEST_BOOKMARK_ARTIST = 19, + PIANO_REQUEST_GET_STATION_INFO = 20, + PIANO_REQUEST_DELETE_FEEDBACK = 21, + PIANO_REQUEST_DELETE_SEED = 22, + PIANO_REQUEST_GET_SETTINGS = 23, + PIANO_REQUEST_CHANGE_SETTINGS = 24, + PIANO_REQUEST_GET_STATION_MODES = 25, + PIANO_REQUEST_SET_STATION_MODE = 26, + PIANO_REQUEST_GET_PLAYLISTS = 27, + PIANO_REQUEST_GET_TRACKS = 28, + PIANO_REQUEST_GET_PLAYBACK_INFO = 29, + PIANO_REQUEST_GET_ITEMS = 30, + PIANO_REQUEST_GET_USER_PROFILE = 31, + PIANO_REQUEST_ANNOTATE_OBJECTS = 32, + PIANO_REQUEST_REMOVE_ITEM = 33, + PIANO_REQUEST_GET_EPISODES = 34, } PianoRequestType_t; typedef struct PianoRequest { - PianoRequestType_t type; - bool secure; - void *data; - char urlPath[1024]; - char *postData; - char *responseData; + PianoRequestType_t type; + bool secure; + void *data; + char urlPath[1024]; + char *postData; + char *responseData; } PianoRequest_t; /* request data structures */ typedef struct { - char *user; - char *password; - unsigned char step; + char *user; + char *password; + unsigned char step; } PianoRequestDataLogin_t; typedef struct { - PianoStation_t *station; - PianoAudioQuality_t quality; - PianoSong_t *retPlaylist; + PianoStation_t *station; + PianoAudioQuality_t quality; + PianoSong_t *retPlaylist; } PianoRequestDataGetPlaylist_t; typedef struct { - PianoSong_t *song; - PianoSongRating_t rating; + PianoSong_t *song; + PianoSongRating_t rating; } PianoRequestDataRateSong_t; typedef struct { - char *stationId; - char *trackToken; - PianoSongRating_t rating; + char *stationId; + char *trackToken; + PianoSongRating_t rating; } PianoRequestDataAddFeedback_t; typedef struct { - PianoStation_t *station; - char *newName; + PianoStation_t *station; + char *newName; } PianoRequestDataRenameStation_t; typedef struct { - char *searchStr; - PianoSearchResult_t searchResult; + char *searchStr; + PianoSearchResult_t searchResult; } PianoRequestDataSearch_t; typedef struct { - char *token; - enum { - PIANO_MUSICTYPE_INVALID = 0, - PIANO_MUSICTYPE_SONG, - PIANO_MUSICTYPE_ARTIST, - } type; + char *token; + enum { + PIANO_MUSICTYPE_INVALID = 0, + PIANO_MUSICTYPE_SONG, + PIANO_MUSICTYPE_ARTIST, + } type; } PianoRequestDataCreateStation_t; typedef struct { - PianoStation_t *station; - char *musicId; + PianoStation_t *station; + char *musicId; } PianoRequestDataAddSeed_t; typedef struct { - PianoSong_t *song; - char *retExplain; + PianoSong_t *song; + char *retExplain; } PianoRequestDataExplain_t; typedef struct { - PianoStation_t *station; - PianoStationInfo_t info; + PianoStation_t *station; + PianoStationInfo_t info; } PianoRequestDataGetStationInfo_t; typedef struct { - PianoSong_t *song; - PianoArtist_t *artist; - PianoStation_t *station; + PianoSong_t *song; + PianoArtist_t *artist; + PianoStation_t *station; } PianoRequestDataDeleteSeed_t; typedef enum { - PIANO_UNDEFINED = 0, - PIANO_FALSE = 1, - PIANO_TRUE = 2, + PIANO_UNDEFINED = 0, + PIANO_FALSE = 1, + PIANO_TRUE = 2, } PianoTristate_t; typedef struct { - char *currentUsername, *newUsername; - char *currentPassword, *newPassword; - PianoTristate_t explicitContentFilter; + char *currentUsername, *newUsername; + char *currentPassword, *newPassword; + PianoTristate_t explicitContentFilter; } PianoRequestDataChangeSettings_t; typedef struct { - PianoStation_t *station; - PianoStationMode_t *retModes; + PianoStation_t *station; + PianoStationMode_t *retModes; } PianoRequestDataGetStationModes_t; typedef struct { - PianoStation_t *station; - unsigned int id; + PianoStation_t *station; + unsigned int id; } PianoRequestDataSetStationMode_t; /* pandora error code offset */ #define PIANO_RET_OFFSET 1024 typedef enum { - PIANO_RET_ERR = 0, - PIANO_RET_OK = 1, - PIANO_RET_INVALID_RESPONSE = 2, - PIANO_RET_CONTINUE_REQUEST = 3, - PIANO_RET_OUT_OF_MEMORY = 4, - PIANO_RET_INVALID_LOGIN = 5, - PIANO_RET_QUALITY_UNAVAILABLE = 6, - PIANO_RET_GCRY_ERR = 7, - - /* pandora error codes */ - PIANO_RET_P_INTERNAL = PIANO_RET_OFFSET+0, - PIANO_RET_P_API_VERSION_NOT_SUPPORTED = PIANO_RET_OFFSET+11, - PIANO_RET_P_BIRTH_YEAR_INVALID = PIANO_RET_OFFSET+1025, - PIANO_RET_P_BIRTH_YEAR_TOO_YOUNG = PIANO_RET_OFFSET+1026, - PIANO_RET_P_CALL_NOT_ALLOWED = PIANO_RET_OFFSET+1008, - PIANO_RET_P_CERTIFICATE_REQUIRED = PIANO_RET_OFFSET+7, - PIANO_RET_P_COMPLIMENTARY_PERIOD_ALREADY_IN_USE = PIANO_RET_OFFSET+1007, - PIANO_RET_P_DAILY_TRIAL_LIMIT_REACHED = PIANO_RET_OFFSET+1035, - PIANO_RET_P_DEVICE_ALREADY_ASSOCIATED_TO_ACCOUNT = PIANO_RET_OFFSET+1014, - PIANO_RET_P_DEVICE_DISABLED = PIANO_RET_OFFSET+1034, - PIANO_RET_P_DEVICE_MODEL_INVALID = PIANO_RET_OFFSET+1023, - PIANO_RET_P_DEVICE_NOT_FOUND = PIANO_RET_OFFSET+1009, - PIANO_RET_P_EXPLICIT_PIN_INCORRECT = PIANO_RET_OFFSET+1018, - PIANO_RET_P_EXPLICIT_PIN_MALFORMED = PIANO_RET_OFFSET+1020, - PIANO_RET_P_INSUFFICIENT_CONNECTIVITY = PIANO_RET_OFFSET+13, - PIANO_RET_P_INVALID_AUTH_TOKEN = PIANO_RET_OFFSET+1001, - PIANO_RET_P_INVALID_COUNTRY_CODE = PIANO_RET_OFFSET+1027, - PIANO_RET_P_INVALID_GENDER = PIANO_RET_OFFSET+1027, - PIANO_RET_P_INVALID_PARTNER_LOGIN = PIANO_RET_OFFSET+1002, - PIANO_RET_P_INVALID_PASSWORD = PIANO_RET_OFFSET+1012, - PIANO_RET_P_INVALID_SPONSOR = PIANO_RET_OFFSET+1036, - PIANO_RET_P_INVALID_USERNAME = PIANO_RET_OFFSET+1011, - PIANO_RET_P_LICENSING_RESTRICTIONS = PIANO_RET_OFFSET+12, - PIANO_RET_P_MAINTENANCE_MODE = PIANO_RET_OFFSET+1, - PIANO_RET_P_MAX_STATIONS_REACHED = PIANO_RET_OFFSET+1005, - PIANO_RET_P_PARAMETER_MISSING = PIANO_RET_OFFSET+9, - PIANO_RET_P_PARAMETER_TYPE_MISMATCH = PIANO_RET_OFFSET+8, - PIANO_RET_P_PARAMETER_VALUE_INVALID = PIANO_RET_OFFSET+10, - PIANO_RET_P_PARTNER_NOT_AUTHORIZED = PIANO_RET_OFFSET+1010, - PIANO_RET_P_READ_ONLY_MODE = PIANO_RET_OFFSET+1000, - PIANO_RET_P_SECURE_PROTOCOL_REQUIRED = PIANO_RET_OFFSET+6, - PIANO_RET_P_STATION_DOES_NOT_EXIST = PIANO_RET_OFFSET+1006, - PIANO_RET_P_UPGRADE_DEVICE_MODEL_INVALID = PIANO_RET_OFFSET+1015, - PIANO_RET_P_URL_PARAM_MISSING_AUTH_TOKEN = PIANO_RET_OFFSET+3, - PIANO_RET_P_URL_PARAM_MISSING_METHOD = PIANO_RET_OFFSET+2, - PIANO_RET_P_URL_PARAM_MISSING_PARTNER_ID = PIANO_RET_OFFSET+4, - PIANO_RET_P_URL_PARAM_MISSING_USER_ID = PIANO_RET_OFFSET+5, - PIANO_RET_P_USERNAME_ALREADY_EXISTS = PIANO_RET_OFFSET+1013, - PIANO_RET_P_USER_ALREADY_USED_TRIAL = PIANO_RET_OFFSET+1037, - PIANO_RET_P_LISTENER_NOT_AUTHORIZED = PIANO_RET_OFFSET+1003, - PIANO_RET_P_USER_NOT_AUTHORIZED = PIANO_RET_OFFSET+1004, - PIANO_RET_P_ZIP_CODE_INVALID = PIANO_RET_OFFSET+1024, - PIANO_RET_P_RATE_LIMIT = PIANO_RET_OFFSET+1039, + PIANO_RET_ERR = 0, + PIANO_RET_OK = 1, + PIANO_RET_INVALID_RESPONSE = 2, + PIANO_RET_CONTINUE_REQUEST = 3, + PIANO_RET_OUT_OF_MEMORY = 4, + PIANO_RET_INVALID_LOGIN = 5, + PIANO_RET_QUALITY_UNAVAILABLE = 6, + PIANO_RET_GCRY_ERR = 7, + + /* pandora error codes */ + PIANO_RET_P_INTERNAL = PIANO_RET_OFFSET+0, + PIANO_RET_P_API_VERSION_NOT_SUPPORTED = PIANO_RET_OFFSET+11, + PIANO_RET_P_BIRTH_YEAR_INVALID = PIANO_RET_OFFSET+1025, + PIANO_RET_P_BIRTH_YEAR_TOO_YOUNG = PIANO_RET_OFFSET+1026, + PIANO_RET_P_CALL_NOT_ALLOWED = PIANO_RET_OFFSET+1008, + PIANO_RET_P_CERTIFICATE_REQUIRED = PIANO_RET_OFFSET+7, + PIANO_RET_P_COMPLIMENTARY_PERIOD_ALREADY_IN_USE = PIANO_RET_OFFSET+1007, + PIANO_RET_P_DAILY_TRIAL_LIMIT_REACHED = PIANO_RET_OFFSET+1035, + PIANO_RET_P_DEVICE_ALREADY_ASSOCIATED_TO_ACCOUNT = PIANO_RET_OFFSET+1014, + PIANO_RET_P_DEVICE_DISABLED = PIANO_RET_OFFSET+1034, + PIANO_RET_P_DEVICE_MODEL_INVALID = PIANO_RET_OFFSET+1023, + PIANO_RET_P_DEVICE_NOT_FOUND = PIANO_RET_OFFSET+1009, + PIANO_RET_P_EXPLICIT_PIN_INCORRECT = PIANO_RET_OFFSET+1018, + PIANO_RET_P_EXPLICIT_PIN_MALFORMED = PIANO_RET_OFFSET+1020, + PIANO_RET_P_INSUFFICIENT_CONNECTIVITY = PIANO_RET_OFFSET+13, + PIANO_RET_P_INVALID_AUTH_TOKEN = PIANO_RET_OFFSET+1001, + PIANO_RET_P_INVALID_COUNTRY_CODE = PIANO_RET_OFFSET+1027, + PIANO_RET_P_INVALID_GENDER = PIANO_RET_OFFSET+1027, + PIANO_RET_P_INVALID_PARTNER_LOGIN = PIANO_RET_OFFSET+1002, + PIANO_RET_P_INVALID_PASSWORD = PIANO_RET_OFFSET+1012, + PIANO_RET_P_INVALID_SPONSOR = PIANO_RET_OFFSET+1036, + PIANO_RET_P_INVALID_USERNAME = PIANO_RET_OFFSET+1011, + PIANO_RET_P_LICENSING_RESTRICTIONS = PIANO_RET_OFFSET+12, + PIANO_RET_P_MAINTENANCE_MODE = PIANO_RET_OFFSET+1, + PIANO_RET_P_MAX_STATIONS_REACHED = PIANO_RET_OFFSET+1005, + PIANO_RET_P_PARAMETER_MISSING = PIANO_RET_OFFSET+9, + PIANO_RET_P_PARAMETER_TYPE_MISMATCH = PIANO_RET_OFFSET+8, + PIANO_RET_P_PARAMETER_VALUE_INVALID = PIANO_RET_OFFSET+10, + PIANO_RET_P_PARTNER_NOT_AUTHORIZED = PIANO_RET_OFFSET+1010, + PIANO_RET_P_READ_ONLY_MODE = PIANO_RET_OFFSET+1000, + PIANO_RET_P_SECURE_PROTOCOL_REQUIRED = PIANO_RET_OFFSET+6, + PIANO_RET_P_STATION_DOES_NOT_EXIST = PIANO_RET_OFFSET+1006, + PIANO_RET_P_UPGRADE_DEVICE_MODEL_INVALID = PIANO_RET_OFFSET+1015, + PIANO_RET_P_URL_PARAM_MISSING_AUTH_TOKEN = PIANO_RET_OFFSET+3, + PIANO_RET_P_URL_PARAM_MISSING_METHOD = PIANO_RET_OFFSET+2, + PIANO_RET_P_URL_PARAM_MISSING_PARTNER_ID = PIANO_RET_OFFSET+4, + PIANO_RET_P_URL_PARAM_MISSING_USER_ID = PIANO_RET_OFFSET+5, + PIANO_RET_P_USERNAME_ALREADY_EXISTS = PIANO_RET_OFFSET+1013, + PIANO_RET_P_USER_ALREADY_USED_TRIAL = PIANO_RET_OFFSET+1037, + PIANO_RET_P_LISTENER_NOT_AUTHORIZED = PIANO_RET_OFFSET+1003, + PIANO_RET_P_USER_NOT_AUTHORIZED = PIANO_RET_OFFSET+1004, + PIANO_RET_P_ZIP_CODE_INVALID = PIANO_RET_OFFSET+1024, + PIANO_RET_P_RATE_LIMIT = PIANO_RET_OFFSET+1039, } PianoReturn_t; /* list stuff */ @@ -392,26 +392,26 @@ typedef enum { size_t PianoListCount (const PianoListHead_t * const l); #define PianoListCountP(l) PianoListCount(&(l)->head) void *PianoListAppend (PianoListHead_t * const l, PianoListHead_t * const e) - __attribute__ ((warn_unused_result)); + __attribute__ ((warn_unused_result)); #define PianoListAppendP(l,e) PianoListAppend(((l) == NULL) ? NULL : &(l)->head, \ - &(e)->head) + &(e)->head) void *PianoListDelete (PianoListHead_t * const l, PianoListHead_t * const e) - __attribute__ ((warn_unused_result)); + __attribute__ ((warn_unused_result)); #define PianoListDeleteP(l,e) PianoListDelete(((l) == NULL) ? NULL : &(l)->head, \ - &(e)->head) + &(e)->head) #define PianoListNextP(e) ((e) == NULL ? NULL : (void *) (e)->head.next) void *PianoListPrepend (PianoListHead_t * const l, PianoListHead_t * const e) - __attribute__ ((warn_unused_result)); + __attribute__ ((warn_unused_result)); #define PianoListPrependP(l,e) PianoListPrepend (((l) == NULL) ? NULL : &(l)->head, \ - &(e)->head) + &(e)->head) void *PianoListGet (PianoListHead_t * const l, const size_t n); #define PianoListGetP(l,n) PianoListGet (&(l)->head, n) #define PianoListForeachP(l) for (; (l) != NULL; (l) = (void *) (l)->head.next) /* memory management */ PianoReturn_t PianoInit (PianoHandle_t *, const char *, - const char *, const char *, const char *, - const char *); + const char *, const char *, const char *, + const char *); void PianoDestroy (PianoHandle_t *); void PianoDestroyPlaylist (PianoSong_t *); void PianoDestroySearchResult (PianoSearchResult_t *); @@ -420,12 +420,12 @@ void PianoDestroyStationMode (PianoStationMode_t * const); /* pandora rpc */ PianoReturn_t PianoRequest (PianoHandle_t *, PianoRequest_t *, - PianoRequestType_t); + PianoRequestType_t); PianoReturn_t PianoResponse (PianoHandle_t *, PianoRequest_t *); void PianoDestroyRequest (PianoRequest_t *); /* misc */ PianoStation_t *PianoFindStationById (PianoStation_t * const, - const char * const); + const char * const); const char *PianoErrorToStr (PianoReturn_t); diff --git a/src/libpiano/response.c b/src/libpiano/response.c index f16559c6d..bc1921449 100644 --- a/src/libpiano/response.c +++ b/src/libpiano/response.c @@ -1,6 +1,6 @@ /* Copyright (c) 2008-2017 - Lars-Dominik Braun + Lars-Dominik Braun Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -34,1294 +34,1295 @@ THE SOFTWARE. #include "crypt.h" static const char *qualityMap[] = { - "", "lowQuality", "mediumQuality","highQuality" + "", "lowQuality", "mediumQuality","highQuality" }; static const char *formatMap[] = { - "", "aacplus", "mp3" + "", "aacplus", "mp3" }; static const char *imageHost = "https://content-images.p-cdn.com/"; static char *PianoJsonStrdup (json_object *j, const char *key) { - assert (j != NULL); - assert (key != NULL); - - json_object *v; - if (json_object_object_get_ex (j, key, &v)) { - return strdup (json_object_get_string (v)); - } else { - return NULL; - } + assert (j != NULL); + assert (key != NULL); + + json_object *v; + if (json_object_object_get_ex (j, key, &v)) { + return strdup (json_object_get_string (v)); + } else { + return NULL; + } } static bool getBoolDefault (json_object * const j, const char * const key, const bool def) { - assert (j != NULL); - assert (key != NULL); - - json_object *v; - if (json_object_object_get_ex (j, key, &v)) { - return json_object_get_boolean (v); - } else { - return def; - } + assert (j != NULL); + assert (key != NULL); + + json_object *v; + if (json_object_object_get_ex (j, key, &v)) { + return json_object_get_boolean (v); + } else { + return def; + } } static const char *PianoJsonGetStr(json_object *j, const char *key) { - assert (j != NULL); - assert (key != NULL); - - json_object *v; - if (json_object_object_get_ex (j, key, &v)) { - return json_object_get_string (v); - } else { - return NULL; - } + assert (j != NULL); + assert (key != NULL); + + json_object *v; + if (json_object_object_get_ex (j, key, &v)) { + return json_object_get_string (v); + } else { + return NULL; + } } static int getInt(json_object * const j, const char * const key) { - assert (j != NULL); - assert (key != NULL); - - json_object *v; - if (json_object_object_get_ex (j, key, &v)) { - return json_object_get_int(v); - } else { - return 0; - } + assert (j != NULL); + assert (key != NULL); + + json_object *v; + if (json_object_object_get_ex (j, key, &v)) { + return json_object_get_int(v); + } else { + return 0; + } } static char *getCoverArt(struct json_object *Val) { - char artUrl[120]; - json_object *v = NULL; - char *Ret = NULL; - - if (json_pointer_get(Val, "/icon/artUrl", &v)) { - LOG("Couldn't get artUrl\n"); - } - else { - assert (v != NULL); - snprintf(artUrl,sizeof(artUrl),"%s%s", - imageHost,json_object_get_string(v)); - Ret = strdup(artUrl); - } - - return Ret; + char artUrl[120]; + json_object *v = NULL; + char *Ret = NULL; + + if (json_pointer_get(Val, "/icon/artUrl", &v)) { + LOG("Couldn't get artUrl\n"); + } + else { + assert (v != NULL); + snprintf(artUrl,sizeof(artUrl),"%s%s", + imageHost,json_object_get_string(v)); + Ret = strdup(artUrl); + } + + return Ret; } static int getBool(json_object * const j, const char * const key) { - assert (j != NULL); - assert (key != NULL); - - json_object *v; - if (json_pointer_get(j, key, &v)) { - return -1; - } else { - return json_object_get_boolean (v) ? 1 : 0; - } + assert (j != NULL); + assert (key != NULL); + + json_object *v; + if (json_pointer_get(j, key, &v)) { + return -1; + } else { + return json_object_get_boolean (v) ? 1 : 0; + } } static void PianoJsonParsePlaylist(json_object *j, PianoStation_t *s) { - s->name = PianoJsonStrdup (j, "name"); - s->id = PianoJsonStrdup (j, "pandoraId"); - s->isCreator = false; - s->isQuickMix = false; + s->name = PianoJsonStrdup (j, "name"); + s->id = PianoJsonStrdup (j, "pandoraId"); + s->isCreator = false; + s->isQuickMix = false; } static void PianoJsonParseStation (json_object *j, PianoStation_t *s) { - s->name = PianoJsonStrdup (j, "stationName"); - s->id = PianoJsonStrdup (j, "stationToken"); - s->isCreator = !getBoolDefault (j, "isShared", !false); - s->isQuickMix = getBoolDefault (j, "isQuickMix", false); + s->name = PianoJsonStrdup (j, "stationName"); + s->id = PianoJsonStrdup (j, "stationToken"); + s->isCreator = !getBoolDefault (j, "isShared", !false); + s->isQuickMix = getBoolDefault (j, "isQuickMix", false); } -/* concat strings - * @param destination - * @param source string - * @param destination size +/* concat strings + * @param destination + * @param source string + * @param destination size */ static void PianoStrpcat (char * restrict dest, const char * restrict src, - size_t len) { - /* skip to end of string */ - while (*dest != '\0' && len > 1) { - ++dest; - --len; - } - - /* append until source exhausted or destination full */ - while (*src != '\0' && len > 1) { - *dest = *src; - ++dest; - ++src; - --len; - } - - *dest = '\0'; + size_t len) { + /* skip to end of string */ + while (*dest != '\0' && len > 1) { + ++dest; + --len; + } + + /* append until source exhausted or destination full */ + while (*src != '\0' && len > 1) { + *dest = *src; + ++dest; + ++src; + --len; + } + + *dest = '\0'; } -/* parse xml response and update data structures/return new data structure - * @param piano handle - * @param initialized request (expects responseData to be a NUL-terminated - * string) +/* parse xml response and update data structures/return new data structure + * @param piano handle + * @param initialized request (expects responseData to be a NUL-terminated + * string) */ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { - PianoReturn_t ret = PIANO_RET_OK; - - assert (ph != NULL); - assert (req != NULL); - - json_object * const j = json_tokener_parse (req->responseData); - - json_object *status; - if (!json_object_object_get_ex (j, "stat", &status)) { - ret = PIANO_RET_INVALID_RESPONSE; - goto cleanup; - } - - /* error handling */ - if (strcmp (json_object_get_string (status), "ok") != 0) { - json_object *code; - if (!json_object_object_get_ex (j, "code", &code)) { - ret = PIANO_RET_INVALID_RESPONSE; - } else { - ret = json_object_get_int (code)+PIANO_RET_OFFSET; - - if (ret == PIANO_RET_P_INVALID_PARTNER_LOGIN && - req->type == PIANO_REQUEST_LOGIN) { - PianoRequestDataLogin_t *reqData = req->data; - if (reqData->step == 1) { - /* return value is ambiguous, as both, partnerLogin and - * userLogin return INVALID_PARTNER_LOGIN. Fix that to provide - * better error messages. */ - ret = PIANO_RET_INVALID_LOGIN; - } - } - } - - goto cleanup; - } - - json_object *result = NULL; - /* missing for some request types */ - json_object_object_get_ex (j, "result", &result); - - switch (req->type) { - case PIANO_REQUEST_LOGIN: { - /* authenticate user */ - PianoRequestDataLogin_t *reqData = req->data; - - assert (req->responseData != NULL); - assert (reqData != NULL); - - switch (reqData->step) { - case 0: { - /* decrypt timestamp */ - json_object *jsonTimestamp; - if (!json_object_object_get_ex (result, "syncTime", &jsonTimestamp)) { - ret = PIANO_RET_INVALID_RESPONSE; - break; - } - assert (jsonTimestamp != NULL); - const char * const cryptedTimestamp = json_object_get_string (jsonTimestamp); - assert (cryptedTimestamp != NULL); - const time_t realTimestamp = time (NULL); - char *decryptedTimestamp = NULL; - size_t decryptedSize; - - ret = PIANO_RET_ERR; - if ((decryptedTimestamp = PianoDecryptString (ph->partner.in, - cryptedTimestamp, &decryptedSize)) != NULL && - decryptedSize > 4) { - /* skip four bytes garbage(?) at beginning */ - const unsigned long timestamp = strtoul ( - decryptedTimestamp+4, NULL, 0); - ph->timeOffset = (long int) realTimestamp - - (long int) timestamp; - ret = PIANO_RET_CONTINUE_REQUEST; - } - free (decryptedTimestamp); - /* get auth token */ - ph->partner.authToken = PianoJsonStrdup (result, - "partnerAuthToken"); - json_object *partnerId; - if (!json_object_object_get_ex (result, "partnerId", &partnerId)) { - ret = PIANO_RET_INVALID_RESPONSE; - break; - } - ph->partner.id = json_object_get_int (partnerId); - ++reqData->step; - break; - } - - case 1: - /* information exists when reauthenticating, destroy to - * avoid memleak */ - if (ph->user.listenerId != NULL) { - PianoDestroyUserInfo (&ph->user); - } - ph->user.listenerId = PianoJsonStrdup (result, "userId"); - ph->user.authToken = PianoJsonStrdup (result, - "userAuthToken"); - ph->user.IsSubscriber = getBoolDefault (result, - "isSubscriber", false); - break; - } - break; - } - - case PIANO_REQUEST_GET_STATIONS: { - /* get stations */ - assert (req->responseData != NULL); - - json_object *stations, *mix = NULL; - - if (!json_object_object_get_ex (result, "stations", &stations)) { - break; - } - - for (unsigned int i = 0; i < json_object_array_length (stations); i++) { - PianoStation_t *tmpStation; - json_object *s = json_object_array_get_idx (stations, i); - - if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - tmpStation->stationType = PIANO_TYPE_STATION; - - PianoJsonParseStation (s, tmpStation); - - if (tmpStation->isQuickMix) { - /* fix flags on other stations later */ - json_object_object_get_ex (s, "quickMixStationIds", &mix); - } - - /* start new linked list or append */ - ph->stations = PianoListAppendP (ph->stations, tmpStation); - } - - /* fix quickmix flags */ - if (mix != NULL) { - PianoStation_t *curStation = ph->stations; - PianoListForeachP (curStation) { - for (unsigned int i = 0; i < json_object_array_length (mix); i++) { - json_object *id = json_object_array_get_idx (mix, i); - if (strcmp (json_object_get_string (id), - curStation->id) == 0) { - curStation->useQuickMix = true; - } - } - } - } - break; - } - - case PIANO_REQUEST_GET_PLAYLISTS: { - /* get playlists */ - assert (req->responseData != NULL); - - json_object *playlists; - - if (!json_object_object_get_ex (result, "items", &playlists)) { - break; - } - - for (int i = 0; i < json_object_array_length (playlists); i++) { - PianoStation_t *tmpStation; - json_object *s = json_object_array_get_idx (playlists, i); - - if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - tmpStation->stationType = PIANO_TYPE_PLAYLIST; - - PianoJsonParsePlaylist(s, tmpStation); - - /* start new linked list or append */ - ph->stations = PianoListAppendP (ph->stations, tmpStation); - } - break; - } - - // Get album or playlist tracks - case PIANO_REQUEST_GET_TRACKS: { - assert (req->responseData != NULL); - - PianoRequestDataGetPlaylist_t *reqData = req->data; - PianoSong_t *playlist = NULL; - PianoSong_t *FullPlaylist = NULL; - - assert (result != NULL); - assert (req->responseData != NULL); - assert (reqData != NULL); - - switch(reqData->station->stationType) { - case PIANO_TYPE_PLAYLIST: { - json_object *tracks = NULL; - json_object *annotations = NULL; - if (!json_object_object_get_ex (result, "tracks", &tracks)) { - break; - } - assert (tracks!= NULL); - LOG("got tracks\n"); - if (!json_object_object_get_ex (result, "annotations", &annotations)) { - break; - } - assert (annotations != NULL); - LOG("got annotations\n"); - - for (int i = 0; i < json_object_array_length (tracks); i++) { - json_object *s = json_object_array_get_idx (tracks, i); - json_object *trackInfo = NULL; - PianoSong_t *song; - - if ((song = calloc (1, sizeof (*song))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - song->seedId = PianoJsonStrdup(result, "pandoraId"); - song->trackToken = PianoJsonStrdup (s, "trackPandoraId"); - assert (song->trackToken != NULL); - - LOG("track %d: %s\n",i + 1,song->trackToken); - - if (!json_object_object_get_ex (annotations, song->trackToken, &trackInfo)) { - break; - } - assert (trackInfo!= NULL); - song->artist = PianoJsonStrdup(trackInfo, "artistName"); - song->album = PianoJsonStrdup(trackInfo, "albumName"); - song->title = PianoJsonStrdup(trackInfo, "name"); - song->fileGain = 0.0; - song->length = getInt(trackInfo, "duration"); - song->coverArt = getCoverArt(trackInfo); - playlist = PianoListAppendP (playlist, song); - } - break; - } - - case PIANO_TYPE_ALBUM: { - char trackTitle[120]; - int totalTracks = 0; - int trackNumber; - - // Count tracks - json_object_object_foreach(result,Key1,Val1) { - if(Key1[0] != 'T' || Key1[1] != 'R') { - continue; - } - totalTracks++; - } - - json_object_object_foreach(result,Key,Val) { - if(Key[0] != 'T' || Key[1] != 'R') { - continue; - } - LOG("got track %s: ",Key); - trackNumber = getInt(Val,"trackNumber"); - snprintf(trackTitle,sizeof(trackTitle), - totalTracks > 9 ? "%02d %s" : "%d %s", - trackNumber,PianoJsonGetStr(Val,"name")); - LOG_RAW("%s\n",trackTitle); - if(getBool(Val,"/rightsInfo/hasInteractive") != 1) { - LOG(" ignored\n"); - LOG(" hasInteractive: %d\n",getBool(Val,"/rightsInfo/hasInteractive")); - LOG(" hasOffline: %d\n",getBool(Val,"/rightsInfo/hasOffline")); - LOG(" hasNonInteractive: %d\n",getBool(Val,"/rightsInfo/hasNonInteractive")); - LOG(" hasStatutory: %d\n",getBool(Val,"/rightsInfo/hasStatutory")); - LOG(" hasRadioRights: %d\n",getBool(Val,"/rightsInfo/hasRadioRights")); - continue; - } - PianoSong_t *song; - - if ((song = calloc (1, sizeof (*song))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - song->stationId = strdup(reqData->station->id); - song->title = strdup(trackTitle); - song->trackToken = strdup(Key); - song->seedId = PianoJsonStrdup(Val,"albumId"); - song->artist = PianoJsonStrdup(Val,"artistName"); - song->album = PianoJsonStrdup(Val,"albumName"); - song->fileGain = 0.0; - song->length = getInt(Val, "duration"); - song->coverArt = getCoverArt(Val); - // Add to playlist in track order - - if(playlist == NULL) { - playlist = song; - } - else { - PianoSong_t *lastSong = (PianoSong_t *) &playlist; - PianoSong_t *nextSong = playlist; - do { - int nextSongTrackNum; - if(nextSong == NULL) { - lastSong->head.next = (struct PianoListHead *) song; - break; - } - sscanf(nextSong->title,"%d",&nextSongTrackNum); - if(nextSongTrackNum > trackNumber) { - lastSong->head.next = (struct PianoListHead *) song; - song->head.next = (struct PianoListHead *) nextSong; - break; - } - lastSong = nextSong; - nextSong = (PianoSong_t *) nextSong->head.next; - } while(true); - } - } - break; - } - - default: - LOG("Invalid stationType 0x%x\n",ph->stations->stationType); - break; - } - reqData->retPlaylist = playlist; - break; - } - - case PIANO_REQUEST_GET_PLAYLIST: { - /* get playlist, usually four songs */ - PianoRequestDataGetPlaylist_t *reqData = req->data; - PianoSong_t *playlist = NULL; - - assert (req->responseData != NULL); - assert (reqData != NULL); - assert (reqData->quality != PIANO_AQ_UNKNOWN); - - json_object *items = NULL; - if (!json_object_object_get_ex (result, "items", &items)) { - break; - } - assert (items != NULL); - - for (unsigned int i = 0; i < json_object_array_length (items); i++) { - json_object *s = json_object_array_get_idx (items, i); - PianoSong_t *song; - - if ((song = calloc (1, sizeof (*song))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - if (!json_object_object_get_ex (s, "artistName", NULL)) { - free (song); - continue; - } - - /* get audio url based on selected quality */ - static const char *qualityMap[] = {"", "lowQuality", "mediumQuality", - "highQuality"}; - assert (reqData->quality < sizeof (qualityMap)/sizeof (*qualityMap)); - static const char *formatMap[] = {"", "aacplus", "mp3"}; - - json_object *umap; - if (json_object_object_get_ex (s, "audioUrlMap", &umap)) { - assert (umap != NULL); - json_object *jsonEncoding, *qmap; - if (json_object_object_get_ex (umap, qualityMap[reqData->quality], &qmap) && - json_object_object_get_ex (qmap, "encoding", &jsonEncoding)) { - assert (qmap != NULL); - const char *encoding = json_object_get_string (jsonEncoding); - assert (encoding != NULL); - for (size_t k = 0; k < sizeof (formatMap)/sizeof (*formatMap); k++) { - if (strcmp (formatMap[k], encoding) == 0) { - song->audioFormat = k; - break; - } - } - song->audioUrl = PianoJsonStrdup (qmap, "audioUrl"); - } else { - /* requested quality is not available */ - ret = PIANO_RET_QUALITY_UNAVAILABLE; - free (song); - PianoDestroyPlaylist (playlist); - goto cleanup; - } - } - - json_object *v; - song->artist = PianoJsonStrdup (s, "artistName"); - song->album = PianoJsonStrdup (s, "albumName"); - song->title = PianoJsonStrdup (s, "songName"); - song->trackToken = PianoJsonStrdup (s, "trackToken"); - song->stationId = PianoJsonStrdup (s, "stationId"); - song->coverArt = PianoJsonStrdup (s, "albumArtUrl"); - song->detailUrl = PianoJsonStrdup (s, "songDetailUrl"); - song->fileGain = json_object_object_get_ex (s, "trackGain", &v) ? - json_object_get_double (v) : 0.0; - song->length = json_object_object_get_ex (s, "trackLength", &v) ? - json_object_get_int (v) : 0; - switch (json_object_object_get_ex (s, "songRating", &v) ? - json_object_get_int (v) : 0) { - case 1: - song->rating = PIANO_RATE_LOVE; - break; - } - - playlist = PianoListAppendP (playlist, song); - } - - reqData->retPlaylist = playlist; - break; - } - - case PIANO_REQUEST_RATE_SONG: { - /* love/ban song */ - PianoRequestDataRateSong_t *reqData = req->data; - reqData->song->rating = reqData->rating; - break; - } - - case PIANO_REQUEST_ADD_FEEDBACK: - /* never ever use this directly, low-level call */ - assert (0); - break; - - case PIANO_REQUEST_RENAME_STATION: { - /* rename station and update PianoStation_t structure */ - PianoRequestDataRenameStation_t *reqData = req->data; - - assert (reqData != NULL); - assert (reqData->station != NULL); - assert (reqData->newName != NULL); - - free (reqData->station->name); - reqData->station->name = strdup (reqData->newName); - break; - } - - case PIANO_REQUEST_REMOVE_ITEM: - case PIANO_REQUEST_DELETE_STATION: { - /* delete station from server and station list */ - PianoStation_t *station = req->data; - - assert (station != NULL); - - ph->stations = PianoListDeleteP (ph->stations, station); - PianoDestroyStation (station); - free (station); - break; - } - - case PIANO_REQUEST_SEARCH: { - /* search artist/song */ - PianoRequestDataSearch_t *reqData = req->data; - PianoSearchResult_t *searchResult; - - assert (req->responseData != NULL); - assert (reqData != NULL); - - searchResult = &reqData->searchResult; - memset (searchResult, 0, sizeof (*searchResult)); - - /* get artists */ - json_object *artists; - if (json_object_object_get_ex (result, "artists", &artists)) { - for (unsigned int i = 0; i < json_object_array_length (artists); i++) { - json_object *a = json_object_array_get_idx (artists, i); - PianoArtist_t *artist; - - if ((artist = calloc (1, sizeof (*artist))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - artist->name = PianoJsonStrdup (a, "artistName"); - artist->musicId = PianoJsonStrdup (a, "musicToken"); - - searchResult->artists = - PianoListAppendP (searchResult->artists, artist); - } - } - - /* get songs */ - json_object *songs; - if (json_object_object_get_ex (result, "songs", &songs)) { - for (unsigned int i = 0; i < json_object_array_length (songs); i++) { - json_object *s = json_object_array_get_idx (songs, i); - PianoSong_t *song; - - if ((song = calloc (1, sizeof (*song))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - song->title = PianoJsonStrdup (s, "songName"); - song->artist = PianoJsonStrdup (s, "artistName"); - song->musicId = PianoJsonStrdup (s, "musicToken"); - - searchResult->songs = - PianoListAppendP (searchResult->songs, song); - } - } - break; - } - - case PIANO_REQUEST_CREATE_STATION: { - /* create station, insert new station into station list on success */ - PianoStation_t *tmpStation; - - if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - PianoJsonParseStation (result, tmpStation); - - PianoStation_t *search = PianoFindStationById (ph->stations, - tmpStation->id); - if (search != NULL) { - ph->stations = PianoListDeleteP (ph->stations, search); - PianoDestroyStation (search); - free (search); - } - ph->stations = PianoListAppendP (ph->stations, tmpStation); - break; - } - - case PIANO_REQUEST_ADD_TIRED_SONG: { - PianoSong_t * const song = req->data; - song->rating = PIANO_RATE_TIRED; - break; - } - - case PIANO_REQUEST_ADD_SEED: - case PIANO_REQUEST_SET_QUICKMIX: - case PIANO_REQUEST_BOOKMARK_SONG: - case PIANO_REQUEST_BOOKMARK_ARTIST: - case PIANO_REQUEST_DELETE_FEEDBACK: - case PIANO_REQUEST_DELETE_SEED: - case PIANO_REQUEST_CHANGE_SETTINGS: - /* response unused */ - break; - - case PIANO_REQUEST_GET_GENRE_STATIONS: { - /* get genre stations */ - json_object *categories; - if (json_object_object_get_ex (result, "categories", &categories)) { - for (unsigned int i = 0; i < json_object_array_length (categories); i++) { - json_object *c = json_object_array_get_idx (categories, i); - PianoGenreCategory_t *tmpGenreCategory; - - if ((tmpGenreCategory = calloc (1, - sizeof (*tmpGenreCategory))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - tmpGenreCategory->name = PianoJsonStrdup (c, - "categoryName"); - - /* get genre subnodes */ - json_object *stations; - if (json_object_object_get_ex (c, "stations", &stations)) { - for (unsigned int k = 0; - k < json_object_array_length (stations); k++) { - json_object *s = - json_object_array_get_idx (stations, k); - PianoGenre_t *tmpGenre; - - if ((tmpGenre = calloc (1, - sizeof (*tmpGenre))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - /* get genre attributes */ - tmpGenre->name = PianoJsonStrdup (s, - "stationName"); - tmpGenre->musicId = PianoJsonStrdup (s, - "stationToken"); - - tmpGenreCategory->genres = - PianoListAppendP (tmpGenreCategory->genres, - tmpGenre); - } - } - - ph->genreStations = PianoListAppendP (ph->genreStations, - tmpGenreCategory); - } - } - break; - } - - case PIANO_REQUEST_TRANSFORM_STATION: { - /* transform shared station into private and update isCreator flag */ - PianoStation_t *station = req->data; - - assert (req->responseData != NULL); - assert (station != NULL); - - station->isCreator = 1; - break; - } - - case PIANO_REQUEST_EXPLAIN: { - /* explain why song was selected */ - PianoRequestDataExplain_t *reqData = req->data; - const size_t strSize = 768; - - assert (reqData != NULL); - - json_object *explanations; - if (json_object_object_get_ex (result, "explanations", &explanations) && - json_object_array_length (explanations) > 0) { - reqData->retExplain = malloc (strSize * - sizeof (*reqData->retExplain)); - strncpy (reqData->retExplain, "We're playing this track " - "because it features ", strSize); - for (unsigned int i = 0; i < json_object_array_length (explanations); i++) { - json_object *e = json_object_array_get_idx (explanations, - i); - json_object *f; - if (!json_object_object_get_ex (e, "focusTraitName", &f)) { - continue; - } - const char *s = json_object_get_string (f); - PianoStrpcat (reqData->retExplain, s, strSize); - if (i < json_object_array_length (explanations)-2) { - PianoStrpcat (reqData->retExplain, ", ", strSize); - } else if (i == json_object_array_length (explanations)-2) { - PianoStrpcat (reqData->retExplain, " and ", strSize); - } else { - PianoStrpcat (reqData->retExplain, ".", strSize); - } - } - } - break; - } - - case PIANO_REQUEST_GET_SETTINGS: { - PianoSettings_t * const settings = req->data; - - assert (settings != NULL); - - settings->explicitContentFilter = getBoolDefault (result, - "isExplicitContentFilterEnabled", false); - settings->username = PianoJsonStrdup (result, "username"); - break; - } - - case PIANO_REQUEST_GET_STATION_INFO: { - /* get station information (seeds and feedback) */ - PianoRequestDataGetStationInfo_t *reqData = req->data; - PianoStationInfo_t *info; - - assert (reqData != NULL); - - info = &reqData->info; - assert (info != NULL); - - /* parse music seeds */ - json_object *music; - if (json_object_object_get_ex (result, "music", &music)) { - /* songs */ - json_object *songs; - if (json_object_object_get_ex (music, "songs", &songs)) { - for (unsigned int i = 0; i < json_object_array_length (songs); i++) { - json_object *s = json_object_array_get_idx (songs, i); - PianoSong_t *seedSong; - - seedSong = calloc (1, sizeof (*seedSong)); - if (seedSong == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - seedSong->title = PianoJsonStrdup (s, "songName"); - seedSong->artist = PianoJsonStrdup (s, "artistName"); - seedSong->seedId = PianoJsonStrdup (s, "seedId"); - - info->songSeeds = PianoListAppendP (info->songSeeds, - seedSong); - } - } - - /* artists */ - json_object *artists; - if (json_object_object_get_ex (music, "artists", &artists)) { - for (unsigned int i = 0; i < json_object_array_length (artists); i++) { - json_object *a = json_object_array_get_idx (artists, i); - PianoArtist_t *seedArtist; - - seedArtist = calloc (1, sizeof (*seedArtist)); - if (seedArtist == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - seedArtist->name = PianoJsonStrdup (a, "artistName"); - seedArtist->seedId = PianoJsonStrdup (a, "seedId"); - - info->artistSeeds = - PianoListAppendP (info->artistSeeds, seedArtist); - } - } - } - - /* parse feedback */ - json_object *feedback; - if (json_object_object_get_ex (result, "feedback", &feedback)) { - static const char * const keys[] = {"thumbsUp", "thumbsDown"}; - for (size_t i = 0; i < sizeof (keys)/sizeof (*keys); i++) { - json_object *val; - if (!json_object_object_get_ex (feedback, keys[i], &val)) { - continue; - } - assert (json_object_is_type (val, json_type_array)); - for (unsigned int i = 0; i < json_object_array_length (val); i++) { - json_object *s = json_object_array_get_idx (val, i); - PianoSong_t *feedbackSong; - - feedbackSong = calloc (1, sizeof (*feedbackSong)); - if (feedbackSong == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - feedbackSong->title = PianoJsonStrdup (s, "songName"); - feedbackSong->artist = PianoJsonStrdup (s, - "artistName"); - feedbackSong->feedbackId = PianoJsonStrdup (s, - "feedbackId"); - feedbackSong->rating = getBoolDefault (s, "isPositive", - false) ? PIANO_RATE_LOVE : PIANO_RATE_BAN; - - json_object *v; - feedbackSong->length = - json_object_object_get_ex (s, "trackLength", &v) ? - json_object_get_int (v) : 0; - - info->feedback = PianoListAppendP (info->feedback, - feedbackSong); - } - } - } - break; - } - - case PIANO_REQUEST_GET_STATION_MODES: { - PianoRequestDataGetStationModes_t *reqData = req->data; - assert (reqData != NULL); - - int active = -1; - - json_object *activeMode; - if (json_object_object_get_ex (result, "currentModeId", &activeMode)) { - active = json_object_get_int (activeMode); - } - - json_object *availableModes; - if (json_object_object_get_ex (result, "availableModes", &availableModes)) { - for (unsigned int i = 0; i < json_object_array_length (availableModes); i++) { - json_object *val = json_object_array_get_idx (availableModes, i); - - assert (json_object_is_type (val, json_type_object)); - - PianoStationMode_t *mode; - if ((mode = calloc (1, sizeof (*mode))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - json_object *modeId; - if (json_object_object_get_ex (val, "modeId", &modeId)) { - mode->id = json_object_get_int (modeId); - mode->name = PianoJsonStrdup (val, "modeName"); - mode->description = PianoJsonStrdup (val, "modeDescription"); - mode->isAlgorithmic = getBoolDefault (val, "isAlgorithmicMode", - false); - mode->isTakeover = getBoolDefault (val, "isTakeoverMode", - false); - mode->active = active == mode->id; - } - - reqData->retModes = PianoListAppendP (reqData->retModes, - mode); - } - } - break; - } - - case PIANO_REQUEST_SET_STATION_MODE: { - PianoRequestDataSetStationMode_t *reqData = req->data; - assert (reqData != NULL); - - int active = -1; - - json_object *activeMode; - if (json_object_object_get_ex (result, "currentModeId", &activeMode)) { - active = json_object_get_int (activeMode); - } - - if (active != reqData->id) { - /* this did not work */ - return PIANO_RET_ERR; - } - break; - } - - case PIANO_REQUEST_GET_PLAYBACK_INFO: { - PianoRequestDataGetPlaylist_t *reqData = req->data; - PianoSong_t *song = reqData->retPlaylist; - - assert (req->responseData != NULL); - assert (reqData != NULL); - assert (reqData->quality < sizeof (qualityMap)/sizeof (*qualityMap)); - assert (song != NULL); - - json_object *audioUrlMap = NULL; - if (!json_object_object_get_ex (result, "audioUrlMap", &audioUrlMap)) { - break; - } - assert (audioUrlMap != NULL); - - const char *quality = qualityMap[reqData->quality]; - json_object *umap; - - json_object *jsonEncoding = NULL; - if (json_object_object_get_ex (audioUrlMap, quality, &umap)) { - assert (umap != NULL); - if (json_object_object_get_ex (umap, "encoding", &jsonEncoding)) { - assert (jsonEncoding != NULL); - const char *encoding = json_object_get_string (jsonEncoding); - assert (encoding != NULL); - for (size_t k = 0; k < sizeof (formatMap)/sizeof (*formatMap); k++) { - if (strcmp (formatMap[k], encoding) == 0) { - song->audioFormat = k; - break; - } - } - song->audioUrl = PianoJsonStrdup (umap, "audioUrl"); - } - } - - if(song->audioUrl == NULL) { - /* requested quality is not available */ - LOG("quality %s not found in audioUrlMap\n",quality); - ret = PIANO_RET_QUALITY_UNAVAILABLE; - PianoDestroyPlaylist (reqData->retPlaylist); - goto cleanup; - } - break; - } - - case PIANO_REQUEST_GET_USER_PROFILE: { - assert (req->responseData != NULL); - - json_object *stations; - json_object *annotations = NULL; - ph->user.IsPremiumUser = getBoolDefault (result,"isPremiumUser", false); - LOG("IsPremiumUser: %d\n",ph->user.IsPremiumUser); - - if (!json_object_object_get_ex (result, "annotations", &annotations)) { - break; - } - assert (annotations != NULL); - int Annotations = 0; - json_object_object_foreach(annotations,Key,Val) { - const char *type = PianoJsonGetStr(Val,"type"); - - Annotations++; - if(strcmp(type,"PL") == 0) { - ph->user.PlayListCount++; - } - else if(strcmp(type,"ST") == 0) { - ph->user.StationCount++; - } - else if(strcmp(type,"AL") == 0) { - ph->user.AlbumCount++; - } - else if(strcmp(type,"TR") == 0) { - ph->user.TrackCount++; - } - else if(strcmp(type,"LI") == 0) { - } - else if(strcmp(type,"AR") == 0) { - } - else { - LOG("type %s ignored\n",type); - } - } - LOG("Found: %d annotations:\n",Annotations); - LOG(" PlayLists: %d:\n",ph->user.PlayListCount); - LOG(" Stations: %d:\n",ph->user.StationCount); - LOG(" Albums: %d:\n",ph->user.AlbumCount); - LOG(" Tracks: %d:\n",ph->user.TrackCount); - break; - } - - case PIANO_REQUEST_GET_ITEMS: { - assert (req->responseData != NULL); - json_object *items = NULL; - if (!json_object_object_get_ex (result, "items", &items)) { - break; - } - assert (items != NULL); - for (int i = 0; i < json_object_array_length (items); i++) { - json_object *s = json_object_array_get_idx (items, i); - const char *type = PianoJsonGetStr(s,"pandoraType"); - PianoSong_t *song; - PianoStationType_t stationType = PIANO_TYPE_NONE; - - assert(type != NULL); - if(strcmp(type,"PL") == 0 || strcmp(type,"ST") == 0) { - // Playlists and stations handled elsewhere - } - else if(strcmp(type,"AL") == 0) { - stationType = PIANO_TYPE_ALBUM; - } - else if(strcmp(type,"TR") == 0) { - stationType = PIANO_TYPE_TRACK; - } - else if(strcmp(type,"PC") == 0) { - stationType = PIANO_TYPE_PODCAST; - ph->user.PodcastCount++; - } - else { - LOG("type %s ignored\n",type); - } - - if(stationType != PIANO_TYPE_NONE) { - PianoStation_t *tmpStation; - if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - tmpStation->stationType = stationType; - tmpStation->id = PianoJsonStrdup (s, "pandoraId"); - /* start new linked list or append */ - ph->stations = PianoListAppendP (ph->stations, tmpStation); - } - else { - LOG("type %s ignored\n",type); - } - } - - if(ph->user.PodcastCount) { - LOG("Found %d podcast stations\n",ph->user.PodcastCount); - } - break; - } - - case PIANO_REQUEST_ANNOTATE_OBJECTS: { - assert (req->responseData != NULL); - assert (req->data != NULL); - PianoStation_t *station = ph->stations; - PianoRequestDataGetPlaylist_t *reqData = req->data; - - while(station != NULL) { - assert(station->id != NULL); - switch(station->stationType) { - case PIANO_TYPE_PODCAST: { - json_object_object_foreach(result,Key,Val) { - if(strcmp(Key,station->id) == 0) { - PianoSong_t *song = station->theSong; - assert (song == NULL); - if ((song = calloc (1, sizeof (*song))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - station->name = PianoJsonStrdup(Val,"name"); - station->seedId = PianoJsonStrdup(Val,"latestEpisodeId"); - station->theSong = song; - song->album = strdup(station->name); - song->coverArt = getCoverArt(Val); // podcast coverArt - LOG("podcast coverart %s\n",song->coverArt); - break; - } - } - if(station->name == NULL) { - LOG("Couldn't find station %s\n",station->id); - } - break; - } - - case PIANO_TYPE_ALBUM: { - json_object_object_foreach(result,Key,Val) { - if(strcmp(Key,station->id) == 0) { - char Temp[120]; - snprintf(Temp,sizeof(Temp),"%s - %s", - PianoJsonGetStr(Val,"artistName"), - PianoJsonGetStr(Val,"name")); - station->name = strdup(Temp); - station->seedId = PianoJsonStrdup(Val,"pandoraId"); - break; - } - } - if(station->name == NULL) { - LOG("Couldn't find station %s\n",station->id); - } - break; - } - - case PIANO_TYPE_TRACK: { - json_object_object_foreach(result,Key,Val) { - if(strcmp(Key,station->id) == 0) { - station->name = PianoJsonStrdup(Val,"name"); - station->seedId = PianoJsonStrdup(Val,"albumId"); - - PianoSong_t *song = station->theSong; - assert (song == NULL); - if ((song = calloc (1, sizeof (*song))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - station->theSong = song; - song->artist = PianoJsonStrdup(Val, "artistName"); - song->album = PianoJsonStrdup(Val, "albumName"); - song->title = PianoJsonStrdup(Val, "name"); - song->length = getInt(Val, "duration"); - song->coverArt = getCoverArt(Val); - song->fileGain = 0.0; - break; - } - } - if(station->name == NULL) { - LOG("Couldn't find station %s\n",station->id); - } - break; - } - } - station = (PianoStation_t *) station->head.next; - } - break; - } - - case PIANO_REQUEST_GET_EPISODES: { - assert (req->responseData != NULL); - assert (req->data != NULL); - PianoRequestDataGetEpisodes_t *reqData = req->data; - PianoStation_t *station = reqData->station; - PianoSong_t *playlist = NULL; - int Added = 0; - PianoSong_t *song; - - json_object *annotations = NULL; - if (json_pointer_get(result, "/details/annotations", &annotations)) { - break; - } - json_object_object_foreach(annotations,Key,Val) { - if(Key[0] != 'P' || Key[1] != 'E') { - // not episode, ignore it - continue; - } - const char *EpisodeTitle = PianoJsonGetStr(Val,"name"); - - if(EpisodeTitle == NULL) { - LOG("Couldn't get title of episode\n"); - continue; - } - const char *Id = PianoJsonGetStr(Val,"podcastId"); - if(Id == NULL) { - LOG("Couldn't get podcastId\n"); - continue; - } - - if(strcmp(station->id,Id) != 0) { - LOG("Episode not for selected podcast (%s != %s)\n", - station->id,Id); - continue; - } - - const char *State = PianoJsonGetStr(Val,"contentState"); - if(State == NULL) { - LOG("Couldn't get contentState\n"); - continue; - } - - if(strcmp(State,"AVAILABLE") != 0) { - if(strlen(EpisodeTitle) > 0) { - LOG(" ignored %s\n",EpisodeTitle); - LOG(" contentState: %s\n",State); - } - continue; - } - if(getBool(Val,"/rightsInfo/hasInteractive") != 1) { - LOG(" ignored %s\n",EpisodeTitle); - LOG(" hasInteractive: %d\n",getBool(Val,"/rightsInfo/hasInteractive")); - LOG(" hasOffline: %d\n",getBool(Val,"/rightsInfo/hasOffline")); - LOG(" hasNonInteractive: %d\n",getBool(Val,"/rightsInfo/hasNonInteractive")); - LOG(" hasStatutory: %d\n",getBool(Val,"/rightsInfo/hasStatutory")); - LOG(" hasRadioRights: %d\n",getBool(Val,"/rightsInfo/hasRadioRights")); - continue; - } - - int Month; - int Day; - int Year; - char trackTitle[120]; - const char *Released = PianoJsonGetStr(Val,"releaseDate"); - if(Released == NULL) { - LOG("Couldn't get releaseDate\n"); - continue; - } - if(sscanf(Released,"%d-%d-%d",&Year,&Month,&Day) != 3) { - LOG("Couldn't convert releaseDate %s\n",Released); - continue; - } - const char *Title = PianoJsonGetStr(Val,"name"); - if(Title == NULL) { - LOG("Couldn't get episode title\n"); - continue; - } - const char *trackToken = PianoJsonGetStr(Val, "pandoraId"); - if(trackToken == NULL) { - LOG("Couldn't get trackToken\n"); - continue; - } - - snprintf(trackTitle,sizeof(trackTitle),"%02d/%02d: %s", - Month,Day,Title); - LOG("Got %s\n",trackTitle); - - if(!reqData->bGetAll) { - // just getting name of the current episode - song = reqData->playList; - if(strcmp(trackToken,song->trackToken) != 0) { - LOG("Ignoring %s, not current episode\n",trackTitle); - continue; - } - song->title = strdup(trackTitle); - LOG("Added name of current episode\n"); - break; - } - if ((song = calloc (1, sizeof (*song))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - song->title = strdup(trackTitle); - song->trackToken = strdup(trackToken); - song->length = getInt(Val, "duration"); - // Save release date for sorting - song->releaseDate = ((Year - 1900) * 10000) + (Month * 100) + Day; - // Add to playlist in release data order - PianoSong_t *thisSong = playlist; - PianoSong_t *lastSong = (PianoSong_t *) &playlist; - do { - if(thisSong == NULL) { - lastSong->head.next = (struct PianoListHead *) song; - // LOG("Added to end of list\n"); - break; - } - if(thisSong->releaseDate > song->releaseDate) { - lastSong->head.next = (struct PianoListHead *) song; - song->head.next = (struct PianoListHead *) thisSong; - // LOG("Added before\n"); - break; - } - lastSong = thisSong; - thisSong = (PianoSong_t *) thisSong->head.next; - } while(true); - Added++; - } - reqData->playList = playlist; - LOG("Added %d episodes:\n",Added); + PianoReturn_t ret = PIANO_RET_OK; + + assert (ph != NULL); + assert (req != NULL); + + json_object * const j = json_tokener_parse (req->responseData); + + json_object *status; + if (!json_object_object_get_ex (j, "stat", &status)) { + ret = PIANO_RET_INVALID_RESPONSE; + goto cleanup; + } + + /* error handling */ + if (strcmp (json_object_get_string (status), "ok") != 0) { + json_object *code; + if (!json_object_object_get_ex (j, "code", &code)) { + ret = PIANO_RET_INVALID_RESPONSE; + } else { + ret = json_object_get_int (code)+PIANO_RET_OFFSET; + + if (ret == PIANO_RET_P_INVALID_PARTNER_LOGIN && + req->type == PIANO_REQUEST_LOGIN) { + PianoRequestDataLogin_t *reqData = req->data; + if (reqData->step == 1) { + /* return value is ambiguous, as both, partnerLogin and + * userLogin return INVALID_PARTNER_LOGIN. Fix that to provide + * better error messages. */ + ret = PIANO_RET_INVALID_LOGIN; + } + } + } + + goto cleanup; + } + + json_object *result = NULL; + /* missing for some request types */ + json_object_object_get_ex (j, "result", &result); + + switch (req->type) { + case PIANO_REQUEST_LOGIN: { + /* authenticate user */ + PianoRequestDataLogin_t *reqData = req->data; + + assert (req->responseData != NULL); + assert (reqData != NULL); + + switch (reqData->step) { + case 0: { + /* decrypt timestamp */ + json_object *jsonTimestamp; + if (!json_object_object_get_ex (result, "syncTime", &jsonTimestamp)) { + ret = PIANO_RET_INVALID_RESPONSE; + break; + } + assert (jsonTimestamp != NULL); + const char * const cryptedTimestamp = json_object_get_string (jsonTimestamp); + assert (cryptedTimestamp != NULL); + const time_t realTimestamp = time (NULL); + char *decryptedTimestamp = NULL; + size_t decryptedSize; + + ret = PIANO_RET_ERR; + if ((decryptedTimestamp = PianoDecryptString (ph->partner.in, + cryptedTimestamp, &decryptedSize)) != NULL && + decryptedSize > 4) { + /* skip four bytes garbage(?) at beginning */ + const unsigned long timestamp = strtoul ( + decryptedTimestamp+4, NULL, 0); + ph->timeOffset = (long int) realTimestamp - + (long int) timestamp; + ret = PIANO_RET_CONTINUE_REQUEST; + } + free (decryptedTimestamp); + /* get auth token */ + ph->partner.authToken = PianoJsonStrdup (result, + "partnerAuthToken"); + json_object *partnerId; + if (!json_object_object_get_ex (result, "partnerId", &partnerId)) { + ret = PIANO_RET_INVALID_RESPONSE; + break; + } + ph->partner.id = json_object_get_int (partnerId); + ++reqData->step; + break; + } + + case 1: + /* information exists when reauthenticating, destroy to + * avoid memleak */ + if (ph->user.listenerId != NULL) { + PianoDestroyUserInfo (&ph->user); + } + ph->user.listenerId = PianoJsonStrdup (result, "userId"); + ph->user.authToken = PianoJsonStrdup (result, + "userAuthToken"); + ph->user.IsSubscriber = getBoolDefault (result, + "isSubscriber", false); + break; + } + break; + } + + case PIANO_REQUEST_GET_STATIONS: { + /* get stations */ + assert (req->responseData != NULL); + + json_object *stations, *mix = NULL; + + if (!json_object_object_get_ex (result, "stations", &stations)) { + break; + } + + for (unsigned int i = 0; i < json_object_array_length (stations); i++) { + PianoStation_t *tmpStation; + json_object *s = json_object_array_get_idx (stations, i); + + if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + tmpStation->stationType = PIANO_TYPE_STATION; + + PianoJsonParseStation (s, tmpStation); + + if (tmpStation->isQuickMix) { + /* fix flags on other stations later */ + json_object_object_get_ex (s, "quickMixStationIds", &mix); + } + + /* start new linked list or append */ + ph->stations = PianoListAppendP (ph->stations, tmpStation); + } + + /* fix quickmix flags */ + if (mix != NULL) { + PianoStation_t *curStation = ph->stations; + PianoListForeachP (curStation) { + for (unsigned int i = 0; i < json_object_array_length (mix); i++) { + json_object *id = json_object_array_get_idx (mix, i); + if (strcmp (json_object_get_string (id), + curStation->id) == 0) { + curStation->useQuickMix = true; + } + } + } + } + break; + } + + case PIANO_REQUEST_GET_PLAYLISTS: { + /* get playlists */ + assert (req->responseData != NULL); + + json_object *playlists; + + if (!json_object_object_get_ex (result, "items", &playlists)) { + break; + } + + for (int i = 0; i < json_object_array_length (playlists); i++) { + PianoStation_t *tmpStation; + json_object *s = json_object_array_get_idx (playlists, i); + + if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + tmpStation->stationType = PIANO_TYPE_PLAYLIST; + + PianoJsonParsePlaylist(s, tmpStation); + + /* start new linked list or append */ + ph->stations = PianoListAppendP (ph->stations, tmpStation); + } + break; + } + + // Get album or playlist tracks + case PIANO_REQUEST_GET_TRACKS: { + assert (req->responseData != NULL); + + PianoRequestDataGetPlaylist_t *reqData = req->data; + PianoSong_t *playlist = NULL; + PianoSong_t *FullPlaylist = NULL; + + assert (result != NULL); + assert (req->responseData != NULL); + assert (reqData != NULL); + + switch(reqData->station->stationType) { + case PIANO_TYPE_PLAYLIST: { + json_object *tracks = NULL; + json_object *annotations = NULL; + if (!json_object_object_get_ex (result, "tracks", &tracks)) { + break; + } + assert (tracks!= NULL); + LOG("got tracks\n"); + if (!json_object_object_get_ex (result, "annotations", &annotations)) { + break; + } + assert (annotations != NULL); + LOG("got annotations\n"); + + for (int i = 0; i < json_object_array_length (tracks); i++) { + json_object *s = json_object_array_get_idx (tracks, i); + json_object *trackInfo = NULL; + PianoSong_t *song; + + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + song->seedId = PianoJsonStrdup(result, "pandoraId"); + song->trackToken = PianoJsonStrdup (s, "trackPandoraId"); + assert (song->trackToken != NULL); + + LOG("track %d: %s\n",i + 1,song->trackToken); + + if (!json_object_object_get_ex (annotations, song->trackToken, &trackInfo)) { + break; + } + assert (trackInfo!= NULL); + song->artist = PianoJsonStrdup(trackInfo, "artistName"); + song->album = PianoJsonStrdup(trackInfo, "albumName"); + song->title = PianoJsonStrdup(trackInfo, "name"); + song->fileGain = 0.0; + song->length = getInt(trackInfo, "duration"); + song->coverArt = getCoverArt(trackInfo); + playlist = PianoListAppendP (playlist, song); + } + break; + } + + case PIANO_TYPE_ALBUM: { + char trackTitle[120]; + int totalTracks = 0; + int trackNumber; + + // Count tracks + json_object_object_foreach(result,Key1,Val1) { + if(Key1[0] != 'T' || Key1[1] != 'R') { + continue; + } + totalTracks++; + } + + json_object_object_foreach(result,Key,Val) { + if(Key[0] != 'T' || Key[1] != 'R') { + continue; + } + LOG("got track %s: ",Key); + trackNumber = getInt(Val,"trackNumber"); + snprintf(trackTitle,sizeof(trackTitle), + totalTracks > 9 ? "%02d %s" : "%d %s", + trackNumber,PianoJsonGetStr(Val,"name")); + LOG_RAW("%s\n",trackTitle); + if(getBool(Val,"/rightsInfo/hasInteractive") != 1) { + LOG(" ignored\n"); + LOG(" hasInteractive: %d\n",getBool(Val,"/rightsInfo/hasInteractive")); + LOG(" hasOffline: %d\n",getBool(Val,"/rightsInfo/hasOffline")); + LOG(" hasNonInteractive: %d\n",getBool(Val,"/rightsInfo/hasNonInteractive")); + LOG(" hasStatutory: %d\n",getBool(Val,"/rightsInfo/hasStatutory")); + LOG(" hasRadioRights: %d\n",getBool(Val,"/rightsInfo/hasRadioRights")); + continue; + } + PianoSong_t *song; + + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + song->stationId = strdup(reqData->station->id); + song->title = strdup(trackTitle); + song->trackToken = strdup(Key); + song->seedId = PianoJsonStrdup(Val,"albumId"); + song->artist = PianoJsonStrdup(Val,"artistName"); + song->album = PianoJsonStrdup(Val,"albumName"); + song->fileGain = 0.0; + song->length = getInt(Val, "duration"); + song->coverArt = getCoverArt(Val); + // Add to playlist in track order + + if(playlist == NULL) { + playlist = song; + } + else { + PianoSong_t *lastSong = (PianoSong_t *) &playlist; + PianoSong_t *nextSong = playlist; + do { + int nextSongTrackNum; + if(nextSong == NULL) { + lastSong->head.next = (struct PianoListHead *) song; + break; + } + sscanf(nextSong->title,"%d",&nextSongTrackNum); + if(nextSongTrackNum > trackNumber) { + lastSong->head.next = (struct PianoListHead *) song; + song->head.next = (struct PianoListHead *) nextSong; + break; + } + lastSong = nextSong; + nextSong = (PianoSong_t *) nextSong->head.next; + } while(true); + } + } + break; + } + + default: + LOG("Invalid stationType 0x%x\n",ph->stations->stationType); + break; + } + reqData->retPlaylist = playlist; + break; + } + + case PIANO_REQUEST_GET_PLAYLIST: { + /* get playlist, usually four songs */ + PianoRequestDataGetPlaylist_t *reqData = req->data; + PianoSong_t *playlist = NULL; + + assert (req->responseData != NULL); + assert (reqData != NULL); + assert (reqData->quality != PIANO_AQ_UNKNOWN); + + json_object *items = NULL; + if (!json_object_object_get_ex (result, "items", &items)) { + break; + } + assert (items != NULL); + + for (unsigned int i = 0; i < json_object_array_length (items); i++) { + json_object *s = json_object_array_get_idx (items, i); + PianoSong_t *song; + + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + if (!json_object_object_get_ex (s, "artistName", NULL)) { + free (song); + continue; + } + + /* get audio url based on selected quality */ + static const char *qualityMap[] = {"", "lowQuality", "mediumQuality", + "highQuality"}; + assert (reqData->quality < sizeof (qualityMap)/sizeof (*qualityMap)); + static const char *formatMap[] = {"", "aacplus", "mp3"}; + + json_object *umap; + if (json_object_object_get_ex (s, "audioUrlMap", &umap)) { + assert (umap != NULL); + json_object *jsonEncoding, *qmap; + if (json_object_object_get_ex (umap, qualityMap[reqData->quality], &qmap) && + json_object_object_get_ex (qmap, "encoding", &jsonEncoding)) { + assert (qmap != NULL); + const char *encoding = json_object_get_string (jsonEncoding); + assert (encoding != NULL); + for (size_t k = 0; k < sizeof (formatMap)/sizeof (*formatMap); k++) { + if (strcmp (formatMap[k], encoding) == 0) { + song->audioFormat = k; + break; + } + } + song->audioUrl = PianoJsonStrdup (qmap, "audioUrl"); + } else { + /* requested quality is not available */ + ret = PIANO_RET_QUALITY_UNAVAILABLE; + free (song); + PianoDestroyPlaylist (playlist); + goto cleanup; + } + } + + json_object *v; + song->artist = PianoJsonStrdup (s, "artistName"); + song->album = PianoJsonStrdup (s, "albumName"); + song->title = PianoJsonStrdup (s, "songName"); + song->trackToken = PianoJsonStrdup (s, "trackToken"); + song->stationId = PianoJsonStrdup (s, "stationId"); + song->coverArt = PianoJsonStrdup (s, "albumArtUrl"); + song->detailUrl = PianoJsonStrdup (s, "songDetailUrl"); + song->fileGain = json_object_object_get_ex (s, "trackGain", &v) ? + json_object_get_double (v) : 0.0; + song->length = json_object_object_get_ex (s, "trackLength", &v) ? + json_object_get_int (v) : 0; + switch (json_object_object_get_ex (s, "songRating", &v) ? + json_object_get_int (v) : 0) { + case 1: + song->rating = PIANO_RATE_LOVE; + break; + } + + playlist = PianoListAppendP (playlist, song); + } + + reqData->retPlaylist = playlist; + break; + } + + case PIANO_REQUEST_RATE_SONG: { + /* love/ban song */ + PianoRequestDataRateSong_t *reqData = req->data; + reqData->song->rating = reqData->rating; + break; + } + + case PIANO_REQUEST_ADD_FEEDBACK: + /* never ever use this directly, low-level call */ + assert (0); + break; + + case PIANO_REQUEST_RENAME_STATION: { + /* rename station and update PianoStation_t structure */ + PianoRequestDataRenameStation_t *reqData = req->data; + + assert (reqData != NULL); + assert (reqData->station != NULL); + assert (reqData->newName != NULL); + + free (reqData->station->name); + reqData->station->name = strdup (reqData->newName); + break; + } + + case PIANO_REQUEST_REMOVE_ITEM: + case PIANO_REQUEST_DELETE_STATION: { + /* delete station from server and station list */ + PianoStation_t *station = req->data; + + assert (station != NULL); + + ph->stations = PianoListDeleteP (ph->stations, station); + PianoDestroyStation (station); + free (station); + break; + } + + case PIANO_REQUEST_SEARCH: { + /* search artist/song */ + PianoRequestDataSearch_t *reqData = req->data; + PianoSearchResult_t *searchResult; + + assert (req->responseData != NULL); + assert (reqData != NULL); + + searchResult = &reqData->searchResult; + memset (searchResult, 0, sizeof (*searchResult)); + + /* get artists */ + json_object *artists; + if (json_object_object_get_ex (result, "artists", &artists)) { + for (unsigned int i = 0; i < json_object_array_length (artists); i++) { + json_object *a = json_object_array_get_idx (artists, i); + PianoArtist_t *artist; + + if ((artist = calloc (1, sizeof (*artist))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + artist->name = PianoJsonStrdup (a, "artistName"); + artist->musicId = PianoJsonStrdup (a, "musicToken"); + + searchResult->artists = + PianoListAppendP (searchResult->artists, artist); + } + } + + /* get songs */ + json_object *songs; + if (json_object_object_get_ex (result, "songs", &songs)) { + for (unsigned int i = 0; i < json_object_array_length (songs); i++) { + json_object *s = json_object_array_get_idx (songs, i); + PianoSong_t *song; + + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + song->title = PianoJsonStrdup (s, "songName"); + song->artist = PianoJsonStrdup (s, "artistName"); + song->musicId = PianoJsonStrdup (s, "musicToken"); + + searchResult->songs = + PianoListAppendP (searchResult->songs, song); + } + } + break; + } + + case PIANO_REQUEST_CREATE_STATION: { + /* create station, insert new station into station list on success */ + PianoStation_t *tmpStation; + + if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + PianoJsonParseStation (result, tmpStation); + + PianoStation_t *search = PianoFindStationById (ph->stations, + tmpStation->id); + if (search != NULL) { + ph->stations = PianoListDeleteP (ph->stations, search); + PianoDestroyStation (search); + free (search); + } + ph->stations = PianoListAppendP (ph->stations, tmpStation); + break; + } + + case PIANO_REQUEST_ADD_TIRED_SONG: { + PianoSong_t * const song = req->data; + song->rating = PIANO_RATE_TIRED; + break; + } + + case PIANO_REQUEST_ADD_SEED: + case PIANO_REQUEST_SET_QUICKMIX: + case PIANO_REQUEST_BOOKMARK_SONG: + case PIANO_REQUEST_BOOKMARK_ARTIST: + case PIANO_REQUEST_DELETE_FEEDBACK: + case PIANO_REQUEST_DELETE_SEED: + case PIANO_REQUEST_CHANGE_SETTINGS: + /* response unused */ + break; + + case PIANO_REQUEST_GET_GENRE_STATIONS: { + /* get genre stations */ + json_object *categories; + if (json_object_object_get_ex (result, "categories", &categories)) { + for (unsigned int i = 0; i < json_object_array_length (categories); i++) { + json_object *c = json_object_array_get_idx (categories, i); + PianoGenreCategory_t *tmpGenreCategory; + + if ((tmpGenreCategory = calloc (1, + sizeof (*tmpGenreCategory))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + tmpGenreCategory->name = PianoJsonStrdup (c, + "categoryName"); + + /* get genre subnodes */ + json_object *stations; + if (json_object_object_get_ex (c, "stations", &stations)) { + for (unsigned int k = 0; + k < json_object_array_length (stations); k++) { + json_object *s = + json_object_array_get_idx (stations, k); + PianoGenre_t *tmpGenre; + + if ((tmpGenre = calloc (1, + sizeof (*tmpGenre))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + /* get genre attributes */ + tmpGenre->name = PianoJsonStrdup (s, + "stationName"); + tmpGenre->musicId = PianoJsonStrdup (s, + "stationToken"); + + tmpGenreCategory->genres = + PianoListAppendP (tmpGenreCategory->genres, + tmpGenre); + } + } + + ph->genreStations = PianoListAppendP (ph->genreStations, + tmpGenreCategory); + } + } + break; + } + + case PIANO_REQUEST_TRANSFORM_STATION: { + /* transform shared station into private and update isCreator flag */ + PianoStation_t *station = req->data; + + assert (req->responseData != NULL); + assert (station != NULL); + + station->isCreator = 1; + break; + } + + case PIANO_REQUEST_EXPLAIN: { + /* explain why song was selected */ + PianoRequestDataExplain_t *reqData = req->data; + const size_t strSize = 768; + + assert (reqData != NULL); + + json_object *explanations; + if (json_object_object_get_ex (result, "explanations", &explanations) && + json_object_array_length (explanations) > 0) { + reqData->retExplain = malloc (strSize * + sizeof (*reqData->retExplain)); + strncpy (reqData->retExplain, "We're playing this track " + "because it features ", strSize); + for (unsigned int i = 0; i < json_object_array_length (explanations); i++) { + json_object *e = json_object_array_get_idx (explanations, + i); + json_object *f; + if (!json_object_object_get_ex (e, "focusTraitName", &f)) { + continue; + } + const char *s = json_object_get_string (f); + PianoStrpcat (reqData->retExplain, s, strSize); + if (i < json_object_array_length (explanations)-2) { + PianoStrpcat (reqData->retExplain, ", ", strSize); + } else if (i == json_object_array_length (explanations)-2) { + PianoStrpcat (reqData->retExplain, " and ", strSize); + } else { + PianoStrpcat (reqData->retExplain, ".", strSize); + } + } + } + break; + } + + case PIANO_REQUEST_GET_SETTINGS: { + PianoSettings_t * const settings = req->data; + + assert (settings != NULL); + + settings->explicitContentFilter = getBoolDefault (result, + "isExplicitContentFilterEnabled", false); + settings->username = PianoJsonStrdup (result, "username"); + break; + } + + case PIANO_REQUEST_GET_STATION_INFO: { + /* get station information (seeds and feedback) */ + PianoRequestDataGetStationInfo_t *reqData = req->data; + PianoStationInfo_t *info; + + assert (reqData != NULL); + + info = &reqData->info; + assert (info != NULL); + + /* parse music seeds */ + json_object *music; + if (json_object_object_get_ex (result, "music", &music)) { + /* songs */ + json_object *songs; + if (json_object_object_get_ex (music, "songs", &songs)) { + for (unsigned int i = 0; i < json_object_array_length (songs); i++) { + json_object *s = json_object_array_get_idx (songs, i); + PianoSong_t *seedSong; + + seedSong = calloc (1, sizeof (*seedSong)); + if (seedSong == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + seedSong->title = PianoJsonStrdup (s, "songName"); + seedSong->artist = PianoJsonStrdup (s, "artistName"); + seedSong->seedId = PianoJsonStrdup (s, "seedId"); + + info->songSeeds = PianoListAppendP (info->songSeeds, + seedSong); + } + } + + /* artists */ + json_object *artists; + if (json_object_object_get_ex (music, "artists", &artists)) { + for (unsigned int i = 0; i < json_object_array_length (artists); i++) { + json_object *a = json_object_array_get_idx (artists, i); + PianoArtist_t *seedArtist; + + seedArtist = calloc (1, sizeof (*seedArtist)); + if (seedArtist == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + seedArtist->name = PianoJsonStrdup (a, "artistName"); + seedArtist->seedId = PianoJsonStrdup (a, "seedId"); + + info->artistSeeds = + PianoListAppendP (info->artistSeeds, seedArtist); + } + } + } + + /* parse feedback */ + json_object *feedback; + if (json_object_object_get_ex (result, "feedback", &feedback)) { + static const char * const keys[] = {"thumbsUp", "thumbsDown"}; + for (size_t i = 0; i < sizeof (keys)/sizeof (*keys); i++) { + json_object *val; + if (!json_object_object_get_ex (feedback, keys[i], &val)) { + continue; + } + assert (json_object_is_type (val, json_type_array)); + for (unsigned int i = 0; i < json_object_array_length (val); i++) { + json_object *s = json_object_array_get_idx (val, i); + PianoSong_t *feedbackSong; + + feedbackSong = calloc (1, sizeof (*feedbackSong)); + if (feedbackSong == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + feedbackSong->title = PianoJsonStrdup (s, "songName"); + feedbackSong->artist = PianoJsonStrdup (s, + "artistName"); + feedbackSong->feedbackId = PianoJsonStrdup (s, + "feedbackId"); + feedbackSong->rating = getBoolDefault (s, "isPositive", + false) ? PIANO_RATE_LOVE : PIANO_RATE_BAN; + + json_object *v; + feedbackSong->length = + json_object_object_get_ex (s, "trackLength", &v) ? + json_object_get_int (v) : 0; + + info->feedback = PianoListAppendP (info->feedback, + feedbackSong); + } + } + } + break; + } + + case PIANO_REQUEST_GET_STATION_MODES: { + PianoRequestDataGetStationModes_t *reqData = req->data; + assert (reqData != NULL); + + int active = -1; + + json_object *activeMode; + if (json_object_object_get_ex (result, "currentModeId", &activeMode)) { + active = json_object_get_int (activeMode); + } + + json_object *availableModes; + if (json_object_object_get_ex (result, "availableModes", &availableModes)) { + for (unsigned int i = 0; i < json_object_array_length (availableModes); i++) { + json_object *val = json_object_array_get_idx (availableModes, i); + + assert (json_object_is_type (val, json_type_object)); + + PianoStationMode_t *mode; + if ((mode = calloc (1, sizeof (*mode))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + json_object *modeId; + if (json_object_object_get_ex (val, "modeId", &modeId)) { + mode->id = json_object_get_int (modeId); + mode->name = PianoJsonStrdup (val, "modeName"); + mode->description = PianoJsonStrdup (val, "modeDescription"); + mode->isAlgorithmic = getBoolDefault (val, "isAlgorithmicMode", + false); + mode->isTakeover = getBoolDefault (val, "isTakeoverMode", + false); + mode->active = active == mode->id; + } + + reqData->retModes = PianoListAppendP (reqData->retModes, + mode); + } + } + break; + } + + case PIANO_REQUEST_SET_STATION_MODE: { + PianoRequestDataSetStationMode_t *reqData = req->data; + assert (reqData != NULL); + + int active = -1; + + json_object *activeMode; + if (json_object_object_get_ex (result, "currentModeId", &activeMode)) { + active = json_object_get_int (activeMode); + } + + if (active != reqData->id) { + /* this did not work */ + return PIANO_RET_ERR; + } + break; + } + + case PIANO_REQUEST_GET_PLAYBACK_INFO: { + PianoRequestDataGetPlaylist_t *reqData = req->data; + PianoSong_t *song = reqData->retPlaylist; + + assert (req->responseData != NULL); + assert (reqData != NULL); + assert (reqData->quality < sizeof (qualityMap)/sizeof (*qualityMap)); + assert (song != NULL); + + json_object *audioUrlMap = NULL; + if (!json_object_object_get_ex (result, "audioUrlMap", &audioUrlMap)) { + break; + } + assert (audioUrlMap != NULL); + + const char *quality = qualityMap[reqData->quality]; + json_object *umap; + + json_object *jsonEncoding = NULL; + if (json_object_object_get_ex (audioUrlMap, quality, &umap)) { + assert (umap != NULL); + if (json_object_object_get_ex (umap, "encoding", &jsonEncoding)) { + assert (jsonEncoding != NULL); + const char *encoding = json_object_get_string (jsonEncoding); + assert (encoding != NULL); + for (size_t k = 0; k < sizeof (formatMap)/sizeof (*formatMap); k++) { + if (strcmp (formatMap[k], encoding) == 0) { + song->audioFormat = k; + break; + } + } + song->audioUrl = PianoJsonStrdup (umap, "audioUrl"); + } + } + + if(song->audioUrl == NULL) { + /* requested quality is not available */ + LOG("quality %s not found in audioUrlMap\n",quality); + ret = PIANO_RET_QUALITY_UNAVAILABLE; + PianoDestroyPlaylist (reqData->retPlaylist); + goto cleanup; + } + break; + } + + case PIANO_REQUEST_GET_USER_PROFILE: { + assert (req->responseData != NULL); + + json_object *stations; + json_object *annotations = NULL; + ph->user.IsPremiumUser = getBoolDefault (result,"isPremiumUser", false); + LOG("IsPremiumUser: %d\n",ph->user.IsPremiumUser); + + if (!json_object_object_get_ex (result, "annotations", &annotations)) { + break; + } + assert (annotations != NULL); + int Annotations = 0; + json_object_object_foreach(annotations,Key,Val) { + const char *type = PianoJsonGetStr(Val,"type"); + + Annotations++; + if(strcmp(type,"PL") == 0) { + ph->user.PlayListCount++; + } + else if(strcmp(type,"ST") == 0) { + ph->user.StationCount++; + } + else if(strcmp(type,"AL") == 0) { + ph->user.AlbumCount++; + } + else if(strcmp(type,"TR") == 0) { + ph->user.TrackCount++; + } + else if(strcmp(type,"LI") == 0) { + } + else if(strcmp(type,"AR") == 0) { + } + else { + LOG("type %s ignored\n",type); + } + } + LOG("Found: %d annotations:\n",Annotations); + LOG(" PlayLists: %d:\n",ph->user.PlayListCount); + LOG(" Stations: %d:\n",ph->user.StationCount); + LOG(" Albums: %d:\n",ph->user.AlbumCount); + LOG(" Tracks: %d:\n",ph->user.TrackCount); + break; + } + + case PIANO_REQUEST_GET_ITEMS: { + assert (req->responseData != NULL); + json_object *items = NULL; + if (!json_object_object_get_ex (result, "items", &items)) { + break; + } + assert (items != NULL); + for (int i = 0; i < json_object_array_length (items); i++) { + json_object *s = json_object_array_get_idx (items, i); + const char *type = PianoJsonGetStr(s,"pandoraType"); + PianoSong_t *song; + PianoStationType_t stationType = PIANO_TYPE_NONE; + + assert(type != NULL); + if(strcmp(type,"PL") == 0 || strcmp(type,"ST") == 0) { + // Playlists and stations handled elsewhere + } + else if(strcmp(type,"AL") == 0) { + stationType = PIANO_TYPE_ALBUM; + } + else if(strcmp(type,"TR") == 0) { + stationType = PIANO_TYPE_TRACK; + } + else if(strcmp(type,"PC") == 0) { + stationType = PIANO_TYPE_PODCAST; + ph->user.PodcastCount++; + } + else { + LOG("type %s ignored\n",type); + } + + if(stationType != PIANO_TYPE_NONE) { + PianoStation_t *tmpStation; + if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + tmpStation->stationType = stationType; + tmpStation->id = PianoJsonStrdup (s, "pandoraId"); + /* start new linked list or append */ + ph->stations = PianoListAppendP (ph->stations, tmpStation); + } + else { + LOG("type %s ignored\n",type); + } + } + + if(ph->user.PodcastCount) { + LOG("Found %d podcast stations\n",ph->user.PodcastCount); + } + break; + } + + case PIANO_REQUEST_ANNOTATE_OBJECTS: { + assert (req->responseData != NULL); + assert (req->data != NULL); + PianoStation_t *station = ph->stations; + PianoRequestDataGetPlaylist_t *reqData = req->data; + + while(station != NULL) { + assert(station->id != NULL); + switch(station->stationType) { + case PIANO_TYPE_PODCAST: { + json_object_object_foreach(result,Key,Val) { + if(strcmp(Key,station->id) == 0) { + PianoSong_t *song = station->theSong; + assert (song == NULL); + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + station->name = PianoJsonStrdup(Val,"name"); + station->seedId = PianoJsonStrdup(Val,"latestEpisodeId"); + station->theSong = song; + song->album = strdup(station->name); + song->coverArt = getCoverArt(Val); // podcast coverArt + LOG("podcast coverart %s\n",song->coverArt); + break; + } + } + if(station->name == NULL) { + LOG("Couldn't find station %s\n",station->id); + } + break; + } + + case PIANO_TYPE_ALBUM: { + json_object_object_foreach(result,Key,Val) { + if(strcmp(Key,station->id) == 0) { + char Temp[120]; + snprintf(Temp,sizeof(Temp),"%s - %s", + PianoJsonGetStr(Val,"artistName"), + PianoJsonGetStr(Val,"name")); + station->name = strdup(Temp); + station->seedId = PianoJsonStrdup(Val,"pandoraId"); + break; + } + } + if(station->name == NULL) { + LOG("Couldn't find station %s\n",station->id); + } + break; + } + + case PIANO_TYPE_TRACK: { + json_object_object_foreach(result,Key,Val) { + if(strcmp(Key,station->id) == 0) { + station->name = PianoJsonStrdup(Val,"name"); + station->seedId = PianoJsonStrdup(Val,"albumId"); + + PianoSong_t *song = station->theSong; + assert (song == NULL); + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + station->theSong = song; + song->artist = PianoJsonStrdup(Val, "artistName"); + song->album = PianoJsonStrdup(Val, "albumName"); + song->title = PianoJsonStrdup(Val, "name"); + song->length = getInt(Val, "duration"); + song->coverArt = getCoverArt(Val); + song->fileGain = 0.0; + break; + } + } + if(station->name == NULL) { + LOG("Couldn't find station %s\n",station->id); + } + break; + } + } + station = (PianoStation_t *) station->head.next; + } + break; + } + + case PIANO_REQUEST_GET_EPISODES: { + assert (req->responseData != NULL); + assert (req->data != NULL); + PianoRequestDataGetEpisodes_t *reqData = req->data; + PianoStation_t *station = reqData->station; + PianoSong_t *playlist = NULL; + int Added = 0; + PianoSong_t *song; + + json_object *annotations = NULL; + if (json_pointer_get(result, "/details/annotations", &annotations)) { + break; + } + json_object_object_foreach(annotations,Key,Val) { + if(Key[0] != 'P' || Key[1] != 'E') { + // not episode, ignore it + continue; + } + const char *EpisodeTitle = PianoJsonGetStr(Val,"name"); + + if(EpisodeTitle == NULL) { + LOG("Couldn't get title of episode\n"); + continue; + } + const char *Id = PianoJsonGetStr(Val,"podcastId"); + if(Id == NULL) { + LOG("Couldn't get podcastId\n"); + continue; + } + + if(strcmp(station->id,Id) != 0) { + LOG("Episode not for selected podcast (%s != %s)\n", + station->id,Id); + continue; + } + + const char *State = PianoJsonGetStr(Val,"contentState"); + if(State == NULL) { + LOG("Couldn't get contentState\n"); + continue; + } + + if(strcmp(State,"AVAILABLE") != 0) { + if(strlen(EpisodeTitle) > 0) { + LOG(" ignored %s\n",EpisodeTitle); + LOG(" contentState: %s\n",State); + } + continue; + } + if(getBool(Val,"/rightsInfo/hasInteractive") != 1) { + LOG(" ignored %s\n",EpisodeTitle); + LOG(" hasInteractive: %d\n",getBool(Val,"/rightsInfo/hasInteractive")); + LOG(" hasOffline: %d\n",getBool(Val,"/rightsInfo/hasOffline")); + LOG(" hasNonInteractive: %d\n",getBool(Val,"/rightsInfo/hasNonInteractive")); + LOG(" hasStatutory: %d\n",getBool(Val,"/rightsInfo/hasStatutory")); + LOG(" hasRadioRights: %d\n",getBool(Val,"/rightsInfo/hasRadioRights")); + continue; + } + + int Month; + int Day; + int Year; + char trackTitle[120]; + const char *Released = PianoJsonGetStr(Val,"releaseDate"); + if(Released == NULL) { + LOG("Couldn't get releaseDate\n"); + continue; + } + if(sscanf(Released,"%d-%d-%d",&Year,&Month,&Day) != 3) { + LOG("Couldn't convert releaseDate %s\n",Released); + continue; + } + const char *Title = PianoJsonGetStr(Val,"name"); + if(Title == NULL) { + LOG("Couldn't get episode title\n"); + continue; + } + const char *trackToken = PianoJsonGetStr(Val, "pandoraId"); + if(trackToken == NULL) { + LOG("Couldn't get trackToken\n"); + continue; + } + + snprintf(trackTitle,sizeof(trackTitle),"%02d/%02d: %s", + Month,Day,Title); + LOG("Got %s\n",trackTitle); + + if(!reqData->bGetAll) { + // just getting name of the current episode + song = reqData->playList; + if(strcmp(trackToken,song->trackToken) != 0) { + LOG("Ignoring %s, not current episode\n",trackTitle); + continue; + } + song->title = strdup(trackTitle); + LOG("Added name of current episode\n"); + break; + } + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + song->title = strdup(trackTitle); + song->trackToken = strdup(trackToken); + song->length = getInt(Val, "duration"); + // Save release date for sorting + song->fileGain = ((Year - 1900) * 10000) + (Month * 100) + Day; + // Add to playlist in release data order + PianoSong_t *thisSong = playlist; + PianoSong_t *lastSong = (PianoSong_t *) &playlist; + do { + if(thisSong == NULL) { + lastSong->head.next = (struct PianoListHead *) song; + // LOG("Added to end of list\n"); + break; + } + // LOG("Comparing to %s\n",thisSong->title); + if(thisSong->fileGain > song->fileGain) { + lastSong->head.next = (struct PianoListHead *) song; + song->head.next = (struct PianoListHead *) thisSong; + // LOG("Added before\n"); + break; + } + lastSong = thisSong; + thisSong = (PianoSong_t *) thisSong->head.next; + } while(true); + Added++; + } + reqData->playList = playlist; + LOG("Added %d episodes:\n",Added); #if 0 - song = playlist; - while(song != NULL) { - LOG(" %s\n",song->title); - song = (PianoSong_t *) song->head.next; - } + song = playlist; + while(song != NULL) { + LOG(" %s\n",song->title); + song = (PianoSong_t *) song->head.next; + } #endif - break; - } + break; + } } cleanup: - json_object_put (j); + json_object_put (j); - return ret; + return ret; } From cbb8e28000e9165f50ff82ace2553f371131f612 Mon Sep 17 00:00:00 2001 From: Skip Hansen Date: Fri, 4 Jul 2025 10:37:07 -0400 Subject: [PATCH 7/7] Removed deviceId parameter from getTracks, getItems and getSortedPlaylists requests. Apparenly not needed and meaning is unknown. --- src/libpiano/request.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libpiano/request.c b/src/libpiano/request.c index b966f53af..cd850a197 100644 --- a/src/libpiano/request.c +++ b/src/libpiano/request.c @@ -515,7 +515,6 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, json_object_object_add(a,"limit",json_object_new_int(100)); json_object_object_add(a,"annotationLimit",json_object_new_int(100)); json_object_object_add(j,"request",a); - json_object_object_add(j,"deviceId",json_object_new_string("1880")); method = "collections.v7.getSortedPlaylists"; break; } @@ -537,7 +536,6 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, json_object_object_add(a,"annotationLimit",json_object_new_int(100)); json_object_object_add(a,"bypassPrivacyRu1les",json_object_new_boolean(true)); json_object_object_add(j,"request",a); - json_object_object_add(j,"deviceId",json_object_new_string("1880")); method = "playlists.v7.getTracks"; break; } @@ -600,7 +598,6 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, json_object_object_add(a,"limit",json_object_new_int(4)); json_object_object_add(a,"cursor",json_object_new_string("g6FjzwAFdTr1OqMYoXbAoXSWokFSolRSolBMolBDokFMolBF")); #endif - json_object_object_add(j,"deviceId",json_object_new_string("1880")); method = "collections.v7.getItems"; break; }