Ejemplo n.º 1
0
/*	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;

	if ((xmlencodedSearchStr = PianoXmlEncodeString (searchStr)) == NULL) {
		return PIANO_RET_OUT_OF_MEMORY;
	}

	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);
	}

	PianoFree (urlencodedSearchStr, 0);
	PianoFree (xmlencodedSearchStr, 0);

	return ret;
}
Ejemplo n.º 2
0
/*	_really_ submit song to last.fm
 *	@param wardrobe handle
 *	@param song
 *	@return _OK or error
 */
static WardrobeReturn_t WardrobeSendSong (WardrobeHandle_t *wh,
		const WardrobeSong_t *ws) {
	char postContent[WARDROBE_HTTP_SEND_SIZE];
	char *urlencArtist, *urlencTitle, *urlencAlbum, *ret;
	WardrobeReturn_t fRet = WARDROBE_RET_ERR;

	urlencArtist = WaitressUrlEncode (ws->artist);
	urlencTitle = WaitressUrlEncode (ws->title);
	urlencAlbum = WaitressUrlEncode (ws->album);

	snprintf (postContent, sizeof (postContent), "s=%s&a[0]=%s&t[0]=%s&"
			"i[0]=%li&o[0]=E&r[0]=&l[0]=%li&b[0]=%s&n[0]=&m[0]=",
			wh->authToken, urlencArtist, urlencTitle, ws->started,
			ws->length, urlencAlbum);

	WaitressSetHPP (&wh->waith, wh->postHost, wh->postPort, wh->postPath);
	wh->waith.method = WAITRESS_METHOD_POST;
	wh->waith.postData = postContent;
	wh->waith.extraHeaders = "Content-Type: application/x-www-form-urlencoded\r\n";
	if (WaitressFetchBuf (&wh->waith, &ret) != WAITRESS_RET_OK) {
		return WARDROBE_RET_CONNECT_ERR;
	}

	if (memcmp (ret, "OK", 2) == 0) {
		fRet = WARDROBE_RET_OK;
	} else if (memcmp (ret, "BADSESSION", 10) == 0) {
		fRet = WARDROBE_RET_BADSESSION;
	}

	WardrobeFree (urlencArtist, 0);
	WardrobeFree (urlencTitle, 0);
	WardrobeFree (urlencAlbum, 0);
	WardrobeFree (ret, 0);

	return fRet;
}
Ejemplo n.º 3
0
/*	rename station (on the server and local)
 *	@public yes
 *	@param piano handle
 *	@param change this stations name
 *	@param new name
 *	@return
 */
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;

	if ((xmlencodedNewName = PianoXmlEncodeString (newName)) == NULL) {
		return PIANO_RET_OUT_OF_MEMORY;
	}

	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);
		}
		PianoFree (retStr, 0);
	}

	PianoFree (urlencodedNewName, 0);
	PianoFree (xmlencodedNewName, 0);

	return ret;
}
Ejemplo n.º 4
0
/*	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) {
	PianoReturn_t ret = PIANO_RET_OK;
	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 (ph->partner.user));
					json_object_object_add (j, "password",
							json_object_new_string (ph->partner.password));
					json_object_object_add (j, "deviceModel",
							json_object_new_string (ph->partner.device));
					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->partner.authToken));
					json_object_object_add (j, "syncTime",
							json_object_new_int (timestamp));

					urlencAuthToken = WaitressUrlEncode (ph->partner.authToken);
					assert (urlencAuthToken != NULL);
					snprintf (req->urlPath, sizeof (req->urlPath),
							PIANO_RPC_PATH "method=auth.userLogin&"
							"auth_token=%s&partner_id=%i", urlencAuthToken,
							ph->partner.id);
					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);

			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->stationId != NULL);
			assert (reqData->rating != PIANO_RATE_NONE);

			json_object_object_add (j, "stationToken",
					json_object_new_string (reqData->stationId));
			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 musicToken or station number */
			PianoRequestDataCreateStation_t *reqData = req->data;

			assert (reqData != NULL);
			assert (reqData->token != NULL);

			if (reqData->type == PIANO_MUSICTYPE_INVALID) {
				json_object_object_add (j, "musicToken",
						json_object_new_string (reqData->token));
			} else {
				json_object_object_add (j, "trackToken",
						json_object_new_string (reqData->token));
				switch (reqData->type) {
					case PIANO_MUSICTYPE_SONG:
						json_object_object_add (j, "musicType",
								json_object_new_string ("song"));
						break;

					case PIANO_MUSICTYPE_ARTIST:
						json_object_object_add (j, "musicType",
								json_object_new_string ("artist"));
						break;

					default:
						assert (0);
						break;
				}
			}

			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 ();

			PianoListForeachP (curStation) {
				/* quick mix can't contain itself */
				if (curStation->useQuickMix && !curStation->isQuickMix) {
					json_object_array_add (a,
							json_object_new_string (curStation->id));
				}
			}

			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_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;

			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) */
			ret = PianoRequest (ph, req, PIANO_REQUEST_ADD_FEEDBACK);
			/* and reset request type/data */
			req->type = PIANO_REQUEST_RATE_SONG;
			req->data = reqData;

			goto cleanup;
			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->partner.id, 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 (ph->partner.out,
				jsonSendBuf)) == NULL) {
			ret = PIANO_RET_OUT_OF_MEMORY;
		}
	} else {
		req->postData = strdup (jsonSendBuf);
	}

