summaryrefslogtreecommitdiff
path: root/libpiano/src/piano.c
diff options
context:
space:
mode:
Diffstat (limited to 'libpiano/src/piano.c')
-rw-r--r--libpiano/src/piano.c908
1 files changed, 908 insertions, 0 deletions
diff --git a/libpiano/src/piano.c b/libpiano/src/piano.c
new file mode 100644
index 0000000..43c675a
--- /dev/null
+++ b/libpiano/src/piano.c
@@ -0,0 +1,908 @@
+/*
+Copyright (c) 2008-2009
+ Lars-Dominik Braun <PromyLOPh@lavabit.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#define _BSD_SOURCE /* required by strdup() */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "piano_private.h"
+#include "piano.h"
+#include "http.h"
+#include "xml.h"
+#include "crypt.h"
+#include "config.h"
+
+#define PIANO_PROTOCOL_VERSION "25"
+#define PIANO_RPC_HOST "www.pandora.com"
+#define PIANO_RPC_PORT "80"
+#define PIANO_RPC_PATH "/radio/xmlrpc/v" PIANO_PROTOCOL_VERSION "?"
+#define PIANO_SEND_BUFFER_SIZE 10000
+
+/* prototypes */
+static PianoReturn_t PianoAddFeedback (PianoHandle_t *, const char *, const char *,
+ const char *, const char *, const char *, PianoSongRating_t);
+const char *PianoAudioFormatToString (PianoAudioFormat_t);
+
+/* more "secure" free version
+ * @param free this pointer
+ * @param zero n bytes; 0 disables zeroing (for strings with unknown size,
+ * e.g.)
+ */
+void PianoFree (void *ptr, size_t size) {
+ if (ptr != NULL) {
+ if (size > 0) {
+ /* avoid reuse of freed memory */
+ memset ((char *) ptr, 0, size);
+ }
+ free (ptr);
+ }
+}
+
+/* initialize piano handle
+ * @param piano handle
+ * @return nothing
+ */
+void PianoInit (PianoHandle_t *ph) {
+ memset (ph, 0, sizeof (*ph));
+
+ WaitressInit (&ph->waith);
+ strncpy (ph->waith.host, PIANO_RPC_HOST, sizeof (ph->waith.host)-1);
+ strncpy (ph->waith.port, PIANO_RPC_PORT, sizeof (ph->waith.port)-1);
+
+ /* route-id seems to be random. we're using time anyway... */
+ snprintf (ph->routeId, sizeof (ph->routeId), "%07liP", time (NULL) % 10000000);
+}
+
+/* free complete search result
+ * @public yes
+ * @param search result
+ */
+void PianoDestroySearchResult (PianoSearchResult_t *searchResult) {
+ PianoArtist_t *curArtist, *lastArtist;
+ PianoSong_t *curSong, *lastSong;
+
+ curArtist = searchResult->artists;
+ while (curArtist != NULL) {
+ PianoFree (curArtist->name, 0);
+ PianoFree (curArtist->musicId, 0);
+ lastArtist = curArtist;
+ curArtist = curArtist->next;
+ PianoFree (lastArtist, sizeof (*lastArtist));
+ }
+
+ curSong = searchResult->songs;
+ while (curSong != NULL) {
+ PianoFree (curSong->title, 0);
+ PianoFree (curSong->artist, 0);
+ PianoFree (curSong->musicId, 0);
+ lastSong = curSong;
+ curSong = curSong->next;
+ PianoFree (lastSong, sizeof (*lastSong));
+ }
+}
+
+/* free single station
+ * @param station
+ */
+void PianoDestroyStation (PianoStation_t *station) {
+ PianoFree (station->name, 0);
+ PianoFree (station->id, 0);
+ memset (station, 0, sizeof (station));
+}
+
+/* free complete station list
+ * @param piano handle
+ */
+void PianoDestroyStations (PianoStation_t *stations) {
+ PianoStation_t *curStation, *lastStation;
+
+ curStation = stations;
+ while (curStation != NULL) {
+ lastStation = curStation;
+ curStation = curStation->next;
+ PianoDestroyStation (lastStation);
+ PianoFree (lastStation, sizeof (*lastStation));
+ }
+}
+
+/* FIXME: copy & waste */
+/* free _all_ elements of playlist
+ * @param piano handle
+ * @return nothing
+ */
+void PianoDestroyPlaylist (PianoSong_t *playlist) {
+ PianoSong_t *curSong, *lastSong;
+
+ curSong = playlist;
+ while (curSong != NULL) {
+ PianoFree (curSong->audioUrl, 0);
+ PianoFree (curSong->artist, 0);
+ PianoFree (curSong->focusTraitId, 0);
+ PianoFree (curSong->matchingSeed, 0);
+ PianoFree (curSong->musicId, 0);
+ PianoFree (curSong->title, 0);
+ PianoFree (curSong->userSeed, 0);
+ PianoFree (curSong->identity, 0);
+ PianoFree (curSong->stationId, 0);
+ PianoFree (curSong->album, 0);
+ lastSong = curSong;
+ curSong = curSong->next;
+ PianoFree (lastSong, sizeof (*lastSong));
+ }
+}
+
+/* frees the whole piano handle structure
+ * @param piano handle
+ * @return nothing
+ */
+void PianoDestroy (PianoHandle_t *ph) {
+ WaitressFree (&ph->waith);
+
+ PianoFree (ph->user.webAuthToken, 0);
+ PianoFree (ph->user.authToken, 0);
+ PianoFree (ph->user.listenerId, 0);
+
+ PianoDestroyStations (ph->stations);
+ /* destroy genre stations */
+ PianoGenreCategory_t *curGenreCat = ph->genreStations, *lastGenreCat;
+ while (curGenreCat != NULL) {
+ PianoDestroyStations (curGenreCat->stations);
+ PianoFree (curGenreCat->name, 0);
+ lastGenreCat = curGenreCat;
+ curGenreCat = curGenreCat->next;
+ PianoFree (lastGenreCat, sizeof (*lastGenreCat));
+ }
+ memset (ph, 0, sizeof (*ph));
+}
+
+/* authenticates user
+ * @param piano handle
+ * @param username (utf-8 encoded)
+ * @param password (plaintext, utf-8 encoded)
+ * @param use ssl when logging in (1 = on, 0 = off), note that the password
+ * is not hashed and will be sent as plain-text!
+ */
+PianoReturn_t PianoConnect (PianoHandle_t *ph, const char *user,
+ const char *password) {
+ char *retStr, xmlSendBuf[PIANO_SEND_BUFFER_SIZE];
+ PianoReturn_t ret;
+
+ /* sync and throw away result (it's an encrypted timestamp, decrypt with
+ * PianoDecryptString) */
+ snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
+ "<methodCall><methodName>misc.sync</methodName>"
+ "<params></params></methodCall>");
+ snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH
+ "rid=%s&method=sync", ph->routeId);
+ ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr);
+ PianoFree (retStr, 0);
+
+ if (ret != PIANO_RET_OK) {
+ return ret;
+ }
+
+ /* authenticate */
+ snprintf (xmlSendBuf, sizeof (xmlSendBuf),
+ "<?xml version=\"1.0\"?><methodCall>"
+ "<methodName>listener.authenticateListener</methodName>"
+ "<params><param><value><int>%li</int></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "</params></methodCall>", time (NULL), user, password);
+ snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH
+ "rid=%s&method=authenticateListener", ph->routeId);
+
+ if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) ==
+ PIANO_RET_OK) {
+ ret = PianoXmlParseUserinfo (ph, retStr);
+ PianoFree (retStr, 0);
+ }
+
+ return ret;
+}
+
+/* get all stations for authenticated user (so: PianoConnect needs to
+ * be run before)
+ * @param piano handle filled with some authentication data by PianoConnect
+ */
+PianoReturn_t PianoGetStations (PianoHandle_t *ph) {
+ char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr;
+ PianoReturn_t ret;
+
+ snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
+ "<methodCall><methodName>station.getStations</methodName>"
+ "<params><param><value><int>%li</int></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "</params></methodCall>", time (NULL), ph->user.authToken);
+ snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH
+ "rid=%s&lid=%s&method=getStations", ph->routeId,
+ ph->user.listenerId);
+
+ if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) ==
+ PIANO_RET_OK) {
+ ret = PianoXmlParseStations (ph, retStr);
+ PianoFree (retStr, 0);
+ }
+
+ return ret;
+}
+
+/* get next songs for station (usually four tracks)
+ * @param piano handle
+ * @param station id
+ * @param audio format
+ * @param return value: playlist
+ */
+PianoReturn_t PianoGetPlaylist (PianoHandle_t *ph, const char *stationId,
+ PianoAudioFormat_t format, PianoSong_t **retPlaylist) {
+ char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr;
+ PianoReturn_t ret;
+
+ /* FIXME: remove static, "magic" numbers */
+ snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
+ "<methodCall><methodName>playlist.getFragment</methodName>"
+ "<params><param><value><int>%li</int></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>0</string></value></param>"
+ "<param><value><string></string></value></param>"
+ "<param><value><string></string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>0</string></value></param>"
+ "<param><value><string>0</string></value></param>"
+ "</params></methodCall>", time (NULL), ph->user.authToken,
+ stationId, PianoAudioFormatToString (format));
+ snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH
+ "rid=%s&lid=%s&method=getFragment&arg1=%s&arg2=0"
+ "&arg3=&arg4=&arg5=%s&arg6=0&arg7=0", ph->routeId,
+ ph->user.listenerId, stationId,
+ PianoAudioFormatToString (format));
+
+ if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) ==
+ PIANO_RET_OK) {
+ ret = PianoXmlParsePlaylist (ph, retStr, retPlaylist);
+ PianoFree (retStr, 0);
+ }
+
+ return ret;
+}
+
+/* love or ban track (you cannot remove your rating, so PIANO_RATE_NONE is
+ * not allowed)
+ * @public yes
+ * @param piano handle
+ * @param rate this track
+ * @param your rating
+ */
+PianoReturn_t PianoRateTrack (PianoHandle_t *ph, PianoSong_t *song,
+ PianoSongRating_t rating) {
+ PianoReturn_t ret;
+
+ ret = PianoAddFeedback (ph, song->stationId, song->musicId,
+ song->matchingSeed, song->userSeed, song->focusTraitId, rating);
+
+ if (ret == PIANO_RET_OK) {
+ song->rating = rating;
+ }
+
+ return ret;
+}
+
+/* move song to another station
+ * @param piano handle
+ * @param move from
+ * @param move here
+ * @param song to move
+ */
+PianoReturn_t PianoMoveSong (PianoHandle_t *ph,
+ const PianoStation_t *stationFrom, const PianoStation_t *stationTo,
+ const PianoSong_t *song) {
+ PianoReturn_t ret;
+
+ /* ban from current station */
+ if ((ret = PianoAddFeedback (ph, stationFrom->id, song->musicId, "", "",
+ "", PIANO_RATE_BAN)) == PIANO_RET_OK) {
+ /* love at new station */
+ return PianoAddFeedback (ph, stationTo->id, song->musicId, "",
+ "", "", PIANO_RATE_LOVE);
+ }
+ return ret;
+}
+
+/* add feedback
+ * @param piano handle
+ * @param station id
+ * @param song id
+ * @param song matching seed or NULL
+ * @param song user seed or NULL
+ * @param song focus trait id or NULL
+ * @param rating
+ */
+static PianoReturn_t PianoAddFeedback (PianoHandle_t *ph, const char *stationId,
+ const char *songMusicId, const char *songMatchingSeed,
+ const char *songUserSeed, const char *songFocusTraitId,
+ PianoSongRating_t rating) {
+ char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr;
+ PianoReturn_t ret = PIANO_RET_ERR;
+
+ snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
+ "<methodCall><methodName>station.addFeedback</methodName>"
+ "<params><param><value><int>%li</int></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value></value></param>"
+ "<param><value><boolean>%i</boolean></value></param>"
+ "<param><value><boolean>0</boolean></value></param>"
+ "</params></methodCall>", time (NULL), ph->user.authToken,
+ stationId, songMusicId,
+ (songMatchingSeed == NULL) ? "" : songMatchingSeed,
+ (songUserSeed == NULL) ? "" : songUserSeed,
+ (songFocusTraitId == NULL) ? "" : songFocusTraitId,
+ (rating == PIANO_RATE_LOVE) ? 1 : 0);
+ snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH
+ "rid=%s&lid=%s&method=addFeedback&arg1=%s&arg2=%s"
+ "&arg3=%s&arg4=%s&arg5=%s&arg6=&arg7=%s&arg8=false", ph->routeId,
+ ph->user.listenerId, stationId, songMusicId,
+ (songMatchingSeed == NULL) ? "" : songMatchingSeed,
+ (songUserSeed == NULL) ? "" : songUserSeed,
+ (songFocusTraitId == NULL) ? "" : songFocusTraitId,
+ (rating == PIANO_RATE_LOVE) ? "true" : "false");
+
+ if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) ==
+ PIANO_RET_OK) {
+ ret = PianoXmlParseSimple (retStr);
+ PianoFree (retStr, 0);
+ }
+
+ return ret;
+}
+
+/* rename station (on the server and local)
+ * @public yes
+ * @param piano handle
+ * @param change this stations name
+ * @param new name
+ * @return
+ */
+PianoReturn_t PianoRenameStation (PianoHandle_t *ph, PianoStation_t *station,
+ const char *newName) {
+ char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr;
+ char *urlencodedNewName, *xmlencodedNewName;
+ PianoReturn_t ret = PIANO_RET_ERR;
+
+ if ((xmlencodedNewName = PianoXmlEncodeString (newName)) == NULL) {
+ return PIANO_RET_OUT_OF_MEMORY;
+ }
+
+ snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
+ "<methodCall><methodName>station.setStationName</methodName>"
+ "<params><param><value><int>%li</int></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "</params></methodCall>", time (NULL), ph->user.authToken,
+ station->id, xmlencodedNewName);
+
+ urlencodedNewName = WaitressUrlEncode (newName);
+ snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH
+ "rid=%s&lid=%s&method=setStationName&arg1=%s&arg2=%s",
+ ph->routeId, ph->user.listenerId, station->id, urlencodedNewName);
+
+ if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) ==
+ PIANO_RET_OK) {
+ if ((ret = PianoXmlParseSimple (retStr)) == PIANO_RET_OK) {
+ PianoFree (station->name, 0);
+ station->name = strdup (newName);
+ }
+ PianoFree (retStr, 0);
+ }
+
+ PianoFree (urlencodedNewName, 0);
+ PianoFree (xmlencodedNewName, 0);
+
+ return ret;
+}
+
+/* delete station
+ * @public yes
+ * @param piano handle
+ * @param station you want to delete
+ */
+PianoReturn_t PianoDeleteStation (PianoHandle_t *ph, PianoStation_t *station) {
+ char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr;
+ PianoReturn_t ret = PIANO_RET_ERR;
+
+ snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
+ "<methodCall><methodName>station.removeStation</methodName>"
+ "<params><param><value><int>%li</int></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "</params></methodCall>", time (NULL), ph->user.authToken,
+ station->id);
+
+ snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH
+ "rid=%s&lid=%s&method=removeStation&arg1=%s", ph->routeId,
+ ph->user.listenerId, station->id);
+ if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) ==
+ PIANO_RET_OK) {
+ if ((ret = PianoXmlParseSimple (retStr)) == PIANO_RET_OK) {
+ /* delete station from local station list */
+ PianoStation_t *curStation = ph->stations, *lastStation = NULL;
+ while (curStation != NULL) {
+ if (curStation == station) {
+ if (lastStation != NULL) {
+ lastStation->next = curStation->next;
+ } else {
+ /* first station in list */
+ ph->stations = curStation->next;
+ }
+ PianoDestroyStation (curStation);
+ PianoFree (curStation, sizeof (*curStation));
+ break;
+ }
+ lastStation = curStation;
+ curStation = curStation->next;
+ }
+ }
+ }
+
+ return ret;
+}
+
+/* search for music (artist or track), needed to create new station; don't
+ * forget to free the search result; beware! searchResult will be nulled
+ * by PianoXmlParseSearch
+ * @public yes
+ * @param piano handle
+ * @param utf-8 search string
+ * @param return search result
+ */
+PianoReturn_t PianoSearchMusic (PianoHandle_t *ph,
+ const char *searchStr, PianoSearchResult_t *searchResult) {
+ char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr;
+ char *xmlencodedSearchStr, *urlencodedSearchStr;
+ PianoReturn_t ret;
+
+ if ((xmlencodedSearchStr = PianoXmlEncodeString (searchStr)) == NULL) {
+ return PIANO_RET_OUT_OF_MEMORY;
+ }
+
+ snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
+ "<methodCall><methodName>music.search</methodName>"
+ "<params><param><value><int>%li</int></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "</params></methodCall>", time (NULL), ph->user.authToken,
+ xmlencodedSearchStr);
+
+ urlencodedSearchStr = WaitressUrlEncode (searchStr);
+ snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH
+ "rid=%s&lid=%s&method=search&arg1=%s", ph->routeId,
+ ph->user.listenerId, urlencodedSearchStr);
+
+ if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) ==
+ PIANO_RET_OK) {
+ ret = PianoXmlParseSearch (retStr, searchResult);
+ PianoFree (retStr, 0);
+ }
+
+ PianoFree (urlencodedSearchStr, 0);
+ PianoFree (xmlencodedSearchStr, 0);
+
+ return ret;
+}
+
+/* create new station on server
+ * @public yes
+ * @param piano handle
+ * @param type: "mi" for music id (from music search) or "sh" for
+ * shared station
+ * @param id
+ */
+PianoReturn_t PianoCreateStation (PianoHandle_t *ph, const char *type,
+ const char *id) {
+ char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr;
+ PianoReturn_t ret;
+
+ snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
+ "<methodCall><methodName>station.createStation</methodName>"
+ "<params><param><value><int>%li</int></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s%s</string></value></param>"
+ "</params></methodCall>", time (NULL), ph->user.authToken,
+ type, id);
+
+ snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH
+ "rid=%s&lid=%s&method=createStation&arg1=%s%s", ph->routeId,
+ ph->user.listenerId, type, id);
+
+ if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) ==
+ PIANO_RET_OK) {
+ ret = PianoXmlParseCreateStation (ph, retStr);
+ PianoFree (retStr, 0);
+ }
+
+ return ret;
+}
+
+/* FIXME: update station data instead of replacing them */
+/* add more music to existing station; multithreaded apps beware! this alters
+ * station data, don't forget to lock the station pointer you passed to this
+ * function
+ * @public yes
+ * @param piano handle
+ * @param add music to this station
+ * @param music id; can be obtained with PianoSearchMusic ()
+ */
+PianoReturn_t PianoStationAddMusic (PianoHandle_t *ph,
+ PianoStation_t *station, const char *musicId) {
+ char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr;
+ PianoReturn_t ret;
+
+ snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
+ "<methodCall><methodName>station.addSeed</methodName><params>"
+ "<param><value><int>%li</int></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "</params></methodCall>", time (NULL), ph->user.authToken,
+ station->id, musicId);
+
+ snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH
+ "rid=%s&lid=%s&method=addSeed&arg1=%s&arg2=%s", ph->routeId,
+ ph->user.listenerId, station->id, musicId);
+
+ if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) ==
+ PIANO_RET_OK) {
+ ret = PianoXmlParseAddSeed (ph, retStr, station);
+ PianoFree (retStr, 0);
+ }
+
+ return ret;
+}
+
+/* ban a song temporary (for one month)
+ * @param piano handle
+ * @param song to be banned
+ * @return _OK or error
+ */
+PianoReturn_t PianoSongTired (PianoHandle_t *ph, const PianoSong_t *song) {
+ char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr;
+ PianoReturn_t ret;
+
+ snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
+ "<methodCall><methodName>listener.addTiredSong</methodName><params>"
+ "<param><value><int>%li</int></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "</params></methodCall>", time (NULL), ph->user.authToken,
+ song->identity);
+
+ snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH
+ "rid=%s&lid=%s&method=addTiredSong&arg1=%s", ph->routeId,
+ ph->user.listenerId, song->identity);
+
+ if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) ==
+ PIANO_RET_OK) {
+ ret = PianoXmlParseSimple (retStr);
+ PianoFree (retStr, 0);
+ }
+
+ return ret;
+}
+
+/* set stations use by quickmix
+ * @param piano handle
+ * @return _OK or error
+ */
+PianoReturn_t PianoSetQuickmix (PianoHandle_t *ph) {
+ char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], valueBuf[1000], urlArgBuf[1000],
+ *retStr;
+ PianoReturn_t ret;
+ PianoStation_t *curStation = ph->stations;
+
+ memset (urlArgBuf, 0, sizeof (urlArgBuf));
+ snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
+ "<methodCall><methodName>station.setQuickMix</methodName><params>"
+ "<param><value><int>%li</int></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>RANDOM</string></value></param>"
+ "<param><value><array><data>", time (NULL), ph->user.authToken);
+ while (curStation != NULL) {
+ /* quick mix can't contain itself */
+ if (!curStation->useQuickMix || curStation->isQuickMix) {
+ curStation = curStation->next;
+ continue;
+ }
+ /* append to xml doc */
+ snprintf (valueBuf, sizeof (valueBuf),
+ "<value><string>%s</string></value>", curStation->id);
+ strncat (xmlSendBuf, valueBuf, sizeof (xmlSendBuf) -
+ strlen (xmlSendBuf) - 1);
+ /* append to url arg */
+ strncat (urlArgBuf, curStation->id, sizeof (urlArgBuf) -
+ strlen (urlArgBuf) - 1);
+ curStation = curStation->next;
+ /* if not last item: append "," */
+ if (curStation != NULL) {
+ strncat (urlArgBuf, "%2C", sizeof (urlArgBuf) -
+ strlen (urlArgBuf) - 1);
+ }
+ }
+ strncat (xmlSendBuf,
+ "</data></array></value></param></params></methodCall>",
+ sizeof (xmlSendBuf) - strlen (xmlSendBuf) - 1);
+
+ snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH
+ "rid=%s&lid=%s&method=setQuickMix&arg1=RANDOM&arg2=%s",
+ ph->routeId, ph->user.listenerId, urlArgBuf);
+
+ if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) ==
+ PIANO_RET_OK) {
+ ret = PianoXmlParseSimple (retStr);
+ PianoFree (retStr, 0);
+ }
+
+ return ret;
+}
+
+/* get station from list by id
+ * @param search here
+ * @param search for this
+ * @return the first station structure matching the given id
+ */
+PianoStation_t *PianoFindStationById (PianoStation_t *stations,
+ const char *searchStation) {
+ while (stations != NULL) {
+ if (strcmp (stations->id, searchStation) == 0) {
+ return stations;
+ }
+ stations = stations->next;
+ }
+ return NULL;
+}
+
+/* receive genre stations
+ * @param piano handle
+ * @return _OK or error
+ */
+PianoReturn_t PianoGetGenreStations (PianoHandle_t *ph) {
+ char *retStr;
+ PianoReturn_t ret;
+
+ snprintf (ph->waith.path, sizeof (ph->waith.path), "/xml/genre?r=%li",
+ time (NULL));
+
+ if ((ret = PianoHttpGet (&ph->waith, &retStr)) ==
+ PIANO_RET_OK) {
+ ret = PianoXmlParseGenreExplorer (ph, retStr);
+ PianoFree (retStr, 0);
+ }
+
+ return ret;
+}
+
+/* make shared stations private, needed to rate songs played on shared
+ * stations
+ * @param piano handle
+ * @param station to transform
+ * @return _OK or error
+ */
+PianoReturn_t PianoTransformShared (PianoHandle_t *ph,
+ PianoStation_t *station) {
+ char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr;
+ PianoReturn_t ret;
+
+ snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
+ "<methodCall><methodName>station.transformShared</methodName>"
+ "<params><param><value><int>%li</int></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "</params></methodCall>", time (NULL), ph->user.authToken,
+ station->id);
+
+ snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH
+ "rid=%s&lid=%s&method=transformShared&arg1=%s", ph->routeId,
+ ph->user.listenerId, station->id);
+
+ if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) ==
+ PIANO_RET_OK) {
+ ret = PianoXmlParseTranformStation (retStr);
+ /* though this call returns a bunch of "new" data only this one is
+ * changed and important (at the moment) */
+ if (ret == PIANO_RET_OK) {
+ station->isCreator = 1;
+ }
+ PianoFree (retStr, 0);
+ }
+
+ return ret;
+}
+
+/* "why dit you play this song?"
+ * @param piano handle
+ * @param song (from playlist)
+ * @param return allocated string; you have to free it yourself
+ * @return _OK or error
+ */
+PianoReturn_t PianoExplain (PianoHandle_t *ph, const PianoSong_t *song,
+ char **retExplain) {
+ char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr;
+ PianoReturn_t ret;
+
+ snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
+ "<methodCall><methodName>playlist.narrative</methodName>"
+ "<params><param><value><int>%li</int></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "</params></methodCall>", time (NULL), ph->user.authToken,
+ song->stationId, song->musicId);
+
+ snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH
+ "rid=%s&lid=%s&method=method=narrative&arg1=%s&arg2=%s",
+ ph->routeId, ph->user.listenerId, song->stationId, song->musicId);
+
+ if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) ==
+ PIANO_RET_OK) {
+ ret = PianoXmlParseNarrative (retStr, retExplain);
+ PianoFree (retStr, 0);
+ }
+
+ return ret;
+}
+
+/* Get seed suggestions by music id
+ * @param piano handle
+ * @param music id
+ * @param max results
+ * @param result buffer
+ */
+PianoReturn_t PianoSeedSuggestions (PianoHandle_t *ph, const char *musicId,
+ unsigned int max, PianoSearchResult_t *searchResult) {
+ char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr;
+ PianoReturn_t ret;
+
+ snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
+ "<methodCall><methodName>music.getSeedSuggestions</methodName>"
+ "<params><param><value><int>%li</int></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><int>%u</int></value></param>"
+ "</params></methodCall>", time (NULL), ph->user.authToken,
+ musicId, max);
+
+ snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH
+ "rid=%s&lid=%s&method=method=getSeedSuggestions&arg1=%s&arg2=%u",
+ ph->routeId, ph->user.listenerId, musicId, max);
+
+ if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) ==
+ PIANO_RET_OK) {
+ ret = PianoXmlParseSeedSuggestions (retStr, searchResult);
+ PianoFree (retStr, 0);
+ }
+
+ return ret;
+}
+
+/* convert return value to human-readable string
+ * @param enum
+ * @return error string
+ */
+const char *PianoErrorToStr (PianoReturn_t ret) {
+ switch (ret) {
+ case PIANO_RET_OK:
+ return "Everything is fine :)";
+ break;
+
+ case PIANO_RET_ERR:
+ 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_NET_ERROR:
+ return "Connection failed.";
+ break;
+
+ case PIANO_RET_NOT_AUTHORIZED:
+ return "Not authorized.";
+ break;
+
+ case PIANO_RET_PROTOCOL_INCOMPATIBLE:
+ return "Protocol incompatible. Please upgrade " PACKAGE ".";
+ 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.";
+ break;
+
+ case PIANO_RET_IP_REJECTED:
+ return "Your ip address was rejected. Please setup a control "
+ "proxy (see manpage).";
+ break;
+
+ case PIANO_RET_STATION_NONEXISTENT:
+ return "Station does not exist.";
+ break;
+
+ case PIANO_RET_OUT_OF_MEMORY:
+ return "Out of memory.";
+ break;
+
+ case PIANO_RET_OUT_OF_SYNC:
+ return "Out of sync. Please correct your system's time.";
+ break;
+
+ default:
+ return "No error message available.";
+ break;
+ }
+}
+
+/* convert audio format id to string that can be used in xml requests
+ * @param format id
+ * @return constant string
+ */
+const char *PianoAudioFormatToString (PianoAudioFormat_t format) {
+ switch (format) {
+ case PIANO_AF_AACPLUS:
+ return "aacplus";
+ break;
+
+ case PIANO_AF_MP3:
+ return "mp3";
+ break;
+
+ case PIANO_AF_MP3_HI:
+ return "mp3-hifi";
+ break;
+
+ default:
+ return NULL;
+ break;
+ }
+}
+