/* piano wrapper: prepare/execute http request and pass result back to * libpiano (updates data structures) * @param piano handle * @param request type * @param waitress handle * @param data pointer (used as request data) * @return 1 on success, 0 otherwise */ int BarUiPianoCall (PianoHandle_t *ph, PianoRequestType_t type, WaitressHandle_t *waith, void *data, PianoReturn_t *pRet, WaitressReturn_t *wRet) { PianoRequest_t req; memset (&req, 0, sizeof (req)); /* repeat as long as there are http requests to do */ do { req.data = data; *pRet = PianoRequest (ph, &req, type); if (*pRet != PIANO_RET_OK) { BarUiMsg (MSG_NONE, "Error: %s\n", PianoErrorToStr (*pRet)); PianoDestroyRequest (&req); return 0; } *wRet = BarPianoHttpRequest (waith, &req); if (*wRet != WAITRESS_RET_OK) { BarUiMsg (MSG_NONE, "Network error: %s\n", WaitressErrorToStr (*wRet)); PianoDestroyRequest (&req); if (req.responseData != NULL) { free (req.responseData); } return 0; } *pRet = PianoResponse (ph, &req); if (*pRet != PIANO_RET_CONTINUE_REQUEST) { if (*pRet != PIANO_RET_OK) { BarUiMsg (MSG_NONE, "Error: %s\n", PianoErrorToStr (*pRet)); PianoDestroyRequest (&req); if (req.responseData != NULL) { free (req.responseData); } return 0; } else { BarUiMsg (MSG_NONE, "Ok.\n"); } } /* we can destroy the request at this point, even when this call needs * more than one http request. persistend data (step counter, e.g.) is * stored in req.data */ if (req.responseData != NULL) { free (req.responseData); } PianoDestroyRequest (&req); } while (*pRet == PIANO_RET_CONTINUE_REQUEST); return 1; }
/* piano wrapper: prepare/execute http request and pass result back to * libpiano (updates data structures) * @param app handle * @param request type * @param request data * @param stores piano return code * @param stores waitress return code * @return 1 on success, 0 otherwise */ int BarUiPianoCall (BarApp_t * const app, PianoRequestType_t type, void *data, PianoReturn_t *pRet, WaitressReturn_t *wRet) { PianoRequest_t req; memset (&req, 0, sizeof (req)); /* repeat as long as there are http requests to do */ do { req.data = data; *pRet = PianoRequest (&app->ph, &req, type); if (*pRet != PIANO_RET_OK) { BarUiMsg (&app->settings, MSG_NONE, "Error: %s\n", PianoErrorToStr (*pRet)); PianoDestroyRequest (&req); return 0; } *wRet = BarPianoHttpRequest (&app->waith, &req); if (*wRet != WAITRESS_RET_OK) { BarUiMsg (&app->settings, MSG_NONE, "Network error: %s\n", WaitressErrorToStr (*wRet)); if (req.responseData != NULL) { free (req.responseData); } PianoDestroyRequest (&req); return 0; } *pRet = PianoResponse (&app->ph, &req); if (*pRet != PIANO_RET_CONTINUE_REQUEST) { /* checking for request type avoids infinite loops */ if (*pRet == PIANO_RET_P_INVALID_AUTH_TOKEN && type != PIANO_REQUEST_LOGIN) { /* reauthenticate */ PianoReturn_t authpRet; WaitressReturn_t authwRet; PianoRequestDataLogin_t reqData; reqData.user = app->settings.username; reqData.password = app->settings.password; reqData.step = 0; BarUiMsg (&app->settings, MSG_NONE, "Reauthentication required... "); if (!BarUiPianoCall (app, PIANO_REQUEST_LOGIN, &reqData, &authpRet, &authwRet)) { *pRet = authpRet; *wRet = authwRet; if (req.responseData != NULL) { free (req.responseData); } PianoDestroyRequest (&req); return 0; } else { /* try again */ *pRet = PIANO_RET_CONTINUE_REQUEST; BarUiMsg (&app->settings, MSG_INFO, "Trying again... "); } } else if (*pRet != PIANO_RET_OK) { BarUiMsg (&app->settings, MSG_NONE, "Error: %s\n", PianoErrorToStr (*pRet)); if (req.responseData != NULL) { free (req.responseData); } PianoDestroyRequest (&req); return 0; } else { BarUiMsg (&app->settings, MSG_NONE, "Ok.\n"); } } /* we can destroy the request at this point, even when this call needs * more than one http request. persistent data (step counter, e.g.) is * stored in req.data */ if (req.responseData != NULL) { free (req.responseData); } PianoDestroyRequest (&req); } while (*pRet == PIANO_RET_CONTINUE_REQUEST); return 1; }
/* prepare piano request (initializes request type, urlpath and postData) * @param piano handle * @param request structure * @param request type */ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, PianoRequestType_t type) { PianoReturn_t ret = PIANO_RET_OK; const char *jsonSendBuf; const char *method = NULL; json_object *j = json_object_new_object (); /* corrected timestamp */ time_t timestamp = time (NULL) - ph->timeOffset; bool encrypted = true; assert (ph != NULL); assert (req != NULL); req->type = type; /* no tls by default */ req->secure = false; switch (req->type) { case PIANO_REQUEST_LOGIN: { /* authenticate user */ PianoRequestDataLogin_t *logindata = req->data; assert (logindata != NULL); switch (logindata->step) { case 0: encrypted = false; req->secure = true; json_object_object_add (j, "username", json_object_new_string (ph->partner.user)); json_object_object_add (j, "password", json_object_new_string (ph->partner.password)); json_object_object_add (j, "deviceModel", json_object_new_string (ph->partner.device)); json_object_object_add (j, "version", json_object_new_string ("5")); json_object_object_add (j, "includeUrls", json_object_new_boolean (true)); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "method=auth.partnerLogin"); break; case 1: { char *urlencAuthToken; req->secure = true; json_object_object_add (j, "loginType", json_object_new_string ("user")); json_object_object_add (j, "username", json_object_new_string (logindata->user)); json_object_object_add (j, "password", json_object_new_string (logindata->password)); json_object_object_add (j, "partnerAuthToken", json_object_new_string (ph->partner.authToken)); json_object_object_add (j, "syncTime", json_object_new_int (timestamp)); urlencAuthToken = WaitressUrlEncode (ph->partner.authToken); assert (urlencAuthToken != NULL); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "method=auth.userLogin&" "auth_token=%s&partner_id=%i", urlencAuthToken, ph->partner.id); free (urlencAuthToken); break; } } break; } case PIANO_REQUEST_GET_STATIONS: { /* get stations, user must be authenticated */ assert (ph->user.listenerId != NULL); method = "user.getStationList"; break; } case PIANO_REQUEST_GET_PLAYLIST: { /* get playlist for specified station */ PianoRequestDataGetPlaylist_t *reqData = req->data; assert (reqData != NULL); assert (reqData->station != NULL); assert (reqData->station->id != NULL); req->secure = true; json_object_object_add (j, "stationToken", json_object_new_string (reqData->station->id)); method = "station.getPlaylist"; break; } case PIANO_REQUEST_ADD_FEEDBACK: { /* low-level, don't use directly (see _RATE_SONG and _MOVE_SONG) */ PianoRequestDataAddFeedback_t *reqData = req->data; assert (reqData != NULL); assert (reqData->trackToken != NULL); assert (reqData->stationId != NULL); assert (reqData->rating != PIANO_RATE_NONE); json_object_object_add (j, "stationToken", json_object_new_string (reqData->stationId)); json_object_object_add (j, "trackToken", json_object_new_string (reqData->trackToken)); json_object_object_add (j, "isPositive", json_object_new_boolean (reqData->rating == PIANO_RATE_LOVE)); method = "station.addFeedback"; break; } case PIANO_REQUEST_RENAME_STATION: { PianoRequestDataRenameStation_t *reqData = req->data; assert (reqData != NULL); assert (reqData->station != NULL); assert (reqData->newName != NULL); json_object_object_add (j, "stationToken", json_object_new_string (reqData->station->id)); json_object_object_add (j, "stationName", json_object_new_string (reqData->newName)); method = "station.renameStation"; break; } case PIANO_REQUEST_DELETE_STATION: { /* delete station */ PianoStation_t *station = req->data; assert (station != NULL); assert (station->id != NULL); json_object_object_add (j, "stationToken", json_object_new_string (station->id)); method = "station.deleteStation"; break; } case PIANO_REQUEST_SEARCH: { /* search for artist/song title */ PianoRequestDataSearch_t *reqData = req->data; assert (reqData != NULL); assert (reqData->searchStr != NULL); json_object_object_add (j, "searchText", json_object_new_string (reqData->searchStr)); method = "music.search"; break; } case PIANO_REQUEST_CREATE_STATION: { /* create new station from specified musicToken or station number */ PianoRequestDataCreateStation_t *reqData = req->data; assert (reqData != NULL); assert (reqData->token != NULL); if (reqData->type == PIANO_MUSICTYPE_INVALID) { json_object_object_add (j, "musicToken", json_object_new_string (reqData->token)); } else { json_object_object_add (j, "trackToken", json_object_new_string (reqData->token)); switch (reqData->type) { case PIANO_MUSICTYPE_SONG: json_object_object_add (j, "musicType", json_object_new_string ("song")); break; case PIANO_MUSICTYPE_ARTIST: json_object_object_add (j, "musicType", json_object_new_string ("artist")); break; default: assert (0); break; } } method = "station.createStation"; break; } case PIANO_REQUEST_ADD_SEED: { /* add another seed to specified station */ PianoRequestDataAddSeed_t *reqData = req->data; assert (reqData != NULL); assert (reqData->station != NULL); assert (reqData->musicId != NULL); json_object_object_add (j, "musicToken", json_object_new_string (reqData->musicId)); json_object_object_add (j, "stationToken", json_object_new_string (reqData->station->id)); method = "station.addMusic"; break; } case PIANO_REQUEST_ADD_TIRED_SONG: { /* ban song for a month from all stations */ PianoSong_t *song = req->data; assert (song != NULL); json_object_object_add (j, "trackToken", json_object_new_string (song->trackToken)); method = "user.sleepSong"; break; } case PIANO_REQUEST_SET_QUICKMIX: { /* select stations included in quickmix (see useQuickMix flag of * PianoStation_t) */ PianoStation_t *curStation = ph->stations; json_object *a = json_object_new_array (); PianoListForeachP (curStation) { /* quick mix can't contain itself */ if (curStation->useQuickMix && !curStation->isQuickMix) { json_object_array_add (a, json_object_new_string (curStation->id)); } } json_object_object_add (j, "quickMixStationIds", a); method = "user.setQuickMix"; break; } case PIANO_REQUEST_GET_GENRE_STATIONS: { /* receive list of pandora's genre stations */ method = "station.getGenreStations"; break; } case PIANO_REQUEST_TRANSFORM_STATION: { /* transform shared station into private */ PianoStation_t *station = req->data; assert (station != NULL); json_object_object_add (j, "stationToken", json_object_new_string (station->id)); method = "station.transformSharedStation"; break; } case PIANO_REQUEST_EXPLAIN: { /* explain why particular song was played */ PianoRequestDataExplain_t *reqData = req->data; assert (reqData != NULL); assert (reqData->song != NULL); json_object_object_add (j, "trackToken", json_object_new_string (reqData->song->trackToken)); method = "track.explainTrack"; break; } case PIANO_REQUEST_BOOKMARK_SONG: { /* bookmark song */ PianoSong_t *song = req->data; assert (song != NULL); json_object_object_add (j, "trackToken", json_object_new_string (song->trackToken)); method = "bookmark.addSongBookmark"; break; } case PIANO_REQUEST_BOOKMARK_ARTIST: { /* bookmark artist */ PianoSong_t *song = req->data; assert (song != NULL); json_object_object_add (j, "trackToken", json_object_new_string (song->trackToken)); method = "bookmark.addArtistBookmark"; break; } case PIANO_REQUEST_GET_STATION_INFO: { /* get station information (seeds and feedback) */ PianoRequestDataGetStationInfo_t *reqData = req->data; assert (reqData != NULL); assert (reqData->station != NULL); json_object_object_add (j, "stationToken", json_object_new_string (reqData->station->id)); json_object_object_add (j, "includeExtendedAttributes", json_object_new_boolean (true)); method = "station.getStation"; break; } case PIANO_REQUEST_DELETE_FEEDBACK: { PianoSong_t *song = req->data; assert (song != NULL); json_object_object_add (j, "feedbackId", json_object_new_string (song->feedbackId)); method = "station.deleteFeedback"; break; } case PIANO_REQUEST_DELETE_SEED: { PianoRequestDataDeleteSeed_t *reqData = req->data; char *seedId = NULL; assert (reqData != NULL); assert (reqData->song != NULL || reqData->artist != NULL || reqData->station != NULL); if (reqData->song != NULL) { seedId = reqData->song->seedId; } else if (reqData->artist != NULL) { seedId = reqData->artist->seedId; } else if (reqData->station != NULL) { seedId = reqData->station->seedId; } assert (seedId != NULL); json_object_object_add (j, "seedId", json_object_new_string (seedId)); method = "station.deleteMusic"; break; } /* "high-level" wrapper */ case PIANO_REQUEST_RATE_SONG: { /* love/ban song */ PianoRequestDataRateSong_t *reqData = req->data; assert (reqData != NULL); assert (reqData->song != NULL); assert (reqData->rating != PIANO_RATE_NONE); PianoRequestDataAddFeedback_t transformedReqData; transformedReqData.stationId = reqData->song->stationId; transformedReqData.trackToken = reqData->song->trackToken; transformedReqData.rating = reqData->rating; req->data = &transformedReqData; /* create request data (url, post data) */ ret = PianoRequest (ph, req, PIANO_REQUEST_ADD_FEEDBACK); /* and reset request type/data */ req->type = PIANO_REQUEST_RATE_SONG; req->data = reqData; goto cleanup; break; } } /* standard parameter */ if (method != NULL) { char *urlencAuthToken; assert (ph->user.authToken != NULL); urlencAuthToken = WaitressUrlEncode (ph->user.authToken); assert (urlencAuthToken != NULL); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "method=%s&auth_token=%s&partner_id=%i&user_id=%s", method, urlencAuthToken, ph->partner.id, ph->user.listenerId); free (urlencAuthToken); json_object_object_add (j, "userAuthToken", json_object_new_string (ph->user.authToken)); json_object_object_add (j, "syncTime", json_object_new_int (timestamp)); } /* json to string */ jsonSendBuf = json_object_to_json_string (j); if (encrypted) { if ((req->postData = PianoEncryptString (ph->partner.out, jsonSendBuf)) == NULL) { ret = PIANO_RET_OUT_OF_MEMORY; } } else { req->postData = strdup (jsonSendBuf); } cleanup: json_object_put (j); return ret; }
int MythPianoService::PianoCall(PianoRequestType_t type, void *data, PianoReturn_t *pRet, WaitressReturn_t *wRet) { if (!m_Piano) return -1; PianoRequest_t req; memset (&req, 0, sizeof (req)); /* repeat as long as there are http requests to do */ do { req.data = data; *pRet = PianoRequest (m_Piano, &req, type); if (*pRet != PIANO_RET_OK) { BroadcastMessage("Error: %s\n", PianoErrorToStr (*pRet)); PianoDestroyRequest (&req); return 0; } *wRet = PianoHttpRequest(&m_Waith, &req); if (*wRet != WAITRESS_RET_OK) { BroadcastMessage ("Network error: %s\n", WaitressErrorToStr (*wRet)); if (req.responseData != NULL) { free (req.responseData); } PianoDestroyRequest (&req); return 0; } *pRet = PianoResponse (m_Piano, &req); if (*pRet != PIANO_RET_CONTINUE_REQUEST) { /* checking for request type avoids infinite loops */ if (*pRet == PIANO_RET_AUTH_TOKEN_INVALID && type != PIANO_REQUEST_LOGIN) { /* reauthenticate */ PianoReturn_t authpRet; WaitressReturn_t authwRet; PianoRequestDataLogin_t reqData; QString username = gCoreContext->GetSetting("pandora-username"); QString password = gCoreContext->GetSetting("pandora-password"); //wtf really? char* usernameBuff = strndup(username.toUtf8().data(), 1024); char* passwordBuff = strndup(password.toUtf8().data(), 1024); reqData.user = usernameBuff; reqData.password = passwordBuff; reqData.step = 0; BroadcastMessage ("Reauthentication required... "); if (!PianoCall(PIANO_REQUEST_LOGIN, &reqData, &authpRet, &authwRet)) { *pRet = authpRet; *wRet = authwRet; if (req.responseData != NULL) { free (req.responseData); } PianoDestroyRequest (&req); return 0; } else { /* try again */ *pRet = PIANO_RET_CONTINUE_REQUEST; BroadcastMessage("Trying again... "); } // wtf free(usernameBuff); free(passwordBuff); } else if (*pRet != PIANO_RET_OK) { BroadcastMessage("Error: %s\n", PianoErrorToStr (*pRet)); if (req.responseData != NULL) { free (req.responseData); } PianoDestroyRequest (&req); return 0; } else { BroadcastMessage("Login Ok.\n"); } } /* we can destroy the request at this point, even when this call needs * more than one http request. persistent data (step counter, e.g.) is * stored in req.data */ if (req.responseData != NULL) { free (req.responseData); } PianoDestroyRequest (&req); } while (*pRet == PIANO_RET_CONTINUE_REQUEST); return 1; }
/* prepare piano request (initializes request type, urlpath and postData) * @param piano handle * @param request structure * @param request type */ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req, PianoRequestType_t type) { char xmlSendBuf[PIANO_SEND_BUFFER_SIZE]; /* corrected timestamp */ time_t timestamp = time (NULL) - ph->timeOffset; assert (ph != NULL); assert (req != NULL); req->type = type; switch (req->type) { case PIANO_REQUEST_LOGIN: { /* authenticate user */ PianoRequestDataLogin_t *logindata = req->data; assert (logindata != NULL); switch (logindata->step) { case 0: snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?><methodCall>" "<methodName>misc.sync</methodName>" "<params></params></methodCall>"); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&method=sync", ph->routeId); break; case 1: { char *xmlencodedPassword = NULL; /* username == email address does not contain &,<,>," */ if ((xmlencodedPassword = PianoXmlEncodeString (logindata->password)) == NULL) { return PIANO_RET_OUT_OF_MEMORY; } snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?><methodCall>" "<methodName>listener.authenticateListener</methodName>" "<params><param><value><int>%lu</int></value></param>" /* user */ "<param><value><string>%s</string></value></param>" /* password */ "<param><value><string>%s</string></value></param>" /* vendor */ "<param><value><string>html5tuner</string></value></param>" "<param><value><string/></value></param>" "<param><value><string/></value></param>" "<param><value><string>HTML5</string></value></param>" "<param><value><boolean>1</boolean></value></param>" "</params></methodCall>", (unsigned long) timestamp, logindata->user, xmlencodedPassword); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&method=authenticateListener", ph->routeId); free (xmlencodedPassword); break; } } break; } case PIANO_REQUEST_GET_STATIONS: /* get stations, user must be authenticated */ assert (ph->user.listenerId != NULL); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" "<methodCall><methodName>station.getStations</methodName>" "<params><param><value><int>%lu</int></value></param>" "<param><value><string>%s</string></value></param>" "</params></methodCall>", (unsigned long) timestamp, ph->user.authToken); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=getStations", ph->routeId, ph->user.listenerId); break; case PIANO_REQUEST_GET_PLAYLIST: { /* get playlist for specified station */ PianoRequestDataGetPlaylist_t *reqData = req->data; assert (reqData != NULL); assert (reqData->station != NULL); assert (reqData->station->id != NULL); assert (reqData->format != PIANO_AF_UNKNOWN); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" "<methodCall><methodName>playlist.getFragment</methodName>" "<params><param><value><int>%lu</int></value></param>" /* auth token */ "<param><value><string>%s</string></value></param>" /* station id */ "<param><value><string>%s</string></value></param>" /* total listening time */ "<param><value><string>0</string></value></param>" /* time since last session */ "<param><value><string></string></value></param>" /* tracking code */ "<param><value><string></string></value></param>" /* audio format */ "<param><value><string>%s</string></value></param>" /* delta listening time */ "<param><value><string>0</string></value></param>" /* listening timestamp */ "<param><value><string>0</string></value></param>" "</params></methodCall>", (unsigned long) timestamp, ph->user.authToken, reqData->station->id, PianoAudioFormatToString (reqData->format)); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=getFragment&arg1=%s&arg2=0" "&arg3=&arg4=&arg5=%s&arg6=0&arg7=0", ph->routeId, ph->user.listenerId, reqData->station->id, PianoAudioFormatToString (reqData->format)); break; } case PIANO_REQUEST_ADD_FEEDBACK: { /* low-level, don't use directly (see _RATE_SONG and _MOVE_SONG) */ PianoRequestDataAddFeedback_t *reqData = req->data; assert (reqData != NULL); assert (reqData->stationId != NULL); assert (reqData->rating != PIANO_RATE_NONE); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" "<methodCall><methodName>station.addFeedback</methodName>" "<params><param><value><int>%lu</int></value></param>" /* auth token */ "<param><value><string>%s</string></value></param>" /* station id */ "<param><value><string>%s</string></value></param>" /* track token */ "<param><value><string>%s</string></value></param>" /* positive */ "<param><value><boolean>%i</boolean></value></param>" "</params></methodCall>", (unsigned long) timestamp, ph->user.authToken, reqData->stationId, reqData->trackToken, (reqData->rating == PIANO_RATE_LOVE) ? 1 : 0); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=addFeedback&arg1=%s&arg2=%s" "&arg3=%s", ph->routeId, ph->user.listenerId, reqData->stationId, reqData->trackToken, (reqData->rating == PIANO_RATE_LOVE) ? "true" : "false"); break; } case PIANO_REQUEST_RENAME_STATION: { /* rename stations */ PianoRequestDataRenameStation_t *reqData = req->data; char *urlencodedNewName, *xmlencodedNewName; assert (reqData != NULL); assert (reqData->station != NULL); assert (reqData->newName != NULL); if ((xmlencodedNewName = PianoXmlEncodeString (reqData->newName)) == NULL) { return PIANO_RET_OUT_OF_MEMORY; } urlencodedNewName = WaitressUrlEncode (reqData->newName); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" "<methodCall><methodName>station.setStationName</methodName>" "<params><param><value><int>%lu</int></value></param>" /* auth token */ "<param><value><string>%s</string></value></param>" /* station id */ "<param><value><string>%s</string></value></param>" /* new name */ "<param><value><string>%s</string></value></param>" "</params></methodCall>", (unsigned long) timestamp, ph->user.authToken, reqData->station->id, xmlencodedNewName); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=setStationName&arg1=%s&arg2=%s", ph->routeId, ph->user.listenerId, reqData->station->id, urlencodedNewName); free (urlencodedNewName); free (xmlencodedNewName); break; } case PIANO_REQUEST_DELETE_STATION: { /* delete station */ PianoStation_t *station = req->data; assert (station != NULL); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" "<methodCall><methodName>station.removeStation</methodName>" "<params><param><value><int>%lu</int></value></param>" /* auth token */ "<param><value><string>%s</string></value></param>" /* station id */ "<param><value><string>%s</string></value></param>" "</params></methodCall>", (unsigned long) timestamp, ph->user.authToken, station->id); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=removeStation&arg1=%s", ph->routeId, ph->user.listenerId, station->id); break; } case PIANO_REQUEST_SEARCH: { /* search for artist/song title */ PianoRequestDataSearch_t *reqData = req->data; char *xmlencodedSearchStr, *urlencodedSearchStr; assert (reqData != NULL); assert (reqData->searchStr != NULL); if ((xmlencodedSearchStr = PianoXmlEncodeString (reqData->searchStr)) == NULL) { return PIANO_RET_OUT_OF_MEMORY; } urlencodedSearchStr = WaitressUrlEncode (reqData->searchStr); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" "<methodCall><methodName>music.search</methodName>" "<params><param><value><int>%lu</int></value></param>" /* auth token */ "<param><value><string>%s</string></value></param>" /* search string */ "<param><value><string>%s</string></value></param>" "</params></methodCall>", (unsigned long) timestamp, ph->user.authToken, xmlencodedSearchStr); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=search&arg1=%s", ph->routeId, ph->user.listenerId, urlencodedSearchStr); free (urlencodedSearchStr); free (xmlencodedSearchStr); break; } case PIANO_REQUEST_CREATE_STATION: { /* create new station from specified musicid (type=mi, get one by * performing a search) or shared station id (type=sh) */ PianoRequestDataCreateStation_t *reqData = req->data; assert (reqData != NULL); assert (reqData->id != NULL); assert (reqData->type != NULL); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" "<methodCall><methodName>station.createStation</methodName>" "<params><param><value><int>%lu</int></value></param>" /* auth token */ "<param><value><string>%s</string></value></param>" /* music id */ "<param><value><string>%s%s</string></value></param>" /* empty */ "<param><value><string></string></value></param>" "</params></methodCall>", (unsigned long) timestamp, ph->user.authToken, reqData->type, reqData->id); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=createStation&arg1=%s%s&arg2=", ph->routeId, ph->user.listenerId, reqData->type, reqData->id); break; } case PIANO_REQUEST_ADD_SEED: { /* add another seed to specified station */ PianoRequestDataAddSeed_t *reqData = req->data; assert (reqData != NULL); assert (reqData->station != NULL); assert (reqData->musicId != NULL); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" "<methodCall><methodName>station.addSeed</methodName><params>" "<param><value><int>%lu</int></value></param>" /* auth token */ "<param><value><string>%s</string></value></param>" /* station id */ "<param><value><string>%s</string></value></param>" /* music id */ "<param><value><string>%s</string></value></param>" "</params></methodCall>", (unsigned long) timestamp, ph->user.authToken, reqData->station->id, reqData->musicId); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=addSeed&arg1=%s&arg2=%s", ph->routeId, ph->user.listenerId, reqData->station->id, reqData->musicId); break; } case PIANO_REQUEST_ADD_TIRED_SONG: { /* ban song for a month from all stations */ PianoSong_t *song = req->data; assert (song != NULL); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" "<methodCall><methodName>listener.addTiredSong</methodName><params>" "<param><value><int>%lu</int></value></param>" "<param><value><string>%s</string></value></param>" /* key */ "<param><value><string>%s</string></value></param>" /* user seed */ "<param><value><string>%s</string></value></param>" /* station id */ "<param><value><string>%s</string></value></param>" "</params></methodCall>", (unsigned long) timestamp, ph->user.authToken, (song->musicId == NULL) ? "" : song->musicId, (song->userSeed == NULL) ? "" : song->userSeed, song->stationId); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=addTiredSong&arg1=%s&arg2=%s&arg3=%s", ph->routeId, ph->user.listenerId, (song->musicId == NULL) ? "" : song->musicId, (song->userSeed == NULL) ? "" : song->userSeed, song->stationId); break; } case PIANO_REQUEST_SET_QUICKMIX: { /* select stations included in quickmix (see useQuickMix flag of * PianoStation_t) */ char valueBuf[1000], urlArgBuf[1000]; PianoStation_t *curStation = ph->stations; memset (urlArgBuf, 0, sizeof (urlArgBuf)); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" "<methodCall><methodName>station.setQuickMix</methodName><params>" "<param><value><int>%lu</int></value></param>" "<param><value><string>%s</string></value></param>" /* quick mix type */ "<param><value><string>RANDOM</string></value></param>" "<param><value><array><data>", (unsigned long) timestamp, ph->user.authToken); while (curStation != NULL) { /* quick mix can't contain itself */ if (!curStation->useQuickMix || curStation->isQuickMix) { curStation = curStation->next; continue; } /* append to xml doc */ snprintf (valueBuf, sizeof (valueBuf), "<value><string>%s</string></value>", curStation->id); strncat (xmlSendBuf, valueBuf, sizeof (xmlSendBuf) - strlen (xmlSendBuf) - 1); /* append to url arg */ strncat (urlArgBuf, curStation->id, sizeof (urlArgBuf) - strlen (urlArgBuf) - 1); curStation = curStation->next; /* if not last item: append "," */ if (curStation != NULL) { strncat (urlArgBuf, "%2C", sizeof (urlArgBuf) - strlen (urlArgBuf) - 1); } } strncat (xmlSendBuf, "</data></array></value></param>" /* empty */ "<param><value><string></string></value></param>" /* empty */ "<param><value><string></string></value></param>" "</params></methodCall>", sizeof (xmlSendBuf) - strlen (xmlSendBuf) - 1); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=setQuickMix&arg1=RANDOM&arg2=%s&arg3=&arg4=", ph->routeId, ph->user.listenerId, urlArgBuf); break; } case PIANO_REQUEST_GET_GENRE_STATIONS: /* receive list of pandora's genre stations */ xmlSendBuf[0] = '\0'; snprintf (req->urlPath, sizeof (req->urlPath), "/xml/genre?r=%lu", (unsigned long) timestamp); break; case PIANO_REQUEST_TRANSFORM_STATION: { /* transform shared station into private */ PianoStation_t *station = req->data; assert (station != NULL); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" "<methodCall><methodName>station.transformShared</methodName>" "<params><param><value><int>%lu</int></value></param>" /* auth token */ "<param><value><string>%s</string></value></param>" /* station id */ "<param><value><string>%s</string></value></param>" "</params></methodCall>", (unsigned long) timestamp, ph->user.authToken, station->id); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=transformShared&arg1=%s", ph->routeId, ph->user.listenerId, station->id); break; } case PIANO_REQUEST_EXPLAIN: { /* explain why particular song was played */ PianoRequestDataExplain_t *reqData = req->data; assert (reqData != NULL); assert (reqData->song != NULL); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" "<methodCall><methodName>playlist.narrative</methodName>" "<params><param><value><int>%lu</int></value></param>" /* auth token */ "<param><value><string>%s</string></value></param>" /* station id */ "<param><value><string>%s</string></value></param>" /* music id */ "<param><value><string>%s</string></value></param>" "</params></methodCall>", (unsigned long) timestamp, ph->user.authToken, reqData->song->stationId, reqData->song->musicId); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=narrative&arg1=%s&arg2=%s", ph->routeId, ph->user.listenerId, reqData->song->stationId, reqData->song->musicId); break; } case PIANO_REQUEST_GET_SEED_SUGGESTIONS: { /* find similar artists */ PianoRequestDataGetSeedSuggestions_t *reqData = req->data; assert (reqData != NULL); assert (reqData->musicId != NULL); assert (reqData->max != 0); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" "<methodCall><methodName>music.getSeedSuggestions</methodName>" "<params><param><value><int>%lu</int></value></param>" /* auth token */ "<param><value><string>%s</string></value></param>" /* station id */ "<param><value><string>%s</string></value></param>" /* seed music id */ "<param><value><string>%s</string></value></param>" /* max */ "<param><value><int>%u</int></value></param>" "</params></methodCall>", (unsigned long) timestamp, ph->user.authToken, reqData->station->id, reqData->musicId, reqData->max); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=getSeedSuggestions&arg1=%s&arg2=%u", ph->routeId, ph->user.listenerId, reqData->musicId, reqData->max); break; } case PIANO_REQUEST_BOOKMARK_SONG: { /* bookmark song */ PianoSong_t *song = req->data; assert (song != NULL); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" "<methodCall><methodName>station.createBookmark</methodName>" "<params><param><value><int>%lu</int></value></param>" /* auth token */ "<param><value><string>%s</string></value></param>" /* station id */ "<param><value><string>%s</string></value></param>" /* music id */ "<param><value><string>%s</string></value></param>" "</params></methodCall>", (unsigned long) timestamp, ph->user.authToken, song->stationId, song->musicId); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=createBookmark&arg1=%s&arg2=%s", ph->routeId, ph->user.listenerId, song->stationId, song->musicId); break; } case PIANO_REQUEST_BOOKMARK_ARTIST: { /* bookmark artist */ PianoSong_t *song = req->data; assert (song != NULL); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" "<methodCall><methodName>station.createArtistBookmark</methodName>" "<params><param><value><int>%lu</int></value></param>" /* auth token */ "<param><value><string>%s</string></value></param>" /* music id */ "<param><value><string>%s</string></value></param>" "</params></methodCall>", (unsigned long) timestamp, ph->user.authToken, song->artistMusicId); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=createArtistBookmark&arg1=%s", ph->routeId, ph->user.listenerId, song->artistMusicId); break; } case PIANO_REQUEST_GET_STATION_INFO: { /* get station information (seeds and feedback) */ PianoRequestDataGetStationInfo_t *reqData = req->data; assert (reqData != NULL); assert (reqData->station != NULL); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" "<methodCall><methodName>station.getStation</methodName>" "<params><param><value><int>%lu</int></value></param>" /* auth token */ "<param><value><string>%s</string></value></param>" /* station id */ "<param><value><string>%s</string></value></param>" "</params></methodCall>", (unsigned long) timestamp, ph->user.authToken, reqData->station->id); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=getStation&arg1=%s", ph->routeId, ph->user.listenerId, reqData->station->id); break; } case PIANO_REQUEST_DELETE_FEEDBACK: { PianoSong_t *song = req->data; assert (song != NULL); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" "<methodCall><methodName>station.deleteFeedback</methodName>" "<params><param><value><int>%lu</int></value></param>" /* auth token */ "<param><value><string>%s</string></value></param>" /* feedback id */ "<param><value><string>%s</string></value></param>" "</params></methodCall>", (unsigned long) timestamp, ph->user.authToken, song->feedbackId); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=deleteFeedback&arg1=%s", ph->routeId, ph->user.listenerId, song->feedbackId); break; } case PIANO_REQUEST_DELETE_SEED: { PianoRequestDataDeleteSeed_t *reqData = req->data; char *seedId = NULL; assert (reqData != NULL); assert (reqData->song != NULL || reqData->artist != NULL || reqData->station != NULL); if (reqData->song != NULL) { seedId = reqData->song->seedId; } else if (reqData->artist != NULL) { seedId = reqData->artist->seedId; } else if (reqData->station != NULL) { seedId = reqData->station->seedId; } assert (seedId != NULL); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" "<methodCall><methodName>station.deleteSeed</methodName>" "<params><param><value><int>%lu</int></value></param>" /* auth token */ "<param><value><string>%s</string></value></param>" /* seed id */ "<param><value><string>%s</string></value></param>" "</params></methodCall>", (unsigned long) timestamp, ph->user.authToken, seedId); snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH "rid=%s&lid=%s&method=deleteSeed&arg1=%s", ph->routeId, ph->user.listenerId, seedId); break; } /* "high-level" wrapper */ case PIANO_REQUEST_RATE_SONG: { /* love/ban song */ PianoRequestDataRateSong_t *reqData = req->data; PianoReturn_t pRet; assert (reqData != NULL); assert (reqData->song != NULL); assert (reqData->rating != PIANO_RATE_NONE); PianoRequestDataAddFeedback_t transformedReqData; transformedReqData.stationId = reqData->song->stationId; transformedReqData.trackToken = reqData->song->trackToken; transformedReqData.rating = reqData->rating; req->data = &transformedReqData; /* create request data (url, post data) */ pRet = PianoRequest (ph, req, PIANO_REQUEST_ADD_FEEDBACK); /* and reset request type/data */ req->type = PIANO_REQUEST_RATE_SONG; req->data = reqData; return pRet; break; } case PIANO_REQUEST_MOVE_SONG: { /* move song to a different station, needs two requests */ PianoRequestDataMoveSong_t *reqData = req->data; PianoRequestDataAddFeedback_t transformedReqData; PianoReturn_t pRet; assert (reqData != NULL); assert (reqData->song != NULL); assert (reqData->from != NULL); assert (reqData->to != NULL); assert (reqData->step < 2); transformedReqData.trackToken = reqData->song->trackToken; req->data = &transformedReqData; switch (reqData->step) { case 0: transformedReqData.stationId = reqData->from->id; transformedReqData.rating = PIANO_RATE_BAN; break; case 1: transformedReqData.stationId = reqData->to->id; transformedReqData.rating = PIANO_RATE_LOVE; break; } /* create request data (url, post data) */ pRet = PianoRequest (ph, req, PIANO_REQUEST_ADD_FEEDBACK); /* and reset request type/data */ req->type = PIANO_REQUEST_MOVE_SONG; req->data = reqData; return pRet; break; } } if ((req->postData = PianoEncryptString (xmlSendBuf)) == NULL) { return PIANO_RET_OUT_OF_MEMORY; } return PIANO_RET_OK; }
/* piano wrapper: prepare/execute http request and pass result back to * libpiano */ bool BarUiPianoCall (BarApp_t * const app, const PianoRequestType_t type, void * const data, PianoReturn_t * const pRet, CURLcode * const wRet) { PianoReturn_t pRetLocal = PIANO_RET_OK; CURLcode wRetLocal = CURLE_OK; bool ret = false; /* repeat as long as there are http requests to do */ do { PianoRequest_t req = { .data = data, .responseData = NULL }; pRetLocal = PianoRequest (&app->ph, &req, type); if (pRetLocal != PIANO_RET_OK) { BarUiMsg (&app->settings, MSG_NONE, "Error: %s\n", PianoErrorToStr (pRetLocal)); goto cleanup; } wRetLocal = BarPianoHttpRequest (app->http, &app->settings, &req); if (wRetLocal == CURLE_ABORTED_BY_CALLBACK) { BarUiMsg (&app->settings, MSG_NONE, "Interrupted.\n"); goto cleanup; } else if (wRetLocal != CURLE_OK) { BarUiMsg (&app->settings, MSG_NONE, "Network error: %s\n", curl_easy_strerror (wRetLocal)); goto cleanup; } pRetLocal = PianoResponse (&app->ph, &req); if (pRetLocal != PIANO_RET_CONTINUE_REQUEST) { /* checking for request type avoids infinite loops */ if (pRetLocal == PIANO_RET_P_INVALID_AUTH_TOKEN && type != PIANO_REQUEST_LOGIN) { /* reauthenticate */ PianoRequestDataLogin_t reqData; reqData.user = app->settings.username; reqData.password = app->settings.password; reqData.step = 0; BarUiMsg (&app->settings, MSG_NONE, "Reauthentication required... "); if (!BarUiPianoCall (app, PIANO_REQUEST_LOGIN, &reqData, &pRetLocal, &wRetLocal)) { goto cleanup; } else { /* try again */ pRetLocal = PIANO_RET_CONTINUE_REQUEST; BarUiMsg (&app->settings, MSG_INFO, "Trying again... "); } } else if (pRetLocal != PIANO_RET_OK) { BarUiMsg (&app->settings, MSG_NONE, "Error: %s\n", PianoErrorToStr (pRetLocal)); goto cleanup; } else { BarUiMsg (&app->settings, MSG_NONE, "Ok.\n"); ret = true; } } cleanup: /* persistent data is stored in req.data */ free (req.responseData); PianoDestroyRequest (&req); } while (pRetLocal == PIANO_RET_CONTINUE_REQUEST); *pRet = pRetLocal; *wRet = wRetLocal; return ret; }