diff options
Diffstat (limited to 'src/libpiano')
-rw-r--r-- | src/libpiano/crypt.c | 205 | ||||
-rw-r--r-- | src/libpiano/piano.c | 1146 | ||||
-rw-r--r-- | src/libpiano/piano.h | 79 | ||||
-rw-r--r-- | src/libpiano/xml.c | 980 | ||||
-rw-r--r-- | src/libpiano/xml.h | 49 |
5 files changed, 749 insertions, 1710 deletions
diff --git a/src/libpiano/crypt.c b/src/libpiano/crypt.c index 3f98f42..6dafcca 100644 --- a/src/libpiano/crypt.c +++ b/src/libpiano/crypt.c @@ -1,5 +1,5 @@ /* -Copyright (c) 2008-2011 +Copyright (c) 2008-2012 Lars-Dominik Braun <lars@6xq.net> Permission is hereby granted, free of charge, to any person obtaining a copy @@ -22,23 +22,13 @@ THE SOFTWARE. */ #include <string.h> +#include <assert.h> +#include <gcrypt.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> -#include <arpa/inet.h> #include "crypt.h" -#include "crypt_key_output.h" -#include "crypt_key_input.h" -#include "piano_private.h" - -#define byteswap32(x) ((((x) >> 24) & 0x000000ff) | \ - (((x) >> 8) & 0x0000ff00) | \ - (((x) << 8) & 0x00ff0000) | \ - (((x) << 24) & 0xff000000)) - -#define hostToBigEndian32(x) htonl(x) -#define bigToHostEndian32(x) ntohl(x) /* decrypt hex-encoded, blowfish-crypted string: decode 2 hex-encoded blocks, * decrypt, byteswap @@ -46,162 +36,69 @@ THE SOFTWARE. * @param decrypted string length (without trailing NUL) * @return decrypted string or NULL */ -#define INITIAL_SHIFT 28 -#define SHIFT_DEC 4 -char *PianoDecryptString (const char * const s, size_t * const retSize) { - const unsigned char *strInput = (const unsigned char *) s; - /* hex-decode => strlen/2 + null-byte */ - uint32_t *iDecrypt; - size_t decryptedSize; - char *strDecrypted; - unsigned char shift = INITIAL_SHIFT, intsDecoded = 0, j; - /* blowfish blocks, 32-bit */ - uint32_t f, l, r, lrExchange; - - decryptedSize = strlen ((const char *) strInput)/2; - if ((iDecrypt = calloc (decryptedSize/sizeof (*iDecrypt)+1, - sizeof (*iDecrypt))) == NULL) { - return NULL; - } - strDecrypted = (char *) iDecrypt; - - while (*strInput != '\0') { - /* hex-decode string */ - if (*strInput >= '0' && *strInput <= '9') { - *iDecrypt |= (*strInput & 0x0f) << shift; - } else if (*strInput >= 'a' && *strInput <= 'f') { - /* 0xa (hex) = 10 (decimal), 'a' & 0x0f == 1 => +9 */ - *iDecrypt |= ((*strInput+9) & 0x0f) << shift; - } - if (shift > 0) { - shift -= SHIFT_DEC; - } else { - shift = INITIAL_SHIFT; - /* initialize next dword */ - *(++iDecrypt) = 0; - ++intsDecoded; - } - - /* two 32-bit hex-decoded boxes available => blowfish decrypt */ - if (intsDecoded == 2) { - l = *(iDecrypt-2); - r = *(iDecrypt-1); - - for (j = in_key_n + 1; j > 1; --j) { - l ^= in_key_p [j]; - - f = in_key_s [0][(l >> 24) & 0xff] + - in_key_s [1][(l >> 16) & 0xff]; - f ^= in_key_s [2][(l >> 8) & 0xff]; - f += in_key_s [3][l & 0xff]; - r ^= f; - /* exchange l & r */ - lrExchange = l; - l = r; - r = lrExchange; - } - /* exchange l & r */ - lrExchange = l; - l = r; - r = lrExchange; - r ^= in_key_p [1]; - l ^= in_key_p [0]; - - *(iDecrypt-2) = bigToHostEndian32 (l); - *(iDecrypt-1) = bigToHostEndian32 (r); - - intsDecoded = 0; - } - ++strInput; +char *PianoDecryptString (const char * const input, size_t * const retSize) { + size_t inputLen = strlen (input); + gcry_error_t gret; + unsigned char *output; + size_t outputLen = inputLen/2; + + assert (inputLen%2 == 0); + + output = calloc (outputLen+1, sizeof (*output)); + /* hex decode */ + for (size_t i = 0; i < outputLen; i++) { + char hex[3]; + memcpy (hex, &input[i*2], 2); + hex[2] = '\0'; + output[i] = strtol (hex, NULL, 16); } - if (retSize != NULL) { - *retSize = decryptedSize; + gcry_cipher_hd_t h; + gcry_cipher_open (&h, GCRY_CIPHER_BLOWFISH, GCRY_CIPHER_MODE_ECB, 0); + gcry_cipher_setkey (h, (unsigned char *) "R=U!LH$O2B#", 11); + gret = gcry_cipher_decrypt (h, output, outputLen, NULL, 0); + if (gret) { + fprintf (stderr, "Failure: %s/%s\n", gcry_strsource (gret), gcry_strerror (gret)); + return NULL; } - return strDecrypted; + gcry_cipher_close (h); + *retSize = outputLen; + + return (char *) output; } -#undef INITIAL_SHIFT -#undef SHIFT_DEC /* blowfish-encrypt/hex-encode string * @param encrypt this * @return encrypted, hex-encoded string */ char *PianoEncryptString (const char *s) { - const unsigned char *strInput = (const unsigned char *) s; - const size_t strInputN = strlen ((const char *) strInput); - /* num of 64-bit blocks, rounded to next block */ - size_t blockN = strInputN / 8 + 1; - uint32_t *blockInput, *blockPtr; - /* output string */ - unsigned char *strHex, *hexPtr; - const char *hexmap = "0123456789abcdef"; - - if ((blockInput = calloc (blockN*2, sizeof (*blockInput))) == NULL) { + unsigned char *paddedInput, *hexOutput; + size_t inputLen = strlen (s); + /* blowfish expects two 32 bit blocks */ + size_t paddedInputLen = (inputLen % 8 == 0) ? inputLen : inputLen + (8-inputLen%8); + gcry_error_t gret; + + paddedInput = calloc (paddedInputLen+1, sizeof (*paddedInput)); + memcpy (paddedInput, s, inputLen); + + gcry_cipher_hd_t h; + gcry_cipher_open (&h, GCRY_CIPHER_BLOWFISH, GCRY_CIPHER_MODE_ECB, 0); + gcry_cipher_setkey (h, (unsigned char *) "6#26FRL$ZWD", 11); + gret = gcry_cipher_encrypt (h, paddedInput, paddedInputLen, NULL, 0); + if (gret) { + fprintf (stderr, "Failure: %s/%s\n", gcry_strsource (gret), gcry_strerror (gret)); return NULL; } - blockPtr = blockInput; - if ((strHex = calloc (blockN*8*2 + 1, sizeof (*strHex))) == NULL) { - return NULL; - } - hexPtr = strHex; - - memcpy (blockInput, strInput, strInputN); - - while (blockN > 0) { - /* encryption blocks */ - uint32_t f, lrExchange; - register uint32_t l, r; - int i; - - l = hostToBigEndian32 (*blockPtr); - r = hostToBigEndian32 (*(blockPtr+1)); - - /* encrypt blocks */ - for (i = 0; i < out_key_n; i++) { - l ^= out_key_p[i]; - - f = out_key_s[0][(l >> 24) & 0xff] + - out_key_s[1][(l >> 16) & 0xff]; - f ^= out_key_s[2][(l >> 8) & 0xff]; - f += out_key_s[3][l & 0xff]; - r ^= f; - /* exchange l & r */ - lrExchange = l; - l = r; - r = lrExchange; - } - /* exchange l & r again */ - lrExchange = l; - l = r; - r = lrExchange; - r ^= out_key_p [out_key_n]; - l ^= out_key_p [out_key_n+1]; - - /* swap bytes again... */ - l = byteswap32 (l); - r = byteswap32 (r); - - /* hex-encode encrypted blocks */ - for (i = 0; i < 4; i++) { - *hexPtr++ = hexmap[(l & 0xf0) >> 4]; - *hexPtr++ = hexmap[l & 0x0f]; - l >>= 8; - } - for (i = 0; i < 4; i++) { - *hexPtr++ = hexmap[(r & 0xf0) >> 4]; - *hexPtr++ = hexmap[r & 0x0f]; - r >>= 8; - } - - /* two! 32-bit blocks encrypted (l & r) */ - blockPtr += 2; - --blockN; + hexOutput = calloc (paddedInputLen*2+1, sizeof (*hexOutput)); + for (size_t i = 0; i < paddedInputLen; i++) { + snprintf ((char * restrict) &hexOutput[i*2], 3, "%02x", paddedInput[i]); } - free (blockInput); + gcry_cipher_close (h); + free (paddedInput); - return (char *) strHex; + return (char *) hexOutput; } + diff --git a/src/libpiano/piano.c b/src/libpiano/piano.c index 342e4ec..0ac96b6 100644 --- a/src/libpiano/piano.c +++ b/src/libpiano/piano.c @@ -1,5 +1,5 @@ /* -Copyright (c) 2008-2011 +Copyright (c) 2008-2012 Lars-Dominik Braun <lars@6xq.net> Permission is hereby granted, free of charge, to any person obtaining a copy @@ -32,20 +32,17 @@ THE SOFTWARE. #include <time.h> #include <assert.h> #include <stdint.h> +#include <json.h> /* needed for urlencode */ #include <waitress.h> #include "piano_private.h" #include "piano.h" -#include "xml.h" #include "crypt.h" #include "config.h" -#define PIANO_PROTOCOL_VERSION "33" -#define PIANO_RPC_HOST "www.pandora.com" -#define PIANO_RPC_PORT "80" -#define PIANO_RPC_PATH "/radio/xmlrpc/v" PIANO_PROTOCOL_VERSION "?" +#define PIANO_RPC_PATH "/services/json/?" #define PIANO_SEND_BUFFER_SIZE 10000 /* initialize piano handle @@ -54,10 +51,6 @@ THE SOFTWARE. */ void PianoInit (PianoHandle_t *ph) { memset (ph, 0, sizeof (*ph)); - - /* route-id seems to be random. we're using time anyway... */ - snprintf (ph->routeId, sizeof (ph->routeId), "%07luP", - (unsigned long) time (NULL) % 10000000); } /* destroy artist linked list @@ -125,10 +118,8 @@ void PianoDestroyPlaylist (PianoSong_t *playlist) { free (curSong->artist); free (curSong->musicId); free (curSong->title); - free (curSong->userSeed); free (curSong->stationId); free (curSong->album); - free (curSong->artistMusicId); free (curSong->feedbackId); free (curSong->seedId); free (curSong->detailUrl); @@ -164,7 +155,6 @@ static void PianoDestroyGenres (PianoGenre_t *genres) { /* destroy user information */ static void PianoDestroyUserInfo (PianoUserInfo_t *user) { - free (user->webAuthToken); free (user->authToken); free (user->listenerId); } @@ -185,6 +175,7 @@ void PianoDestroy (PianoHandle_t *ph) { curGenreCat = curGenreCat->next; free (lastGenreCat); } + free (ph->partnerAuthToken); memset (ph, 0, sizeof (*ph)); } @@ -228,9 +219,12 @@ static const char *PianoAudioFormatToString (PianoAudioFormat_t format) { */ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, PianoRequestType_t type) { - char xmlSendBuf[PIANO_SEND_BUFFER_SIZE]; + 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); @@ -248,66 +242,59 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, 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); + encrypted = false; + req->secure = true; + + json_object_object_add (j, "username", + json_object_new_string ("android")); + json_object_object_add (j, "password", + json_object_new_string ("AC7IBG09A3DTSYM4R41UJWL07VLN8JI7")); + json_object_object_add (j, "deviceModel", + json_object_new_string ("android-generic")); + json_object_object_add (j, "version", + json_object_new_string ("5")); + json_object_object_add (j, "includeUrls", + json_object_new_boolean (true)); + snprintf (req->urlPath, sizeof (req->urlPath), + PIANO_RPC_PATH "method=auth.partnerLogin"); break; case 1: { - char *xmlencodedPassword = NULL; + char *urlencAuthToken; req->secure = true; - /* username == email address does not contain &,<,>," */ - if ((xmlencodedPassword = - PianoXmlEncodeString (logindata->password)) == - NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } + json_object_object_add (j, "loginType", + json_object_new_string ("user")); + json_object_object_add (j, "username", + json_object_new_string (logindata->user)); + json_object_object_add (j, "password", + json_object_new_string (logindata->password)); + json_object_object_add (j, "partnerAuthToken", + json_object_new_string (ph->partnerAuthToken)); + json_object_object_add (j, "syncTime", + json_object_new_int (timestamp)); + + urlencAuthToken = WaitressUrlEncode (ph->partnerAuthToken); + assert (urlencAuthToken != NULL); + snprintf (req->urlPath, sizeof (req->urlPath), + PIANO_RPC_PATH "method=auth.userLogin&" + "auth_token=%s&partner_id=%i", urlencAuthToken, + ph->partnerId); + free (urlencAuthToken); - 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: + 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); + method = "user.getStationList"; break; + } case PIANO_REQUEST_GET_PLAYLIST: { /* get playlist for specified station */ @@ -318,33 +305,12 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, 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)); + req->secure = true; + + json_object_object_add (j, "stationToken", + json_object_new_string (reqData->station->id)); + + method = "station.getPlaylist"; break; } @@ -353,65 +319,31 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, PianoRequestDataAddFeedback_t *reqData = req->data; assert (reqData != NULL); - assert (reqData->stationId != NULL); + assert (reqData->trackToken != 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"); + 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: { - /* 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); + 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)); - free (urlencodedNewName); - free (xmlencodedNewName); + method = "station.renameStation"; break; } @@ -420,50 +352,26 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, PianoStation_t *station = req->data; assert (station != NULL); + assert (station->id != 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); + 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; - 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); + json_object_object_add (j, "searchText", + json_object_new_string (reqData->searchStr)); - free (urlencodedSearchStr); - free (xmlencodedSearchStr); + method = "music.search"; break; } @@ -474,23 +382,11 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, 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); + json_object_object_add (j, "musicToken", + json_object_new_string (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); + method = "station.createStation"; break; } @@ -502,20 +398,12 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, 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); + 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; } @@ -525,87 +413,40 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, 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); + 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) */ - char valueBuf[1000], urlArgBuf[1000]; PianoStation_t *curStation = ph->stations; + json_object *a = json_object_new_array (); - 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; + if (curStation->useQuickMix && !curStation->isQuickMix) { + json_object_array_add (a, + json_object_new_string (curStation->id)); } - /* 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); + json_object_object_add (j, "quickMixStationIds", a); + + method = "user.setQuickMix"; break; } - case PIANO_REQUEST_GET_GENRE_STATIONS: + 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); + method = "station.getGenreStations"; break; + } case PIANO_REQUEST_TRANSFORM_STATION: { /* transform shared station into private */ @@ -613,18 +454,10 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, 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); + json_object_object_add (j, "stationToken", + json_object_new_string (station->id)); + + method = "station.transformSharedStation"; break; } @@ -635,26 +468,15 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, 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); + json_object_object_add (j, "trackToken", + json_object_new_string (reqData->song->trackToken)); + + method = "track.explainTrack"; break; } case PIANO_REQUEST_GET_SEED_SUGGESTIONS: { +#if 0 /* find similar artists */ PianoRequestDataGetSeedSuggestions_t *reqData = req->data; @@ -679,6 +501,7 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=getSeedSuggestions&arg1=%s&arg2=%u", ph->routeId, ph->user.listenerId, reqData->musicId, reqData->max); +#endif break; } @@ -688,21 +511,10 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, 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); + json_object_object_add (j, "trackToken", + json_object_new_string (song->trackToken)); + + method = "bookmark.addSongBookmark"; break; } @@ -712,18 +524,10 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, 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); + json_object_object_add (j, "trackToken", + json_object_new_string (song->trackToken)); + + method = "bookmark.addArtistBookmark"; break; } @@ -734,18 +538,12 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, 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); + 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; } @@ -754,18 +552,10 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, 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); + json_object_object_add (j, "feedbackId", + json_object_new_string (song->feedbackId)); + + method = "station.deleteFeedback"; break; } @@ -787,18 +577,10 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, 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); + json_object_object_add (j, "seedId", + json_object_new_string (seedId)); + + method = "station.deleteMusic"; break; } @@ -866,24 +648,89 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, } } - if ((req->postData = PianoEncryptString (xmlSendBuf)) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; + /* standard parameter */ + if (method != NULL) { + char *urlencAuthToken; + + assert (ph->user.authToken != NULL); + + urlencAuthToken = WaitressUrlEncode (ph->user.authToken); + assert (urlencAuthToken != NULL); + + snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH + "method=%s&auth_token=%s&partner_id=%i&user_id=%s", method, + urlencAuthToken, ph->partnerId, ph->user.listenerId); + + free (urlencAuthToken); + + json_object_object_add (j, "userAuthToken", + json_object_new_string (ph->user.authToken)); + json_object_object_add (j, "syncTime", + json_object_new_int (timestamp)); + } + + /* json to string */ + jsonSendBuf = json_object_to_json_string (j); + if (encrypted) { + if ((req->postData = PianoEncryptString (jsonSendBuf)) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + } else { + req->postData = strdup (jsonSendBuf); } + json_object_put (j); return PIANO_RET_OK; } +static char *PianoJsonStrdup (json_object *j, const char *key) { + return strdup (json_object_get_string (json_object_object_get (j, key))); +} + +static void PianoJsonParseStation (json_object *j, PianoStation_t *s) { + s->name = PianoJsonStrdup (j, "stationName"); + s->id = PianoJsonStrdup (j, "stationToken"); + s->isCreator = !json_object_get_boolean (json_object_object_get (j, + "isShared")); + s->isQuickMix = json_object_get_boolean (json_object_object_get (j, + "isQuickMix")); +} + /* parse xml response and update data structures/return new data structure * @param piano handle * @param initialized request (expects responseData to be a NUL-terminated * string) */ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { - PianoReturn_t ret = PIANO_RET_ERR; + PianoReturn_t ret = PIANO_RET_OK; + json_object *j, *result, *status; assert (ph != NULL); assert (req != NULL); + j = json_tokener_parse (req->responseData); + + status = json_object_object_get (j, "stat"); + if (status == NULL) { + json_object_put (j); + return PIANO_RET_INVALID_RESPONSE; + } + + /* error handling */ + if (strcmp (json_object_get_string (status), "ok") != 0) { + json_object *code = json_object_object_get (j, "code"); + if (code == NULL) { + ret = PIANO_RET_INVALID_RESPONSE; + } else { + ret = json_object_get_int (code)+PIANO_RET_OFFSET; + } + + json_object_put (j); + return ret; + } + + result = json_object_object_get (j, "result"); + switch (req->type) { case PIANO_REQUEST_LOGIN: { /* authenticate user */ @@ -894,29 +741,28 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { switch (reqData->step) { case 0: { - char *cryptedTimestamp = NULL; - - assert (req->responseData != NULL); - - /* abusing parseNarrative; has same xml structure */ - ret = PianoXmlParseNarrative (req->responseData, &cryptedTimestamp); - if (ret == PIANO_RET_OK && cryptedTimestamp != NULL) { - unsigned long timestamp = 0; - const time_t realTimestamp = time (NULL); - char *decryptedTimestamp = NULL; - size_t decryptedSize; - - ret = PIANO_RET_ERR; - if ((decryptedTimestamp = PianoDecryptString (cryptedTimestamp, - &decryptedSize)) != NULL && decryptedSize > 4) { - /* skip four bytes garbage(?) at beginning */ - timestamp = strtoul (decryptedTimestamp+4, NULL, 0); - ph->timeOffset = realTimestamp - timestamp; - ret = PIANO_RET_CONTINUE_REQUEST; - } - free (decryptedTimestamp); + /* decrypt timestamp */ + const char *cryptedTimestamp = json_object_get_string ( + json_object_object_get (result, "syncTime")); + unsigned long timestamp = 0; + const time_t realTimestamp = time (NULL); + char *decryptedTimestamp = NULL; + size_t decryptedSize; + + ret = PIANO_RET_ERR; + if ((decryptedTimestamp = PianoDecryptString (cryptedTimestamp, + &decryptedSize)) != NULL && decryptedSize > 4) { + /* skip four bytes garbage(?) at beginning */ + timestamp = strtoul (decryptedTimestamp+4, NULL, 0); + ph->timeOffset = realTimestamp - timestamp; + ret = PIANO_RET_CONTINUE_REQUEST; } - free (cryptedTimestamp); + free (decryptedTimestamp); + /* get auth token */ + ph->partnerAuthToken = PianoJsonStrdup (result, + "partnerAuthToken"); + ph->partnerId = json_object_get_int ( + json_object_object_get (result, "partnerId")); ++reqData->step; break; } @@ -927,48 +773,145 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { if (ph->user.listenerId != NULL) { PianoDestroyUserInfo (&ph->user); } - ret = PianoXmlParseUserinfo (ph, req->responseData); + ph->user.listenerId = PianoJsonStrdup (result, "userId"); + ph->user.authToken = PianoJsonStrdup (result, + "userAuthToken"); break; } break; } - case PIANO_REQUEST_GET_STATIONS: + case PIANO_REQUEST_GET_STATIONS: { /* get stations */ assert (req->responseData != NULL); - - ret = PianoXmlParseStations (ph, req->responseData); + + json_object *stations = json_object_object_get (result, + "stations"), *mix = NULL; + + for (size_t i=0; i < json_object_array_length (stations); i++) { + PianoStation_t *tmpStation; + json_object *s = json_object_array_get_idx (stations, i); + + if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + PianoJsonParseStation (s, tmpStation); + + if (tmpStation->isQuickMix) { + /* fix flags on other stations later */ + mix = json_object_object_get (s, "quickMixStationIds"); + } + + /* start new linked list or append */ + if (ph->stations == NULL) { + ph->stations = tmpStation; + } else { + PianoStation_t *curStation = ph->stations; + while (curStation->next != NULL) { + curStation = curStation->next; + } + curStation->next = tmpStation; + } + } + + /* fix quickmix flags */ + if (mix != NULL) { + PianoStation_t *curStation = ph->stations; + while (curStation != NULL) { + for (size_t i = 0; i < json_object_array_length (mix); i++) { + json_object *id = json_object_array_get_idx (mix, i); + if (strcmp (json_object_get_string (id), + curStation->id) == 0) { + curStation->useQuickMix = true; + } + } + curStation = curStation->next; + } + } break; + } case PIANO_REQUEST_GET_PLAYLIST: { /* get playlist, usually four songs */ PianoRequestDataGetPlaylist_t *reqData = req->data; + PianoSong_t *playlist = NULL; assert (req->responseData != NULL); assert (reqData != NULL); - reqData->retPlaylist = NULL; - ret = PianoXmlParsePlaylist (ph, req->responseData, - &reqData->retPlaylist); + json_object *items = json_object_object_get (result, "items"); + assert (items != NULL); + + for (size_t i=0; i < json_object_array_length (items); i++) { + json_object *s = json_object_array_get_idx (items, i); + PianoSong_t *song; + + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + if (json_object_object_get (s, "artistName") == NULL) { + free (song); + continue; + } + song->audioUrl = strdup (json_object_get_string (json_object_object_get (json_object_object_get (json_object_object_get (s, "audioUrlMap"), "highQuality"), "audioUrl"))); + song->artist = PianoJsonStrdup (s, "artistName"); + song->album = PianoJsonStrdup (s, "albumName"); + song->title = PianoJsonStrdup (s, "songName"); + song->trackToken = PianoJsonStrdup (s, "trackToken"); + song->stationId = PianoJsonStrdup (s, "stationId"); + song->fileGain = json_object_get_double ( + json_object_object_get (s, "trackGain")); + song->audioFormat = PIANO_AF_AACPLUS; + switch (json_object_get_int (json_object_object_get (s, + "songRating"))) { + case 1: + song->rating = PIANO_RATE_LOVE; + break; + } + + /* begin linked list or append */ + if (playlist == NULL) { + playlist = song; + } else { + PianoSong_t *curSong = playlist; + while (curSong->next != NULL) { + curSong = curSong->next; + } + curSong->next = song; + } + } + + reqData->retPlaylist = playlist; break; } - case PIANO_REQUEST_RATE_SONG: + 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; - } + PianoRequestDataRateSong_t *reqData = req->data; + reqData->song->rating = reqData->rating; break; + } case PIANO_REQUEST_ADD_FEEDBACK: /* never ever use this directly, low-level call */ assert (0); break; + case PIANO_REQUEST_RENAME_STATION: { + /* rename station and update PianoStation_t structure */ + PianoRequestDataRenameStation_t *reqData = req->data; + + assert (reqData != NULL); + assert (reqData->station != NULL); + assert (reqData->newName != NULL); + + free (reqData->station->name); + reqData->station->name = strdup (reqData->newName); + break; + } + case PIANO_REQUEST_MOVE_SONG: { /* move song to different station */ PianoRequestDataMoveSong_t *reqData = req->data; @@ -977,107 +920,204 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { assert (reqData != NULL); assert (reqData->step < 2); - ret = PianoXmlParseSimple (req->responseData); - if (ret == PIANO_RET_OK && reqData->step == 0) { + if (reqData->step == 0) { ret = PIANO_RET_CONTINUE_REQUEST; ++reqData->step; } break; } - case PIANO_REQUEST_RENAME_STATION: - /* rename station and update PianoStation_t structure */ - assert (req->responseData != NULL); - - if ((ret = PianoXmlParseSimple (req->responseData)) == PIANO_RET_OK) { - PianoRequestDataRenameStation_t *reqData = req->data; + case PIANO_REQUEST_DELETE_STATION: { + /* delete station from server and station list */ + PianoStation_t *station = req->data; - assert (reqData != NULL); - assert (reqData->station != NULL); - assert (reqData->newName != NULL); + assert (station != NULL); - free (reqData->station->name); - reqData->station->name = strdup (reqData->newName); + /* delete station from local station list */ + PianoStation_t *curStation = ph->stations, *lastStation = NULL; + while (curStation != NULL) { + if (curStation == station) { + if (lastStation != NULL) { + lastStation->next = curStation->next; + } else { + /* first station in list */ + ph->stations = curStation->next; + } + PianoDestroyStation (curStation); + free (curStation); + break; + } + lastStation = curStation; + curStation = curStation->next; } break; + } + + case PIANO_REQUEST_SEARCH: { + /* search artist/song */ + PianoRequestDataSearch_t *reqData = req->data; + PianoSearchResult_t *searchResult; - case PIANO_REQUEST_DELETE_STATION: - /* delete station from server and station list */ assert (req->responseData != NULL); + assert (reqData != NULL); - if ((ret = PianoXmlParseSimple (req->responseData)) == PIANO_RET_OK) { - PianoStation_t *station = req->data; + searchResult = &reqData->searchResult; + memset (searchResult, 0, sizeof (*searchResult)); - assert (station != NULL); + /* get artists */ + json_object *artists = json_object_object_get (result, "artists"); + if (artists != NULL) { + for (size_t i=0; i < json_object_array_length (artists); i++) { + json_object *a = json_object_array_get_idx (artists, i); + PianoArtist_t *artist; - /* 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; + if ((artist = calloc (1, sizeof (*artist))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + artist->name = PianoJsonStrdup (a, "artistName"); + artist->musicId = PianoJsonStrdup (a, "musicToken"); + + /* add result to linked list */ + if (searchResult->artists == NULL) { + searchResult->artists = artist; + } else { + PianoArtist_t *curArtist = searchResult->artists; + while (curArtist->next != NULL) { + curArtist = curArtist->next; } - PianoDestroyStation (curStation); - free (curStation); - break; + curArtist->next = artist; } - lastStation = curStation; - curStation = curStation->next; } } - break; - case PIANO_REQUEST_SEARCH: { - /* search artist/song */ - PianoRequestDataSearch_t *reqData = req->data; + /* get songs */ + json_object *songs = json_object_object_get (result, "songs"); + if (songs != NULL) { + for (size_t i=0; i < json_object_array_length (songs); i++) { + json_object *s = json_object_array_get_idx (songs, i); + PianoSong_t *song; - assert (req->responseData != NULL); - assert (reqData != NULL); + if ((song = calloc (1, sizeof (*song))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } - ret = PianoXmlParseSearch (req->responseData, &reqData->searchResult); + song->title = PianoJsonStrdup (s, "songName"); + song->artist = PianoJsonStrdup (s, "artistName"); + song->musicId = PianoJsonStrdup (s, "musicToken"); + + /* add result to linked list */ + if (searchResult->songs == NULL) { + searchResult->songs = song; + } else { + PianoSong_t *curSong = searchResult->songs; + while (curSong->next != NULL) { + curSong = curSong->next; + } + curSong->next = song; + } + } + } break; } case PIANO_REQUEST_CREATE_STATION: { /* create station, insert new station into station list on success */ - assert (req->responseData != NULL); + PianoStation_t *tmpStation; - ret = PianoXmlParseCreateStation (ph, req->responseData); - break; - } - - case PIANO_REQUEST_ADD_SEED: { - /* add seed to station, updates station structure */ - PianoRequestDataAddSeed_t *reqData = req->data; + if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } - assert (req->responseData != NULL); - assert (reqData != NULL); - assert (reqData->station != NULL); + PianoJsonParseStation (result, tmpStation); - /* FIXME: update station data instead of replacing them */ - ret = PianoXmlParseAddSeed (ph, req->responseData, reqData->station); + /* start new linked list or append */ + if (ph->stations == NULL) { + ph->stations = tmpStation; + } else { + PianoStation_t *curStation = ph->stations; + while (curStation->next != NULL) { + curStation = curStation->next; + } + curStation->next = tmpStation; + } break; } + case PIANO_REQUEST_ADD_SEED: case PIANO_REQUEST_ADD_TIRED_SONG: case PIANO_REQUEST_SET_QUICKMIX: case PIANO_REQUEST_BOOKMARK_SONG: case PIANO_REQUEST_BOOKMARK_ARTIST: case PIANO_REQUEST_DELETE_FEEDBACK: - assert (req->responseData != NULL); - - ret = PianoXmlParseSimple (req->responseData); + case PIANO_REQUEST_DELETE_SEED: + /* response unused */ break; - case PIANO_REQUEST_GET_GENRE_STATIONS: + case PIANO_REQUEST_GET_GENRE_STATIONS: { /* get genre stations */ - assert (req->responseData != NULL); + json_object *categories = json_object_object_get (result, "categories"); + if (categories != NULL) { + for (size_t i = 0; i < json_object_array_length (categories); i++) { + json_object *c = json_object_array_get_idx (categories, i); + PianoGenreCategory_t *tmpGenreCategory; + + if ((tmpGenreCategory = calloc (1, + sizeof (*tmpGenreCategory))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } - ret = PianoXmlParseGenreExplorer (ph, req->responseData); + tmpGenreCategory->name = PianoJsonStrdup (c, + "categoryName"); + + /* get genre subnodes */ + json_object *stations = json_object_object_get (c, + "stations"); + if (stations != NULL) { + for (size_t k = 0; + k < json_object_array_length (stations); k++) { + json_object *s = + json_object_array_get_idx (stations, k); + PianoGenre_t *tmpGenre; + + if ((tmpGenre = calloc (1, + sizeof (*tmpGenre))) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + /* get genre attributes */ + tmpGenre->name = PianoJsonStrdup (s, + "stationName"); + tmpGenre->musicId = PianoJsonStrdup (s, + "stationToken"); + + /* append station */ + if (tmpGenreCategory->genres == NULL) { + tmpGenreCategory->genres = tmpGenre; + } else { + PianoGenre_t *curGenre = + tmpGenreCategory->genres; + while (curGenre->next != NULL) { + curGenre = curGenre->next; + } + curGenre->next = tmpGenre; + } + } + } + /* append category */ + if (ph->genreStations == NULL) { + ph->genreStations = tmpGenreCategory; + } else { + PianoGenreCategory_t *curCat = ph->genreStations; + while (curCat->next != NULL) { + curCat = curCat->next; + } + curCat->next = tmpGenreCategory; + } + } + } break; + } case PIANO_REQUEST_TRANSFORM_STATION: { /* transform shared station into private and update isCreator flag */ @@ -1086,27 +1126,51 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { 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; - } + station->isCreator = 1; break; } case PIANO_REQUEST_EXPLAIN: { /* explain why song was selected */ PianoRequestDataExplain_t *reqData = req->data; + const size_t strSize = 1024; + size_t pos = 0; - assert (req->responseData != NULL); assert (reqData != NULL); - ret = PianoXmlParseNarrative (req->responseData, &reqData->retExplain); + json_object *explanations = json_object_object_get (result, + "explanations"); + if (explanations != NULL) { + reqData->retExplain = malloc (strSize * + sizeof (*reqData->retExplain)); + strncpy (reqData->retExplain, "We're playing this track " + "because it features ", strSize); + pos = strlen (reqData->retExplain); + for (size_t i=0; i < json_object_array_length (explanations); i++) { + json_object *e = json_object_array_get_idx (explanations, + i); + const char *s = json_object_get_string ( + json_object_object_get (e, "focusTraitName")); + + strncpy (&reqData->retExplain[pos], s, strSize-pos-1); + pos += strlen (s); + if (i < json_object_array_length (explanations)-2) { + strncpy (&reqData->retExplain[pos], ", ", strSize-pos-1); + pos += 2; + } else if (i == json_object_array_length (explanations)-2) { + strncpy (&reqData->retExplain[pos], " and ", strSize-pos-1); + pos += 5; + } else { + strncpy (&reqData->retExplain[pos], ".", strSize-pos-1); + pos += 1; + } + } + } break; } case PIANO_REQUEST_GET_SEED_SUGGESTIONS: { +#if 0 /* find similar artists */ PianoRequestDataGetSeedSuggestions_t *reqData = req->data; @@ -1115,29 +1179,122 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { ret = PianoXmlParseSeedSuggestions (req->responseData, &reqData->searchResult); +#endif break; } case PIANO_REQUEST_GET_STATION_INFO: { /* get station information (seeds and feedback) */ PianoRequestDataGetStationInfo_t *reqData = req->data; + PianoStationInfo_t *info; - assert (req->responseData != NULL); assert (reqData != NULL); - ret = PianoXmlParseGetStationInfo (req->responseData, - &reqData->info); - break; - } + info = &reqData->info; + assert (info != NULL); + + /* parse music seeds */ + json_object *music = json_object_object_get (result, "music"); + if (music != NULL) { + /* songs */ + json_object *songs = json_object_object_get (music, "songs"); + if (songs != NULL) { + for (size_t i = 0; i < json_object_array_length (songs); i++) { + json_object *s = json_object_array_get_idx (songs, i); + PianoSong_t *seedSong; + + seedSong = calloc (1, sizeof (*seedSong)); + if (seedSong == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } - case PIANO_REQUEST_DELETE_SEED: { - assert (req->responseData != NULL); + seedSong->title = PianoJsonStrdup (s, "songName"); + seedSong->artist = PianoJsonStrdup (s, "artistName"); + seedSong->seedId = PianoJsonStrdup (s, "seedId"); - /* dummy function, checks for errors only */ - ret = PianoXmlParseTranformStation (req->responseData); + if (info->songSeeds == NULL) { + info->songSeeds = seedSong; + } else { + PianoSong_t *curSong = info->songSeeds; + while (curSong->next != NULL) { + curSong = curSong->next; + } + curSong->next = seedSong; + } + } + } + + /* artists */ + json_object *artists = json_object_object_get (music, + "artists"); + if (artists != NULL) { + for (size_t i = 0; i < json_object_array_length (artists); i++) { + json_object *a = json_object_array_get_idx (artists, i); + PianoArtist_t *seedArtist; + + seedArtist = calloc (1, sizeof (*seedArtist)); + if (seedArtist == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + seedArtist->name = PianoJsonStrdup (a, "artistName"); + seedArtist->seedId = PianoJsonStrdup (a, "seedId"); + + if (info->artistSeeds == NULL) { + info->artistSeeds = seedArtist; + } else { + PianoArtist_t *curArtist = info->artistSeeds; + while (curArtist->next != NULL) { + curArtist = curArtist->next; + } + curArtist->next = seedArtist; + } + } + } + } + + /* parse feedback */ + json_object *feedback = json_object_object_get (result, + "feedback"); + if (feedback != NULL) { + json_object_object_foreach (feedback, key, val) { + for (size_t i = 0; i < json_object_array_length (val); i++) { + json_object *s = json_object_array_get_idx (val, i); + PianoSong_t *feedbackSong; + + feedbackSong = calloc (1, sizeof (*feedbackSong)); + if (feedbackSong == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + feedbackSong->title = PianoJsonStrdup (s, "songName"); + feedbackSong->artist = PianoJsonStrdup (s, + "artistName"); + feedbackSong->feedbackId = PianoJsonStrdup (s, + "feedbackId"); + feedbackSong->rating = json_object_get_boolean ( + json_object_object_get (s, "isPositive")) ? + PIANO_RATE_LOVE : PIANO_RATE_BAN; + + + if (info->feedback == NULL) { + info->feedback = feedbackSong; + } else { + PianoSong_t *curSong = info->feedback; + while (curSong->next != NULL) { + curSong = curSong->next; + } + curSong->next = feedbackSong; + } + } + } + } + break; } } + json_object_put (j); + return ret; } @@ -1171,70 +1328,51 @@ const char *PianoErrorToStr (PianoReturn_t ret) { return "Unknown."; break; - case PIANO_RET_XML_INVALID: - return "Invalid XML."; - break; - - case PIANO_RET_AUTH_TOKEN_INVALID: - return "Invalid auth token."; - break; - - case PIANO_RET_AUTH_USER_PASSWORD_INVALID: - return "Username and/or password not correct."; - break; - - case PIANO_RET_NOT_AUTHORIZED: - return "Not authorized."; - break; - - case PIANO_RET_PROTOCOL_INCOMPATIBLE: - return "Protocol incompatible. Please upgrade " PACKAGE "."; + case PIANO_RET_INVALID_RESPONSE: + return "Invalid response."; break; - case PIANO_RET_READONLY_MODE: - return "Request cannot be completed at this time, please try " - "again later."; - break; - - case PIANO_RET_STATION_CODE_INVALID: - return "Station id is invalid."; + case PIANO_RET_CONTINUE_REQUEST: + /* never shown to the user */ + assert (0); + return "Fix your program."; break; - case PIANO_RET_IP_REJECTED: - return "Your ip address was rejected. Please setup a control " - "proxy (see manpage)."; + case PIANO_RET_OUT_OF_MEMORY: + return "Out of memory."; break; - case PIANO_RET_STATION_NONEXISTENT: - return "Station does not exist."; + /* pandora error messages */ + case PIANO_RET_P_INTERNAL: + return "Internal error."; break; - case PIANO_RET_OUT_OF_MEMORY: - return "Out of memory."; + case PIANO_RET_P_CALL_NOT_ALLOWED: + return "Call not allowed."; break; - case PIANO_RET_OUT_OF_SYNC: - return "Out of sync. Please correct your system's time."; + case PIANO_RET_P_INVALID_AUTH_TOKEN: + return "Invalid auth token."; break; - case PIANO_RET_PLAYLIST_END: - return "Playlist end."; + case PIANO_RET_P_MAINTENANCE_MODE: + return "Maintenance mode."; break; - case PIANO_RET_QUICKMIX_NOT_PLAYABLE: - return "Quickmix not playable."; + case PIANO_RET_P_MAX_STATIONS_REACHED: + return "Max number of stations reached."; break; - case PIANO_RET_REMOVING_TOO_MANY_SEEDS: - return "Last seed cannot be removed."; + case PIANO_RET_P_READ_ONLY_MODE: + return "Read only mode. Try again later."; break; - case PIANO_RET_EXCESSIVE_ACTIVITY: - return "Excessive activity."; + case PIANO_RET_P_STATION_DOES_NOT_EXIST: + return "Station does not exist."; break; - case PIANO_RET_DAILY_SKIP_LIMIT_REACHED: - return "Daily skip limit reached."; + case PIANO_RET_P_INVALID_PARTNER_LOGIN: + return "Invalid partner login."; break; default: diff --git a/src/libpiano/piano.h b/src/libpiano/piano.h index d582844..8a21d05 100644 --- a/src/libpiano/piano.h +++ b/src/libpiano/piano.h @@ -31,11 +31,13 @@ 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 :) */ -#define PIANO_RPC_HOST "www.pandora.com" -#define PIANO_RPC_PORT "80" +/* Pandora API documentation is available at + * http://pan-do-ra-api.wikia.com + */ + +#define PIANO_RPC_HOST "tuner.pandora.com" typedef struct PianoUserInfo { - char *webAuthToken; char *listenerId; char *authToken; } PianoUserInfo_t; @@ -66,10 +68,8 @@ typedef enum { typedef struct PianoSong { char *artist; - char *artistMusicId; char *stationId; char *album; - char *userSeed; char *audioUrl; char *coverArt; char *musicId; @@ -106,12 +106,13 @@ typedef struct PianoGenreCategory { } PianoGenreCategory_t; typedef struct PianoHandle { - char routeId[9]; PianoUserInfo_t user; /* linked lists */ PianoStation_t *stations; PianoGenreCategory_t *genreStations; int timeOffset; + char *partnerAuthToken; + unsigned int partnerId; } PianoHandle_t; typedef struct PianoSearchResult { @@ -235,26 +236,58 @@ typedef struct { PianoStation_t *station; } PianoRequestDataDeleteSeed_t; +/* pandora error code offset */ +#define PIANO_RET_OFFSET 1024 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_CONTINUE_REQUEST = 5, - PIANO_RET_NOT_AUTHORIZED = 6, - PIANO_RET_PROTOCOL_INCOMPATIBLE = 7, - PIANO_RET_READONLY_MODE = 8, - PIANO_RET_STATION_CODE_INVALID = 9, - PIANO_RET_IP_REJECTED = 10, - PIANO_RET_STATION_NONEXISTENT = 11, - PIANO_RET_OUT_OF_MEMORY = 12, - PIANO_RET_OUT_OF_SYNC = 13, - PIANO_RET_PLAYLIST_END = 14, - PIANO_RET_QUICKMIX_NOT_PLAYABLE = 15, - PIANO_RET_REMOVING_TOO_MANY_SEEDS = 16, - PIANO_RET_EXCESSIVE_ACTIVITY = 17, - PIANO_RET_DAILY_SKIP_LIMIT_REACHED = 18, + PIANO_RET_INVALID_RESPONSE = 2, + PIANO_RET_CONTINUE_REQUEST = 3, + PIANO_RET_OUT_OF_MEMORY = 4, + + PIANO_RET_P_INTERNAL = PIANO_RET_OFFSET+0, + PIANO_RET_P_API_VERSION_NOT_SUPPORTED = PIANO_RET_OFFSET+11, + PIANO_RET_P_BIRTH_YEAR_INVALID = PIANO_RET_OFFSET+1025, + PIANO_RET_P_BIRTH_YEAR_TOO_YOUNG = PIANO_RET_OFFSET+1026, + PIANO_RET_P_CALL_NOT_ALLOWED = PIANO_RET_OFFSET+1008, + PIANO_RET_P_CERTIFICATE_REQUIRED = PIANO_RET_OFFSET+7, + PIANO_RET_P_COMPLIMENTARY_PERIOD_ALREADY_IN_USE = PIANO_RET_OFFSET+1007, + PIANO_RET_P_DAILY_TRIAL_LIMIT_REACHED = PIANO_RET_OFFSET+1035, + PIANO_RET_P_DEVICE_ALREADY_ASSOCIATED_TO_ACCOUNT = PIANO_RET_OFFSET+1014, + PIANO_RET_P_DEVICE_DISABLED = PIANO_RET_OFFSET+1034, + PIANO_RET_P_DEVICE_MODEL_INVALID = PIANO_RET_OFFSET+1023, + PIANO_RET_P_DEVICE_NOT_FOUND = PIANO_RET_OFFSET+1009, + PIANO_RET_P_EXPLICIT_PIN_INCORRECT = PIANO_RET_OFFSET+1018, + PIANO_RET_P_EXPLICIT_PIN_MALFORMED = PIANO_RET_OFFSET+1020, + PIANO_RET_P_INSUFFICIENT_CONNECTIVITY = PIANO_RET_OFFSET+13, + PIANO_RET_P_INVALID_AUTH_TOKEN = PIANO_RET_OFFSET+1001, + PIANO_RET_P_INVALID_COUNTRY_CODE = PIANO_RET_OFFSET+1027, + PIANO_RET_P_INVALID_GENDER = PIANO_RET_OFFSET+1027, + PIANO_RET_P_INVALID_PARTNER_LOGIN = PIANO_RET_OFFSET+1002, + PIANO_RET_P_INVALID_PASSWORD = PIANO_RET_OFFSET+1012, + PIANO_RET_P_INVALID_SPONSOR = PIANO_RET_OFFSET+1036, + PIANO_RET_P_INVALID_USERNAME = PIANO_RET_OFFSET+1011, + PIANO_RET_P_LICENSING_RESTRICTIONS = PIANO_RET_OFFSET+12, + PIANO_RET_P_MAINTENANCE_MODE = PIANO_RET_OFFSET+1, + PIANO_RET_P_MAX_STATIONS_REACHED = PIANO_RET_OFFSET+1005, + PIANO_RET_P_PARAMETER_MISSING = PIANO_RET_OFFSET+9, + PIANO_RET_P_PARAMETER_TYPE_MISMATCH = PIANO_RET_OFFSET+8, + PIANO_RET_P_PARAMETER_VALUE_INVALID = PIANO_RET_OFFSET+10, + PIANO_RET_P_PARTNER_NOT_AUTHORIZED = PIANO_RET_OFFSET+1010, + PIANO_RET_P_READ_ONLY_MODE = PIANO_RET_OFFSET+1000, + PIANO_RET_P_SECURE_PROTOCOL_REQUIRED = PIANO_RET_OFFSET+6, + PIANO_RET_P_STATION_DOES_NOT_EXIST = PIANO_RET_OFFSET+1006, + PIANO_RET_P_UPGRADE_DEVICE_MODEL_INVALID = PIANO_RET_OFFSET+1015, + PIANO_RET_P_URL_PARAM_MISSING_AUTH_TOKEN = PIANO_RET_OFFSET+3, + PIANO_RET_P_URL_PARAM_MISSING_METHOD = PIANO_RET_OFFSET+2, + PIANO_RET_P_URL_PARAM_MISSING_PARTNER_ID = PIANO_RET_OFFSET+4, + PIANO_RET_P_URL_PARAM_MISSING_USER_ID = PIANO_RET_OFFSET+5, + PIANO_RET_P_USERNAME_ALREADY_EXISTS = PIANO_RET_OFFSET+1013, + PIANO_RET_P_USER_ALREADY_USED_TRIAL = PIANO_RET_OFFSET+1037, + PIANO_RET_P_USER_NOT_ACTIVE = PIANO_RET_OFFSET+1003, + PIANO_RET_P_USER_NOT_AUTHORIZED = PIANO_RET_OFFSET+1004, + PIANO_RET_P_ZIP_CODE_INVALID = PIANO_RET_OFFSET+1024, + } PianoReturn_t; void PianoInit (PianoHandle_t *); diff --git a/src/libpiano/xml.c b/src/libpiano/xml.c deleted file mode 100644 index d194963..0000000 --- a/src/libpiano/xml.c +++ /dev/null @@ -1,980 +0,0 @@ -/* -Copyright (c) 2008-2011 - Lars-Dominik Braun <lars@6xq.net> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -#ifndef __FreeBSD__ -#define _BSD_SOURCE /* required by strdup() */ -#define _DARWIN_C_SOURCE /* strdup() on OS X */ -#endif - -#include <stdio.h> -#include <string.h> -#include <stdlib.h> -#include <ezxml.h> -#include <assert.h> - -#include "xml.h" -#include "piano.h" -#include "crypt.h" -#include "config.h" -#include "piano_private.h" - -static void PianoXmlStructParser (const ezxml_t, - void (*callback) (const char *, const ezxml_t, void *), void *); -static char *PianoXmlGetNodeText (const ezxml_t); - -/* parse fault and get fault type - * @param xml <name> content - * @param xml <value> node - * @param return error string - * @return nothing - */ -static void PianoXmlIsFaultCb (const char *key, const ezxml_t value, - void *data) { - PianoReturn_t *ret = data; - char *valueStr = PianoXmlGetNodeText (value); - char *matchStart, *matchEnd; - - if (strcmp ("faultString", key) == 0) { - *ret = PIANO_RET_ERR; - /* find fault identifier in a string like this: - * com.savagebeast.radio.api.protocol.xmlrpc.RadioXmlRpcException: - * 192.168.160.78|1213101717317|AUTH_INVALID_TOKEN| - * Invalid auth token */ - if ((matchStart = strchr (valueStr, '|')) != NULL) { - if ((matchStart = strchr (matchStart+1, '|')) != NULL) { - if ((matchEnd = strchr (matchStart+1, '|')) != NULL) { - /* changes text in xml node, but we don't care... */ - *matchEnd = '\0'; - ++matchStart; - /* translate to our error message system */ - if (strcmp ("AUTH_INVALID_TOKEN", matchStart) == 0) { - *ret = PIANO_RET_AUTH_TOKEN_INVALID; - } else if (strcmp ("AUTH_INVALID_USERNAME_PASSWORD", - matchStart) == 0) { - *ret = PIANO_RET_AUTH_USER_PASSWORD_INVALID; - } else if (strcmp ("LISTENER_NOT_AUTHORIZED", - matchStart) == 0) { - *ret = PIANO_RET_NOT_AUTHORIZED; - } else if (strcmp ("INCOMPATIBLE_VERSION", - matchStart) == 0) { - *ret = PIANO_RET_PROTOCOL_INCOMPATIBLE; - } else if (strcmp ("READONLY_MODE", matchStart) == 0) { - *ret = PIANO_RET_READONLY_MODE; - } else if (strcmp ("STATION_CODE_INVALID", - matchStart) == 0) { - *ret = PIANO_RET_STATION_CODE_INVALID; - } else if (strcmp ("STATION_DOES_NOT_EXIST", - matchStart) == 0) { - *ret = PIANO_RET_STATION_NONEXISTENT; - } else if (strcmp ("OUT_OF_SYNC", matchStart) == 0) { - *ret = PIANO_RET_OUT_OF_SYNC; - } else if (strcmp ("PLAYLIST_END", matchStart) == 0) { - *ret = PIANO_RET_PLAYLIST_END; - } else if (strcmp ("QUICKMIX_NOT_PLAYABLE", matchStart) == 0) { - *ret = PIANO_RET_QUICKMIX_NOT_PLAYABLE; - } else if (strcmp ("REMOVING_TOO_MANY_SEEDS", matchStart) == 0) { - *ret = PIANO_RET_REMOVING_TOO_MANY_SEEDS; - } else if (strcmp ("EXCESSIVE_ACTIVITY", matchStart) == 0) { - *ret = PIANO_RET_EXCESSIVE_ACTIVITY; - } else if (strcmp ("DAILY_SKIP_LIMIT_REACHED", matchStart) == 0) { - *ret = PIANO_RET_DAILY_SKIP_LIMIT_REACHED; - } else { - *ret = PIANO_RET_ERR; - printf (PACKAGE ": Unknown error %s in %s\n", - matchStart, valueStr); - } - } - } - } - } else if (strcmp ("faultCode", key) == 0) { - /* some errors can only be identified by looking at their id */ - /* detect pandora's ip restriction */ - if (strcmp ("12", valueStr) == 0) { - *ret = PIANO_RET_IP_REJECTED; - } - } -} - -/* check whether pandora returned an error or not - * @param document root of xml doc - * @return _RET_OK or fault code (_RET_*) - */ -static PianoReturn_t PianoXmlIsFault (ezxml_t xmlDoc) { - PianoReturn_t ret; - - if ((xmlDoc = ezxml_child (xmlDoc, "fault")) != NULL) { - xmlDoc = ezxml_get (xmlDoc, "value", 0, "struct", -1); - PianoXmlStructParser (xmlDoc, PianoXmlIsFaultCb, &ret); - return ret; - } - return PIANO_RET_OK; -} - -/* parses things like this: - * <struct> - * <member> - * <name /> - * <value /> - * </member> - * <!-- ... --> - * </struct> - * @param xml node named "struct" (or containing a similar structure) - * @param who wants to use this data? callback: content of <name> as - * string, content of <value> as xmlNode (may contain other nodes - * or text), additional data used by callback(); don't forget - * to *copy* data taken from <name> or <value> as they will be - * freed soon - * @param extra data for callback - */ -static void PianoXmlStructParser (const ezxml_t structRoot, - void (*callback) (const char *, const ezxml_t, void *), void *data) { - ezxml_t curNode, keyNode, valueNode; - char *key; - - /* get all <member> nodes */ - for (curNode = ezxml_child (structRoot, "member"); curNode; curNode = curNode->next) { - /* reset variables */ - key = NULL; - valueNode = keyNode = NULL; - - keyNode = ezxml_child (curNode, "name"); - if (keyNode != NULL) { - key = ezxml_txt (keyNode); - } - - valueNode = ezxml_child (curNode, "value"); - /* this will ignore empty <value /> nodes, but well... */ - if (*key != '\0' && valueNode != NULL) { - (*callback) ((char *) key, valueNode, data); - } - } -} - -/* create xml parser from string - * @param xml document - * @param returns document pointer (needed to free memory later) - * @param returns document root - * @return _OK or error - */ -static PianoReturn_t PianoXmlInitDoc (char *xmlStr, ezxml_t *xmlDoc) { - PianoReturn_t ret; - - if ((*xmlDoc = ezxml_parse_str (xmlStr, strlen (xmlStr))) == NULL) { - return PIANO_RET_XML_INVALID; - } - - if ((ret = PianoXmlIsFault (*xmlDoc)) != PIANO_RET_OK) { - ezxml_free (*xmlDoc); - return ret; - } - - return PIANO_RET_OK; -} - -/* get text from <value> nodes; some of them have <boolean>, <string> - * or <int> subnodes, just ignore them - * @param xml node <value> - */ -static char *PianoXmlGetNodeText (const ezxml_t node) { - char *retTxt = NULL; - - retTxt = ezxml_txt (node); - /* no text => empty string */ - if (*retTxt == '\0') { - retTxt = ezxml_txt (node->child); - } - return retTxt; -} - -/* structParser callback; writes userinfo to PianoUserInfo structure - * @param value identifier - * @param value node - * @param pointer to userinfo structure - * @return nothing - */ -static void PianoXmlParseUserinfoCb (const char *key, const ezxml_t value, - void *data) { - PianoUserInfo_t *user = data; - char *valueStr = PianoXmlGetNodeText (value); - - if (strcmp ("webAuthToken", key) == 0) { - user->webAuthToken = strdup (valueStr); - } else if (strcmp ("authToken", key) == 0) { - user->authToken = strdup (valueStr); - } else if (strcmp ("listenerId", key) == 0) { - user->listenerId = strdup (valueStr); - } -} - -static void PianoXmlParseStationsCb (const char *key, const ezxml_t value, - void *data) { - PianoStation_t *station = data; - char *valueStr = PianoXmlGetNodeText (value); - - if (strcmp ("stationName", key) == 0) { - station->name = strdup (valueStr); - } else if (strcmp ("stationId", key) == 0) { - station->id = strdup (valueStr); - } else if (strcmp ("isQuickMix", key) == 0) { - station->isQuickMix = (strcmp (valueStr, "1") == 0); - } else if (strcmp ("isCreator", key) == 0) { - station->isCreator = (strcmp (valueStr, "1") == 0); - } -} - -static void PianoXmlParsePlaylistCb (const char *key, const ezxml_t value, - void *data) { - PianoSong_t *song = data; - char *valueStr = PianoXmlGetNodeText (value); - - if (strcmp ("audioURL", key) == 0) { - /* last 48 chars of audioUrl are encrypted, but they put the key - * into the door's lock... */ - const char urlTailN = 48; - const size_t valueStrN = strlen (valueStr); - char *urlTail = NULL, - *urlTailCrypted = &valueStr[valueStrN - urlTailN]; - - /* don't try to decrypt if string is too short (=> invalid memory - * reads/writes) */ - if (valueStrN > urlTailN && - (urlTail = PianoDecryptString (urlTailCrypted, NULL)) != NULL) { - if ((song->audioUrl = calloc (valueStrN + 1, - sizeof (*song->audioUrl))) != NULL) { - memcpy (song->audioUrl, valueStr, valueStrN - urlTailN); - /* FIXME: the key seems to be broken... so ignore 8 x 0x08 - * postfix; urlTailN/2 because the encrypted hex string is now - * decoded */ - memcpy (&song->audioUrl[valueStrN - urlTailN], urlTail, - urlTailN/2 - 8); - } - free (urlTail); - } - } else if (strcmp ("artRadio", key) == 0) { - song->coverArt = strdup (valueStr); - } else if (strcmp ("artistSummary", key) == 0) { - song->artist = strdup (valueStr); - } else if (strcmp ("musicId", key) == 0) { - song->musicId = strdup (valueStr); - } else if (strcmp ("userSeed", key) == 0) { - song->userSeed = strdup (valueStr); - } else if (strcmp ("songTitle", key) == 0) { - song->title = strdup (valueStr); - } else if (strcmp ("rating", key) == 0) { - if (strcmp (valueStr, "1") == 0) { - song->rating = PIANO_RATE_LOVE; - } else { - song->rating = PIANO_RATE_NONE; - } - } else if (strcmp ("isPositive", key) == 0) { - if (strcmp (valueStr, "1") == 0) { - song->rating = PIANO_RATE_LOVE; - } else { - song->rating = PIANO_RATE_BAN; - } - } else if (strcmp ("stationId", key) == 0) { - song->stationId = strdup (valueStr); - } else if (strcmp ("albumTitle", key) == 0) { - song->album = strdup (valueStr); - } else if (strcmp ("fileGain", key) == 0) { - song->fileGain = atof (valueStr); - } else if (strcmp ("audioEncoding", key) == 0) { - if (strcmp (valueStr, "aacplus") == 0) { - song->audioFormat = PIANO_AF_AACPLUS; - } else if (strcmp (valueStr, "mp3") == 0) { - song->audioFormat = PIANO_AF_MP3; - } else if (strcmp (valueStr, "mp3-hifi") == 0) { - song->audioFormat = PIANO_AF_MP3_HI; - } - } else if (strcmp ("artistMusicId", key) == 0) { - song->artistMusicId = strdup (valueStr); - } else if (strcmp ("feedbackId", key) == 0) { - song->feedbackId = strdup (valueStr); - } else if (strcmp ("songDetailURL", key) == 0) { - song->detailUrl = strdup (valueStr); - } else if (strcmp ("trackToken", key) == 0) { - song->trackToken = strdup (valueStr); - } -} - -/* parses userinfos sent by pandora as login response - * @param piano handle - * @param utf-8 string - * @return _RET_OK or error - */ -PianoReturn_t PianoXmlParseUserinfo (PianoHandle_t *ph, char *xml) { - ezxml_t xmlDoc, structNode; - PianoReturn_t ret; - - if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { - return ret; - } - - /* <methodResponse> <params> <param> <value> <struct> */ - structNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "struct", -1); - PianoXmlStructParser (structNode, PianoXmlParseUserinfoCb, &ph->user); - - ezxml_free (xmlDoc); - - return PIANO_RET_OK; -} - -static void PianoXmlParseQuickMixStationsCb (const char *key, const ezxml_t value, - void *data) { - char ***retIds = data; - char **ids = NULL; - size_t idsN = 0; - ezxml_t curNode; - - if (strcmp ("quickMixStationIds", key) == 0) { - for (curNode = ezxml_child (ezxml_get (value, "array", 0, "data", -1), "value"); - curNode; curNode = curNode->next) { - idsN++; - if (ids == NULL) { - if ((ids = calloc (idsN, sizeof (*ids))) == NULL) { - *retIds = NULL; - return; - } - } else { - /* FIXME: memory leak (on failure) */ - if ((ids = realloc (ids, idsN * sizeof (*ids))) == NULL) { - *retIds = NULL; - return; - } - } - ids[idsN-1] = strdup (PianoXmlGetNodeText (curNode)); - } - /* append NULL: list ends here */ - idsN++; - /* FIXME: copy&waste */ - if (ids == NULL) { - if ((ids = calloc (idsN, sizeof (*ids))) == NULL) { - *retIds = NULL; - return; - } - } else { - if ((ids = realloc (ids, idsN * sizeof (*ids))) == NULL) { - *retIds = NULL; - return; - } - } - ids[idsN-1] = NULL; - - *retIds = ids; - } -} - -/* parse stations returned by pandora - * @param piano handle - * @param xml returned by pandora - * @return _RET_OK or error - */ -PianoReturn_t PianoXmlParseStations (PianoHandle_t *ph, char *xml) { - ezxml_t xmlDoc, dataNode; - PianoReturn_t ret; - char **quickMixIds = NULL, **curQuickMixId = NULL; - - if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { - return ret; - } - - dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "array", - 0, "data", -1); - - for (dataNode = ezxml_child (dataNode, "value"); dataNode; - dataNode = dataNode->next) { - PianoStation_t *tmpStation; - - if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { - ezxml_free (xmlDoc); - return PIANO_RET_OUT_OF_MEMORY; - } - - PianoXmlStructParser (ezxml_child (dataNode, "struct"), - PianoXmlParseStationsCb, tmpStation); - - /* get stations selected for quickmix */ - if (tmpStation->isQuickMix) { - PianoXmlStructParser (ezxml_child (dataNode, "struct"), - PianoXmlParseQuickMixStationsCb, &quickMixIds); - } - /* start new linked list or append */ - if (ph->stations == NULL) { - ph->stations = tmpStation; - } else { - PianoStation_t *curStation = ph->stations; - while (curStation->next != NULL) { - curStation = curStation->next; - } - curStation->next = tmpStation; - } - } - /* set quickmix flags after all stations are read */ - if (quickMixIds != NULL) { - curQuickMixId = quickMixIds; - while (*curQuickMixId != NULL) { - PianoStation_t *curStation = PianoFindStationById (ph->stations, - *curQuickMixId); - if (curStation != NULL) { - curStation->useQuickMix = 1; - } - free (*curQuickMixId); - curQuickMixId++; - } - free (quickMixIds); - } - - ezxml_free (xmlDoc); - - return PIANO_RET_OK; -} - -/* parse "create station" answer (it returns a new station structure) - * @param piano handle - * @param xml document - * @return nothing yet - */ -PianoReturn_t PianoXmlParseCreateStation (PianoHandle_t *ph, char *xml) { - ezxml_t xmlDoc, dataNode; - PianoStation_t *tmpStation; - PianoReturn_t ret; - - if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { - return ret; - } - - dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "struct", -1); - - if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { - ezxml_free (xmlDoc); - return PIANO_RET_OUT_OF_MEMORY; - } - PianoXmlStructParser (dataNode, PianoXmlParseStationsCb, tmpStation); - /* FIXME: copy & waste */ - /* start new linked list or append */ - if (ph->stations == NULL) { - ph->stations = tmpStation; - } else { - PianoStation_t *curStation = ph->stations; - while (curStation->next != NULL) { - curStation = curStation->next; - } - curStation->next = tmpStation; - } - - ezxml_free (xmlDoc); - - return PIANO_RET_OK; -} - -/* parse "add seed" answer, nearly the same as ParseCreateStation - * @param piano handle - * @param xml document - * @param update this station - */ -PianoReturn_t PianoXmlParseAddSeed (PianoHandle_t *ph, char *xml, - PianoStation_t *station) { - ezxml_t xmlDoc, dataNode; - PianoReturn_t ret; - - if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { - return ret; - } - - dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "struct", -1); - PianoDestroyStation (station); - PianoXmlStructParser (dataNode, PianoXmlParseStationsCb, station); - - ezxml_free (xmlDoc); - - return PIANO_RET_OK; -} - -static PianoReturn_t PianoXmlParsePlaylistStruct (ezxml_t xml, - PianoSong_t **retSong) { - PianoSong_t *playlist = *retSong, *tmpSong; - - if ((tmpSong = calloc (1, sizeof (*tmpSong))) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - PianoXmlStructParser (ezxml_child (xml, "struct"), PianoXmlParsePlaylistCb, - tmpSong); - /* begin linked list or append */ - if (playlist == NULL) { - playlist = tmpSong; - } else { - PianoSong_t *curSong = playlist; - while (curSong->next != NULL) { - curSong = curSong->next; - } - curSong->next = tmpSong; - } - - *retSong = playlist; - - return PIANO_RET_OK; -} - -/* parses playlist; used when searching too - * @param piano handle - * @param xml document - * @param return: playlist - */ -PianoReturn_t PianoXmlParsePlaylist (PianoHandle_t *ph, char *xml, - PianoSong_t **retPlaylist) { - ezxml_t xmlDoc, dataNode; - PianoReturn_t ret = PIANO_RET_OK; - - if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { - return ret; - } - - dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "array", - 0, "data", -1); - - for (dataNode = ezxml_child (dataNode, "value"); dataNode; - dataNode = dataNode->next) { - if ((ret = PianoXmlParsePlaylistStruct (dataNode, retPlaylist)) != - PIANO_RET_OK) { - break; - } - } - - ezxml_free (xmlDoc); - - return ret; -} - -/* check for exception only - * @param xml string - * @return _OK or error - */ -PianoReturn_t PianoXmlParseSimple (char *xml) { - ezxml_t xmlDoc; - PianoReturn_t ret; - - if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { - return ret; - } - - ezxml_free (xmlDoc); - - return ret; -} - -/* xml struct parser callback, used in PianoXmlParseSearchCb - */ -static void PianoXmlParseSearchArtistCb (const char *key, const ezxml_t value, - void *data) { - PianoArtist_t *artist = data; - char *valueStr = PianoXmlGetNodeText (value); - - if (strcmp ("artistName", key) == 0) { - artist->name = strdup (valueStr); - } else if (strcmp ("musicId", key) == 0) { - artist->musicId = strdup (valueStr); - } -} - -/* callback for xml struct parser used in PianoXmlParseSearch, "switch" for - * PianoXmlParseSearchArtistCb and PianoXmlParsePlaylistCb - */ -static void PianoXmlParseSearchCb (const char *key, const ezxml_t value, - void *data) { - PianoSearchResult_t *searchResult = data; - ezxml_t curNode; - - if (strcmp ("artists", key) == 0) { - /* skip <value><array><data> */ - for (curNode = ezxml_child (ezxml_get (value, "array", 0, "data", -1), "value"); - curNode; curNode = curNode->next) { - PianoArtist_t *artist; - - if ((artist = calloc (1, sizeof (*artist))) == NULL) { - /* fail silently */ - break; - } - - memset (artist, 0, sizeof (*artist)); - - PianoXmlStructParser (ezxml_child (curNode, "struct"), - PianoXmlParseSearchArtistCb, artist); - - /* add result to linked list */ - if (searchResult->artists == NULL) { - searchResult->artists = artist; - } else { - PianoArtist_t *curArtist = searchResult->artists; - while (curArtist->next != NULL) { - curArtist = curArtist->next; - } - curArtist->next = artist; - } - } - } else if (strcmp ("songs", key) == 0) { - for (curNode = ezxml_child (ezxml_get (value, "array", 0, "data", -1), "value"); - curNode; curNode = curNode->next) { - if (PianoXmlParsePlaylistStruct (curNode, &searchResult->songs) != - PIANO_RET_OK) { - break; - } - } - } -} - -/* parse search result; searchResult is nulled before use - * @param xml document - * @param returns search result - * @return nothing yet - */ -PianoReturn_t PianoXmlParseSearch (char *xml, - PianoSearchResult_t *searchResult) { - ezxml_t xmlDoc, dataNode; - PianoReturn_t ret; - - if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { - return ret; - } - - dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "struct", -1); - /* we need a "clean" search result (with null pointers) */ - memset (searchResult, 0, sizeof (*searchResult)); - PianoXmlStructParser (dataNode, PianoXmlParseSearchCb, searchResult); - - ezxml_free (xmlDoc); - - return PIANO_RET_OK; -} - -/* FIXME: copy&waste (PianoXmlParseSearch) - */ -PianoReturn_t PianoXmlParseSeedSuggestions (char *xml, - PianoSearchResult_t *searchResult) { - ezxml_t xmlDoc, dataNode; - PianoReturn_t ret; - - if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { - return ret; - } - - dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", -1); - /* we need a "clean" search result (with null pointers) */ - memset (searchResult, 0, sizeof (*searchResult)); - /* reuse seach result parser; structure is nearly the same */ - PianoXmlParseSearchCb ("artists", dataNode, searchResult); - - ezxml_free (xmlDoc); - - return PIANO_RET_OK; -} - -/* encode reserved xml chars - * TODO: remove and use ezxml_ampencode - * @param encode this - * @return encoded string or NULL - */ -char *PianoXmlEncodeString (const char *s) { - static const char *replacements[] = {"&&", "''", "\""", - "<<", ">>", NULL}; - const char **r; - char *sOut, *sOutCurr, found; - - if ((sOut = calloc (strlen (s) * 5 + 1, sizeof (*sOut))) == NULL) { - return NULL; - } - - sOutCurr = sOut; - - while (*s != '\0') { - r = replacements; - found = 0; - while (*r != NULL) { - if (*s == *r[0]) { - found = 1; - strcat (sOutCurr, (*r) + 1); - sOutCurr += strlen ((*r) + 1); - break; - } - r++; - } - if (!found) { - *sOutCurr = *s; - sOutCurr++; - } - s++; - } - return sOut; -} - -PianoReturn_t PianoXmlParseGenreExplorer (PianoHandle_t *ph, char *xml) { - ezxml_t xmlDoc, catNode; - PianoReturn_t ret; - - if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { - return ret; - } - - /* get all <member> nodes */ - for (catNode = ezxml_child (xmlDoc, "category"); catNode; - catNode = catNode->next) { - PianoGenreCategory_t *tmpGenreCategory; - ezxml_t genreNode; - - if ((tmpGenreCategory = calloc (1, sizeof (*tmpGenreCategory))) == NULL) { - ezxml_free (xmlDoc); - return PIANO_RET_OUT_OF_MEMORY; - } - - tmpGenreCategory->name = strdup (ezxml_attr (catNode, "categoryName")); - - /* get genre subnodes */ - for (genreNode = ezxml_child (catNode, "genre"); genreNode; - genreNode = genreNode->next) { - PianoGenre_t *tmpGenre; - - if ((tmpGenre = calloc (1, sizeof (*tmpGenre))) == NULL) { - ezxml_free (xmlDoc); - return PIANO_RET_OUT_OF_MEMORY; - } - - /* get genre attributes */ - tmpGenre->name = strdup (ezxml_attr (genreNode, "name")); - tmpGenre->musicId = strdup (ezxml_attr (genreNode, "musicId")); - - /* append station */ - if (tmpGenreCategory->genres == NULL) { - tmpGenreCategory->genres = tmpGenre; - } else { - PianoGenre_t *curGenre = - tmpGenreCategory->genres; - while (curGenre->next != NULL) { - curGenre = curGenre->next; - } - curGenre->next = tmpGenre; - } - } - /* append category */ - if (ph->genreStations == NULL) { - ph->genreStations = tmpGenreCategory; - } else { - PianoGenreCategory_t *curCat = ph->genreStations; - while (curCat->next != NULL) { - curCat = curCat->next; - } - curCat->next = tmpGenreCategory; - } - } - - ezxml_free (xmlDoc); - - return PIANO_RET_OK; -} - -/* dummy function, only checks for errors - * @param xml doc - * @return _OK or error - */ -PianoReturn_t PianoXmlParseTranformStation (char *xml) { - ezxml_t xmlDoc; - PianoReturn_t ret; - - if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { - return ret; - } - - ezxml_free (xmlDoc); - - return PIANO_RET_OK; -} - -/* parses "why did you play ...?" answer - * @param xml - * @param returns the answer - * @return _OK or error - */ -PianoReturn_t PianoXmlParseNarrative (char *xml, char **retNarrative) { - ezxml_t xmlDoc, dataNode; - PianoReturn_t ret; - - if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { - return ret; - } - - /* <methodResponse> <params> <param> <value> $textnode */ - dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", -1); - *retNarrative = strdup (ezxml_txt (dataNode)); - - ezxml_free (xmlDoc); - - return ret; -} - -/* seed bag, required because seedId is not part of artist/song struct in - * pandora's xml response - */ -struct PianoXmlParseSeedBag { - char *seedId; - PianoSong_t *song; - PianoArtist_t *artist; - PianoStation_t *station; -}; - -/* parse seed struct - */ -static void PianoXmlParseSeedCb (const char *key, const ezxml_t value, - void *data) { - struct PianoXmlParseSeedBag *bag = data; - - assert (bag != NULL); - - if (strcmp ("song", key) == 0) { - assert (bag->song == NULL); - - if ((bag->song = calloc (1, sizeof (*bag->song))) == NULL) { - return; - } - - PianoXmlStructParser (ezxml_child (value, "struct"), - PianoXmlParsePlaylistCb, bag->song); - } else if (strcmp ("artist", key) == 0) { - assert (bag->artist == NULL); - - if ((bag->artist = calloc (1, sizeof (*bag->artist))) == NULL) { - return; - } - - PianoXmlStructParser (ezxml_child (value, "struct"), - PianoXmlParseSearchArtistCb, bag->artist); - } else if (strcmp ("nonGenomeStation", key) == 0) { - /* genre stations are "non genome" station seeds */ - assert (bag->station == NULL); - - if ((bag->station = calloc (1, sizeof (*bag->station))) == NULL) { - return; - } - - PianoXmlStructParser (ezxml_child (value, "struct"), - PianoXmlParseStationsCb, bag->station); - } else if (strcmp ("seedId", key) == 0) { - char *valueStr = PianoXmlGetNodeText (value); - bag->seedId = strdup (valueStr); - } -} - -/* parse getStation xml struct - */ -static void PianoXmlParseGetStationInfoCb (const char *key, const ezxml_t value, - void *data) { - PianoStationInfo_t *info = data; - - if (strcmp ("seeds", key) == 0) { - const ezxml_t dataNode = ezxml_get (value, "array", 0, "data", -1); - for (ezxml_t seedNode = ezxml_child (dataNode, "value"); seedNode; - seedNode = seedNode->next) { - struct PianoXmlParseSeedBag bag; - memset (&bag, 0, sizeof (bag)); - - PianoXmlStructParser (ezxml_child (seedNode, "struct"), - PianoXmlParseSeedCb, &bag); - - assert (bag.song != NULL || bag.artist != NULL || - bag.station != NULL); - - if (bag.seedId == NULL) { - /* seeds without id are useless */ - continue; - } - - /* FIXME: copy&waste */ - if (bag.song != NULL) { - bag.song->seedId = bag.seedId; - - if (info->songSeeds == NULL) { - info->songSeeds = bag.song; - } else { - PianoSong_t *curSong = info->songSeeds; - while (curSong->next != NULL) { - curSong = curSong->next; - } - curSong->next = bag.song; - } - } else if (bag.artist != NULL) { - bag.artist->seedId = bag.seedId; - - if (info->artistSeeds == NULL) { - info->artistSeeds = bag.artist; - } else { - PianoArtist_t *curSong = info->artistSeeds; - while (curSong->next != NULL) { - curSong = curSong->next; - } - curSong->next = bag.artist; - } - } else if (bag.station != NULL) { - bag.station->seedId = bag.seedId; - - if (info->stationSeeds == NULL) { - info->stationSeeds = bag.station; - } else { - PianoStation_t *curStation = info->stationSeeds; - while (curStation->next != NULL) { - curStation = curStation->next; - } - curStation->next = bag.station; - } - } else { - free (bag.seedId); - } - } - } else if (strcmp ("feedback", key) == 0) { - const ezxml_t dataNode = ezxml_get (value, "array", 0, "data", -1); - for (ezxml_t feedbackNode = ezxml_child (dataNode, "value"); feedbackNode; - feedbackNode = feedbackNode->next) { - if (PianoXmlParsePlaylistStruct (feedbackNode, &info->feedback) != - PIANO_RET_OK) { - break; - } - } - } -} - -/* parse getStation response - */ -PianoReturn_t PianoXmlParseGetStationInfo (char *xml, - PianoStationInfo_t *stationInfo) { - ezxml_t xmlDoc, dataNode; - PianoReturn_t ret; - - if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { - return ret; - } - - dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "struct", -1); - PianoXmlStructParser (dataNode, PianoXmlParseGetStationInfoCb, stationInfo); - - ezxml_free (xmlDoc); - - return PIANO_RET_OK; -} - diff --git a/src/libpiano/xml.h b/src/libpiano/xml.h deleted file mode 100644 index 58ee28f..0000000 --- a/src/libpiano/xml.h +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright (c) 2008-2011 - Lars-Dominik Braun <lars@6xq.net> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -#ifndef _XML_H -#define _XML_H - -#include "piano.h" - -PianoReturn_t PianoXmlParseUserinfo (PianoHandle_t *ph, char *xml); -PianoReturn_t PianoXmlParseStations (PianoHandle_t *ph, char *xml); -PianoReturn_t PianoXmlParsePlaylist (PianoHandle_t *ph, char *xml, - PianoSong_t **); -PianoReturn_t PianoXmlParseSearch (char *searchXml, - PianoSearchResult_t *searchResult); -PianoReturn_t PianoXmlParseSimple (char *xml); -PianoReturn_t PianoXmlParseCreateStation (PianoHandle_t *ph, - char *xml); -PianoReturn_t PianoXmlParseAddSeed (PianoHandle_t *ph, char *xml, - PianoStation_t *station); -PianoReturn_t PianoXmlParseGenreExplorer (PianoHandle_t *ph, - char *xmlContent); -PianoReturn_t PianoXmlParseTranformStation (char *searchXml); -PianoReturn_t PianoXmlParseNarrative (char *xml, char **retNarrative); -PianoReturn_t PianoXmlParseSeedSuggestions (char *, PianoSearchResult_t *); -PianoReturn_t PianoXmlParseGetStationInfo (char *, PianoStationInfo_t *); - -char *PianoXmlEncodeString (const char *s); - -#endif /* _XML_H */ |