diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/libpiano/piano.c | 1096 | ||||
| -rw-r--r-- | src/libpiano/piano.h | 1 | ||||
| -rw-r--r-- | src/libpiano/piano_private.h | 9 | ||||
| -rw-r--r-- | src/libpiano/request.c | 509 | ||||
| -rw-r--r-- | src/libpiano/response.c | 653 | 
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; +} +  | 
