summaryrefslogtreecommitdiff
path: root/src/libpiano
diff options
context:
space:
mode:
Diffstat (limited to 'src/libpiano')
-rw-r--r--src/libpiano/piano.c1096
-rw-r--r--src/libpiano/piano.h1
-rw-r--r--src/libpiano/piano_private.h9
-rw-r--r--src/libpiano/request.c509
-rw-r--r--src/libpiano/response.c653
5 files changed, 1169 insertions, 1099 deletions
diff --git a/src/libpiano/piano.c b/src/libpiano/piano.c
index 0ac96b6..f9dfa51 100644
--- a/src/libpiano/piano.c
+++ b/src/libpiano/piano.c
@@ -29,22 +29,13 @@ THE SOFTWARE.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
-#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 "crypt.h"
#include "config.h"
-#define PIANO_RPC_PATH "/services/json/?"
-#define PIANO_SEND_BUFFER_SIZE 10000
-
/* initialize piano handle
* @param piano handle
* @return nothing
@@ -154,7 +145,7 @@ static void PianoDestroyGenres (PianoGenre_t *genres) {
/* destroy user information
*/
-static void PianoDestroyUserInfo (PianoUserInfo_t *user) {
+void PianoDestroyUserInfo (PianoUserInfo_t *user) {
free (user->authToken);
free (user->listenerId);
}
@@ -212,1091 +203,6 @@ static const char *PianoAudioFormatToString (PianoAudioFormat_t format) {
}
}
-/* prepare piano request (initializes request type, urlpath and postData)
- * @param piano handle
- * @param request structure
- * @param request type
- */
-PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,
- PianoRequestType_t type) {
- 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);
-
- req->type = type;
- /* no tls by default */
- req->secure = false;
-
- switch (req->type) {
- case PIANO_REQUEST_LOGIN: {
- /* authenticate user */
- PianoRequestDataLogin_t *logindata = req->data;
-
- assert (logindata != NULL);
-
- switch (logindata->step) {
- case 0:
- 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 *urlencAuthToken;
-
- req->secure = true;
-
- 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);
-
- break;
- }
- }
- break;
- }
-
- case PIANO_REQUEST_GET_STATIONS: {
- /* get stations, user must be authenticated */
- assert (ph->user.listenerId != NULL);
- method = "user.getStationList";
- break;
- }
-
- case PIANO_REQUEST_GET_PLAYLIST: {
- /* get playlist for specified station */
- PianoRequestDataGetPlaylist_t *reqData = req->data;
-
- assert (reqData != NULL);
- assert (reqData->station != NULL);
- assert (reqData->station->id != NULL);
- assert (reqData->format != PIANO_AF_UNKNOWN);
-
- req->secure = true;
-
- json_object_object_add (j, "stationToken",
- json_object_new_string (reqData->station->id));
-
- method = "station.getPlaylist";
- break;
- }
-
- case PIANO_REQUEST_ADD_FEEDBACK: {
- /* low-level, don't use directly (see _RATE_SONG and _MOVE_SONG) */
- PianoRequestDataAddFeedback_t *reqData = req->data;
-
- assert (reqData != NULL);
- assert (reqData->trackToken != NULL);
- assert (reqData->rating != PIANO_RATE_NONE);
-
- 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: {
- PianoRequestDataRenameStation_t *reqData = req->data;
-
- assert (reqData != NULL);
- assert (reqData->station != NULL);
- assert (reqData->newName != NULL);
-
- 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));
-
- method = "station.renameStation";
- break;
- }
-
- case PIANO_REQUEST_DELETE_STATION: {
- /* delete station */
- PianoStation_t *station = req->data;
-
- assert (station != NULL);
- assert (station->id != NULL);
-
- 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;
-
- assert (reqData != NULL);
- assert (reqData->searchStr != NULL);
-
- json_object_object_add (j, "searchText",
- json_object_new_string (reqData->searchStr));
-
- method = "music.search";
- break;
- }
-
- case PIANO_REQUEST_CREATE_STATION: {
- /* create new station from specified musicid (type=mi, get one by
- * performing a search) or shared station id (type=sh) */
- PianoRequestDataCreateStation_t *reqData = req->data;
-
- assert (reqData != NULL);
- assert (reqData->id != NULL);
-
- json_object_object_add (j, "musicToken",
- json_object_new_string (reqData->id));
-
- method = "station.createStation";
- break;
- }
-
- case PIANO_REQUEST_ADD_SEED: {
- /* add another seed to specified station */
- PianoRequestDataAddSeed_t *reqData = req->data;
-
- assert (reqData != NULL);
- assert (reqData->station != NULL);
- assert (reqData->musicId != NULL);
-
- 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;
- }
-
- case PIANO_REQUEST_ADD_TIRED_SONG: {
- /* ban song for a month from all stations */
- PianoSong_t *song = req->data;
-
- assert (song != NULL);
-
- 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) */
- PianoStation_t *curStation = ph->stations;
- json_object *a = json_object_new_array ();
-
- while (curStation != NULL) {
- /* quick mix can't contain itself */
- if (curStation->useQuickMix && !curStation->isQuickMix) {
- json_object_array_add (a,
- json_object_new_string (curStation->id));
- }
-
- curStation = curStation->next;
- }
-
- json_object_object_add (j, "quickMixStationIds", a);
-
- method = "user.setQuickMix";
- break;
- }
-
- case PIANO_REQUEST_GET_GENRE_STATIONS: {
- /* receive list of pandora's genre stations */
- method = "station.getGenreStations";
- break;
- }
-
- case PIANO_REQUEST_TRANSFORM_STATION: {
- /* transform shared station into private */
- PianoStation_t *station = req->data;
-
- assert (station != NULL);
-
- json_object_object_add (j, "stationToken",
- json_object_new_string (station->id));
-
- method = "station.transformSharedStation";
- break;
- }
-
- case PIANO_REQUEST_EXPLAIN: {
- /* explain why particular song was played */
- PianoRequestDataExplain_t *reqData = req->data;
-
- assert (reqData != NULL);
- assert (reqData->song != NULL);
-
- 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;
-
- assert (reqData != NULL);
- assert (reqData->musicId != NULL);
- assert (reqData->max != 0);
-
- snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
- "<methodCall><methodName>music.getSeedSuggestions</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>"
- /* seed music id */
- "<param><value><string>%s</string></value></param>"
- /* max */
- "<param><value><int>%u</int></value></param>"
- "</params></methodCall>", (unsigned long) timestamp,
- ph->user.authToken, reqData->station->id, reqData->musicId,
- reqData->max);
- 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;
- }
-
- case PIANO_REQUEST_BOOKMARK_SONG: {
- /* bookmark song */
- PianoSong_t *song = req->data;
-
- assert (song != NULL);
-
- json_object_object_add (j, "trackToken",
- json_object_new_string (song->trackToken));
-
- method = "bookmark.addSongBookmark";
- break;
- }
-
- case PIANO_REQUEST_BOOKMARK_ARTIST: {
- /* bookmark artist */
- PianoSong_t *song = req->data;
-
- assert (song != NULL);
-
- json_object_object_add (j, "trackToken",
- json_object_new_string (song->trackToken));
-
- method = "bookmark.addArtistBookmark";
- break;
- }
-
- case PIANO_REQUEST_GET_STATION_INFO: {
- /* get station information (seeds and feedback) */
- PianoRequestDataGetStationInfo_t *reqData = req->data;
-
- assert (reqData != NULL);
- assert (reqData->station != NULL);
-
- 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;
- }
-
- case PIANO_REQUEST_DELETE_FEEDBACK: {
- PianoSong_t *song = req->data;
-
- assert (song != NULL);
-
- json_object_object_add (j, "feedbackId",
- json_object_new_string (song->feedbackId));
-
- method = "station.deleteFeedback";
- break;
- }
-
- case PIANO_REQUEST_DELETE_SEED: {
- PianoRequestDataDeleteSeed_t *reqData = req->data;
- char *seedId = NULL;
-
- assert (reqData != NULL);
- assert (reqData->song != NULL || reqData->artist != NULL ||
- reqData->station != NULL);
-
- if (reqData->song != NULL) {
- seedId = reqData->song->seedId;
- } else if (reqData->artist != NULL) {
- seedId = reqData->artist->seedId;
- } else if (reqData->station != NULL) {
- seedId = reqData->station->seedId;
- }
-
- assert (seedId != NULL);
-
- json_object_object_add (j, "seedId",
- json_object_new_string (seedId));
-
- method = "station.deleteMusic";
- break;
- }
-
- /* "high-level" wrapper */
- case PIANO_REQUEST_RATE_SONG: {
- /* love/ban song */
- PianoRequestDataRateSong_t *reqData = req->data;
- PianoReturn_t pRet;
-
- assert (reqData != NULL);
- assert (reqData->song != NULL);
- assert (reqData->rating != PIANO_RATE_NONE);
-
- PianoRequestDataAddFeedback_t transformedReqData;
- transformedReqData.stationId = reqData->song->stationId;
- transformedReqData.trackToken = reqData->song->trackToken;
- transformedReqData.rating = reqData->rating;
- req->data = &transformedReqData;
-
- /* create request data (url, post data) */
- pRet = PianoRequest (ph, req, PIANO_REQUEST_ADD_FEEDBACK);
- /* and reset request type/data */
- req->type = PIANO_REQUEST_RATE_SONG;
- req->data = reqData;
-
- return pRet;
- break;
- }
-
- case PIANO_REQUEST_MOVE_SONG: {
- /* move song to a different station, needs two requests */
- PianoRequestDataMoveSong_t *reqData = req->data;
- PianoRequestDataAddFeedback_t transformedReqData;
- PianoReturn_t pRet;
-
- assert (reqData != NULL);
- assert (reqData->song != NULL);
- assert (reqData->from != NULL);
- assert (reqData->to != NULL);
- assert (reqData->step < 2);
-
- transformedReqData.trackToken = reqData->song->trackToken;
- req->data = &transformedReqData;
-
- switch (reqData->step) {
- case 0:
- transformedReqData.stationId = reqData->from->id;
- transformedReqData.rating = PIANO_RATE_BAN;
- break;
-
- case 1:
- transformedReqData.stationId = reqData->to->id;
- transformedReqData.rating = PIANO_RATE_LOVE;
- break;
- }
-
- /* create request data (url, post data) */
- pRet = PianoRequest (ph, req, PIANO_REQUEST_ADD_FEEDBACK);
- /* and reset request type/data */
- req->type = PIANO_REQUEST_MOVE_SONG;
- req->data = reqData;
-
- return pRet;
- break;
- }
- }
-
- /* 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_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 */
- PianoRequestDataLogin_t *reqData = req->data;
-
- assert (req->responseData != NULL);
- assert (reqData != NULL);
-
- switch (reqData->step) {
- case 0: {
- /* 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 (decryptedTimestamp);
- /* get auth token */
- ph->partnerAuthToken = PianoJsonStrdup (result,
- "partnerAuthToken");
- ph->partnerId = json_object_get_int (
- json_object_object_get (result, "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");
- break;
- }
- break;
- }
-
- case PIANO_REQUEST_GET_STATIONS: {
- /* get stations */
- assert (req->responseData != NULL);
-
- 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);
-
- 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: {
- /* 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_MOVE_SONG: {
- /* move song to different station */
- PianoRequestDataMoveSong_t *reqData = req->data;
-
- assert (req->responseData != NULL);
- assert (reqData != NULL);
- assert (reqData->step < 2);
-
- if (reqData->step == 0) {
- ret = PIANO_RET_CONTINUE_REQUEST;
- ++reqData->step;
- }
- break;
- }
-
- case PIANO_REQUEST_DELETE_STATION: {
- /* delete station from server and station list */
- PianoStation_t *station = req->data;
-
- assert (station != NULL);
-
- /* 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;
-
- assert (req->responseData != NULL);
- assert (reqData != NULL);
-
- searchResult = &reqData->searchResult;
- memset (searchResult, 0, sizeof (*searchResult));
-
- /* 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;
-
- 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;
- }
- curArtist->next = artist;
- }
- }
- }
-
- /* 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;
-
- 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");
-
- /* 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 */
- PianoStation_t *tmpStation;
-
- if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) {
- return PIANO_RET_OUT_OF_MEMORY;
- }
-
- PianoJsonParseStation (result, tmpStation);
-
- /* 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:
- case PIANO_REQUEST_DELETE_SEED:
- /* response unused */
- break;
-
- case PIANO_REQUEST_GET_GENRE_STATIONS: {
- /* get genre stations */
- 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;
- }
-
- 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 */
- 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 = 1024;
- size_t pos = 0;
-
- assert (reqData != NULL);
-
- 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;
-
- assert (req->responseData != NULL);
- assert (reqData != NULL);
-
- 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 (reqData != NULL);
-
- 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;
- }
-
- seedSong->title = PianoJsonStrdup (s, "songName");
- seedSong->artist = PianoJsonStrdup (s, "artistName");
- seedSong->seedId = PianoJsonStrdup (s, "seedId");
-
- 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;
-}
/* get station from list by id
* @param search here
diff --git a/src/libpiano/piano.h b/src/libpiano/piano.h
index 8a21d05..6d20747 100644
--- a/src/libpiano/piano.h
+++ b/src/libpiano/piano.h
@@ -36,6 +36,7 @@ THE SOFTWARE.
*/
#define PIANO_RPC_HOST "tuner.pandora.com"
+#define PIANO_RPC_PATH "/services/json/?"
typedef struct PianoUserInfo {
char *listenerId;
diff --git a/src/libpiano/piano_private.h b/src/libpiano/piano_private.h
index 726fc02..751f98d 100644
--- a/src/libpiano/piano_private.h
+++ b/src/libpiano/piano_private.h
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2008-2010
+Copyright (c) 2008-2012
Lars-Dominik Braun <lars@6xq.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -21,11 +21,12 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
-#ifndef _MAIN_H
-#define _MAIN_H
+#ifndef _PIANO_PRIVATE_H
+#define _PIANO_PRIVATE_H
#include "piano.h"
void PianoDestroyStation (PianoStation_t *station);
+void PianoDestroyUserInfo (PianoUserInfo_t *user);
-#endif /* _MAIN_H */
+#endif /* _PIANO_PRIVATE_H */
diff --git a/src/libpiano/request.c b/src/libpiano/request.c
new file mode 100644
index 0000000..a5a33e9
--- /dev/null
+++ b/src/libpiano/request.c
@@ -0,0 +1,509 @@
+/*
+Copyright (c) 2008-2012
+ Lars-Dominik Braun <lars@6xq.net>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#ifndef __FreeBSD__
+#define _BSD_SOURCE /* required by strdup() */
+#define _DARWIN_C_SOURCE /* strdup() on OS X */
+#endif
+
+#include <json.h>
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+/* needed for urlencode */
+#include <waitress.h>
+
+#include "piano.h"
+#include "crypt.h"
+
+/* prepare piano request (initializes request type, urlpath and postData)
+ * @param piano handle
+ * @param request structure
+ * @param request type
+ */
+PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,
+ PianoRequestType_t type) {
+ 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);
+
+ req->type = type;
+ /* no tls by default */
+ req->secure = false;
+
+ switch (req->type) {
+ case PIANO_REQUEST_LOGIN: {
+ /* authenticate user */
+ PianoRequestDataLogin_t *logindata = req->data;
+
+ assert (logindata != NULL);
+
+ switch (logindata->step) {
+ case 0:
+ 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 *urlencAuthToken;
+
+ req->secure = true;
+
+ 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);
+
+ break;
+ }
+ }
+ break;
+ }
+
+ case PIANO_REQUEST_GET_STATIONS: {
+ /* get stations, user must be authenticated */
+ assert (ph->user.listenerId != NULL);
+ method = "user.getStationList";
+ break;
+ }
+
+ case PIANO_REQUEST_GET_PLAYLIST: {
+ /* get playlist for specified station */
+ PianoRequestDataGetPlaylist_t *reqData = req->data;
+
+ assert (reqData != NULL);
+ assert (reqData->station != NULL);
+ assert (reqData->station->id != NULL);
+ assert (reqData->format != PIANO_AF_UNKNOWN);
+
+ req->secure = true;
+
+ json_object_object_add (j, "stationToken",
+ json_object_new_string (reqData->station->id));
+
+ method = "station.getPlaylist";
+ break;
+ }
+
+ case PIANO_REQUEST_ADD_FEEDBACK: {
+ /* low-level, don't use directly (see _RATE_SONG and _MOVE_SONG) */
+ PianoRequestDataAddFeedback_t *reqData = req->data;
+
+ assert (reqData != NULL);
+ assert (reqData->trackToken != NULL);
+ assert (reqData->rating != PIANO_RATE_NONE);
+
+ 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: {
+ PianoRequestDataRenameStation_t *reqData = req->data;
+
+ assert (reqData != NULL);
+ assert (reqData->station != NULL);
+ assert (reqData->newName != NULL);
+
+ 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));
+
+ method = "station.renameStation";
+ break;
+ }
+
+ case PIANO_REQUEST_DELETE_STATION: {
+ /* delete station */
+ PianoStation_t *station = req->data;
+
+ assert (station != NULL);
+ assert (station->id != NULL);
+
+ 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;
+
+ assert (reqData != NULL);
+ assert (reqData->searchStr != NULL);
+
+ json_object_object_add (j, "searchText",
+ json_object_new_string (reqData->searchStr));
+
+ method = "music.search";
+ break;
+ }
+
+ case PIANO_REQUEST_CREATE_STATION: {
+ /* create new station from specified musicid (type=mi, get one by
+ * performing a search) or shared station id (type=sh) */
+ PianoRequestDataCreateStation_t *reqData = req->data;
+
+ assert (reqData != NULL);
+ assert (reqData->id != NULL);
+
+ json_object_object_add (j, "musicToken",
+ json_object_new_string (reqData->id));
+
+ method = "station.createStation";
+ break;
+ }
+
+ case PIANO_REQUEST_ADD_SEED: {
+ /* add another seed to specified station */
+ PianoRequestDataAddSeed_t *reqData = req->data;
+
+ assert (reqData != NULL);
+ assert (reqData->station != NULL);
+ assert (reqData->musicId != NULL);
+
+ 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;
+ }
+
+ case PIANO_REQUEST_ADD_TIRED_SONG: {
+ /* ban song for a month from all stations */
+ PianoSong_t *song = req->data;
+
+ assert (song != NULL);
+
+ 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) */
+ PianoStation_t *curStation = ph->stations;
+ json_object *a = json_object_new_array ();
+
+ while (curStation != NULL) {
+ /* quick mix can't contain itself */
+ if (curStation->useQuickMix && !curStation->isQuickMix) {
+ json_object_array_add (a,
+ json_object_new_string (curStation->id));
+ }
+
+ curStation = curStation->next;
+ }
+
+ json_object_object_add (j, "quickMixStationIds", a);
+
+ method = "user.setQuickMix";
+ break;
+ }
+
+ case PIANO_REQUEST_GET_GENRE_STATIONS: {
+ /* receive list of pandora's genre stations */
+ method = "station.getGenreStations";
+ break;
+ }
+
+ case PIANO_REQUEST_TRANSFORM_STATION: {
+ /* transform shared station into private */
+ PianoStation_t *station = req->data;
+
+ assert (station != NULL);
+
+ json_object_object_add (j, "stationToken",
+ json_object_new_string (station->id));
+
+ method = "station.transformSharedStation";
+ break;
+ }
+
+ case PIANO_REQUEST_EXPLAIN: {
+ /* explain why particular song was played */
+ PianoRequestDataExplain_t *reqData = req->data;
+
+ assert (reqData != NULL);
+ assert (reqData->song != NULL);
+
+ 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;
+
+ assert (reqData != NULL);
+ assert (reqData->musicId != NULL);
+ assert (reqData->max != 0);
+
+ snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
+ "<methodCall><methodName>music.getSeedSuggestions</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>"
+ /* seed music id */
+ "<param><value><string>%s</string></value></param>"
+ /* max */
+ "<param><value><int>%u</int></value></param>"
+ "</params></methodCall>", (unsigned long) timestamp,
+ ph->user.authToken, reqData->station->id, reqData->musicId,
+ reqData->max);
+ 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;
+ }
+
+ case PIANO_REQUEST_BOOKMARK_SONG: {
+ /* bookmark song */
+ PianoSong_t *song = req->data;
+
+ assert (song != NULL);
+
+ json_object_object_add (j, "trackToken",
+ json_object_new_string (song->trackToken));
+
+ method = "bookmark.addSongBookmark";
+ break;
+ }
+
+ case PIANO_REQUEST_BOOKMARK_ARTIST: {
+ /* bookmark artist */
+ PianoSong_t *song = req->data;
+
+ assert (song != NULL);
+
+ json_object_object_add (j, "trackToken",
+ json_object_new_string (song->trackToken));
+
+ method = "bookmark.addArtistBookmark";
+ break;
+ }
+
+ case PIANO_REQUEST_GET_STATION_INFO: {
+ /* get station information (seeds and feedback) */
+ PianoRequestDataGetStationInfo_t *reqData = req->data;
+
+ assert (reqData != NULL);
+ assert (reqData->station != NULL);
+
+ 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;
+ }
+
+ case PIANO_REQUEST_DELETE_FEEDBACK: {
+ PianoSong_t *song = req->data;
+
+ assert (song != NULL);
+
+ json_object_object_add (j, "feedbackId",
+ json_object_new_string (song->feedbackId));
+
+ method = "station.deleteFeedback";
+ break;
+ }
+
+ case PIANO_REQUEST_DELETE_SEED: {
+ PianoRequestDataDeleteSeed_t *reqData = req->data;
+ char *seedId = NULL;
+
+ assert (reqData != NULL);
+ assert (reqData->song != NULL || reqData->artist != NULL ||
+ reqData->station != NULL);
+
+ if (reqData->song != NULL) {
+ seedId = reqData->song->seedId;
+ } else if (reqData->artist != NULL) {
+ seedId = reqData->artist->seedId;
+ } else if (reqData->station != NULL) {
+ seedId = reqData->station->seedId;
+ }
+
+ assert (seedId != NULL);
+
+ json_object_object_add (j, "seedId",
+ json_object_new_string (seedId));
+
+ method = "station.deleteMusic";
+ break;
+ }
+
+ /* "high-level" wrapper */
+ case PIANO_REQUEST_RATE_SONG: {
+ /* love/ban song */
+ PianoRequestDataRateSong_t *reqData = req->data;
+ PianoReturn_t pRet;
+
+ assert (reqData != NULL);
+ assert (reqData->song != NULL);
+ assert (reqData->rating != PIANO_RATE_NONE);
+
+ PianoRequestDataAddFeedback_t transformedReqData;
+ transformedReqData.stationId = reqData->song->stationId;
+ transformedReqData.trackToken = reqData->song->trackToken;
+ transformedReqData.rating = reqData->rating;
+ req->data = &transformedReqData;
+
+ /* create request data (url, post data) */
+ pRet = PianoRequest (ph, req, PIANO_REQUEST_ADD_FEEDBACK);
+ /* and reset request type/data */
+ req->type = PIANO_REQUEST_RATE_SONG;
+ req->data = reqData;
+
+ return pRet;
+ break;
+ }
+
+ case PIANO_REQUEST_MOVE_SONG: {
+ /* move song to a different station, needs two requests */
+ PianoRequestDataMoveSong_t *reqData = req->data;
+ PianoRequestDataAddFeedback_t transformedReqData;
+ PianoReturn_t pRet;
+
+ assert (reqData != NULL);
+ assert (reqData->song != NULL);
+ assert (reqData->from != NULL);
+ assert (reqData->to != NULL);
+ assert (reqData->step < 2);
+
+ transformedReqData.trackToken = reqData->song->trackToken;
+ req->data = &transformedReqData;
+
+ switch (reqData->step) {
+ case 0:
+ transformedReqData.stationId = reqData->from->id;
+ transformedReqData.rating = PIANO_RATE_BAN;
+ break;
+
+ case 1:
+ transformedReqData.stationId = reqData->to->id;
+ transformedReqData.rating = PIANO_RATE_LOVE;
+ break;
+ }
+
+ /* create request data (url, post data) */
+ pRet = PianoRequest (ph, req, PIANO_REQUEST_ADD_FEEDBACK);
+ /* and reset request type/data */
+ req->type = PIANO_REQUEST_MOVE_SONG;
+ req->data = reqData;
+
+ return pRet;
+ break;
+ }
+ }
+
+ /* 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;
+}
+
diff --git a/src/libpiano/response.c b/src/libpiano/response.c
new file mode 100644
index 0000000..65af76e
--- /dev/null
+++ b/src/libpiano/response.c
@@ -0,0 +1,653 @@
+/*
+Copyright (c) 2008-2012
+ Lars-Dominik Braun <lars@6xq.net>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#ifndef __FreeBSD__
+#define _BSD_SOURCE /* required by strdup() */
+#define _DARWIN_C_SOURCE /* strdup() on OS X */
+#endif
+
+#include <json.h>
+#include <string.h>
+#include <assert.h>
+#include <time.h>
+#include <stdlib.h>
+
+#include "piano.h"
+#include "piano_private.h"
+#include "crypt.h"
+
+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_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 */
+ PianoRequestDataLogin_t *reqData = req->data;
+
+ assert (req->responseData != NULL);
+ assert (reqData != NULL);
+
+ switch (reqData->step) {
+ case 0: {
+ /* 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 (decryptedTimestamp);
+ /* get auth token */
+ ph->partnerAuthToken = PianoJsonStrdup (result,
+ "partnerAuthToken");
+ ph->partnerId = json_object_get_int (
+ json_object_object_get (result, "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");
+ break;
+ }
+ break;
+ }
+
+ case PIANO_REQUEST_GET_STATIONS: {
+ /* get stations */
+ assert (req->responseData != NULL);
+
+ 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);
+
+ 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: {
+ /* 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_MOVE_SONG: {
+ /* move song to different station */
+ PianoRequestDataMoveSong_t *reqData = req->data;
+
+ assert (req->responseData != NULL);
+ assert (reqData != NULL);
+ assert (reqData->step < 2);
+
+ if (reqData->step == 0) {
+ ret = PIANO_RET_CONTINUE_REQUEST;
+ ++reqData->step;
+ }
+ break;
+ }
+
+ case PIANO_REQUEST_DELETE_STATION: {
+ /* delete station from server and station list */
+ PianoStation_t *station = req->data;
+
+ assert (station != NULL);
+
+ /* 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;
+
+ assert (req->responseData != NULL);
+ assert (reqData != NULL);
+
+ searchResult = &reqData->searchResult;
+ memset (searchResult, 0, sizeof (*searchResult));
+
+ /* 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;
+
+ 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;
+ }
+ curArtist->next = artist;
+ }
+ }
+ }
+
+ /* 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;
+
+ 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");
+
+ /* 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 */
+ PianoStation_t *tmpStation;
+
+ if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) {
+ return PIANO_RET_OUT_OF_MEMORY;
+ }
+
+ PianoJsonParseStation (result, tmpStation);
+
+ /* 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:
+ case PIANO_REQUEST_DELETE_SEED:
+ /* response unused */
+ break;
+
+ case PIANO_REQUEST_GET_GENRE_STATIONS: {
+ /* get genre stations */
+ 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;
+ }
+
+ 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 */
+ 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 = 1024;
+ size_t pos = 0;
+
+ assert (reqData != NULL);
+
+ 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;
+
+ assert (req->responseData != NULL);
+ assert (reqData != NULL);
+
+ 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 (reqData != NULL);
+
+ 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;
+ }
+
+ seedSong->title = PianoJsonStrdup (s, "songName");
+ seedSong->artist = PianoJsonStrdup (s, "artistName");
+ seedSong->seedId = PianoJsonStrdup (s, "seedId");
+
+ 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;
+}
+