/* Copyright (c) 2008 Lars-Dominik Braun Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include "const.h" #include "main.h" #include "piano.h" #include "http.h" #include "xml.h" #include "crypt.h" #include "config.h" /* prototypes */ PianoReturn_t PianoAddFeedback (PianoHandle_t *, char *, char *, char *, char *, char *, PianoSongRating_t); /* more "secure" free version; only use this function, not original free () * in this library * @public no!!! * @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, set up curl handle and settings; note: you * _must_ init curl and libxml2 using curl_global_init (CURL_GLOBAL_SSL) * and xmlInitParser (), you also _must_ cleanup their garbage on your own! * @param piano handle * @return nothing */ void PianoInit (PianoHandle_t *ph) { memset (ph, 0, sizeof (*ph)); ph->curlHandle = curl_easy_init (); /* FIXME: 64-bit may make this hack useless */ snprintf (ph->routeId, sizeof (ph->routeId), "%07liP", time (NULL)>>8); curl_easy_setopt (ph->curlHandle, CURLOPT_USERAGENT, PACKAGE_STRING); curl_easy_setopt (ph->curlHandle, CURLOPT_CONNECTTIMEOUT, 60); } /* 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 * @public yes * @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 (PianoHandle_t *ph) { PianoStation_t *curStation, *lastStation; curStation = ph->stations; while (curStation != NULL) { lastStation = curStation; curStation = curStation->next; PianoDestroyStation (lastStation); PianoFree (lastStation, sizeof (*lastStation)); } ph->stations = NULL; } /* FIXME: copy & waste */ /* free _all_ elements of playlist * @param piano handle * @return nothing */ void PianoDestroyPlaylist (PianoHandle_t *ph) { PianoSong_t *curSong, *lastSong; curSong = ph->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); lastSong = curSong; curSong = curSong->next; PianoFree (lastSong, sizeof (*lastSong)); } ph->playlist = NULL; } /* frees the whole piano handle structure; this will _not_ cleanup curl's * internal garbage, you have to call curl_global_cleanup () and * xmlCleanupParser () for libxml2 * @param piano handle * @return nothing */ void PianoDestroy (PianoHandle_t *ph) { curl_easy_cleanup (ph->curlHandle); PianoFree (ph->user.webAuthToken, 0); PianoFree (ph->user.authToken, 0); PianoFree (ph->user.listenerId, 0); PianoDestroyStations (ph); PianoDestroyPlaylist (ph); memset (ph, 0, sizeof (*ph)); } /* authenticates user * @param piano handle * @param username (utf-8 encoded) * @param password (plaintext, utf-8 encoded) */ PianoReturn_t PianoConnect (PianoHandle_t *ph, char *user, char *password) { char url[PIANO_URL_BUFFER_SIZE]; char *requestStr = PianoEncryptString ("" "misc.sync" ""); char *retStr, requestStrPlain[10000]; PianoReturn_t ret; /* sync (is the return value used by pandora? for now: ignore result) */ snprintf (url, sizeof (url), PIANO_RPC_URL "rid=%s&method=sync", ph->routeId); PianoHttpPost (ph->curlHandle, url, requestStr, &retStr); PianoFree (requestStr, 0); PianoFree (retStr, 0); /* authenticate */ snprintf (requestStrPlain, sizeof (requestStrPlain), "" "listener.authenticateListener" "%li" "%s" "%s" "", time (NULL), user, password); requestStr = PianoEncryptString (requestStrPlain); snprintf (url, sizeof (url), PIANO_SECURE_RPC_URL "rid=%s" "&method=authenticateListener", ph->routeId); PianoHttpPost (ph->curlHandle, url, requestStr, &retStr); ret = PianoXmlParseUserinfo (ph, retStr); PianoFree (requestStr, 0); 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[10000], url[PIANO_URL_BUFFER_SIZE]; char *requestStr, *retStr; PianoReturn_t ret; snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" "station.getStations" "%li" "%s" "", time (NULL), ph->user.authToken); requestStr = PianoEncryptString (xmlSendBuf); snprintf (url, sizeof (url), PIANO_RPC_URL "rid=%s&lid=%s&method=getStations", ph->routeId, ph->user.listenerId); PianoHttpPost (ph->curlHandle, url, requestStr, &retStr); ret = PianoXmlParseStations (ph, retStr); PianoFree (retStr, 0); PianoFree (requestStr, 0); return ret; } /* get next songs for station (usually four tracks) * @param piano handle * @param station id */ PianoReturn_t PianoGetPlaylist (PianoHandle_t *ph, char *stationId) { char xmlSendBuf[10000], url[PIANO_URL_BUFFER_SIZE]; char *requestStr, *retStr; PianoReturn_t ret; /* FIXME: remove static numbers */ snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" "playlist.getFragment" "%li" "%s" "%s" "15941546" "181840822" "" "" "aacplus" "", time (NULL), ph->user.authToken, stationId); requestStr = PianoEncryptString (xmlSendBuf); snprintf (url, sizeof (url), PIANO_RPC_URL "rid=%s&lid=%s&method=getFragment&arg1=%s&arg2=15941546" "&arg3=181840822&arg4=&arg5=&arg6=aacplus", ph->routeId, ph->user.listenerId, stationId); PianoHttpPost (ph->curlHandle, url, requestStr, &retStr); ret = PianoXmlParsePlaylist (ph, retStr); PianoFree (retStr, 0); PianoFree (requestStr, 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 track will be added to this stations loved tracks list * @param rate this track * @param your rating */ PianoReturn_t PianoRateTrack (PianoHandle_t *ph, PianoStation_t *station, PianoSong_t *song, PianoSongRating_t rating) { PianoReturn_t ret; ret = PianoAddFeedback (ph, station->id, 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, PianoStation_t *stationFrom, PianoStation_t *stationTo, 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 */ PianoReturn_t PianoAddFeedback (PianoHandle_t *ph, char *stationId, char *songMusicId, char *songMatchingSeed, char *songUserSeed, char *songFocusTraitId, PianoSongRating_t rating) { char xmlSendBuf[10000], url[PIANO_URL_BUFFER_SIZE]; char *requestStr, *retStr; PianoReturn_t ret = PIANO_RET_ERR; snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" "station.addFeedback" "%li" "%s" "%s" "%s" "%s" "%s" "%s" "" "%i" "0" "", time (NULL), ph->user.authToken, stationId, songMusicId, (songMatchingSeed == NULL) ? "" : songMatchingSeed, (songUserSeed == NULL) ? "" : songUserSeed, (songFocusTraitId == NULL) ? "" : songFocusTraitId, (rating == PIANO_RATE_LOVE) ? 1 : 0); requestStr = PianoEncryptString (xmlSendBuf); snprintf (url, sizeof (url), PIANO_RPC_URL "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"); PianoHttpPost (ph->curlHandle, url, requestStr, &retStr); ret = PianoXmlParseSimple (retStr); PianoFree (requestStr, 0); 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, char *newName) { char xmlSendBuf[10000], url[PIANO_URL_BUFFER_SIZE]; char *requestStr, *retStr, *urlencodedNewName, *xmlencodedNewName; PianoReturn_t ret = PIANO_RET_ERR; xmlencodedNewName = PianoXmlEncodeString (newName); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" "station.setStationName" "%li" "%s" "%s" "%s" "", time (NULL), ph->user.authToken, station->id, xmlencodedNewName); requestStr = PianoEncryptString (xmlSendBuf); urlencodedNewName = curl_easy_escape (ph->curlHandle, newName, 0); snprintf (url, sizeof (url), PIANO_RPC_URL "rid=%s&lid=%s" "&method=setStationName&arg1=%s&arg2=%s", ph->routeId, ph->user.listenerId, station->id, urlencodedNewName); PianoHttpPost (ph->curlHandle, url, requestStr, &retStr); ret = PianoXmlParseSimple (retStr); if (ret == PIANO_RET_OK) { PianoFree (station->name, 0); station->name = strdup (newName); } curl_free (urlencodedNewName); PianoFree (xmlencodedNewName, 0); PianoFree (requestStr, 0); PianoFree (retStr, 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[10000], url[PIANO_URL_BUFFER_SIZE]; char *requestStr, *retStr; PianoReturn_t ret = PIANO_RET_ERR; snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" "station.removeStation" "%li" "%s" "%s" "", time (NULL), ph->user.authToken, station->id); requestStr = PianoEncryptString (xmlSendBuf); snprintf (url, sizeof (url), PIANO_RPC_URL "rid=%s&lid=%s" "&method=removeStation&arg1=%s", ph->routeId, ph->user.listenerId, station->id); PianoHttpPost (ph->curlHandle, url, requestStr, &retStr); ret = PianoXmlParseSimple (retStr); if (ret == PIANO_RET_OK) { /* delete station from local station list */ PianoStation_t *curStation = ph->stations, *lastStation = NULL; while (curStation != NULL) { if (curStation == station) { printf ("deleting station\n"); if (lastStation != NULL) { lastStation->next = curStation->next; } else { /* first station in list */ ph->stations = curStation->next; } PianoDestroyStation (curStation); PianoFree (curStation, sizeof (*curStation)); } lastStation = curStation; curStation = curStation->next; } } PianoFree (requestStr, 0); PianoFree (retStr, 0); 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, char *searchStr, PianoSearchResult_t *searchResult) { char xmlSendBuf[10000], url[PIANO_URL_BUFFER_SIZE]; char *requestStr, *retStr, *xmlencodedSearchStr, *urlencodedSearchStr; PianoReturn_t ret; xmlencodedSearchStr = PianoXmlEncodeString (searchStr); snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" "music.search" "%li" "%s" "%s" "", time (NULL), ph->user.authToken, xmlencodedSearchStr); requestStr = PianoEncryptString (xmlSendBuf); urlencodedSearchStr = curl_easy_escape (ph->curlHandle, searchStr, 0); snprintf (url, sizeof (url), PIANO_RPC_URL "rid=%s&lid=%s&" "method=search&arg1=%s", ph->routeId, ph->user.listenerId, urlencodedSearchStr); PianoHttpPost (ph->curlHandle, url, requestStr, &retStr); ret = PianoXmlParseSearch (retStr, searchResult); curl_free (urlencodedSearchStr); PianoFree (xmlencodedSearchStr, 0); PianoFree (retStr, 0); PianoFree (requestStr, 0); return ret; } /* create new station on server * @public yes * @param piano handle * @param music id from artist or track, you may obtain one by calling * PianoSearchMusic */ PianoReturn_t PianoCreateStation (PianoHandle_t *ph, char *musicId) { char xmlSendBuf[10000], url[PIANO_URL_BUFFER_SIZE]; char *requestStr, *retStr; PianoReturn_t ret; snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" "station.createStation" "%li" "%s" "mi%s" "", time (NULL), ph->user.authToken, musicId); requestStr = PianoEncryptString (xmlSendBuf); snprintf (url, sizeof (url), PIANO_RPC_URL "rid=%s&lid=%s" "&method=createStation&arg1=mi%s", ph->routeId, ph->user.listenerId, musicId); PianoHttpPost (ph->curlHandle, url, requestStr, &retStr); ret = PianoXmlParseCreateStation (ph, retStr); PianoFree (requestStr, 0); 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, char *musicId) { char xmlSendBuf[10000], url[PIANO_URL_BUFFER_SIZE]; char *requestStr, *retStr; PianoReturn_t ret; snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" "station.addSeed" "%li" "%s" "%s" "%s" "", time (NULL), ph->user.authToken, station->id, musicId); requestStr = PianoEncryptString (xmlSendBuf); snprintf (url, sizeof (url), PIANO_RPC_URL "rid=%s&lid=%s" "&method=addSeed&arg1=%s&arg2=%s", ph->routeId, ph->user.listenerId, station->id, musicId); PianoHttpPost (ph->curlHandle, url, requestStr, &retStr); ret = PianoXmlParseAddSeed (ph, retStr, station); PianoFree (requestStr, 0); 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, PianoSong_t *song) { char xmlSendBuf[10000], url[PIANO_URL_BUFFER_SIZE]; char *requestStr, *retStr; PianoReturn_t ret; snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" "listener.addTiredSong" "%li" "%s" "%s" "", time (NULL), ph->user.authToken, song->identity); requestStr = PianoEncryptString (xmlSendBuf); snprintf (url, sizeof (url), PIANO_RPC_URL "rid=%s&lid=%s&" "method=addTiredSong&arg1=%s", ph->routeId, ph->user.listenerId, song->identity); PianoHttpPost (ph->curlHandle, url, requestStr, &retStr); ret = PianoXmlParseSimple (retStr); PianoFree (retStr, 0); PianoFree (requestStr, 0); return ret; }