diff options
| author | Lars-Dominik Braun <PromyLOPh@lavabit.com> | 2010-03-30 10:56:59 +0200 | 
|---|---|---|
| committer | Lars-Dominik Braun <PromyLOPh@lavabit.com> | 2010-04-05 17:19:49 +0200 | 
| commit | 37cee3b9d6d3f5fd7a477791ebfdb1e85e8faf9d (patch) | |
| tree | 6fcd8ca5ced40164353bb9cbbc9cea10fa6bd9fe /libpiano/src | |
| parent | 34ff325e217b33be3ad80c87a377eb7dc0902f46 (diff) | |
| download | pianobar-37cee3b9d6d3f5fd7a477791ebfdb1e85e8faf9d.tar.gz pianobar-37cee3b9d6d3f5fd7a477791ebfdb1e85e8faf9d.tar.bz2 pianobar-37cee3b9d6d3f5fd7a477791ebfdb1e85e8faf9d.zip | |
piano: New request/response api
Removed HTTP stuff from libpiano. The user is now responsible for
POSTing data to the server.
Diffstat (limited to 'libpiano/src')
| -rw-r--r-- | libpiano/src/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | libpiano/src/http.c | 80 | ||||
| -rw-r--r-- | libpiano/src/http.h | 33 | ||||
| -rw-r--r-- | libpiano/src/piano.c | 1334 | ||||
| -rw-r--r-- | libpiano/src/piano.h | 137 | 
5 files changed, 787 insertions, 799 deletions
| diff --git a/libpiano/src/CMakeLists.txt b/libpiano/src/CMakeLists.txt index 8d1b77e..4aaeb5b 100644 --- a/libpiano/src/CMakeLists.txt +++ b/libpiano/src/CMakeLists.txt @@ -7,6 +7,6 @@ include_directories (${CMAKE_CURRENT_SOURCE_DIR}/../../libwaitress/src          ${CMAKE_CURRENT_BINARY_DIR}  	${CMAKE_CURRENT_SOURCE_DIR}/../../libezxml/src) -add_library (piano STATIC crypt.c http.c piano.c xml.c) +add_library (piano STATIC crypt.c piano.c xml.c)  target_link_libraries (piano waitress ezxml) diff --git a/libpiano/src/http.c b/libpiano/src/http.c deleted file mode 100644 index a1a9c67..0000000 --- a/libpiano/src/http.c +++ /dev/null @@ -1,80 +0,0 @@ -/* -Copyright (c) 2008-2010 -	Lars-Dominik Braun <PromyLOPh@lavabit.com> - -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. -*/ - -#include <stdlib.h> -#include <string.h> -#include <stdio.h> - -#include <waitress.h> - -#include "piano_private.h" -#include "crypt.h" -#include "http.h" - -/*	post data to url and receive answer as string - *	@param initialized waitress handle - *	@param null-terminated post data string - *	@param receive buffer - *	@param buffer size - *	@return _RET_OK or _RET_NET_ERROR - */ -PianoReturn_t PianoHttpPost (WaitressHandle_t *waith, const char *postData, -		char **retData) { -	PianoReturn_t pRet = PIANO_RET_NET_ERROR; -	char *reqPostData = PianoEncryptString (postData); - -	if (reqPostData == NULL) { -		return PIANO_RET_OUT_OF_MEMORY; -	} - -	waith->extraHeaders = "Content-Type: text/xml\r\n"; -	waith->postData = reqPostData; -	waith->method = WAITRESS_METHOD_POST; - -	if (WaitressFetchBuf (waith, retData) == WAITRESS_RET_OK && -			*retData != NULL) { -		pRet = PIANO_RET_OK; -	} - -	PianoFree (reqPostData, 0); - -	return pRet; -} - -/*	http get request, return server response body - *	@param initialized waitress handle - *	@param receive buffer - *	@param buffer size - *	@return _RET_OK or _RET_NET_ERROR - */ -PianoReturn_t PianoHttpGet (WaitressHandle_t *waith, char **retData) { -	waith->extraHeaders = NULL; -	waith->postData = NULL; -	waith->method = WAITRESS_METHOD_GET; - -	if (WaitressFetchBuf (waith, retData) == WAITRESS_RET_OK && -			*retData != NULL) { -		return PIANO_RET_OK; -	} -	return PIANO_RET_NET_ERROR; -} diff --git a/libpiano/src/http.h b/libpiano/src/http.h deleted file mode 100644 index 44a5ac1..0000000 --- a/libpiano/src/http.h +++ /dev/null @@ -1,33 +0,0 @@ -/* -Copyright (c) 2008-2010 -	Lars-Dominik Braun <PromyLOPh@lavabit.com> - -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 _HTTP_H -#define _HTTP_H - -#include <waitress.h> -#include "piano.h" - -PianoReturn_t PianoHttpPost (WaitressHandle_t *, const char *, char **); -PianoReturn_t PianoHttpGet (WaitressHandle_t *, char **); - -#endif /* _HTTP_H */ diff --git a/libpiano/src/piano.c b/libpiano/src/piano.c index 407f5f7..84a497b 100644 --- a/libpiano/src/piano.c +++ b/libpiano/src/piano.c @@ -27,10 +27,13 @@ THE SOFTWARE.  #include <string.h>  #include <stdlib.h>  #include <time.h> +#include <assert.h> + +/* needed for urlencode */ +#include <waitress.h>  #include "piano_private.h"  #include "piano.h" -#include "http.h"  #include "xml.h"  #include "crypt.h"  #include "config.h" @@ -41,11 +44,6 @@ THE SOFTWARE.  #define PIANO_RPC_PATH "/radio/xmlrpc/v" PIANO_PROTOCOL_VERSION "?"  #define PIANO_SEND_BUFFER_SIZE 10000 -/* prototypes */ -static PianoReturn_t PianoAddFeedback (PianoHandle_t *, const char *, const char *, -		const char *, const char *, const char *, PianoSongRating_t); -const char *PianoAudioFormatToString (PianoAudioFormat_t); -  /*	more "secure" free version   *	@param free this pointer   *	@param zero n bytes; 0 disables zeroing (for strings with unknown size, @@ -68,10 +66,6 @@ void PianoFree (void *ptr, size_t size) {  void PianoInit (PianoHandle_t *ph) {  	memset (ph, 0, sizeof (*ph)); -	WaitressInit (&ph->waith); -	strncpy (ph->waith.host, PIANO_RPC_HOST, sizeof (ph->waith.host)-1); -	strncpy (ph->waith.port, PIANO_RPC_PORT, sizeof (ph->waith.port)-1); -  	/* route-id seems to be random. we're using time anyway... */  	snprintf (ph->routeId, sizeof (ph->routeId), "%07liP", time (NULL) % 10000000);  } @@ -160,8 +154,6 @@ void PianoDestroyPlaylist (PianoSong_t *playlist) {   *	@return nothing   */  void PianoDestroy (PianoHandle_t *ph) { -	WaitressFree (&ph->waith); -  	PianoFree (ph->user.webAuthToken, 0);  	PianoFree (ph->user.authToken, 0);  	PianoFree (ph->user.listenerId, 0); @@ -179,689 +171,753 @@ void PianoDestroy (PianoHandle_t *ph) {  	memset (ph, 0, sizeof (*ph));  } -/*	authenticates user - *	@param piano handle - *	@param username (utf-8 encoded) - *	@param password (plaintext, utf-8 encoded) +/*	destroy request, free post data. req->responseData is *not* freed here! + *	@param piano request   */ -PianoReturn_t PianoConnect (PianoHandle_t *ph, const char *user, -		const char *password) { -	char *retStr, xmlSendBuf[PIANO_SEND_BUFFER_SIZE]; -	PianoReturn_t ret; - -	/* sync and throw away result (it's an encrypted timestamp, decrypt with -	 * PianoDecryptString) */ -	snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -			"<methodCall><methodName>misc.sync</methodName>" -			"<params></params></methodCall>"); -	snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH -			"rid=%s&method=sync", ph->routeId); -	ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr); -	PianoFree (retStr, 0); - -	if (ret != PIANO_RET_OK) { -		return ret; -	} - -	/* authenticate */ -	snprintf (xmlSendBuf, sizeof (xmlSendBuf),  -			"<?xml version=\"1.0\"?><methodCall>" -			"<methodName>listener.authenticateListener</methodName>" -			"<params><param><value><int>%li</int></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"</params></methodCall>", time (NULL), user, password); -	snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH -			"rid=%s&method=authenticateListener", ph->routeId); - -	if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == -			PIANO_RET_OK) { -		ret = PianoXmlParseUserinfo (ph, retStr); -		PianoFree (retStr, 0); -	} - -	return ret; +void PianoDestroyRequest (PianoRequest_t *req) { +	PianoFree (req->postData, 0); +	memset (req, 0, sizeof (*req));  } -/*	get all stations for authenticated user (so: PianoConnect needs to - *	be run before) - *	@param piano handle filled with some authentication data by PianoConnect +/*	convert audio format id to string that can be used in xml requests + *	@param format id + *	@return constant string   */ -PianoReturn_t PianoGetStations (PianoHandle_t *ph) { -	char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; -	PianoReturn_t ret; - -	snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -			"<methodCall><methodName>station.getStations</methodName>" -			"<params><param><value><int>%li</int></value></param>" -			"<param><value><string>%s</string></value></param>" -			"</params></methodCall>", time (NULL), ph->user.authToken); -	snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH -			"rid=%s&lid=%s&method=getStations", ph->routeId, -			ph->user.listenerId); - -	if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == -			PIANO_RET_OK) { -		ret = PianoXmlParseStations (ph, retStr); -		PianoFree (retStr, 0); -	} +static const char *PianoAudioFormatToString (PianoAudioFormat_t format) { +	switch (format) { +		case PIANO_AF_AACPLUS: +			return "aacplus"; +			break; -	return ret; -} +		case PIANO_AF_MP3: +			return "mp3"; +			break; -/*	get next songs for station (usually four tracks) - *	@param piano handle - *	@param station id - *	@param audio format - *	@param return value: playlist - */ -PianoReturn_t PianoGetPlaylist (PianoHandle_t *ph, const char *stationId, -		PianoAudioFormat_t format, PianoSong_t **retPlaylist) { -	char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; -	PianoReturn_t ret; - -	/* FIXME: remove static, "magic" numbers */ -	snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -			"<methodCall><methodName>playlist.getFragment</methodName>" -			"<params><param><value><int>%li</int></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>0</string></value></param>" -			"<param><value><string></string></value></param>" -			"<param><value><string></string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>0</string></value></param>" -			"<param><value><string>0</string></value></param>" -			"</params></methodCall>", time (NULL), ph->user.authToken, -			stationId, PianoAudioFormatToString (format)); -	snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH -			"rid=%s&lid=%s&method=getFragment&arg1=%s&arg2=0" -			"&arg3=&arg4=&arg5=%s&arg6=0&arg7=0", ph->routeId, -			ph->user.listenerId, stationId, -			PianoAudioFormatToString (format)); - -	if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == -			PIANO_RET_OK) { -		ret = PianoXmlParsePlaylist (ph, retStr, retPlaylist); -		PianoFree (retStr, 0); -	} +		case PIANO_AF_MP3_HI: +			return "mp3-hifi"; +			break; -	return ret; +		default: +			return NULL; +			break; +	}  } -/*	love or ban track (you cannot remove your rating, so PIANO_RATE_NONE is - *	not allowed) - *	@public yes +/*	prepare piano request (initializes request type, urlpath and postData)   *	@param piano handle - *	@param rate this track - *	@param your rating + *	@param request structure + *	@param request type   */ -PianoReturn_t PianoRateTrack (PianoHandle_t *ph, PianoSong_t *song, -		PianoSongRating_t rating) { -	PianoReturn_t ret; +PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, +		PianoRequestType_t type) { +	char xmlSendBuf[PIANO_SEND_BUFFER_SIZE]; + +	assert (ph != NULL); +	assert (req != NULL); + +	req->type = type; + +	switch (req->type) { +		case PIANO_REQUEST_LOGIN: { +			/* authenticate user */ +			PianoRequestDataLogin_t *logindata = req->data; + +			assert (logindata != NULL); + +			snprintf (xmlSendBuf, sizeof (xmlSendBuf),  +					"<?xml version=\"1.0\"?><methodCall>" +					"<methodName>listener.authenticateListener</methodName>" +					"<params><param><value><int>%li</int></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"</params></methodCall>", time (NULL), logindata->user, +					logindata->password); +			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH +					"rid=%s&method=authenticateListener", ph->routeId); +			break; +		} -	ret = PianoAddFeedback (ph, song->stationId, song->musicId, -			song->matchingSeed, song->userSeed, song->focusTraitId, rating); +		case PIANO_REQUEST_GET_STATIONS: +			/* get stations, user must be authenticated */ +			assert (ph->user.listenerId != NULL); + +			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" +					"<methodCall><methodName>station.getStations</methodName>" +					"<params><param><value><int>%li</int></value></param>" +					"<param><value><string>%s</string></value></param>" +					"</params></methodCall>", time (NULL), ph->user.authToken); +			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH +					"rid=%s&lid=%s&method=getStations", ph->routeId, +					ph->user.listenerId); +			break; -	if (ret == PIANO_RET_OK) { -		song->rating = rating; -	} +		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); + +			/* FIXME: remove static, "magic" numbers */ +			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" +					"<methodCall><methodName>playlist.getFragment</methodName>" +					"<params><param><value><int>%li</int></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>0</string></value></param>" +					"<param><value><string></string></value></param>" +					"<param><value><string></string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>0</string></value></param>" +					"<param><value><string>0</string></value></param>" +					"</params></methodCall>", time (NULL), ph->user.authToken, +					reqData->station->id, +					PianoAudioFormatToString (reqData->format)); +			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH +					"rid=%s&lid=%s&method=getFragment&arg1=%s&arg2=0" +					"&arg3=&arg4=&arg5=%s&arg6=0&arg7=0", ph->routeId, +					ph->user.listenerId, reqData->station->id, +					PianoAudioFormatToString (reqData->format)); +			break; +		} -	return ret; -} +		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->stationId != NULL); +			assert (reqData->musicId != NULL); +			assert (reqData->rating != PIANO_RATE_NONE); + +			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" +					"<methodCall><methodName>station.addFeedback</methodName>" +					"<params><param><value><int>%li</int></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value></value></param>" +					"<param><value><boolean>%i</boolean></value></param>" +					"<param><value><boolean>0</boolean></value></param>" +					"</params></methodCall>", time (NULL), ph->user.authToken, +					reqData->stationId, reqData->musicId, +					(reqData->matchingSeed == NULL) ? "" : reqData->matchingSeed, +					(reqData->userSeed == NULL) ? "" : reqData->userSeed, +					(reqData->focusTraitId == NULL) ? "" : reqData->focusTraitId, +					(reqData->rating == PIANO_RATE_LOVE) ? 1 : 0); +			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH +					"rid=%s&lid=%s&method=addFeedback&arg1=%s&arg2=%s" +					"&arg3=%s&arg4=%s&arg5=%s&arg6=&arg7=%s&arg8=false", +					ph->routeId, ph->user.listenerId, reqData->stationId, +					reqData->musicId, +					(reqData->matchingSeed == NULL) ? "" : reqData->matchingSeed, +					(reqData->userSeed == NULL) ? "" : reqData->userSeed, +					(reqData->focusTraitId == NULL) ? "" : reqData->focusTraitId, +					(reqData->rating == PIANO_RATE_LOVE) ? "true" : "false"); +			break; +		} -/*	move song to another station - *	@param piano handle - *	@param move from - *	@param move here - *	@param song to move - */ -PianoReturn_t PianoMoveSong (PianoHandle_t *ph, -		const PianoStation_t *stationFrom, const PianoStation_t *stationTo, -		const PianoSong_t *song) { -	PianoReturn_t ret; - -	/* ban from current station */ -	if ((ret = PianoAddFeedback (ph, stationFrom->id, song->musicId, "", "", -			"", PIANO_RATE_BAN)) == PIANO_RET_OK) { -		/* love at new station */ -		return PianoAddFeedback (ph, stationTo->id, song->musicId, "", -				"", "", PIANO_RATE_LOVE); -	} -	return ret; -} +		case PIANO_REQUEST_RENAME_STATION: { +			/* rename stations */ +			PianoRequestDataRenameStation_t *reqData = req->data; +			char *urlencodedNewName, *xmlencodedNewName; -/*	add feedback - *	@param piano handle - *	@param station id - *	@param song id - *	@param song matching seed or NULL - *	@param song user seed or NULL - *	@param song focus trait id or NULL - *	@param rating - */ -static PianoReturn_t PianoAddFeedback (PianoHandle_t *ph, const char *stationId, -		const char *songMusicId, const char *songMatchingSeed, -		const char *songUserSeed, const char *songFocusTraitId, -		PianoSongRating_t rating) { -	char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; -	PianoReturn_t ret = PIANO_RET_ERR; +			assert (reqData != NULL); +			assert (reqData->station != NULL); +			assert (reqData->newName != NULL); -	snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -			"<methodCall><methodName>station.addFeedback</methodName>" -			"<params><param><value><int>%li</int></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value></value></param>" -			"<param><value><boolean>%i</boolean></value></param>" -			"<param><value><boolean>0</boolean></value></param>" -			"</params></methodCall>", time (NULL), ph->user.authToken, -			stationId, songMusicId, -			(songMatchingSeed == NULL) ? "" : songMatchingSeed, -			(songUserSeed == NULL) ? "" : songUserSeed, -			(songFocusTraitId == NULL) ? "" : songFocusTraitId, -			(rating == PIANO_RATE_LOVE) ? 1 : 0); -	snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH -			"rid=%s&lid=%s&method=addFeedback&arg1=%s&arg2=%s" -			"&arg3=%s&arg4=%s&arg5=%s&arg6=&arg7=%s&arg8=false", ph->routeId, -			ph->user.listenerId, stationId, songMusicId, -			(songMatchingSeed == NULL) ? "" : songMatchingSeed, -			(songUserSeed == NULL) ? "" : songUserSeed, -			(songFocusTraitId == NULL) ? "" : songFocusTraitId, -			(rating == PIANO_RATE_LOVE) ? "true" : "false"); - -	if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == -			PIANO_RET_OK) { -		ret = PianoXmlParseSimple (retStr); -		PianoFree (retStr, 0); -	} +			if ((xmlencodedNewName = PianoXmlEncodeString (reqData->newName)) == NULL) { +				return PIANO_RET_OUT_OF_MEMORY; +			} +			urlencodedNewName = WaitressUrlEncode (reqData->newName); + +			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" +					"<methodCall><methodName>station.setStationName</methodName>" +					"<params><param><value><int>%li</int></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"</params></methodCall>", time (NULL), ph->user.authToken, +					reqData->station->id, xmlencodedNewName); +			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH +					"rid=%s&lid=%s&method=setStationName&arg1=%s&arg2=%s", +					ph->routeId, ph->user.listenerId, reqData->station->id, +					urlencodedNewName); + +			PianoFree (urlencodedNewName, 0); +			PianoFree (xmlencodedNewName, 0); +			break; +		} -	return ret; -} +		case PIANO_REQUEST_DELETE_STATION: { +			/* delete station */ +			PianoStation_t *station = req->data; + +			assert (station != NULL); + +			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" +					"<methodCall><methodName>station.removeStation</methodName>" +					"<params><param><value><int>%li</int></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"</params></methodCall>", time (NULL), ph->user.authToken, +					station->id); +			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH +					"rid=%s&lid=%s&method=removeStation&arg1=%s", ph->routeId, +					ph->user.listenerId, station->id); +			break; +		} -/*	rename station (on the server and local) - *	@public yes - *	@param piano handle - *	@param change this stations name - *	@param new name - */ -PianoReturn_t PianoRenameStation (PianoHandle_t *ph, PianoStation_t *station, -		const char *newName) { -	char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; -	char *urlencodedNewName, *xmlencodedNewName; -	PianoReturn_t ret = PIANO_RET_ERR; +		case PIANO_REQUEST_SEARCH: { +			/* search for artist/song title */ +			PianoRequestDataSearch_t *reqData = req->data; +			char *xmlencodedSearchStr, *urlencodedSearchStr; -	if ((xmlencodedNewName = PianoXmlEncodeString (newName)) == NULL) { -		return PIANO_RET_OUT_OF_MEMORY; -	} +			assert (reqData != NULL); +			assert (reqData->searchStr != NULL); -	snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -			"<methodCall><methodName>station.setStationName</methodName>" -			"<params><param><value><int>%li</int></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"</params></methodCall>", time (NULL), ph->user.authToken, -			station->id, xmlencodedNewName); - -	urlencodedNewName = WaitressUrlEncode (newName); -	snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH -			"rid=%s&lid=%s&method=setStationName&arg1=%s&arg2=%s", -			ph->routeId, ph->user.listenerId, station->id, urlencodedNewName); - -	if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == -			PIANO_RET_OK) { -		if ((ret = PianoXmlParseSimple (retStr)) == PIANO_RET_OK) { -			PianoFree (station->name, 0); -			station->name = strdup (newName); +			if ((xmlencodedSearchStr = PianoXmlEncodeString (reqData->searchStr)) == NULL) { +				return PIANO_RET_OUT_OF_MEMORY; +			} +			urlencodedSearchStr = WaitressUrlEncode (reqData->searchStr); + +			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" +					"<methodCall><methodName>music.search</methodName>" +					"<params><param><value><int>%li</int></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"</params></methodCall>", time (NULL), ph->user.authToken, +					xmlencodedSearchStr); +			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH +					"rid=%s&lid=%s&method=search&arg1=%s", ph->routeId, +					ph->user.listenerId, urlencodedSearchStr); + +			PianoFree (urlencodedSearchStr, 0); +			PianoFree (xmlencodedSearchStr, 0); +			break;  		} -		PianoFree (retStr, 0); -	} -	PianoFree (urlencodedNewName, 0); -	PianoFree (xmlencodedNewName, 0); +		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); +			assert (reqData->type != NULL); + +			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" +					"<methodCall><methodName>station.createStation</methodName>" +					"<params><param><value><int>%li</int></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s%s</string></value></param>" +					"</params></methodCall>", time (NULL), ph->user.authToken, +					reqData->type, reqData->id); + +			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH +					"rid=%s&lid=%s&method=createStation&arg1=%s%s", ph->routeId, +					ph->user.listenerId, reqData->type, reqData->id); +			break; +		} -	return ret; -} +		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); + +			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" +					"<methodCall><methodName>station.addSeed</methodName><params>" +					"<param><value><int>%li</int></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"</params></methodCall>", time (NULL), ph->user.authToken, +					reqData->station->id, reqData->musicId); +			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH +					"rid=%s&lid=%s&method=addSeed&arg1=%s&arg2=%s", ph->routeId, +					ph->user.listenerId, reqData->station->id, reqData->musicId); +			break; +		} -/*	delete station - *	@public yes - *	@param piano handle - *	@param station you want to delete - */ -PianoReturn_t PianoDeleteStation (PianoHandle_t *ph, PianoStation_t *station) { -	char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; -	PianoReturn_t ret = PIANO_RET_ERR; +		case PIANO_REQUEST_ADD_TIRED_SONG: { +			/* ban song for a month from all stations */ +			PianoSong_t *song = req->data; + +			assert (song != NULL); + +			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" +					"<methodCall><methodName>listener.addTiredSong</methodName><params>" +					"<param><value><int>%li</int></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"</params></methodCall>", time (NULL), ph->user.authToken, +					song->identity); +			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH +					"rid=%s&lid=%s&method=addTiredSong&arg1=%s", ph->routeId, +					ph->user.listenerId, song->identity); +			break; +		} -	snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -			"<methodCall><methodName>station.removeStation</methodName>" -			"<params><param><value><int>%li</int></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"</params></methodCall>", time (NULL), ph->user.authToken, -			station->id); - -	snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH -			"rid=%s&lid=%s&method=removeStation&arg1=%s", ph->routeId, -			ph->user.listenerId, station->id); -	if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == -			PIANO_RET_OK) { -		if ((ret = PianoXmlParseSimple (retStr)) == PIANO_RET_OK) { -			/* delete station from local station list */ -			PianoStation_t *curStation = ph->stations, *lastStation = NULL; +		case PIANO_REQUEST_SET_QUICKMIX: { +			/* select stations included in quickmix (see useQuickMix flag of +			 * PianoStation_t) */ +			char valueBuf[1000], urlArgBuf[1000]; +			PianoStation_t *curStation = ph->stations; + +			memset (urlArgBuf, 0, sizeof (urlArgBuf)); +			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" +					"<methodCall><methodName>station.setQuickMix</methodName><params>" +					"<param><value><int>%li</int></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>RANDOM</string></value></param>" +					"<param><value><array><data>", time (NULL), ph->user.authToken);  			while (curStation != NULL) { -				if (curStation == station) { -					if (lastStation != NULL) { -						lastStation->next = curStation->next; -					} else { -						/* first station in list */ -						ph->stations = curStation->next; -					} -					PianoDestroyStation (curStation); -					PianoFree (curStation, sizeof (*curStation)); -					break; +				/* quick mix can't contain itself */ +				if (!curStation->useQuickMix || curStation->isQuickMix) { +					curStation = curStation->next; +					continue;  				} -				lastStation = curStation; +				/* append to xml doc */ +				snprintf (valueBuf, sizeof (valueBuf), +						"<value><string>%s</string></value>", curStation->id); +				strncat (xmlSendBuf, valueBuf, sizeof (xmlSendBuf) - +						strlen (xmlSendBuf) - 1); +				/* append to url arg */ +				strncat (urlArgBuf, curStation->id, sizeof (urlArgBuf) - +						strlen (urlArgBuf) - 1);  				curStation = curStation->next; +				/* if not last item: append "," */ +				if (curStation != NULL) { +					strncat (urlArgBuf, "%2C", sizeof (urlArgBuf) - +							strlen (urlArgBuf) - 1); +				}  			} +			strncat (xmlSendBuf, +					"</data></array></value></param></params></methodCall>", +					sizeof (xmlSendBuf) - strlen (xmlSendBuf) - 1); + +			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH +					"rid=%s&lid=%s&method=setQuickMix&arg1=RANDOM&arg2=%s", +					ph->routeId, ph->user.listenerId, urlArgBuf); +			break;  		} -	} -	return ret; -} +		case PIANO_REQUEST_GET_GENRE_STATIONS: +			/* receive list of pandora's genre stations */ +			xmlSendBuf[0] = '\0'; +			snprintf (req->urlPath, sizeof (req->urlPath), "/xml/genre?r=%li", +					time (NULL)); +			break; -/*	search for music (artist or track), needed to create new station; don't - *	forget to free the search result; beware! searchResult will be nulled - *	by PianoXmlParseSearch - *	@public yes - *	@param piano handle - *	@param utf-8 search string - *	@param return search result - */ -PianoReturn_t PianoSearchMusic (PianoHandle_t *ph, -		const char *searchStr, PianoSearchResult_t *searchResult) { -	char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; -	char *xmlencodedSearchStr, *urlencodedSearchStr; -	PianoReturn_t ret; +		case PIANO_REQUEST_TRANSFORM_STATION: { +			/* transform shared station into private */ +			PianoStation_t *station = req->data; + +			assert (station != NULL); + +			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" +					"<methodCall><methodName>station.transformShared</methodName>" +					"<params><param><value><int>%li</int></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"</params></methodCall>", time (NULL), ph->user.authToken, +					station->id); +			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH +					"rid=%s&lid=%s&method=transformShared&arg1=%s", ph->routeId, +					ph->user.listenerId, station->id); +			break; +		} -	if ((xmlencodedSearchStr = PianoXmlEncodeString (searchStr)) == NULL) { -		return PIANO_RET_OUT_OF_MEMORY; -	} +		case PIANO_REQUEST_EXPLAIN: { +			/* explain why particular song was played */ +			PianoRequestDataExplain_t *reqData = req->data; + +			assert (reqData != NULL); +			assert (reqData->song != NULL); + +			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" +					"<methodCall><methodName>playlist.narrative</methodName>" +					"<params><param><value><int>%li</int></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"</params></methodCall>", time (NULL), ph->user.authToken, +					reqData->song->stationId, reqData->song->musicId); +			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH +					"rid=%s&lid=%s&method=method=narrative&arg1=%s&arg2=%s", +					ph->routeId, ph->user.listenerId, reqData->song->stationId, +					reqData->song->musicId); +			break; +		} -	snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -			"<methodCall><methodName>music.search</methodName>" -			"<params><param><value><int>%li</int></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"</params></methodCall>", time (NULL), ph->user.authToken, -			xmlencodedSearchStr); - -	urlencodedSearchStr = WaitressUrlEncode (searchStr); -	snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH -			"rid=%s&lid=%s&method=search&arg1=%s", ph->routeId, -			ph->user.listenerId, urlencodedSearchStr); -	 -	if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == -			PIANO_RET_OK) { -		ret = PianoXmlParseSearch (retStr, searchResult); -		PianoFree (retStr, 0); -	} +		case PIANO_REQUEST_GET_SEED_SUGGESTIONS: { +			/* 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>%li</int></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><int>%u</int></value></param>" +					"</params></methodCall>", time (NULL), ph->user.authToken, +					reqData->musicId, reqData->max); +			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH +					"rid=%s&lid=%s&method=method=getSeedSuggestions&arg1=%s&arg2=%u", +					ph->routeId, ph->user.listenerId, reqData->musicId, reqData->max); +			break; +		} -	PianoFree (urlencodedSearchStr, 0); -	PianoFree (xmlencodedSearchStr, 0); +		case PIANO_REQUEST_BOOKMARK_SONG: { +			/* bookmark song */ +			PianoSong_t *song = req->data; + +			assert (song != NULL); + +			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" +					"<methodCall><methodName>station.createBookmark</methodName>" +					"<params><param><value><int>%li</int></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"</params></methodCall>", time (NULL), ph->user.authToken, +					song->stationId, song->musicId); +			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH +					"rid=%s&lid=%s&method=method=createBookmark&arg1=%s&arg2=%s", +					ph->routeId, ph->user.listenerId, song->stationId, +					song->musicId); +			break; +		} -	return ret; -} +		case PIANO_REQUEST_BOOKMARK_ARTIST: { +			/* bookmark artist */ +			PianoSong_t *song = req->data; + +			assert (song != NULL); + +			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" +					"<methodCall><methodName>station.createArtistBookmark</methodName>" +					"<params><param><value><int>%li</int></value></param>" +					"<param><value><string>%s</string></value></param>" +					"<param><value><string>%s</string></value></param>" +					"</params></methodCall>", time (NULL), ph->user.authToken, +					song->artistMusicId); +			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH +					"rid=%s&lid=%s&method=method=createArtistBookmark&arg1=%s", +					ph->routeId, ph->user.listenerId, song->artistMusicId); +			break; +		} -/*	create new station on server - *	@public yes - *	@param piano handle - *	@param type: "mi" for music id (from music search) or "sh" for - *			shared station - *	@param id - */ -PianoReturn_t PianoCreateStation (PianoHandle_t *ph, const char *type, -		const char *id) { -	char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; -	PianoReturn_t ret; - -	snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -			"<methodCall><methodName>station.createStation</methodName>" -			"<params><param><value><int>%li</int></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s%s</string></value></param>" -			"</params></methodCall>", time (NULL), ph->user.authToken, -			type, id); - -	snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH -			"rid=%s&lid=%s&method=createStation&arg1=%s%s", ph->routeId, -			ph->user.listenerId, type, id); -	 -	if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == -			PIANO_RET_OK) { -		ret = PianoXmlParseCreateStation (ph, retStr); -		PianoFree (retStr, 0); -	} +		/* "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.musicId = reqData->song->musicId; +			transformedReqData.matchingSeed = reqData->song->matchingSeed; +			transformedReqData.userSeed = reqData->song->userSeed; +			transformedReqData.focusTraitId = reqData->song->focusTraitId; +			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; +		} -	return ret; -} +		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.musicId = reqData->song->musicId; +			transformedReqData.matchingSeed = ""; +			transformedReqData.userSeed = ""; +			transformedReqData.focusTraitId = ""; +			req->data = &transformedReqData; + +			switch (reqData->step) { +				case 0: +					transformedReqData.stationId = reqData->from->id; +					transformedReqData.rating = PIANO_RATE_BAN; +					break; -/* FIXME: update station data instead of replacing them */ -/*	add more music to existing station; multithreaded apps beware! this alters - *	station data, don't forget to lock the station pointer you passed to this - *	function - *	@param piano handle - *	@param add music to this station - *	@param music id; can be obtained with PianoSearchMusic () - */ -PianoReturn_t PianoStationAddMusic (PianoHandle_t *ph, -		PianoStation_t *station, const char *musicId) { -	char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; -	PianoReturn_t ret; - -	snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -			"<methodCall><methodName>station.addSeed</methodName><params>" -			"<param><value><int>%li</int></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"</params></methodCall>", time (NULL), ph->user.authToken, -			station->id, musicId); - -	snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH -			"rid=%s&lid=%s&method=addSeed&arg1=%s&arg2=%s", ph->routeId, -			ph->user.listenerId, station->id, musicId); -	 -	if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == -			PIANO_RET_OK) { -		ret = PianoXmlParseAddSeed (ph, retStr, station); -		PianoFree (retStr, 0); -	} +				case 1: +					transformedReqData.stationId = reqData->to->id; +					transformedReqData.rating = PIANO_RATE_LOVE; +					break; +			} -	return ret; -} +			/* 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; -/*	ban a song temporary (for one month) - *	@param piano handle - *	@param song to be banned - */ -PianoReturn_t PianoSongTired (PianoHandle_t *ph, const PianoSong_t *song) { -	char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; -	PianoReturn_t ret; - -	snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -			"<methodCall><methodName>listener.addTiredSong</methodName><params>" -			"<param><value><int>%li</int></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"</params></methodCall>", time (NULL), ph->user.authToken, -			song->identity); - -	snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH -			"rid=%s&lid=%s&method=addTiredSong&arg1=%s", ph->routeId, -			ph->user.listenerId, song->identity); - -	if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == -			PIANO_RET_OK) { -		ret = PianoXmlParseSimple (retStr); -		PianoFree (retStr, 0); +			return pRet; +			break; +		}  	} -	return ret; +	if ((req->postData = PianoEncryptString (xmlSendBuf)) == NULL) { +		return PIANO_RET_OUT_OF_MEMORY; +	} + +	return PIANO_RET_OK;  } -/*	set stations use by quickmix +/*	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 PianoSetQuickmix (PianoHandle_t *ph) { -	char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], valueBuf[1000], urlArgBuf[1000], -			*retStr; -	PianoReturn_t ret; -	PianoStation_t *curStation = ph->stations; - -	memset (urlArgBuf, 0, sizeof (urlArgBuf)); -	snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -			"<methodCall><methodName>station.setQuickMix</methodName><params>" -			"<param><value><int>%li</int></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>RANDOM</string></value></param>" -			"<param><value><array><data>", time (NULL), ph->user.authToken); -	while (curStation != NULL) { -		/* quick mix can't contain itself */ -		if (!curStation->useQuickMix || curStation->isQuickMix) { -			curStation = curStation->next; -			continue; +PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { +	PianoReturn_t ret = PIANO_RET_ERR; + +	assert (ph != NULL); +	assert (req != NULL); + +	switch (req->type) { +		case PIANO_REQUEST_LOGIN: +			/* authenticate user */ +			assert (req->responseData != NULL); + +			ret = PianoXmlParseUserinfo (ph, req->responseData); +			break; + +		case PIANO_REQUEST_GET_STATIONS: +			/* get stations */ +			assert (req->responseData != NULL); +			 +			ret = PianoXmlParseStations (ph, req->responseData); +			break; + +		case PIANO_REQUEST_GET_PLAYLIST: { +			/* get playlist, usually four songs */ +			PianoRequestDataGetPlaylist_t *reqData = req->data; + +			assert (req->responseData != NULL); +			assert (reqData != NULL); + +			reqData->retPlaylist = NULL; +			ret = PianoXmlParsePlaylist (ph, req->responseData, +					&reqData->retPlaylist); +			break;  		} -		/* append to xml doc */ -		snprintf (valueBuf, sizeof (valueBuf), -				"<value><string>%s</string></value>", curStation->id); -		strncat (xmlSendBuf, valueBuf, sizeof (xmlSendBuf) - -				strlen (xmlSendBuf) - 1); -		/* append to url arg */ -		strncat (urlArgBuf, curStation->id, sizeof (urlArgBuf) - -				strlen (urlArgBuf) - 1); -		curStation = curStation->next; -		/* if not last item: append "," */ -		if (curStation != NULL) { -			strncat (urlArgBuf, "%2C", sizeof (urlArgBuf) - -					strlen (urlArgBuf) - 1); + +		case PIANO_REQUEST_RATE_SONG: +			/* love/ban song */ +			assert (req->responseData != NULL); + +			ret = PianoXmlParseSimple (req->responseData); +			if (ret == PIANO_RET_OK) { +				PianoRequestDataRateSong_t *reqData = req->data; +				reqData->song->rating = reqData->rating; +			} +			break; + +		case PIANO_REQUEST_ADD_FEEDBACK: +			/* never ever use this directly, low-level call */ +			assert (0); +			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); + +			ret = PianoXmlParseSimple (req->responseData); +			if (ret == PIANO_RET_OK && reqData->step == 0) { +				ret = PIANO_RET_CONTINUE_REQUEST; +				++reqData->step; +			} +			break;  		} -	} -	strncat (xmlSendBuf, -			"</data></array></value></param></params></methodCall>", -			sizeof (xmlSendBuf) - strlen (xmlSendBuf) - 1); - -	snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH -			"rid=%s&lid=%s&method=setQuickMix&arg1=RANDOM&arg2=%s", -			ph->routeId, ph->user.listenerId, urlArgBuf); -	 -	if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == -			PIANO_RET_OK) { -		ret = PianoXmlParseSimple (retStr); -		PianoFree (retStr, 0); -	} -	return ret; -} +		case PIANO_REQUEST_RENAME_STATION: +			/* rename station and update PianoStation_t structure */ +			assert (req->responseData != NULL); -/*	get station from list by id - *	@param search here - *	@param search for this - *	@return the first station structure matching the given id - */ -PianoStation_t *PianoFindStationById (PianoStation_t *stations, -		const char *searchStation) { -	while (stations != NULL) { -		if (strcmp (stations->id, searchStation) == 0) { -			return stations; +			if ((ret = PianoXmlParseSimple (req->responseData)) == PIANO_RET_OK) { +				PianoRequestDataRenameStation_t *reqData = req->data; + +				assert (reqData != NULL); +				assert (reqData->station != NULL); +				assert (reqData->newName != NULL); + +				PianoFree (reqData->station->name, 0); +				reqData->station->name = strdup (reqData->newName); +			} +			break; + +		case PIANO_REQUEST_DELETE_STATION: +			/* delete station from server and station list */ +			assert (req->responseData != NULL); + +			if ((ret = PianoXmlParseSimple (req->responseData)) == PIANO_RET_OK) { +				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); +						PianoFree (curStation, sizeof (*curStation)); +						break; +					} +					lastStation = curStation; +					curStation = curStation->next; +				} +			} +			break; + +		case PIANO_REQUEST_SEARCH: { +			/* search artist/song */ +			PianoRequestDataSearch_t *reqData = req->data; + +			assert (req->responseData != NULL); +			assert (reqData != NULL); + +			ret = PianoXmlParseSearch (req->responseData, &reqData->searchResult); +			break;  		} -		stations = stations->next; -	} -	return NULL; -} -/*	receive genre stations - *	@param piano handle - */ -PianoReturn_t PianoGetGenreStations (PianoHandle_t *ph) { -	char *retStr; -	PianoReturn_t ret; - -	snprintf (ph->waith.path, sizeof (ph->waith.path), "/xml/genre?r=%li", -			time (NULL)); -	 -	if ((ret = PianoHttpGet (&ph->waith, &retStr)) == -			PIANO_RET_OK) { -		ret = PianoXmlParseGenreExplorer (ph, retStr); -		PianoFree (retStr, 0); -	} +		case PIANO_REQUEST_CREATE_STATION: { +			/* create station, insert new station into station list on success */ +			assert (req->responseData != NULL); -	return ret; -} +			ret = PianoXmlParseCreateStation (ph, req->responseData); +			break; +		} -/*	make shared stations private, needed to rate songs played on shared - *	stations - *	@param piano handle - *	@param station to transform - */ -PianoReturn_t PianoTransformShared (PianoHandle_t *ph, -		PianoStation_t *station) { -	char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; -	PianoReturn_t ret; - -	snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -			"<methodCall><methodName>station.transformShared</methodName>" -			"<params><param><value><int>%li</int></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"</params></methodCall>", time (NULL), ph->user.authToken, -			station->id); - -	snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH -			"rid=%s&lid=%s&method=transformShared&arg1=%s", ph->routeId, -			ph->user.listenerId, station->id); -	 -	if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == -			PIANO_RET_OK) { -		ret = PianoXmlParseTranformStation (retStr); -		/* though this call returns a bunch of "new" data only this one is -		 * changed and important (at the moment) */ -		if (ret == PIANO_RET_OK) { -			station->isCreator = 1; +		case PIANO_REQUEST_ADD_SEED: { +			/* add seed to station, updates station structure */ +			PianoRequestDataAddSeed_t *reqData = req->data; + +			assert (req->responseData != NULL); +			assert (reqData != NULL); +			assert (reqData->station != NULL); + +			/* FIXME: update station data instead of replacing them */ +			ret = PianoXmlParseAddSeed (ph, req->responseData, reqData->station); +			break;  		} -		PianoFree (retStr, 0); -	} -	return ret; -} +		case PIANO_REQUEST_ADD_TIRED_SONG: +		case PIANO_REQUEST_SET_QUICKMIX: +		case PIANO_REQUEST_BOOKMARK_SONG: +		case PIANO_REQUEST_BOOKMARK_ARTIST: +			assert (req->responseData != NULL); -/*	"why dit you play this song?" - *	@param piano handle - *	@param song (from playlist) - *	@param return allocated string; you have to free it yourself - */ -PianoReturn_t PianoExplain (PianoHandle_t *ph, const PianoSong_t *song, -		char **retExplain) { -	char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; -	PianoReturn_t ret; - -	snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -			"<methodCall><methodName>playlist.narrative</methodName>" -			"<params><param><value><int>%li</int></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"</params></methodCall>", time (NULL), ph->user.authToken, -			song->stationId, song->musicId); -	 -	snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH -			"rid=%s&lid=%s&method=method=narrative&arg1=%s&arg2=%s", -			ph->routeId, ph->user.listenerId, song->stationId, song->musicId); -	 -	if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == -			PIANO_RET_OK) { -		ret = PianoXmlParseNarrative (retStr, retExplain); -		PianoFree (retStr, 0); -	} +			ret = PianoXmlParseSimple (req->responseData); +			break; -	return ret; -} +		case PIANO_REQUEST_GET_GENRE_STATIONS: +			/* get genre stations */ +			assert (req->responseData != NULL); -/*	Get seed suggestions by music id - *	@param piano handle - *	@param music id - *	@param max results - *	@param result buffer - */ -PianoReturn_t PianoSeedSuggestions (PianoHandle_t *ph, const char *musicId, -		unsigned int max, PianoSearchResult_t *searchResult) { -	char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; -	PianoReturn_t ret; - -	snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -			"<methodCall><methodName>music.getSeedSuggestions</methodName>" -			"<params><param><value><int>%li</int></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><int>%u</int></value></param>" -			"</params></methodCall>", time (NULL), ph->user.authToken, -			musicId, max); -	 -	snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH -			"rid=%s&lid=%s&method=method=getSeedSuggestions&arg1=%s&arg2=%u", -			ph->routeId, ph->user.listenerId, musicId, max); -	 -	if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == -			PIANO_RET_OK) { -		ret = PianoXmlParseSeedSuggestions (retStr, searchResult); -		PianoFree (retStr, 0); -	} +			ret = PianoXmlParseGenreExplorer (ph, req->responseData); +			break; -	return ret; -} +		case PIANO_REQUEST_TRANSFORM_STATION: { +			/* transform shared station into private and update isCreator flag */ +			PianoStation_t *station = req->data; -/*	Create song bookmark - *	@param piano handle - *	@param song - */ -PianoReturn_t PianoBookmarkSong (PianoHandle_t *ph, PianoSong_t *song) { -	char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; -	PianoReturn_t ret; - -	snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -			"<methodCall><methodName>station.createBookmark</methodName>" -			"<params><param><value><int>%li</int></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"</params></methodCall>", time (NULL), ph->user.authToken, -			song->stationId, song->musicId); -	 -	snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH -			"rid=%s&lid=%s&method=method=createBookmark&arg1=%s&arg2=%s", -			ph->routeId, ph->user.listenerId, song->stationId, -			song->musicId); -	 -	if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == -			PIANO_RET_OK) { -		ret = PianoXmlParseSimple (retStr); -		PianoFree (retStr, 0); +			assert (req->responseData != NULL); +			assert (station != NULL); + +			/* though this call returns a bunch of "new" data only this one is +			 * changed and important (at the moment) */ +			if ((ret = PianoXmlParseTranformStation (req->responseData)) == +					PIANO_RET_OK) { +				station->isCreator = 1; +			} +			break; +		} + +		case PIANO_REQUEST_EXPLAIN: { +			/* explain why song was selected */ +			PianoRequestDataExplain_t *reqData = req->data; + +			assert (req->responseData != NULL); +			assert (reqData != NULL); + +			ret = PianoXmlParseNarrative (req->responseData, &reqData->retExplain); +			break; +		} + +		case PIANO_REQUEST_GET_SEED_SUGGESTIONS: { +			/* find similar artists */ +			PianoRequestDataGetSeedSuggestions_t *reqData = req->data; + +			assert (req->responseData != NULL); +			assert (reqData != NULL); + +			ret = PianoXmlParseSeedSuggestions (req->responseData, +					&reqData->searchResult); +			break; +		}  	}  	return ret;  } -/*	Create artist bookmark - *	@param piano handle - *	@param song of artist +/*	get station from list by id + *	@param search here + *	@param search for this + *	@return the first station structure matching the given id   */ -PianoReturn_t PianoBookmarkArtist (PianoHandle_t *ph, PianoSong_t *song) { -	char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; -	PianoReturn_t ret; - -	snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -			"<methodCall><methodName>station.createArtistBookmark</methodName>" -			"<params><param><value><int>%li</int></value></param>" -			"<param><value><string>%s</string></value></param>" -			"<param><value><string>%s</string></value></param>" -			"</params></methodCall>", time (NULL), ph->user.authToken, -			song->artistMusicId); -	 -	snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH -			"rid=%s&lid=%s&method=method=createArtistBookmark&arg1=%s", -			ph->routeId, ph->user.listenerId, song->artistMusicId); -	 -	if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == -			PIANO_RET_OK) { -		ret = PianoXmlParseSimple (retStr); -		PianoFree (retStr, 0); +PianoStation_t *PianoFindStationById (PianoStation_t *stations, +		const char *searchStation) { +	while (stations != NULL) { +		if (strcmp (stations->id, searchStation) == 0) { +			return stations; +		} +		stations = stations->next;  	} - -	return ret; +	return NULL;  }  /*	convert return value to human-readable string @@ -890,10 +946,6 @@ const char *PianoErrorToStr (PianoReturn_t ret) {  			return "Username and/or password not correct.";  			break; -		case PIANO_RET_NET_ERROR: -			return "Connection failed."; -			break; -  		case PIANO_RET_NOT_AUTHORIZED:  			return "Not authorized.";  			break; @@ -938,27 +990,3 @@ const char *PianoErrorToStr (PianoReturn_t ret) {  	}  } -/*	convert audio format id to string that can be used in xml requests - *	@param format id - *	@return constant string - */ -const char *PianoAudioFormatToString (PianoAudioFormat_t format) { -	switch (format) { -		case PIANO_AF_AACPLUS: -			return "aacplus"; -			break; - -		case PIANO_AF_MP3: -			return "mp3"; -			break; - -		case PIANO_AF_MP3_HI: -			return "mp3-hifi"; -			break; - -		default: -			return NULL; -			break; -	} -} - diff --git a/libpiano/src/piano.h b/libpiano/src/piano.h index 553a465..4feccd9 100644 --- a/libpiano/src/piano.h +++ b/libpiano/src/piano.h @@ -29,7 +29,8 @@ THE SOFTWARE.   * all strings _must_ be utf-8 encoded. i won't care, but pandora does. so   * be nice and check the encoding of your strings. thanks :) */ -#include <waitress.h> +#define PIANO_RPC_HOST "www.pandora.com" +#define PIANO_RPC_PORT "80"  typedef struct PianoUserInfo {  	char *webAuthToken; @@ -93,7 +94,6 @@ typedef struct PianoGenreCategory {  } PianoGenreCategory_t;  typedef struct PianoHandle { -	WaitressHandle_t waith;  	char routeId[9];  	PianoUserInfo_t user;  	/* linked lists */ @@ -107,12 +107,107 @@ typedef struct PianoSearchResult {  } PianoSearchResult_t;  typedef enum { +	/* 0 is reserved: memset (x, 0, sizeof (x)) */ +	PIANO_REQUEST_LOGIN = 1, +	PIANO_REQUEST_GET_STATIONS = 2, +	PIANO_REQUEST_GET_PLAYLIST = 3, +	PIANO_REQUEST_RATE_SONG = 4, +	PIANO_REQUEST_ADD_FEEDBACK = 5, +	PIANO_REQUEST_MOVE_SONG = 6, +	PIANO_REQUEST_RENAME_STATION = 7, +	PIANO_REQUEST_DELETE_STATION = 8, +	PIANO_REQUEST_SEARCH = 9, +	PIANO_REQUEST_CREATE_STATION = 10, +	PIANO_REQUEST_ADD_SEED = 11, +	PIANO_REQUEST_ADD_TIRED_SONG = 12, +	PIANO_REQUEST_SET_QUICKMIX = 13, +	PIANO_REQUEST_GET_GENRE_STATIONS = 14, +	PIANO_REQUEST_TRANSFORM_STATION = 15, +	PIANO_REQUEST_EXPLAIN = 16, +	PIANO_REQUEST_GET_SEED_SUGGESTIONS = 17, +	PIANO_REQUEST_BOOKMARK_SONG = 18, +	PIANO_REQUEST_BOOKMARK_ARTIST = 19, +} PianoRequestType_t; + +typedef struct PianoRequest { +	PianoRequestType_t type; +	void *data; +	char urlPath[1024]; +	char *postData; +	char *responseData; +} PianoRequest_t; + +/* request data structures */ +typedef struct { +	char *user; +	char *password; +} PianoRequestDataLogin_t; + +typedef struct { +	PianoStation_t *station; +	PianoAudioFormat_t format; +	PianoSong_t *retPlaylist; +} PianoRequestDataGetPlaylist_t; + +typedef struct { +	PianoSong_t *song; +	PianoSongRating_t rating; +} PianoRequestDataRateSong_t; + +typedef struct { +	char *stationId; +	char *musicId; +	char *matchingSeed; +	char *userSeed; +	char *focusTraitId; +	PianoSongRating_t rating; +} PianoRequestDataAddFeedback_t; + +typedef struct { +	PianoSong_t *song; +	PianoStation_t *from; +	PianoStation_t *to; +	unsigned short step; +} PianoRequestDataMoveSong_t; + +typedef struct { +	PianoStation_t *station; +	char *newName; +} PianoRequestDataRenameStation_t; + +typedef struct { +	char *searchStr; +	PianoSearchResult_t searchResult; +} PianoRequestDataSearch_t; + +typedef struct { +	char *type; +	char *id; +} PianoRequestDataCreateStation_t; + +typedef struct { +	PianoStation_t *station; +	char *musicId; +} PianoRequestDataAddSeed_t; + +typedef struct { +	PianoSong_t *song; +	char *retExplain; +} PianoRequestDataExplain_t; + +typedef struct { +	char *musicId; +	unsigned short max; +	PianoSearchResult_t searchResult; +} PianoRequestDataGetSeedSuggestions_t; + +typedef enum {  	PIANO_RET_ERR = 0,  	PIANO_RET_OK = 1,  	PIANO_RET_XML_INVALID = 2,  	PIANO_RET_AUTH_TOKEN_INVALID = 3,  	PIANO_RET_AUTH_USER_PASSWORD_INVALID = 4, -	PIANO_RET_NET_ERROR = 5, +	PIANO_RET_CONTINUE_REQUEST = 5,  	PIANO_RET_NOT_AUTHORIZED = 6,  	PIANO_RET_PROTOCOL_INCOMPATIBLE = 7,  	PIANO_RET_READONLY_MODE = 8, @@ -121,42 +216,20 @@ typedef enum {  	PIANO_RET_STATION_NONEXISTENT = 11,  	PIANO_RET_OUT_OF_MEMORY = 12,  	PIANO_RET_OUT_OF_SYNC = 13, -	PIANO_RET_PLAYLIST_END = 14 +	PIANO_RET_PLAYLIST_END = 14,  } PianoReturn_t;  void PianoInit (PianoHandle_t *);  void PianoDestroy (PianoHandle_t *);  void PianoDestroyPlaylist (PianoSong_t *);  void PianoDestroySearchResult (PianoSearchResult_t *); -PianoReturn_t PianoConnect (PianoHandle_t *, const char *, const char *); - -PianoReturn_t PianoGetStations (PianoHandle_t *); -PianoReturn_t PianoGetPlaylist (PianoHandle_t *, const char *, -		PianoAudioFormat_t, PianoSong_t **); - -PianoReturn_t PianoRateTrack (PianoHandle_t *, PianoSong_t *, -		PianoSongRating_t); -PianoReturn_t PianoMoveSong (PianoHandle_t *, const PianoStation_t *, -		const PianoStation_t *, const PianoSong_t *); -PianoReturn_t PianoRenameStation (PianoHandle_t *, PianoStation_t *, -		const char *); -PianoReturn_t PianoDeleteStation (PianoHandle_t *, PianoStation_t *); -PianoReturn_t PianoSearchMusic (PianoHandle_t *, const char *, -		PianoSearchResult_t *); -PianoReturn_t PianoCreateStation (PianoHandle_t *, const char *, -		const char *); -PianoReturn_t PianoStationAddMusic (PianoHandle_t *, PianoStation_t *, -		const char *); -PianoReturn_t PianoSongTired (PianoHandle_t *, const PianoSong_t *); -PianoReturn_t PianoSetQuickmix (PianoHandle_t *); + +PianoReturn_t PianoRequest (PianoHandle_t *, PianoRequest_t *, +		PianoRequestType_t); +PianoReturn_t PianoResponse (PianoHandle_t *, PianoRequest_t *); +void PianoDestroyRequest (PianoRequest_t *); +  PianoStation_t *PianoFindStationById (PianoStation_t *, const char *); -PianoReturn_t PianoGetGenreStations (PianoHandle_t *); -PianoReturn_t PianoTransformShared (PianoHandle_t *, PianoStation_t *); -PianoReturn_t PianoExplain (PianoHandle_t *, const PianoSong_t *, char **);  const char *PianoErrorToStr (PianoReturn_t); -PianoReturn_t PianoSeedSuggestions (PianoHandle_t *, const char *, -		unsigned int, PianoSearchResult_t *); -PianoReturn_t PianoBookmarkSong (PianoHandle_t *, PianoSong_t *); -PianoReturn_t PianoBookmarkArtist (PianoHandle_t *, PianoSong_t *);  #endif /* _PIANO_H */ | 
