2
Copyright (c) 2008-2010
3
Lars-Dominik Braun <lars@6xq.net>
5
Permission is hereby granted, free of charge, to any person obtaining a copy
6
of this software and associated documentation files (the "Software"), to deal
7
in the Software without restriction, including without limitation the rights
8
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
copies of the Software, and to permit persons to whom the Software is
10
furnished to do so, subject to the following conditions:
12
The above copyright notice and this permission notice shall be included in
13
all copies or substantial portions of the Software.
15
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
#define _BSD_SOURCE /* required by strdup() */
33
/* needed for urlencode */
36
#include "piano_private.h"
42
#define PIANO_PROTOCOL_VERSION "29"
43
#define PIANO_RPC_HOST "www.pandora.com"
44
#define PIANO_RPC_PORT "80"
45
#define PIANO_RPC_PATH "/radio/xmlrpc/v" PIANO_PROTOCOL_VERSION "?"
46
#define PIANO_SEND_BUFFER_SIZE 10000
48
/* initialize piano handle
52
void PianoInit (PianoHandle_t *ph) {
53
memset (ph, 0, sizeof (*ph));
55
/* route-id seems to be random. we're using time anyway... */
56
snprintf (ph->routeId, sizeof (ph->routeId), "%07luP",
57
(unsigned long) time (NULL) % 10000000);
60
/* free complete search result
62
* @param search result
64
void PianoDestroySearchResult (PianoSearchResult_t *searchResult) {
65
PianoArtist_t *curArtist, *lastArtist;
66
PianoSong_t *curSong, *lastSong;
68
curArtist = searchResult->artists;
69
while (curArtist != NULL) {
70
free (curArtist->name);
71
free (curArtist->musicId);
72
lastArtist = curArtist;
73
curArtist = curArtist->next;
77
curSong = searchResult->songs;
78
while (curSong != NULL) {
79
free (curSong->title);
80
free (curSong->artist);
81
free (curSong->musicId);
83
curSong = curSong->next;
88
/* free single station
91
void PianoDestroyStation (PianoStation_t *station) {
94
memset (station, 0, sizeof (station));
97
/* free complete station list
100
void PianoDestroyStations (PianoStation_t *stations) {
101
PianoStation_t *curStation, *lastStation;
103
curStation = stations;
104
while (curStation != NULL) {
105
lastStation = curStation;
106
curStation = curStation->next;
107
PianoDestroyStation (lastStation);
112
/* FIXME: copy & waste */
113
/* free _all_ elements of playlist
114
* @param piano handle
117
void PianoDestroyPlaylist (PianoSong_t *playlist) {
118
PianoSong_t *curSong, *lastSong;
121
while (curSong != NULL) {
122
free (curSong->audioUrl);
123
free (curSong->coverArt);
124
free (curSong->artist);
125
free (curSong->musicId);
126
free (curSong->title);
127
free (curSong->userSeed);
128
free (curSong->stationId);
129
free (curSong->album);
130
free (curSong->artistMusicId);
132
curSong = curSong->next;
137
/* destroy genre linked list
139
void PianoDestroyGenres (PianoGenre_t *genres) {
140
PianoGenre_t *curGenre, *lastGenre;
143
while (curGenre != NULL) {
144
free (curGenre->name);
145
free (curGenre->musicId);
146
lastGenre = curGenre;
147
curGenre = curGenre->next;
152
/* destroy user information
154
void PianoDestroyUserInfo (PianoUserInfo_t *user) {
155
free (user->webAuthToken);
156
free (user->authToken);
157
free (user->listenerId);
160
/* frees the whole piano handle structure
161
* @param piano handle
164
void PianoDestroy (PianoHandle_t *ph) {
165
PianoDestroyUserInfo (&ph->user);
166
PianoDestroyStations (ph->stations);
167
/* destroy genre stations */
168
PianoGenreCategory_t *curGenreCat = ph->genreStations, *lastGenreCat;
169
while (curGenreCat != NULL) {
170
PianoDestroyGenres (curGenreCat->genres);
171
free (curGenreCat->name);
172
lastGenreCat = curGenreCat;
173
curGenreCat = curGenreCat->next;
176
memset (ph, 0, sizeof (*ph));
179
/* destroy request, free post data. req->responseData is *not* freed here, as
180
* it might be allocated by something else than malloc!
181
* @param piano request
183
void PianoDestroyRequest (PianoRequest_t *req) {
184
free (req->postData);
185
memset (req, 0, sizeof (*req));
188
/* convert audio format id to string that can be used in xml requests
190
* @return constant string
192
static const char *PianoAudioFormatToString (PianoAudioFormat_t format) {
194
case PIANO_AF_AACPLUS:
202
case PIANO_AF_MP3_HI:
212
/* prepare piano request (initializes request type, urlpath and postData)
213
* @param piano handle
214
* @param request structure
215
* @param request type
217
PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,
218
PianoRequestType_t type) {
219
char xmlSendBuf[PIANO_SEND_BUFFER_SIZE];
220
/* corrected timestamp */
221
time_t timestamp = time (NULL) - ph->timeOffset;
224
assert (req != NULL);
229
case PIANO_REQUEST_LOGIN: {
230
/* authenticate user */
231
PianoRequestDataLogin_t *logindata = req->data;
233
assert (logindata != NULL);
235
switch (logindata->step) {
237
snprintf (xmlSendBuf, sizeof (xmlSendBuf),
238
"<?xml version=\"1.0\"?><methodCall>"
239
"<methodName>misc.sync</methodName>"
240
"<params></params></methodCall>");
241
snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
242
"rid=%s&method=sync", ph->routeId);
246
snprintf (xmlSendBuf, sizeof (xmlSendBuf),
247
"<?xml version=\"1.0\"?><methodCall>"
248
"<methodName>listener.authenticateListener</methodName>"
249
"<params><param><value><int>%lu</int></value></param>"
250
"<param><value><string>%s</string></value></param>"
251
"<param><value><string>%s</string></value></param>"
252
"</params></methodCall>", (unsigned long) timestamp,
253
logindata->user, logindata->password);
254
snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
255
"rid=%s&method=authenticateListener", ph->routeId);
261
case PIANO_REQUEST_GET_STATIONS:
262
/* get stations, user must be authenticated */
263
assert (ph->user.listenerId != NULL);
265
snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
266
"<methodCall><methodName>station.getStations</methodName>"
267
"<params><param><value><int>%lu</int></value></param>"
268
"<param><value><string>%s</string></value></param>"
269
"</params></methodCall>", (unsigned long) timestamp,
271
snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
272
"rid=%s&lid=%s&method=getStations", ph->routeId,
273
ph->user.listenerId);
276
case PIANO_REQUEST_GET_PLAYLIST: {
277
/* get playlist for specified station */
278
PianoRequestDataGetPlaylist_t *reqData = req->data;
280
assert (reqData != NULL);
281
assert (reqData->station != NULL);
282
assert (reqData->station->id != NULL);
283
assert (reqData->format != PIANO_AF_UNKNOWN);
285
snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
286
"<methodCall><methodName>playlist.getFragment</methodName>"
287
"<params><param><value><int>%lu</int></value></param>"
289
"<param><value><string>%s</string></value></param>"
291
"<param><value><string>%s</string></value></param>"
292
/* total listening time */
293
"<param><value><string>0</string></value></param>"
294
/* time since last session */
295
"<param><value><string></string></value></param>"
297
"<param><value><string></string></value></param>"
299
"<param><value><string>%s</string></value></param>"
300
/* delta listening time */
301
"<param><value><string>0</string></value></param>"
302
/* listening timestamp */
303
"<param><value><string>0</string></value></param>"
304
"</params></methodCall>", (unsigned long) timestamp,
305
ph->user.authToken, reqData->station->id,
306
PianoAudioFormatToString (reqData->format));
307
snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
308
"rid=%s&lid=%s&method=getFragment&arg1=%s&arg2=0"
309
"&arg3=&arg4=&arg5=%s&arg6=0&arg7=0", ph->routeId,
310
ph->user.listenerId, reqData->station->id,
311
PianoAudioFormatToString (reqData->format));
315
case PIANO_REQUEST_ADD_FEEDBACK: {
316
/* low-level, don't use directly (see _RATE_SONG and _MOVE_SONG) */
317
PianoRequestDataAddFeedback_t *reqData = req->data;
319
assert (reqData != NULL);
320
assert (reqData->stationId != NULL);
321
assert (reqData->musicId != NULL);
322
assert (reqData->rating != PIANO_RATE_NONE);
324
snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
325
"<methodCall><methodName>station.addFeedback</methodName>"
326
"<params><param><value><int>%lu</int></value></param>"
328
"<param><value><string>%s</string></value></param>"
330
"<param><value><string>%s</string></value></param>"
332
"<param><value><string>%s</string></value></param>"
334
"<param><value><string>%s</string></value></param>"
336
"<param><value>%u</value></param>"
338
"<param><value><boolean>%i</boolean></value></param>"
339
/* "is-creator-quickmix" */
340
"<param><value><boolean>0</boolean></value></param>"
342
"<param><value><int>%u</int></value></param>"
343
"</params></methodCall>", (unsigned long) timestamp,
344
ph->user.authToken, reqData->stationId, reqData->musicId,
345
(reqData->userSeed == NULL) ? "" : reqData->userSeed,
346
reqData->testStrategy,
347
(reqData->rating == PIANO_RATE_LOVE) ? 1 : 0,
349
snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
350
"rid=%s&lid=%s&method=addFeedback&arg1=%s&arg2=%s"
351
"&arg3=%s&arg4=%u&arg5=%s&arg6=false&arg7=%u",
352
ph->routeId, ph->user.listenerId, reqData->stationId,
354
(reqData->userSeed == NULL) ? "" : reqData->userSeed,
355
reqData->testStrategy,
356
(reqData->rating == PIANO_RATE_LOVE) ? "true" : "false",
361
case PIANO_REQUEST_RENAME_STATION: {
362
/* rename stations */
363
PianoRequestDataRenameStation_t *reqData = req->data;
364
char *urlencodedNewName, *xmlencodedNewName;
366
assert (reqData != NULL);
367
assert (reqData->station != NULL);
368
assert (reqData->newName != NULL);
370
if ((xmlencodedNewName = PianoXmlEncodeString (reqData->newName)) == NULL) {
371
return PIANO_RET_OUT_OF_MEMORY;
373
urlencodedNewName = WaitressUrlEncode (reqData->newName);
375
snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
376
"<methodCall><methodName>station.setStationName</methodName>"
377
"<params><param><value><int>%lu</int></value></param>"
379
"<param><value><string>%s</string></value></param>"
381
"<param><value><string>%s</string></value></param>"
383
"<param><value><string>%s</string></value></param>"
384
"</params></methodCall>", (unsigned long) timestamp,
385
ph->user.authToken, reqData->station->id,
387
snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
388
"rid=%s&lid=%s&method=setStationName&arg1=%s&arg2=%s",
389
ph->routeId, ph->user.listenerId, reqData->station->id,
392
free (urlencodedNewName);
393
free (xmlencodedNewName);
397
case PIANO_REQUEST_DELETE_STATION: {
399
PianoStation_t *station = req->data;
401
assert (station != NULL);
403
snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
404
"<methodCall><methodName>station.removeStation</methodName>"
405
"<params><param><value><int>%lu</int></value></param>"
407
"<param><value><string>%s</string></value></param>"
409
"<param><value><string>%s</string></value></param>"
410
"</params></methodCall>", (unsigned long) timestamp,
411
ph->user.authToken, station->id);
412
snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
413
"rid=%s&lid=%s&method=removeStation&arg1=%s", ph->routeId,
414
ph->user.listenerId, station->id);
418
case PIANO_REQUEST_SEARCH: {
419
/* search for artist/song title */
420
PianoRequestDataSearch_t *reqData = req->data;
421
char *xmlencodedSearchStr, *urlencodedSearchStr;
423
assert (reqData != NULL);
424
assert (reqData->searchStr != NULL);
426
if ((xmlencodedSearchStr = PianoXmlEncodeString (reqData->searchStr)) == NULL) {
427
return PIANO_RET_OUT_OF_MEMORY;
429
urlencodedSearchStr = WaitressUrlEncode (reqData->searchStr);
431
snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
432
"<methodCall><methodName>music.search</methodName>"
433
"<params><param><value><int>%lu</int></value></param>"
435
"<param><value><string>%s</string></value></param>"
437
"<param><value><string>%s</string></value></param>"
438
"</params></methodCall>", (unsigned long) timestamp,
439
ph->user.authToken, xmlencodedSearchStr);
440
snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
441
"rid=%s&lid=%s&method=search&arg1=%s", ph->routeId,
442
ph->user.listenerId, urlencodedSearchStr);
444
free (urlencodedSearchStr);
445
free (xmlencodedSearchStr);
449
case PIANO_REQUEST_CREATE_STATION: {
450
/* create new station from specified musicid (type=mi, get one by
451
* performing a search) or shared station id (type=sh) */
452
PianoRequestDataCreateStation_t *reqData = req->data;
454
assert (reqData != NULL);
455
assert (reqData->id != NULL);
456
assert (reqData->type != NULL);
458
snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
459
"<methodCall><methodName>station.createStation</methodName>"
460
"<params><param><value><int>%lu</int></value></param>"
461
"<param><value><string>%s</string></value></param>"
462
"<param><value><string>%s%s</string></value></param>"
463
"</params></methodCall>", (unsigned long) timestamp,
464
ph->user.authToken, reqData->type, reqData->id);
466
snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
467
"rid=%s&lid=%s&method=createStation&arg1=%s%s", ph->routeId,
468
ph->user.listenerId, reqData->type, reqData->id);
472
case PIANO_REQUEST_ADD_SEED: {
473
/* add another seed to specified station */
474
PianoRequestDataAddSeed_t *reqData = req->data;
476
assert (reqData != NULL);
477
assert (reqData->station != NULL);
478
assert (reqData->musicId != NULL);
480
snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
481
"<methodCall><methodName>station.addSeed</methodName><params>"
482
"<param><value><int>%lu</int></value></param>"
483
"<param><value><string>%s</string></value></param>"
484
"<param><value><string>%s</string></value></param>"
485
"<param><value><string>%s</string></value></param>"
486
"</params></methodCall>", (unsigned long) timestamp,
487
ph->user.authToken, reqData->station->id, reqData->musicId);
488
snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
489
"rid=%s&lid=%s&method=addSeed&arg1=%s&arg2=%s", ph->routeId,
490
ph->user.listenerId, reqData->station->id, reqData->musicId);
494
case PIANO_REQUEST_ADD_TIRED_SONG: {
495
/* ban song for a month from all stations */
496
PianoSong_t *song = req->data;
498
assert (song != NULL);
500
snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
501
"<methodCall><methodName>listener.addTiredSong</methodName><params>"
502
"<param><value><int>%lu</int></value></param>"
503
"<param><value><string>%s</string></value></param>"
505
"<param><value><string>%s</string></value></param>"
507
"<param><value><string>%s</string></value></param>"
509
"<param><value><string>%s</string></value></param>"
510
"</params></methodCall>", (unsigned long) timestamp,
512
(song->musicId == NULL) ? "" : song->musicId,
513
(song->userSeed == NULL) ? "" : song->userSeed,
515
snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
516
"rid=%s&lid=%s&method=addTiredSong&arg1=%s&arg2=%s&arg3=%s",
517
ph->routeId, ph->user.listenerId,
518
(song->musicId == NULL) ? "" : song->musicId,
519
(song->userSeed == NULL) ? "" : song->userSeed,
524
case PIANO_REQUEST_SET_QUICKMIX: {
525
/* select stations included in quickmix (see useQuickMix flag of
527
char valueBuf[1000], urlArgBuf[1000];
528
PianoStation_t *curStation = ph->stations;
530
memset (urlArgBuf, 0, sizeof (urlArgBuf));
531
snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
532
"<methodCall><methodName>station.setQuickMix</methodName><params>"
533
"<param><value><int>%lu</int></value></param>"
534
"<param><value><string>%s</string></value></param>"
536
"<param><value><string>RANDOM</string></value></param>"
537
"<param><value><array><data>", (unsigned long) timestamp,
539
while (curStation != NULL) {
540
/* quick mix can't contain itself */
541
if (!curStation->useQuickMix || curStation->isQuickMix) {
542
curStation = curStation->next;
545
/* append to xml doc */
546
snprintf (valueBuf, sizeof (valueBuf),
547
"<value><string>%s</string></value>", curStation->id);
548
strncat (xmlSendBuf, valueBuf, sizeof (xmlSendBuf) -
549
strlen (xmlSendBuf) - 1);
550
/* append to url arg */
551
strncat (urlArgBuf, curStation->id, sizeof (urlArgBuf) -
552
strlen (urlArgBuf) - 1);
553
curStation = curStation->next;
554
/* if not last item: append "," */
555
if (curStation != NULL) {
556
strncat (urlArgBuf, "%2C", sizeof (urlArgBuf) -
557
strlen (urlArgBuf) - 1);
561
"</data></array></value></param>"
563
"<param><value><string>CUSTOM</string></value></param>"
565
"<param><value><string></string></value></param>"
566
"</params></methodCall>",
567
sizeof (xmlSendBuf) - strlen (xmlSendBuf) - 1);
569
snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
570
"rid=%s&lid=%s&method=setQuickMix&arg1=RANDOM&arg2=%s&arg3=CUSTOM&arg4=",
571
ph->routeId, ph->user.listenerId, urlArgBuf);
575
case PIANO_REQUEST_GET_GENRE_STATIONS:
576
/* receive list of pandora's genre stations */
577
xmlSendBuf[0] = '\0';
578
snprintf (req->urlPath, sizeof (req->urlPath), "/xml/genre?r=%lu",
579
(unsigned long) timestamp);
582
case PIANO_REQUEST_TRANSFORM_STATION: {
583
/* transform shared station into private */
584
PianoStation_t *station = req->data;
586
assert (station != NULL);
588
snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
589
"<methodCall><methodName>station.transformShared</methodName>"
590
"<params><param><value><int>%lu</int></value></param>"
592
"<param><value><string>%s</string></value></param>"
594
"<param><value><string>%s</string></value></param>"
595
"</params></methodCall>", (unsigned long) timestamp,
596
ph->user.authToken, station->id);
597
snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
598
"rid=%s&lid=%s&method=transformShared&arg1=%s", ph->routeId,
599
ph->user.listenerId, station->id);
603
case PIANO_REQUEST_EXPLAIN: {
604
/* explain why particular song was played */
605
PianoRequestDataExplain_t *reqData = req->data;
607
assert (reqData != NULL);
608
assert (reqData->song != NULL);
610
snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
611
"<methodCall><methodName>playlist.narrative</methodName>"
612
"<params><param><value><int>%lu</int></value></param>"
614
"<param><value><string>%s</string></value></param>"
616
"<param><value><string>%s</string></value></param>"
618
"<param><value><string>%s</string></value></param>"
619
"</params></methodCall>", (unsigned long) timestamp,
620
ph->user.authToken, reqData->song->stationId,
621
reqData->song->musicId);
622
snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
623
"rid=%s&lid=%s&method=narrative&arg1=%s&arg2=%s",
624
ph->routeId, ph->user.listenerId, reqData->song->stationId,
625
reqData->song->musicId);
629
case PIANO_REQUEST_GET_SEED_SUGGESTIONS: {
630
/* find similar artists */
631
PianoRequestDataGetSeedSuggestions_t *reqData = req->data;
633
assert (reqData != NULL);
634
assert (reqData->musicId != NULL);
635
assert (reqData->max != 0);
637
snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
638
"<methodCall><methodName>music.getSeedSuggestions</methodName>"
639
"<params><param><value><int>%lu</int></value></param>"
641
"<param><value><string>%s</string></value></param>"
643
"<param><value><string>%s</string></value></param>"
645
"<param><value><int>%u</int></value></param>"
646
"</params></methodCall>", (unsigned long) timestamp,
647
ph->user.authToken, reqData->musicId, reqData->max);
648
snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
649
"rid=%s&lid=%s&method=getSeedSuggestions&arg1=%s&arg2=%u",
650
ph->routeId, ph->user.listenerId, reqData->musicId, reqData->max);
654
case PIANO_REQUEST_BOOKMARK_SONG: {
656
PianoSong_t *song = req->data;
658
assert (song != NULL);
660
snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
661
"<methodCall><methodName>station.createBookmark</methodName>"
662
"<params><param><value><int>%lu</int></value></param>"
664
"<param><value><string>%s</string></value></param>"
666
"<param><value><string>%s</string></value></param>"
668
"<param><value><string>%s</string></value></param>"
669
"</params></methodCall>", (unsigned long) timestamp,
670
ph->user.authToken, song->stationId, song->musicId);
671
snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
672
"rid=%s&lid=%s&method=createBookmark&arg1=%s&arg2=%s",
673
ph->routeId, ph->user.listenerId, song->stationId,
678
case PIANO_REQUEST_BOOKMARK_ARTIST: {
679
/* bookmark artist */
680
PianoSong_t *song = req->data;
682
assert (song != NULL);
684
snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
685
"<methodCall><methodName>station.createArtistBookmark</methodName>"
686
"<params><param><value><int>%lu</int></value></param>"
688
"<param><value><string>%s</string></value></param>"
690
"<param><value><string>%s</string></value></param>"
691
"</params></methodCall>", (unsigned long) timestamp,
692
ph->user.authToken, song->artistMusicId);
693
snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
694
"rid=%s&lid=%s&method=createArtistBookmark&arg1=%s",
695
ph->routeId, ph->user.listenerId, song->artistMusicId);
699
/* "high-level" wrapper */
700
case PIANO_REQUEST_RATE_SONG: {
702
PianoRequestDataRateSong_t *reqData = req->data;
705
assert (reqData != NULL);
706
assert (reqData->song != NULL);
707
assert (reqData->rating != PIANO_RATE_NONE);
709
PianoRequestDataAddFeedback_t transformedReqData;
710
transformedReqData.stationId = reqData->song->stationId;
711
transformedReqData.musicId = reqData->song->musicId;
712
transformedReqData.userSeed = reqData->song->userSeed;
713
transformedReqData.rating = reqData->rating;
714
transformedReqData.testStrategy = reqData->song->testStrategy;
715
transformedReqData.songType = reqData->song->songType;
716
req->data = &transformedReqData;
718
/* create request data (url, post data) */
719
pRet = PianoRequest (ph, req, PIANO_REQUEST_ADD_FEEDBACK);
720
/* and reset request type/data */
721
req->type = PIANO_REQUEST_RATE_SONG;
728
case PIANO_REQUEST_MOVE_SONG: {
729
/* move song to a different station, needs two requests */
730
PianoRequestDataMoveSong_t *reqData = req->data;
731
PianoRequestDataAddFeedback_t transformedReqData;
734
assert (reqData != NULL);
735
assert (reqData->song != NULL);
736
assert (reqData->from != NULL);
737
assert (reqData->to != NULL);
738
assert (reqData->step < 2);
740
transformedReqData.musicId = reqData->song->musicId;
741
transformedReqData.userSeed = "";
742
transformedReqData.songType = reqData->song->songType;
743
transformedReqData.testStrategy = reqData->song->testStrategy;
744
req->data = &transformedReqData;
746
switch (reqData->step) {
748
transformedReqData.stationId = reqData->from->id;
749
transformedReqData.rating = PIANO_RATE_BAN;
753
transformedReqData.stationId = reqData->to->id;
754
transformedReqData.rating = PIANO_RATE_LOVE;
758
/* create request data (url, post data) */
759
pRet = PianoRequest (ph, req, PIANO_REQUEST_ADD_FEEDBACK);
760
/* and reset request type/data */
761
req->type = PIANO_REQUEST_MOVE_SONG;
769
if ((req->postData = PianoEncryptString (xmlSendBuf)) == NULL) {
770
return PIANO_RET_OUT_OF_MEMORY;
776
/* parse xml response and update data structures/return new data structure
777
* @param piano handle
778
* @param initialized request (expects responseData to be a NUL-terminated
781
PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {
782
PianoReturn_t ret = PIANO_RET_ERR;
785
assert (req != NULL);
788
case PIANO_REQUEST_LOGIN: {
789
/* authenticate user */
790
PianoRequestDataLogin_t *reqData = req->data;
792
assert (req->responseData != NULL);
793
assert (reqData != NULL);
795
switch (reqData->step) {
797
char *cryptedTimestamp = NULL;
799
assert (req->responseData != NULL);
801
/* abusing parseNarrative; has same xml structure */
802
ret = PianoXmlParseNarrative (req->responseData, &cryptedTimestamp);
803
if (cryptedTimestamp != NULL) {
804
unsigned long timestamp = 0;
805
time_t realTimestamp = time (NULL);
806
char *decryptedTimestamp = NULL, *decryptedPos = NULL;
809
if ((decryptedTimestamp = PianoDecryptString (cryptedTimestamp)) != NULL) {
810
decryptedPos = decryptedTimestamp;
811
/* skip four bytes garbage? at beginning */
812
while (i-- > 0 && *decryptedPos++ != '\0');
813
timestamp = strtoul (decryptedPos, NULL, 0);
814
ph->timeOffset = realTimestamp - timestamp;
816
free (decryptedTimestamp);
818
free (cryptedTimestamp);
820
ret = PIANO_RET_CONTINUE_REQUEST;
826
/* information exists when reauthenticating, destroy to
828
if (ph->user.listenerId != NULL) {
829
PianoDestroyUserInfo (&ph->user);
831
ret = PianoXmlParseUserinfo (ph, req->responseData);
837
case PIANO_REQUEST_GET_STATIONS:
839
assert (req->responseData != NULL);
841
ret = PianoXmlParseStations (ph, req->responseData);
844
case PIANO_REQUEST_GET_PLAYLIST: {
845
/* get playlist, usually four songs */
846
PianoRequestDataGetPlaylist_t *reqData = req->data;
848
assert (req->responseData != NULL);
849
assert (reqData != NULL);
851
reqData->retPlaylist = NULL;
852
ret = PianoXmlParsePlaylist (ph, req->responseData,
853
&reqData->retPlaylist);
857
case PIANO_REQUEST_RATE_SONG:
859
assert (req->responseData != NULL);
861
ret = PianoXmlParseSimple (req->responseData);
862
if (ret == PIANO_RET_OK) {
863
PianoRequestDataRateSong_t *reqData = req->data;
864
reqData->song->rating = reqData->rating;
868
case PIANO_REQUEST_ADD_FEEDBACK:
869
/* never ever use this directly, low-level call */
873
case PIANO_REQUEST_MOVE_SONG: {
874
/* move song to different station */
875
PianoRequestDataMoveSong_t *reqData = req->data;
877
assert (req->responseData != NULL);
878
assert (reqData != NULL);
879
assert (reqData->step < 2);
881
ret = PianoXmlParseSimple (req->responseData);
882
if (ret == PIANO_RET_OK && reqData->step == 0) {
883
ret = PIANO_RET_CONTINUE_REQUEST;
889
case PIANO_REQUEST_RENAME_STATION:
890
/* rename station and update PianoStation_t structure */
891
assert (req->responseData != NULL);
893
if ((ret = PianoXmlParseSimple (req->responseData)) == PIANO_RET_OK) {
894
PianoRequestDataRenameStation_t *reqData = req->data;
896
assert (reqData != NULL);
897
assert (reqData->station != NULL);
898
assert (reqData->newName != NULL);
900
free (reqData->station->name);
901
reqData->station->name = strdup (reqData->newName);
905
case PIANO_REQUEST_DELETE_STATION:
906
/* delete station from server and station list */
907
assert (req->responseData != NULL);
909
if ((ret = PianoXmlParseSimple (req->responseData)) == PIANO_RET_OK) {
910
PianoStation_t *station = req->data;
912
assert (station != NULL);
914
/* delete station from local station list */
915
PianoStation_t *curStation = ph->stations, *lastStation = NULL;
916
while (curStation != NULL) {
917
if (curStation == station) {
918
if (lastStation != NULL) {
919
lastStation->next = curStation->next;
921
/* first station in list */
922
ph->stations = curStation->next;
924
PianoDestroyStation (curStation);
928
lastStation = curStation;
929
curStation = curStation->next;
934
case PIANO_REQUEST_SEARCH: {
935
/* search artist/song */
936
PianoRequestDataSearch_t *reqData = req->data;
938
assert (req->responseData != NULL);
939
assert (reqData != NULL);
941
ret = PianoXmlParseSearch (req->responseData, &reqData->searchResult);
945
case PIANO_REQUEST_CREATE_STATION: {
946
/* create station, insert new station into station list on success */
947
assert (req->responseData != NULL);
949
ret = PianoXmlParseCreateStation (ph, req->responseData);
953
case PIANO_REQUEST_ADD_SEED: {
954
/* add seed to station, updates station structure */
955
PianoRequestDataAddSeed_t *reqData = req->data;
957
assert (req->responseData != NULL);
958
assert (reqData != NULL);
959
assert (reqData->station != NULL);
961
/* FIXME: update station data instead of replacing them */
962
ret = PianoXmlParseAddSeed (ph, req->responseData, reqData->station);
966
case PIANO_REQUEST_ADD_TIRED_SONG:
967
case PIANO_REQUEST_SET_QUICKMIX:
968
case PIANO_REQUEST_BOOKMARK_SONG:
969
case PIANO_REQUEST_BOOKMARK_ARTIST:
970
assert (req->responseData != NULL);
972
ret = PianoXmlParseSimple (req->responseData);
975
case PIANO_REQUEST_GET_GENRE_STATIONS:
976
/* get genre stations */
977
assert (req->responseData != NULL);
979
ret = PianoXmlParseGenreExplorer (ph, req->responseData);
982
case PIANO_REQUEST_TRANSFORM_STATION: {
983
/* transform shared station into private and update isCreator flag */
984
PianoStation_t *station = req->data;
986
assert (req->responseData != NULL);
987
assert (station != NULL);
989
/* though this call returns a bunch of "new" data only this one is
990
* changed and important (at the moment) */
991
if ((ret = PianoXmlParseTranformStation (req->responseData)) ==
993
station->isCreator = 1;
998
case PIANO_REQUEST_EXPLAIN: {
999
/* explain why song was selected */
1000
PianoRequestDataExplain_t *reqData = req->data;
1002
assert (req->responseData != NULL);
1003
assert (reqData != NULL);
1005
ret = PianoXmlParseNarrative (req->responseData, &reqData->retExplain);
1009
case PIANO_REQUEST_GET_SEED_SUGGESTIONS: {
1010
/* find similar artists */
1011
PianoRequestDataGetSeedSuggestions_t *reqData = req->data;
1013
assert (req->responseData != NULL);
1014
assert (reqData != NULL);
1016
ret = PianoXmlParseSeedSuggestions (req->responseData,
1017
&reqData->searchResult);
1025
/* get station from list by id
1026
* @param search here
1027
* @param search for this
1028
* @return the first station structure matching the given id
1030
PianoStation_t *PianoFindStationById (PianoStation_t *stations,
1031
const char *searchStation) {
1032
while (stations != NULL) {
1033
if (strcmp (stations->id, searchStation) == 0) {
1036
stations = stations->next;
1041
/* convert return value to human-readable string
1043
* @return error string
1045
const char *PianoErrorToStr (PianoReturn_t ret) {
1048
return "Everything is fine :)";
1055
case PIANO_RET_XML_INVALID:
1056
return "Invalid XML.";
1059
case PIANO_RET_AUTH_TOKEN_INVALID:
1060
return "Invalid auth token.";
1063
case PIANO_RET_AUTH_USER_PASSWORD_INVALID:
1064
return "Username and/or password not correct.";
1067
case PIANO_RET_NOT_AUTHORIZED:
1068
return "Not authorized.";
1071
case PIANO_RET_PROTOCOL_INCOMPATIBLE:
1072
return "Protocol incompatible. Please upgrade " PACKAGE ".";
1075
case PIANO_RET_READONLY_MODE:
1076
return "Request cannot be completed at this time, please try "
1080
case PIANO_RET_STATION_CODE_INVALID:
1081
return "Station id is invalid.";
1084
case PIANO_RET_IP_REJECTED:
1085
return "Your ip address was rejected. Please setup a control "
1086
"proxy (see manpage).";
1089
case PIANO_RET_STATION_NONEXISTENT:
1090
return "Station does not exist.";
1093
case PIANO_RET_OUT_OF_MEMORY:
1094
return "Out of memory.";
1097
case PIANO_RET_OUT_OF_SYNC:
1098
return "Out of sync. Please correct your system's time.";
1101
case PIANO_RET_PLAYLIST_END:
1102
return "Playlist end.";
1105
case PIANO_RET_QUICKMIX_NOT_PLAYABLE:
1106
return "Quickmix not playable.";
1110
return "No error message available.";