diff options
Diffstat (limited to 'src/libpiano/piano.c')
-rw-r--r-- | src/libpiano/piano.c | 1146 |
1 files changed, 642 insertions, 504 deletions
diff --git a/src/libpiano/piano.c b/src/libpiano/piano.c index 342e4ec..0ac96b6 100644 --- a/src/libpiano/piano.c +++ b/src/libpiano/piano.c @@ -1,5 +1,5 @@ /* -Copyright (c) 2008-2011 +Copyright (c) 2008-2012 Lars-Dominik Braun <lars@6xq.net> Permission is hereby granted, free of charge, to any person obtaining a copy @@ -32,20 +32,17 @@ THE SOFTWARE. #include <time.h> #include <assert.h> #include <stdint.h> +#include <json.h> /* needed for urlencode */ #include <waitress.h> #include "piano_private.h" #include "piano.h" -#include "xml.h" #include "crypt.h" #include "config.h" -#define PIANO_PROTOCOL_VERSION "33" -#define PIANO_RPC_HOST "www.pandora.com" -#define PIANO_RPC_PORT "80" -#define PIANO_RPC_PATH "/radio/xmlrpc/v" PIANO_PROTOCOL_VERSION "?" +#define PIANO_RPC_PATH "/services/json/?" #define PIANO_SEND_BUFFER_SIZE 10000 /* initialize piano handle @@ -54,10 +51,6 @@ THE SOFTWARE. */ void PianoInit (PianoHandle_t *ph) { memset (ph, 0, sizeof (*ph)); - - /* route-id seems to be random. we're using time anyway... */ - snprintf (ph->routeId, sizeof (ph->routeId), "%07luP", - (unsigned long) time (NULL) % 10000000); } /* destroy artist linked list @@ -125,10 +118,8 @@ void PianoDestroyPlaylist (PianoSong_t *playlist) { free (curSong->artist); free (curSong->musicId); free (curSong->title); - free (curSong->userSeed); free (curSong->stationId); free (curSong->album); - free (curSong->artistMusicId); free (curSong->feedbackId); free (curSong->seedId); free (curSong->detailUrl); @@ -164,7 +155,6 @@ static void PianoDestroyGenres (PianoGenre_t *genres) { /* destroy user information */ static void PianoDestroyUserInfo (PianoUserInfo_t *user) { - free (user->webAuthToken); free (user->authToken); free (user->listenerId); } @@ -185,6 +175,7 @@ void PianoDestroy (PianoHandle_t *ph) { curGenreCat = curGenreCat->next; free (lastGenreCat); } + free (ph->partnerAuthToken); memset (ph, 0, sizeof (*ph)); } @@ -228,9 +219,12 @@ static const char *PianoAudioFormatToString (PianoAudioFormat_t format) { */ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, PianoRequestType_t type) { - char xmlSendBuf[PIANO_SEND_BUFFER_SIZE]; + const char *jsonSendBuf; + const char *method = NULL; + json_object *j = json_object_new_object (); /* corrected timestamp */ time_t timestamp = time (NULL) - ph->timeOffset; + bool encrypted = true; assert (ph != NULL); assert (req != NULL); @@ -248,66 +242,59 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, switch (logindata->step) { case 0: - snprintf (xmlSendBuf, sizeof (xmlSendBuf), - "<?xml version=\"1.0\"?><methodCall>" - "<methodName>misc.sync</methodName>" - "<params></params></methodCall>"); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&method=sync", ph->routeId); + encrypted = false; + req->secure = true; + + json_object_object_add (j, "username", + json_object_new_string ("android")); + json_object_object_add (j, "password", + json_object_new_string ("AC7IBG09A3DTSYM4R41UJWL07VLN8JI7")); + json_object_object_add (j, "deviceModel", + json_object_new_string ("android-generic")); + json_object_object_add (j, "version", + json_object_new_string ("5")); + json_object_object_add (j, "includeUrls", + json_object_new_boolean (true)); + snprintf (req->urlPath, sizeof (req->urlPath), + PIANO_RPC_PATH "method=auth.partnerLogin"); break; case 1: { - char *xmlencodedPassword = NULL; + char *urlencAuthToken; req->secure = true; - /* username == email address does not contain &,<,>," */ - if ((xmlencodedPassword = - PianoXmlEncodeString (logindata->password)) == - NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } + json_object_object_add (j, "loginType", + json_object_new_string ("user")); + json_object_object_add (j, "username", + json_object_new_string (logindata->user)); + json_object_object_add (j, "password", + json_object_new_string (logindata->password)); + json_object_object_add (j, "partnerAuthToken", + json_object_new_string (ph->partnerAuthToken)); + json_object_object_add (j, "syncTime", + json_object_new_int (timestamp)); + + urlencAuthToken = WaitressUrlEncode (ph->partnerAuthToken); + assert (urlencAuthToken != NULL); + snprintf (req->urlPath, sizeof (req->urlPath), + PIANO_RPC_PATH "method=auth.userLogin&" + "auth_token=%s&partner_id=%i", urlencAuthToken, + ph->partnerId); + free (urlencAuthToken); - snprintf (xmlSendBuf, sizeof (xmlSendBuf), - "<?xml version=\"1.0\"?><methodCall>" - "<methodName>listener.authenticateListener</methodName>" - "<params><param><value><int>%lu</int></value></param>" - /* user */ - "<param><value><string>%s</string></value></param>" - /* password */ - "<param><value><string>%s</string></value></param>" - /* vendor */ - "<param><value><string>html5tuner</string></value></param>" - "<param><value><string/></value></param>" - "<param><value><string/></value></param>" - "<param><value><string>HTML5</string></value></param>" - "<param><value><boolean>1</boolean></value></param>" - "</params></methodCall>", (unsigned long) timestamp, - logindata->user, xmlencodedPassword); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&method=authenticateListener", ph->routeId); - - free (xmlencodedPassword); break; } } break; } - case PIANO_REQUEST_GET_STATIONS: + case PIANO_REQUEST_GET_STATIONS: { /* get stations, user must be authenticated */ assert (ph->user.listenerId != NULL); - - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" - "<methodCall><methodName>station.getStations</methodName>" - "<params><param><value><int>%lu</int></value></param>" - "<param><value><string>%s</string></value></param>" - "</params></methodCall>", (unsigned long) timestamp, - ph->user.authToken); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&lid=%s&method=getStations", ph->routeId, - ph->user.listenerId); + method = "user.getStationList"; break; + } case PIANO_REQUEST_GET_PLAYLIST: { /* get playlist for specified station */ @@ -318,33 +305,12 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, assert (reqData->station->id != NULL); assert (reqData->format != PIANO_AF_UNKNOWN); - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" - "<methodCall><methodName>playlist.getFragment</methodName>" - "<params><param><value><int>%lu</int></value></param>" - /* auth token */ - "<param><value><string>%s</string></value></param>" - /* station id */ - "<param><value><string>%s</string></value></param>" - /* total listening time */ - "<param><value><string>0</string></value></param>" - /* time since last session */ - "<param><value><string></string></value></param>" - /* tracking code */ - "<param><value><string></string></value></param>" - /* audio format */ - "<param><value><string>%s</string></value></param>" - /* delta listening time */ - "<param><value><string>0</string></value></param>" - /* listening timestamp */ - "<param><value><string>0</string></value></param>" - "</params></methodCall>", (unsigned long) timestamp, - ph->user.authToken, reqData->station->id, - PianoAudioFormatToString (reqData->format)); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&lid=%s&method=getFragment&arg1=%s&arg2=0" - "&arg3=&arg4=&arg5=%s&arg6=0&arg7=0", ph->routeId, - ph->user.listenerId, reqData->station->id, - PianoAudioFormatToString (reqData->format)); + req->secure = true; + + json_object_object_add (j, "stationToken", + json_object_new_string (reqData->station->id)); + + method = "station.getPlaylist"; break; } @@ -353,65 +319,31 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, PianoRequestDataAddFeedback_t *reqData = req->data; assert (reqData != NULL); - assert (reqData->stationId != NULL); + assert (reqData->trackToken != NULL); assert (reqData->rating != PIANO_RATE_NONE); - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" - "<methodCall><methodName>station.addFeedback</methodName>" - "<params><param><value><int>%lu</int></value></param>" - /* auth token */ - "<param><value><string>%s</string></value></param>" - /* station id */ - "<param><value><string>%s</string></value></param>" - /* track token */ - "<param><value><string>%s</string></value></param>" - /* positive */ - "<param><value><boolean>%i</boolean></value></param>" - "</params></methodCall>", (unsigned long) timestamp, - ph->user.authToken, reqData->stationId, reqData->trackToken, - (reqData->rating == PIANO_RATE_LOVE) ? 1 : 0); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&lid=%s&method=addFeedback&arg1=%s&arg2=%s" - "&arg3=%s", - ph->routeId, ph->user.listenerId, reqData->stationId, - reqData->trackToken, - (reqData->rating == PIANO_RATE_LOVE) ? "true" : "false"); + json_object_object_add (j, "trackToken", + json_object_new_string (reqData->trackToken)); + json_object_object_add (j, "isPositive", + json_object_new_boolean (reqData->rating == PIANO_RATE_LOVE)); + + method = "station.addFeedback"; break; } case PIANO_REQUEST_RENAME_STATION: { - /* rename stations */ PianoRequestDataRenameStation_t *reqData = req->data; - char *urlencodedNewName, *xmlencodedNewName; assert (reqData != NULL); assert (reqData->station != NULL); assert (reqData->newName != NULL); - if ((xmlencodedNewName = PianoXmlEncodeString (reqData->newName)) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - urlencodedNewName = WaitressUrlEncode (reqData->newName); - - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" - "<methodCall><methodName>station.setStationName</methodName>" - "<params><param><value><int>%lu</int></value></param>" - /* auth token */ - "<param><value><string>%s</string></value></param>" - /* station id */ - "<param><value><string>%s</string></value></param>" - /* new name */ - "<param><value><string>%s</string></value></param>" - "</params></methodCall>", (unsigned long) timestamp, - ph->user.authToken, reqData->station->id, - xmlencodedNewName); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&lid=%s&method=setStationName&arg1=%s&arg2=%s", - ph->routeId, ph->user.listenerId, reqData->station->id, - urlencodedNewName); + json_object_object_add (j, "stationToken", + json_object_new_string (reqData->station->id)); + json_object_object_add (j, "stationName", + json_object_new_string (reqData->newName)); - free (urlencodedNewName); - free (xmlencodedNewName); + method = "station.renameStation"; break; } @@ -420,50 +352,26 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, PianoStation_t *station = req->data; assert (station != NULL); + assert (station->id != NULL); - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" - "<methodCall><methodName>station.removeStation</methodName>" - "<params><param><value><int>%lu</int></value></param>" - /* auth token */ - "<param><value><string>%s</string></value></param>" - /* station id */ - "<param><value><string>%s</string></value></param>" - "</params></methodCall>", (unsigned long) timestamp, - ph->user.authToken, station->id); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&lid=%s&method=removeStation&arg1=%s", ph->routeId, - ph->user.listenerId, station->id); + json_object_object_add (j, "stationToken", + json_object_new_string (station->id)); + + method = "station.deleteStation"; break; } case PIANO_REQUEST_SEARCH: { /* search for artist/song title */ PianoRequestDataSearch_t *reqData = req->data; - char *xmlencodedSearchStr, *urlencodedSearchStr; assert (reqData != NULL); assert (reqData->searchStr != NULL); - if ((xmlencodedSearchStr = PianoXmlEncodeString (reqData->searchStr)) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - urlencodedSearchStr = WaitressUrlEncode (reqData->searchStr); - - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" - "<methodCall><methodName>music.search</methodName>" - "<params><param><value><int>%lu</int></value></param>" - /* auth token */ - "<param><value><string>%s</string></value></param>" - /* search string */ - "<param><value><string>%s</string></value></param>" - "</params></methodCall>", (unsigned long) timestamp, - ph->user.authToken, xmlencodedSearchStr); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&lid=%s&method=search&arg1=%s", ph->routeId, - ph->user.listenerId, urlencodedSearchStr); + json_object_object_add (j, "searchText", + json_object_new_string (reqData->searchStr)); - free (urlencodedSearchStr); - free (xmlencodedSearchStr); + method = "music.search"; break; } @@ -474,23 +382,11 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, assert (reqData != NULL); assert (reqData->id != NULL); - assert (reqData->type != NULL); - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" - "<methodCall><methodName>station.createStation</methodName>" - "<params><param><value><int>%lu</int></value></param>" - /* auth token */ - "<param><value><string>%s</string></value></param>" - /* music id */ - "<param><value><string>%s%s</string></value></param>" - /* empty */ - "<param><value><string></string></value></param>" - "</params></methodCall>", (unsigned long) timestamp, - ph->user.authToken, reqData->type, reqData->id); + json_object_object_add (j, "musicToken", + json_object_new_string (reqData->id)); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&lid=%s&method=createStation&arg1=%s%s&arg2=", ph->routeId, - ph->user.listenerId, reqData->type, reqData->id); + method = "station.createStation"; break; } @@ -502,20 +398,12 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, assert (reqData->station != NULL); assert (reqData->musicId != NULL); - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" - "<methodCall><methodName>station.addSeed</methodName><params>" - "<param><value><int>%lu</int></value></param>" - /* auth token */ - "<param><value><string>%s</string></value></param>" - /* station id */ - "<param><value><string>%s</string></value></param>" - /* music id */ - "<param><value><string>%s</string></value></param>" - "</params></methodCall>", (unsigned long) timestamp, - ph->user.authToken, reqData->station->id, reqData->musicId); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&lid=%s&method=addSeed&arg1=%s&arg2=%s", ph->routeId, - ph->user.listenerId, reqData->station->id, reqData->musicId); + json_object_object_add (j, "musicToken", + json_object_new_string (reqData->musicId)); + json_object_object_add (j, "stationToken", + json_object_new_string (reqData->station->id)); + + method = "station.addMusic"; break; } @@ -525,87 +413,40 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, assert (song != NULL); - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" - "<methodCall><methodName>listener.addTiredSong</methodName><params>" - "<param><value><int>%lu</int></value></param>" - "<param><value><string>%s</string></value></param>" - /* key */ - "<param><value><string>%s</string></value></param>" - /* user seed */ - "<param><value><string>%s</string></value></param>" - /* station id */ - "<param><value><string>%s</string></value></param>" - "</params></methodCall>", (unsigned long) timestamp, - ph->user.authToken, - (song->musicId == NULL) ? "" : song->musicId, - (song->userSeed == NULL) ? "" : song->userSeed, - song->stationId); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&lid=%s&method=addTiredSong&arg1=%s&arg2=%s&arg3=%s", - ph->routeId, ph->user.listenerId, - (song->musicId == NULL) ? "" : song->musicId, - (song->userSeed == NULL) ? "" : song->userSeed, - song->stationId); + json_object_object_add (j, "trackToken", + json_object_new_string (song->trackToken)); + + method = "user.sleepSong"; break; } case PIANO_REQUEST_SET_QUICKMIX: { /* select stations included in quickmix (see useQuickMix flag of * PianoStation_t) */ - char valueBuf[1000], urlArgBuf[1000]; PianoStation_t *curStation = ph->stations; + json_object *a = json_object_new_array (); - memset (urlArgBuf, 0, sizeof (urlArgBuf)); - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" - "<methodCall><methodName>station.setQuickMix</methodName><params>" - "<param><value><int>%lu</int></value></param>" - "<param><value><string>%s</string></value></param>" - /* quick mix type */ - "<param><value><string>RANDOM</string></value></param>" - "<param><value><array><data>", (unsigned long) timestamp, - ph->user.authToken); while (curStation != NULL) { /* quick mix can't contain itself */ - if (!curStation->useQuickMix || curStation->isQuickMix) { - curStation = curStation->next; - continue; + if (curStation->useQuickMix && !curStation->isQuickMix) { + json_object_array_add (a, + json_object_new_string (curStation->id)); } - /* append to xml doc */ - snprintf (valueBuf, sizeof (valueBuf), - "<value><string>%s</string></value>", curStation->id); - strncat (xmlSendBuf, valueBuf, sizeof (xmlSendBuf) - - strlen (xmlSendBuf) - 1); - /* append to url arg */ - strncat (urlArgBuf, curStation->id, sizeof (urlArgBuf) - - strlen (urlArgBuf) - 1); + curStation = curStation->next; - /* if not last item: append "," */ - if (curStation != NULL) { - strncat (urlArgBuf, "%2C", sizeof (urlArgBuf) - - strlen (urlArgBuf) - 1); - } } - strncat (xmlSendBuf, - "</data></array></value></param>" - /* empty */ - "<param><value><string></string></value></param>" - /* empty */ - "<param><value><string></string></value></param>" - "</params></methodCall>", - sizeof (xmlSendBuf) - strlen (xmlSendBuf) - 1); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&lid=%s&method=setQuickMix&arg1=RANDOM&arg2=%s&arg3=&arg4=", - ph->routeId, ph->user.listenerId, urlArgBuf); + json_object_object_add (j, "quickMixStationIds", a); + + method = "user.setQuickMix"; break; } - case PIANO_REQUEST_GET_GENRE_STATIONS: + case PIANO_REQUEST_GET_GENRE_STATIONS: { /* receive list of pandora's genre stations */ - xmlSendBuf[0] = '\0'; - snprintf (req->urlPath, sizeof (req->urlPath), "/xml/genre?r=%lu", - (unsigned long) timestamp); + method = "station.getGenreStations"; break; + } case PIANO_REQUEST_TRANSFORM_STATION: { /* transform shared station into private */ @@ -613,18 +454,10 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, assert (station != NULL); - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" - "<methodCall><methodName>station.transformShared</methodName>" - "<params><param><value><int>%lu</int></value></param>" - /* auth token */ - "<param><value><string>%s</string></value></param>" - /* station id */ - "<param><value><string>%s</string></value></param>" - "</params></methodCall>", (unsigned long) timestamp, - ph->user.authToken, station->id); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&lid=%s&method=transformShared&arg1=%s", ph->routeId, - ph->user.listenerId, station->id); + json_object_object_add (j, "stationToken", + json_object_new_string (station->id)); + + method = "station.transformSharedStation"; break; } @@ -635,26 +468,15 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, assert (reqData != NULL); assert (reqData->song != NULL); - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" - "<methodCall><methodName>playlist.narrative</methodName>" - "<params><param><value><int>%lu</int></value></param>" - /* auth token */ - "<param><value><string>%s</string></value></param>" - /* station id */ - "<param><value><string>%s</string></value></param>" - /* music id */ - "<param><value><string>%s</string></value></param>" - "</params></methodCall>", (unsigned long) timestamp, - ph->user.authToken, reqData->song->stationId, - reqData->song->musicId); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&lid=%s&method=narrative&arg1=%s&arg2=%s", - ph->routeId, ph->user.listenerId, reqData->song->stationId, - reqData->song->musicId); + json_object_object_add (j, "trackToken", + json_object_new_string (reqData->song->trackToken)); + + method = "track.explainTrack"; break; } case PIANO_REQUEST_GET_SEED_SUGGESTIONS: { +#if 0 /* find similar artists */ PianoRequestDataGetSeedSuggestions_t *reqData = req->data; @@ -679,6 +501,7 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=getSeedSuggestions&arg1=%s&arg2=%u", ph->routeId, ph->user.listenerId, reqData->musicId, reqData->max); +#endif break; } @@ -688,21 +511,10 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, assert (song != NULL); - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" - "<methodCall><methodName>station.createBookmark</methodName>" - "<params><param><value><int>%lu</int></value></param>" - /* auth token */ - "<param><value><string>%s</string></value></param>" - /* station id */ - "<param><value><string>%s</string></value></param>" - /* music id */ - "<param><value><string>%s</string></value></param>" - "</params></methodCall>", (unsigned long) timestamp, - ph->user.authToken, song->stationId, song->musicId); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&lid=%s&method=createBookmark&arg1=%s&arg2=%s", - ph->routeId, ph->user.listenerId, song->stationId, - song->musicId); + json_object_object_add (j, "trackToken", + json_object_new_string (song->trackToken)); + + method = "bookmark.addSongBookmark"; break; } @@ -712,18 +524,10 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, assert (song != NULL); - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" - "<methodCall><methodName>station.createArtistBookmark</methodName>" - "<params><param><value><int>%lu</int></value></param>" - /* auth token */ - "<param><value><string>%s</string></value></param>" - /* music id */ - "<param><value><string>%s</string></value></param>" - "</params></methodCall>", (unsigned long) timestamp, - ph->user.authToken, song->artistMusicId); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&lid=%s&method=createArtistBookmark&arg1=%s", - ph->routeId, ph->user.listenerId, song->artistMusicId); + json_object_object_add (j, "trackToken", + json_object_new_string (song->trackToken)); + + method = "bookmark.addArtistBookmark"; break; } @@ -734,18 +538,12 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, assert (reqData != NULL); assert (reqData->station != NULL); - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" - "<methodCall><methodName>station.getStation</methodName>" - "<params><param><value><int>%lu</int></value></param>" - /* auth token */ - "<param><value><string>%s</string></value></param>" - /* station id */ - "<param><value><string>%s</string></value></param>" - "</params></methodCall>", (unsigned long) timestamp, - ph->user.authToken, reqData->station->id); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&lid=%s&method=getStation&arg1=%s", - ph->routeId, ph->user.listenerId, reqData->station->id); + json_object_object_add (j, "stationToken", + json_object_new_string (reqData->station->id)); + json_object_object_add (j, "includeExtendedAttributes", + json_object_new_boolean (true)); + + method = "station.getStation"; break; } @@ -754,18 +552,10 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, assert (song != NULL); - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" - "<methodCall><methodName>station.deleteFeedback</methodName>" - "<params><param><value><int>%lu</int></value></param>" - /* auth token */ - "<param><value><string>%s</string></value></param>" - /* feedback id */ - "<param><value><string>%s</string></value></param>" - "</params></methodCall>", (unsigned long) timestamp, - ph->user.authToken, song->feedbackId); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&lid=%s&method=deleteFeedback&arg1=%s", - ph->routeId, ph->user.listenerId, song->feedbackId); + json_object_object_add (j, "feedbackId", + json_object_new_string (song->feedbackId)); + + method = "station.deleteFeedback"; break; } @@ -787,18 +577,10 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, assert (seedId != NULL); - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" - "<methodCall><methodName>station.deleteSeed</methodName>" - "<params><param><value><int>%lu</int></value></param>" - /* auth token */ - "<param><value><string>%s</string></value></param>" - /* seed id */ - "<param><value><string>%s</string></value></param>" - "</params></methodCall>", (unsigned long) timestamp, - ph->user.authToken, seedId); - snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH - "rid=%s&lid=%s&method=deleteSeed&arg1=%s", - ph->routeId, ph->user.listenerId, seedId); + json_object_object_add (j, "seedId", + json_object_new_string (seedId)); + + method = "station.deleteMusic"; break; } @@ -866,24 +648,89 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, } } - if ((req->postData = PianoEncryptString (xmlSendBuf)) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; + /* standard parameter */ + if (method != NULL) { + char *urlencAuthToken; + + assert (ph->user.authToken != NULL); + + urlencAuthToken = WaitressUrlEncode (ph->user.authToken); + assert (urlencAuthToken != NULL); + + snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH + "method=%s&auth_token=%s&partner_id=%i&user_id=%s", method, + urlencAuthToken, ph->partnerId, ph->user.listenerId); + + free (urlencAuthToken); + + json_object_object_add (j, "userAuthToken", + json_object_new_string (ph->user.authToken)); + json_object_object_add (j, "syncTime", + json_object_new_int (timestamp)); + } + + /* json to string */ + jsonSendBuf = json_object_to_json_string (j); + if (encrypted) { + if ((req->postData = PianoEncryptString (jsonSendBuf)) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + } else { + req->postData = strdup (jsonSendBuf); } + json_object_put (j); return PIANO_RET_OK; } +static char *PianoJsonStrdup (json_object *j, const char *key) { + return strdup (json_object_get_string (json_object_object_get (j, key))); +} + +static void PianoJsonParseStation (json_object *j, PianoStation_t *s) { + s->name = PianoJsonStrdup (j, "stationName"); + s->id = PianoJsonStrdup (j, "stationToken"); + s->isCreator = !json_object_get_boolean (json_object_object_get (j, + "isShared")); + s->isQuickMix = json_object_get_boolean (json_object_object_get (j, + "isQuickMix")); +} + /* 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_ERR; + PianoReturn_t ret = PIANO_RET_OK; + json_object *j, *result, *status; assert (ph != NULL); assert (req != NULL); + j = json_tokener_parse (req->responseData); + + status = json_object_object_get (j, "stat"); + if (status == NULL) { + json_object_put (j); + return PIANO_RET_INVALID_RESPONSE; + } + + /* error handling */ + if (strcmp (json_object_get_string (status), "ok") != 0) { + json_object *code = json_object_object_get (j, "code"); + if (code == NULL) { + ret = PIANO_RET_INVALID_RESPONSE; + } else { + ret = json_object_get_int (code)+PIANO_RET_OFFSET; + } + + json_object_put (j); + return ret; + } + + result = json_object_object_get (j, "result"); + switch (req->type) { case PIANO_REQUEST_LOGIN: { /* authenticate user */ @@ -894,29 +741,28 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { switch (reqData->step) { case 0: { - char *cryptedTimestamp = NULL; - - assert (req->responseData != NULL); - - /* abusing parseNarrative; has same xml structure */ - ret = PianoXmlParseNarrative (req->responseData, &cryptedTimestamp); - if (ret == PIANO_RET_OK && cryptedTimestamp != NULL) { - unsigned long timestamp = 0; - const time_t realTimestamp = time (NULL); - char *decryptedTimestamp = NULL; - size_t decryptedSize; - - ret = PIANO_RET_ERR; - if ((decryptedTimestamp = PianoDecryptString (cryptedTimestamp, - &decryptedSize)) != NULL && decryptedSize > 4) { - /* skip four bytes garbage(?) at beginning */ - timestamp = strtoul (decryptedTimestamp+4, NULL, 0); - ph->timeOffset = realTimestamp - timestamp; - ret = PIANO_RET_CONTINUE_REQUEST; - } - free (decryptedTimestamp); + /* decrypt timestamp */ + const char *cryptedTimestamp = json_object_get_string ( + json_object_object_get (result, "syncTime")); + unsigned long timestamp = 0; + const time_t realTimestamp = time (NULL); + char *decryptedTimestamp = NULL; + size_t decryptedSize; + + ret = PIANO_RET_ERR; + if ((decryptedTimestamp = PianoDecryptString (cryptedTimestamp, + &decryptedSize)) != NULL && decryptedSize > 4) { + /* skip four bytes garbage(?) at beginning */ + timestamp = strtoul (decryptedTimestamp+4, NULL, 0); + ph->timeOffset = realTimestamp - timestamp; + ret = PIANO_RET_CONTINUE_REQUEST; } - free (cryptedTimestamp); + free (decryptedTimestamp); + /* get auth token */ + ph->partnerAuthToken = PianoJsonStrdup (result, + "partnerAuthToken"); + ph->partnerId = json_object_get_int ( + json_object_object_get (result, "partnerId")); ++reqData->step; break; } @@ -927,48 +773,145 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { if (ph->user.listenerId != NULL) { PianoDestroyUserInfo (&ph->user); } - ret = PianoXmlParseUserinfo (ph, req->responseData); + ph->user.listenerId = PianoJsonStrdup (result, "userId"); + ph->user.authToken = PianoJsonStrdup (result, + "userAuthToken"); break; } break; } - case PIANO_REQUEST_GET_STATIONS: + case PIANO_REQUEST_GET_STATIONS: { /* get stations */ assert (req->responseData != NULL); - - ret = PianoXmlParseStations (ph, req->responseData); + + json_object *stations = json_object_object_get (result, + "stations"), *mix = NULL; + + for (size_t 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; + } + + PianoJsonParseStation (s, tmpStation); + + if (tmpStation->isQuickMix) { + /* fix flags on other stations later */ + mix = json_object_object_get (s, "quickMixStationIds"); + } + + /* start new linked list or append */ + if (ph->stations == NULL) { + ph->stations = tmpStation; + } else { + PianoStation_t *curStation = ph->stations; + while (curStation->next != NULL) { + curStation = curStation->next; + } + curStation->next = tmpStation; + } + } + + /* fix quickmix flags */ + if (mix != NULL) { + PianoStation_t *curStation = ph->stations; + while (curStation != NULL) { + for (size_t 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; + } + } + curStation = curStation->next; + } + } 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); - reqData->retPlaylist = NULL; - ret = PianoXmlParsePlaylist (ph, req->responseData, - &reqData->retPlaylist); + json_object *items = json_object_object_get (result, "items"); + assert (items != NULL); + + for (size_t 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 (s, "artistName") == NULL) { + free (song); + continue; + } + song->audioUrl = strdup (json_object_get_string (json_object_object_get (json_object_object_get (json_object_object_get (s, "audioUrlMap"), "highQuality"), "audioUrl"))); + 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->fileGain = json_object_get_double ( + json_object_object_get (s, "trackGain")); + song->audioFormat = PIANO_AF_AACPLUS; + switch (json_object_get_int (json_object_object_get (s, + "songRating"))) { + case 1: + song->rating = PIANO_RATE_LOVE; + break; + } + + /* begin linked list or append */ + if (playlist == NULL) { + playlist = song; + } else { + PianoSong_t *curSong = playlist; + while (curSong->next != NULL) { + curSong = curSong->next; + } + curSong->next = song; + } + } + + reqData->retPlaylist = playlist; break; } - case PIANO_REQUEST_RATE_SONG: + case PIANO_REQUEST_RATE_SONG: { /* love/ban song */ - assert (req->responseData != NULL); - - ret = PianoXmlParseSimple (req->responseData); - if (ret == PIANO_RET_OK) { - PianoRequestDataRateSong_t *reqData = req->data; - reqData->song->rating = reqData->rating; - } + 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_MOVE_SONG: { /* move song to different station */ PianoRequestDataMoveSong_t *reqData = req->data; @@ -977,107 +920,204 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { assert (reqData != NULL); assert (reqData->step < 2); - ret = PianoXmlParseSimple (req->responseData); - if (ret == PIANO_RET_OK && reqData->step == 0) { + if (reqData->step == 0) { ret = PIANO_RET_CONTINUE_REQUEST; ++reqData->step; } break; } - case PIANO_REQUEST_RENAME_STATION: - /* rename station and update PianoStation_t structure */ - assert (req->responseData != NULL); - - if ((ret = PianoXmlParseSimple (req->responseData)) == PIANO_RET_OK) { - PianoRequestDataRenameStation_t *reqData = req->data; + case PIANO_REQUEST_DELETE_STATION: { + /* delete station from server and station list */ + PianoStation_t *station = req->data; - assert (reqData != NULL); - assert (reqData->station != NULL); - assert (reqData->newName != NULL); + assert (station != NULL); - free (reqData->station->name); - reqData->station->name = strdup (reqData->newName); + /* delete station from local station list */ + PianoStation_t *curStation = ph->stations, *lastStation = NULL; + while (curStation != NULL) { + if (curStation == station) { + if (lastStation != NULL) { + lastStation->next = curStation->next; + } else { + /* first station in list */ + ph->stations = curStation->next; + } + PianoDestroyStation (curStation); + free (curStation); + break; + } + lastStation = curStation; + curStation = curStation->next; } break; + } + + case PIANO_REQUEST_SEARCH: { + /* search artist/song */ + PianoRequestDataSearch_t *reqData = req->data; + PianoSearchResult_t *searchResult; - case PIANO_REQUEST_DELETE_STATION: - /* delete station from server and station list */ assert (req->responseData != NULL); + assert (reqData != NULL); - if ((ret = PianoXmlParseSimple (req->responseData)) == PIANO_RET_OK) { - PianoStation_t *station = req->data; + searchResult = &reqData->searchResult; + memset (searchResult, 0, sizeof (*searchResult)); - assert (station != NULL); + /* get artists */ + json_object *artists = json_object_object_get (result, "artists"); + if (artists != NULL) { + for (size_t i=0; i < json_object_array_length (artists); i++) { + json_object *a = json_object_array_get_idx (artists, i); + PianoArtist_t *artist; - /* delete station from local station list */ - PianoStation_t *curStation = ph->stations, *lastStation = NULL; - while (curStation != NULL) { - if (curStation == station) { - if (lastStation != NULL) { - lastStation->next = curStation->next; - } else { - /* first station in list */ - ph->stations = curStation->next; + if ((artist = calloc (1, sizeof (*artist))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + artist->name = PianoJsonStrdup (a, "artistName"); + artist->musicId = PianoJsonStrdup (a, "musicToken"); + + /* add result to linked list */ + if (searchResult->artists == NULL) { + searchResult->artists = artist; + } else { + PianoArtist_t *curArtist = searchResult->artists; + while (curArtist->next != NULL) { + curArtist = curArtist->next; } - PianoDestroyStation (curStation); - free (curStation); - break; + curArtist->next = artist; } - lastStation = curStation; - curStation = curStation->next; } } - break; - case PIANO_REQUEST_SEARCH: { - /* search artist/song */ - PianoRequestDataSearch_t *reqData = req->data; + /* get songs */ + json_object *songs = json_object_object_get (result, "songs"); + if (songs != NULL) { + for (size_t i=0; i < json_object_array_length (songs); i++) { + json_object *s = json_object_array_get_idx (songs, i); + PianoSong_t *song; - assert (req->responseData != NULL); - assert (reqData != NULL); + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } - ret = PianoXmlParseSearch (req->responseData, &reqData->searchResult); + song->title = PianoJsonStrdup (s, "songName"); + song->artist = PianoJsonStrdup (s, "artistName"); + song->musicId = PianoJsonStrdup (s, "musicToken"); + + /* add result to linked list */ + if (searchResult->songs == NULL) { + searchResult->songs = song; + } else { + PianoSong_t *curSong = searchResult->songs; + while (curSong->next != NULL) { + curSong = curSong->next; + } + curSong->next = song; + } + } + } break; } case PIANO_REQUEST_CREATE_STATION: { /* create station, insert new station into station list on success */ - assert (req->responseData != NULL); + PianoStation_t *tmpStation; - ret = PianoXmlParseCreateStation (ph, req->responseData); - break; - } - - case PIANO_REQUEST_ADD_SEED: { - /* add seed to station, updates station structure */ - PianoRequestDataAddSeed_t *reqData = req->data; + if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } - assert (req->responseData != NULL); - assert (reqData != NULL); - assert (reqData->station != NULL); + PianoJsonParseStation (result, tmpStation); - /* FIXME: update station data instead of replacing them */ - ret = PianoXmlParseAddSeed (ph, req->responseData, reqData->station); + /* start new linked list or append */ + if (ph->stations == NULL) { + ph->stations = tmpStation; + } else { + PianoStation_t *curStation = ph->stations; + while (curStation->next != NULL) { + curStation = curStation->next; + } + curStation->next = tmpStation; + } break; } + case PIANO_REQUEST_ADD_SEED: case PIANO_REQUEST_ADD_TIRED_SONG: case PIANO_REQUEST_SET_QUICKMIX: case PIANO_REQUEST_BOOKMARK_SONG: case PIANO_REQUEST_BOOKMARK_ARTIST: case PIANO_REQUEST_DELETE_FEEDBACK: - assert (req->responseData != NULL); - - ret = PianoXmlParseSimple (req->responseData); + case PIANO_REQUEST_DELETE_SEED: + /* response unused */ break; - case PIANO_REQUEST_GET_GENRE_STATIONS: + case PIANO_REQUEST_GET_GENRE_STATIONS: { /* get genre stations */ - assert (req->responseData != NULL); + json_object *categories = json_object_object_get (result, "categories"); + if (categories != NULL) { + for (size_t 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; + } - ret = PianoXmlParseGenreExplorer (ph, req->responseData); + tmpGenreCategory->name = PianoJsonStrdup (c, + "categoryName"); + + /* get genre subnodes */ + json_object *stations = json_object_object_get (c, + "stations"); + if (stations != NULL) { + for (size_t 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"); + + /* append station */ + if (tmpGenreCategory->genres == NULL) { + tmpGenreCategory->genres = tmpGenre; + } else { + PianoGenre_t *curGenre = + tmpGenreCategory->genres; + while (curGenre->next != NULL) { + curGenre = curGenre->next; + } + curGenre->next = tmpGenre; + } + } + } + /* append category */ + if (ph->genreStations == NULL) { + ph->genreStations = tmpGenreCategory; + } else { + PianoGenreCategory_t *curCat = ph->genreStations; + while (curCat->next != NULL) { + curCat = curCat->next; + } + curCat->next = tmpGenreCategory; + } + } + } break; + } case PIANO_REQUEST_TRANSFORM_STATION: { /* transform shared station into private and update isCreator flag */ @@ -1086,27 +1126,51 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { assert (req->responseData != NULL); assert (station != NULL); - /* though this call returns a bunch of "new" data only this one is - * changed and important (at the moment) */ - if ((ret = PianoXmlParseTranformStation (req->responseData)) == - PIANO_RET_OK) { - station->isCreator = 1; - } + station->isCreator = 1; break; } case PIANO_REQUEST_EXPLAIN: { /* explain why song was selected */ PianoRequestDataExplain_t *reqData = req->data; + const size_t strSize = 1024; + size_t pos = 0; - assert (req->responseData != NULL); assert (reqData != NULL); - ret = PianoXmlParseNarrative (req->responseData, &reqData->retExplain); + json_object *explanations = json_object_object_get (result, + "explanations"); + if (explanations != NULL) { + reqData->retExplain = malloc (strSize * + sizeof (*reqData->retExplain)); + strncpy (reqData->retExplain, "We're playing this track " + "because it features ", strSize); + pos = strlen (reqData->retExplain); + for (size_t i=0; i < json_object_array_length (explanations); i++) { + json_object *e = json_object_array_get_idx (explanations, + i); + const char *s = json_object_get_string ( + json_object_object_get (e, "focusTraitName")); + + strncpy (&reqData->retExplain[pos], s, strSize-pos-1); + pos += strlen (s); + if (i < json_object_array_length (explanations)-2) { + strncpy (&reqData->retExplain[pos], ", ", strSize-pos-1); + pos += 2; + } else if (i == json_object_array_length (explanations)-2) { + strncpy (&reqData->retExplain[pos], " and ", strSize-pos-1); + pos += 5; + } else { + strncpy (&reqData->retExplain[pos], ".", strSize-pos-1); + pos += 1; + } + } + } break; } case PIANO_REQUEST_GET_SEED_SUGGESTIONS: { +#if 0 /* find similar artists */ PianoRequestDataGetSeedSuggestions_t *reqData = req->data; @@ -1115,29 +1179,122 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { ret = PianoXmlParseSeedSuggestions (req->responseData, &reqData->searchResult); +#endif break; } case PIANO_REQUEST_GET_STATION_INFO: { /* get station information (seeds and feedback) */ PianoRequestDataGetStationInfo_t *reqData = req->data; + PianoStationInfo_t *info; - assert (req->responseData != NULL); assert (reqData != NULL); - ret = PianoXmlParseGetStationInfo (req->responseData, - &reqData->info); - break; - } + info = &reqData->info; + assert (info != NULL); + + /* parse music seeds */ + json_object *music = json_object_object_get (result, "music"); + if (music != NULL) { + /* songs */ + json_object *songs = json_object_object_get (music, "songs"); + if (songs != NULL) { + for (size_t 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; + } - case PIANO_REQUEST_DELETE_SEED: { - assert (req->responseData != NULL); + seedSong->title = PianoJsonStrdup (s, "songName"); + seedSong->artist = PianoJsonStrdup (s, "artistName"); + seedSong->seedId = PianoJsonStrdup (s, "seedId"); - /* dummy function, checks for errors only */ - ret = PianoXmlParseTranformStation (req->responseData); + if (info->songSeeds == NULL) { + info->songSeeds = seedSong; + } else { + PianoSong_t *curSong = info->songSeeds; + while (curSong->next != NULL) { + curSong = curSong->next; + } + curSong->next = seedSong; + } + } + } + + /* artists */ + json_object *artists = json_object_object_get (music, + "artists"); + if (artists != NULL) { + for (size_t 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"); + + if (info->artistSeeds == NULL) { + info->artistSeeds = seedArtist; + } else { + PianoArtist_t *curArtist = info->artistSeeds; + while (curArtist->next != NULL) { + curArtist = curArtist->next; + } + curArtist->next = seedArtist; + } + } + } + } + + /* parse feedback */ + json_object *feedback = json_object_object_get (result, + "feedback"); + if (feedback != NULL) { + json_object_object_foreach (feedback, key, val) { + for (size_t 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 = json_object_get_boolean ( + json_object_object_get (s, "isPositive")) ? + PIANO_RATE_LOVE : PIANO_RATE_BAN; + + + if (info->feedback == NULL) { + info->feedback = feedbackSong; + } else { + PianoSong_t *curSong = info->feedback; + while (curSong->next != NULL) { + curSong = curSong->next; + } + curSong->next = feedbackSong; + } + } + } + } + break; } } + json_object_put (j); + return ret; } @@ -1171,70 +1328,51 @@ const char *PianoErrorToStr (PianoReturn_t ret) { return "Unknown."; break; - case PIANO_RET_XML_INVALID: - return "Invalid XML."; - break; - - case PIANO_RET_AUTH_TOKEN_INVALID: - return "Invalid auth token."; - break; - - case PIANO_RET_AUTH_USER_PASSWORD_INVALID: - return "Username and/or password not correct."; - break; - - case PIANO_RET_NOT_AUTHORIZED: - return "Not authorized."; - break; - - case PIANO_RET_PROTOCOL_INCOMPATIBLE: - return "Protocol incompatible. Please upgrade " PACKAGE "."; + case PIANO_RET_INVALID_RESPONSE: + return "Invalid response."; break; - case PIANO_RET_READONLY_MODE: - return "Request cannot be completed at this time, please try " - "again later."; - break; - - case PIANO_RET_STATION_CODE_INVALID: - return "Station id is invalid."; + case PIANO_RET_CONTINUE_REQUEST: + /* never shown to the user */ + assert (0); + return "Fix your program."; break; - case PIANO_RET_IP_REJECTED: - return "Your ip address was rejected. Please setup a control " - "proxy (see manpage)."; + case PIANO_RET_OUT_OF_MEMORY: + return "Out of memory."; break; - case PIANO_RET_STATION_NONEXISTENT: - return "Station does not exist."; + /* pandora error messages */ + case PIANO_RET_P_INTERNAL: + return "Internal error."; break; - case PIANO_RET_OUT_OF_MEMORY: - return "Out of memory."; + case PIANO_RET_P_CALL_NOT_ALLOWED: + return "Call not allowed."; break; - case PIANO_RET_OUT_OF_SYNC: - return "Out of sync. Please correct your system's time."; + case PIANO_RET_P_INVALID_AUTH_TOKEN: + return "Invalid auth token."; break; - case PIANO_RET_PLAYLIST_END: - return "Playlist end."; + case PIANO_RET_P_MAINTENANCE_MODE: + return "Maintenance mode."; break; - case PIANO_RET_QUICKMIX_NOT_PLAYABLE: - return "Quickmix not playable."; + case PIANO_RET_P_MAX_STATIONS_REACHED: + return "Max number of stations reached."; break; - case PIANO_RET_REMOVING_TOO_MANY_SEEDS: - return "Last seed cannot be removed."; + case PIANO_RET_P_READ_ONLY_MODE: + return "Read only mode. Try again later."; break; - case PIANO_RET_EXCESSIVE_ACTIVITY: - return "Excessive activity."; + case PIANO_RET_P_STATION_DOES_NOT_EXIST: + return "Station does not exist."; break; - case PIANO_RET_DAILY_SKIP_LIMIT_REACHED: - return "Daily skip limit reached."; + case PIANO_RET_P_INVALID_PARTNER_LOGIN: + return "Invalid partner login."; break; default: |