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 | |
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.
-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 */ |