cleanup:
	json_object_put (j);

	return ret;
}
Ejemplo n.º 5
0
/*	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) {
	char xmlSendBuf[PIANO_SEND_BUFFER_SIZE];
	/* corrected timestamp */
	time_t timestamp = time (NULL) - ph->timeOffset;

	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);

			switch (logindata->step) {
				case 0:
					snprintf (xmlSendBuf, sizeof (xmlSendBuf), 
							"<?xml version=\"1.0\"?><methodCall>"
							"<methodName>misc.sync</methodName>"
							"<params></params></methodCall>");
					snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
							"rid=%s&method=sync", ph->routeId);
					break;

				case 1: {
					char *xmlencodedPassword = NULL;

					/* username == email address does not contain &,<,>," */
					if ((xmlencodedPassword =
							PianoXmlEncodeString (logindata->password)) ==
							NULL) {
						return PIANO_RET_OUT_OF_MEMORY;
					}

					snprintf (xmlSendBuf, sizeof (xmlSendBuf), 
							"<?xml version=\"1.0\"?><methodCall>"
							"<methodName>listener.authenticateListener</methodName>"
							"<params><param><value><int>%lu</int></value></param>"
							/* user */
							"<param><value><string>%s</string></value></param>"
							/* password */
							"<param><value><string>%s</string></value></param>"
							/* vendor */
							"<param><value><string>html5tuner</string></value></param>"
							"<param><value><string/></value></param>"
							"<param><value><string/></value></param>"
							"<param><value><string>HTML5</string></value></param>"
							"<param><value><boolean>1</boolean></value></param>"
							"</params></methodCall>", (unsigned long) timestamp,
							logindata->user, xmlencodedPassword);
					snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
							"rid=%s&method=authenticateListener", ph->routeId);

					free (xmlencodedPassword);
					break;
				}
			}
			break;
		}

		case PIANO_REQUEST_GET_STATIONS:
			/* get stations, user must be authenticated */
			assert (ph->user.listenerId != NULL);

			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
					"<methodCall><methodName>station.getStations</methodName>"
					"<params><param><value><int>%lu</int></value></param>"
					"<param><value><string>%s</string></value></param>"
					"</params></methodCall>", (unsigned long) timestamp,
					ph->user.authToken);
			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
					"rid=%s&lid=%s&method=getStations", ph->routeId,
					ph->user.listenerId);
			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);

			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
					"<methodCall><methodName>playlist.getFragment</methodName>"
					"<params><param><value><int>%lu</int></value></param>"
					/* auth token */
					"<param><value><string>%s</string></value></param>"
					/* station id */
					"<param><value><string>%s</string></value></param>"
					/* total listening time */
					"<param><value><string>0</string></value></param>"
					/* time since last session */
					"<param><value><string></string></value></param>"
					/* tracking code */
					"<param><value><string></string></value></param>"
					/* audio format */
					"<param><value><string>%s</string></value></param>"
					/* delta listening time */
					"<param><value><string>0</string></value></param>"
					/* listening timestamp */
					"<param><value><string>0</string></value></param>"
					"</params></methodCall>", (unsigned long) timestamp,
					ph->user.authToken, reqData->station->id,
					PianoAudioFormatToString (reqData->format));
			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
					"rid=%s&lid=%s&method=getFragment&arg1=%s&arg2=0"
					"&arg3=&arg4=&arg5=%s&arg6=0&arg7=0", ph->routeId,
					ph->user.listenerId, reqData->station->id,
					PianoAudioFormatToString (reqData->format));
			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->stationId != NULL);
			assert (reqData->rating != PIANO_RATE_NONE);

			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
					"<methodCall><methodName>station.addFeedback</methodName>"
					"<params><param><value><int>%lu</int></value></param>"
					/* auth token */
					"<param><value><string>%s</string></value></param>"
					/* station id */
					"<param><value><string>%s</string></value></param>"
					/* track token */
					"<param><value><string>%s</string></value></param>"
					/* positive */
					"<param><value><boolean>%i</boolean></value></param>"
					"</params></methodCall>", (unsigned long) timestamp,
					ph->user.authToken, reqData->stationId, reqData->trackToken,
					(reqData->rating == PIANO_RATE_LOVE) ? 1 : 0);
			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
					"rid=%s&lid=%s&method=addFeedback&arg1=%s&arg2=%s"
					"&arg3=%s",
					ph->routeId, ph->user.listenerId, reqData->stationId,
					reqData->trackToken,
					(reqData->rating == PIANO_RATE_LOVE) ? "true" : "false");
			break;
		}

		case PIANO_REQUEST_RENAME_STATION: {
			/* rename stations */
			PianoRequestDataRenameStation_t *reqData = req->data;
			char *urlencodedNewName, *xmlencodedNewName;

			assert (reqData != NULL);
			assert (reqData->station != NULL);
			assert (reqData->newName != NULL);

			if ((xmlencodedNewName = PianoXmlEncodeString (reqData->newName)) == NULL) {
				return PIANO_RET_OUT_OF_MEMORY;
			}
			urlencodedNewName = WaitressUrlEncode (reqData->newName);

			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
					"<methodCall><methodName>station.setStationName</methodName>"
					"<params><param><value><int>%lu</int></value></param>"
					/* auth token */
					"<param><value><string>%s</string></value></param>"
					/* station id */
					"<param><value><string>%s</string></value></param>"
					/* new name */
					"<param><value><string>%s</string></value></param>"
					"</params></methodCall>", (unsigned long) timestamp,
					ph->user.authToken, reqData->station->id,
					xmlencodedNewName);
			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
					"rid=%s&lid=%s&method=setStationName&arg1=%s&arg2=%s",
					ph->routeId, ph->user.listenerId, reqData->station->id,
					urlencodedNewName);

			free (urlencodedNewName);
			free (xmlencodedNewName);
			break;
		}

		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>%lu</int></value></param>"
					/* auth token */
					"<param><value><string>%s</string></value></param>"
					/* station id */
					"<param><value><string>%s</string></value></param>"
					"</params></methodCall>", (unsigned long) timestamp,
					ph->user.authToken, station->id);
			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
					"rid=%s&lid=%s&method=removeStation&arg1=%s", ph->routeId,
					ph->user.listenerId, station->id);
			break;
		}

		case PIANO_REQUEST_SEARCH: {
			/* search for artist/song title */
			PianoRequestDataSearch_t *reqData = req->data;
			char *xmlencodedSearchStr, *urlencodedSearchStr;

			assert (reqData != NULL);
			assert (reqData->searchStr != NULL);

			if ((xmlencodedSearchStr = PianoXmlEncodeString (reqData->searchStr)) == NULL) {
				return PIANO_RET_OUT_OF_MEMORY;
			}
			urlencodedSearchStr = WaitressUrlEncode (reqData->searchStr);

			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
					"<methodCall><methodName>music.search</methodName>"
					"<params><param><value><int>%lu</int></value></param>"
					/* auth token */
					"<param><value><string>%s</string></value></param>"
					/* search string */
					"<param><value><string>%s</string></value></param>"
					"</params></methodCall>", (unsigned long) timestamp,
					ph->user.authToken, xmlencodedSearchStr);
			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
					"rid=%s&lid=%s&method=search&arg1=%s", ph->routeId,
					ph->user.listenerId, urlencodedSearchStr);

			free (urlencodedSearchStr);
			free (xmlencodedSearchStr);
			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);
			assert (reqData->type != NULL);

			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
					"<methodCall><methodName>station.createStation</methodName>"
					"<params><param><value><int>%lu</int></value></param>"
					/* auth token */
					"<param><value><string>%s</string></value></param>"
					/* music id */
					"<param><value><string>%s%s</string></value></param>"
					/* empty */
					"<param><value><string></string></value></param>"
					"</params></methodCall>", (unsigned long) timestamp,
					ph->user.authToken, reqData->type, reqData->id);

			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
					"rid=%s&lid=%s&method=createStation&arg1=%s%s&arg2=", ph->routeId,
					ph->user.listenerId, reqData->type, reqData->id);
			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);

			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
					"<methodCall><methodName>station.addSeed</methodName><params>"
					"<param><value><int>%lu</int></value></param>"
					/* auth token */
					"<param><value><string>%s</string></value></param>"
					/* station id */
					"<param><value><string>%s</string></value></param>"
					/* music id */
					"<param><value><string>%s</string></value></param>"
					"</params></methodCall>", (unsigned long) timestamp,
					ph->user.authToken, reqData->station->id, reqData->musicId);
			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
					"rid=%s&lid=%s&method=addSeed&arg1=%s&arg2=%s", ph->routeId,
					ph->user.listenerId, reqData->station->id, reqData->musicId);
			break;
		}

		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>%lu</int></value></param>"
					"<param><value><string>%s</string></value></param>"
					/* key */
					"<param><value><string>%s</string></value></param>"
					/* user seed */
					"<param><value><string>%s</string></value></param>"
					/* station id */
					"<param><value><string>%s</string></value></param>"
					"</params></methodCall>", (unsigned long) timestamp,
					ph->user.authToken,
					(song->musicId == NULL) ? "" : song->musicId,
					(song->userSeed == NULL) ? "" : song->userSeed,
					song->stationId);
			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
					"rid=%s&lid=%s&method=addTiredSong&arg1=%s&arg2=%s&arg3=%s",
					ph->routeId, ph->user.listenerId,
					(song->musicId == NULL) ? "" : song->musicId,
					(song->userSeed == NULL) ? "" : song->userSeed,
					song->stationId);
			break;
		}

		case PIANO_REQUEST_SET_QUICKMIX: {
			/* select stations included in quickmix (see useQuickMix flag of
			 * PianoStation_t) */
			char valueBuf[1000], urlArgBuf[1000];
			PianoStation_t *curStation = ph->stations;

			memset (urlArgBuf, 0, sizeof (urlArgBuf));
			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
					"<methodCall><methodName>station.setQuickMix</methodName><params>"
					"<param><value><int>%lu</int></value></param>"
					"<param><value><string>%s</string></value></param>"
					/* quick mix type */
					"<param><value><string>RANDOM</string></value></param>"
					"<param><value><array><data>", (unsigned long) timestamp,
					ph->user.authToken);
			while (curStation != NULL) {
				/* quick mix can't contain itself */
				if (!curStation->useQuickMix || curStation->isQuickMix) {
					curStation = curStation->next;
					continue;
				}
				/* append to xml doc */
				snprintf (valueBuf, sizeof (valueBuf),
						"<value><string>%s</string></value>", curStation->id);
				strncat (xmlSendBuf, valueBuf, sizeof (xmlSendBuf) -
						strlen (xmlSendBuf) - 1);
				/* append to url arg */
				strncat (urlArgBuf, curStation->id, sizeof (urlArgBuf) -
						strlen (urlArgBuf) - 1);
				curStation = curStation->next;
				/* if not last item: append "," */
				if (curStation != NULL) {
					strncat (urlArgBuf, "%2C", sizeof (urlArgBuf) -
							strlen (urlArgBuf) - 1);
				}
			}
			strncat (xmlSendBuf,
					"</data></array></value></param>"
					/* empty */
					"<param><value><string></string></value></param>"
					/* empty */
					"<param><value><string></string></value></param>"
					"</params></methodCall>",
					sizeof (xmlSendBuf) - strlen (xmlSendBuf) - 1);

			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
					"rid=%s&lid=%s&method=setQuickMix&arg1=RANDOM&arg2=%s&arg3=&arg4=",
					ph->routeId, ph->user.listenerId, urlArgBuf);
			break;
		}

		case PIANO_REQUEST_GET_GENRE_STATIONS:
			/* receive list of pandora's genre stations */
			xmlSendBuf[0] = '\0';
			snprintf (req->urlPath, sizeof (req->urlPath), "/xml/genre?r=%lu",
					(unsigned long) timestamp);
			break;

		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>%lu</int></value></param>"
					/* auth token */
					"<param><value><string>%s</string></value></param>"
					/* station id */
					"<param><value><string>%s</string></value></param>"
					"</params></methodCall>", (unsigned long) timestamp,
					ph->user.authToken, station->id);
			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
					"rid=%s&lid=%s&method=transformShared&arg1=%s", ph->routeId,
					ph->user.listenerId, station->id);
			break;
		}

		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>%lu</int></value></param>"
					/* auth token */
					"<param><value><string>%s</string></value></param>"
					/* station id */
					"<param><value><string>%s</string></value></param>"
					/* music id */
					"<param><value><string>%s</string></value></param>"
					"</params></methodCall>", (unsigned long) timestamp,
					ph->user.authToken, reqData->song->stationId,
					reqData->song->musicId);
			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
					"rid=%s&lid=%s&method=narrative&arg1=%s&arg2=%s",
					ph->routeId, ph->user.listenerId, reqData->song->stationId,
					reqData->song->musicId);
			break;
		}

		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>%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);
			break;
		}

		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>%lu</int></value></param>"
					/* auth token */
					"<param><value><string>%s</string></value></param>"
					/* station id */
					"<param><value><string>%s</string></value></param>"
					/* music id */
					"<param><value><string>%s</string></value></param>"
					"</params></methodCall>", (unsigned long) timestamp,
					ph->user.authToken, song->stationId, song->musicId);
			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
					"rid=%s&lid=%s&method=createBookmark&arg1=%s&arg2=%s",
					ph->routeId, ph->user.listenerId, song->stationId,
					song->musicId);
			break;
		}

		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>%lu</int></value></param>"
					/* auth token */
					"<param><value><string>%s</string></value></param>"
					/* music id */
					"<param><value><string>%s</string></value></param>"
					"</params></methodCall>", (unsigned long) timestamp,
					ph->user.authToken, song->artistMusicId);
			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
					"rid=%s&lid=%s&method=createArtistBookmark&arg1=%s",
					ph->routeId, ph->user.listenerId, song->artistMusicId);
			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);

			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
					"<methodCall><methodName>station.getStation</methodName>"
					"<params><param><value><int>%lu</int></value></param>"
					/* auth token */
					"<param><value><string>%s</string></value></param>"
					/* station id */
					"<param><value><string>%s</string></value></param>"
					"</params></methodCall>", (unsigned long) timestamp,
					ph->user.authToken, reqData->station->id);
			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
					"rid=%s&lid=%s&method=getStation&arg1=%s",
					ph->routeId, ph->user.listenerId, reqData->station->id);
			break;
		}

		case PIANO_REQUEST_DELETE_FEEDBACK: {
			PianoSong_t *song = req->data;

			assert (song != NULL);

			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
					"<methodCall><methodName>station.deleteFeedback</methodName>"
					"<params><param><value><int>%lu</int></value></param>"
					/* auth token */
					"<param><value><string>%s</string></value></param>"
					/* feedback id */
					"<param><value><string>%s</string></value></param>"
					"</params></methodCall>", (unsigned long) timestamp,
					ph->user.authToken, song->feedbackId);
			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
					"rid=%s&lid=%s&method=deleteFeedback&arg1=%s",
					ph->routeId, ph->user.listenerId, song->feedbackId);
			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);

			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
					"<methodCall><methodName>station.deleteSeed</methodName>"
					"<params><param><value><int>%lu</int></value></param>"
					/* auth token */
					"<param><value><string>%s</string></value></param>"
					/* seed id */
					"<param><value><string>%s</string></value></param>"
					"</params></methodCall>", (unsigned long) timestamp,
					ph->user.authToken, seedId);
			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
					"rid=%s&lid=%s&method=deleteSeed&arg1=%s",
					ph->routeId, ph->user.listenerId, seedId);
			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;
		}
	}

	if ((req->postData = PianoEncryptString (xmlSendBuf)) == NULL) {
		return PIANO_RET_OUT_OF_MEMORY;
	}

	return PIANO_RET_OK;
}