2
Copyright (c) 2008-2011
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
25
#define _BSD_SOURCE /* required by strdup() */
26
#define _DARWIN_C_SOURCE /* strdup() on OS X */
39
#include "piano_private.h"
41
static void PianoXmlStructParser (const ezxml_t,
42
void (*callback) (const char *, const ezxml_t, void *), void *);
43
static char *PianoXmlGetNodeText (const ezxml_t);
45
/* parse fault and get fault type
46
* @param xml <name> content
47
* @param xml <value> node
48
* @param return error string
51
static void PianoXmlIsFaultCb (const char *key, const ezxml_t value,
53
PianoReturn_t *ret = data;
54
char *valueStr = PianoXmlGetNodeText (value);
55
char *matchStart, *matchEnd;
57
if (strcmp ("faultString", key) == 0) {
59
/* find fault identifier in a string like this:
60
* com.savagebeast.radio.api.protocol.xmlrpc.RadioXmlRpcException:
61
* 192.168.160.78|1213101717317|AUTH_INVALID_TOKEN|
62
* Invalid auth token */
63
if ((matchStart = strchr (valueStr, '|')) != NULL) {
64
if ((matchStart = strchr (matchStart+1, '|')) != NULL) {
65
if ((matchEnd = strchr (matchStart+1, '|')) != NULL) {
66
/* changes text in xml node, but we don't care... */
69
/* translate to our error message system */
70
if (strcmp ("AUTH_INVALID_TOKEN", matchStart) == 0) {
71
*ret = PIANO_RET_AUTH_TOKEN_INVALID;
72
} else if (strcmp ("AUTH_INVALID_USERNAME_PASSWORD",
74
*ret = PIANO_RET_AUTH_USER_PASSWORD_INVALID;
75
} else if (strcmp ("LISTENER_NOT_AUTHORIZED",
77
*ret = PIANO_RET_NOT_AUTHORIZED;
78
} else if (strcmp ("INCOMPATIBLE_VERSION",
80
*ret = PIANO_RET_PROTOCOL_INCOMPATIBLE;
81
} else if (strcmp ("READONLY_MODE", matchStart) == 0) {
82
*ret = PIANO_RET_READONLY_MODE;
83
} else if (strcmp ("STATION_CODE_INVALID",
85
*ret = PIANO_RET_STATION_CODE_INVALID;
86
} else if (strcmp ("STATION_DOES_NOT_EXIST",
88
*ret = PIANO_RET_STATION_NONEXISTENT;
89
} else if (strcmp ("OUT_OF_SYNC", matchStart) == 0) {
90
*ret = PIANO_RET_OUT_OF_SYNC;
91
} else if (strcmp ("PLAYLIST_END", matchStart) == 0) {
92
*ret = PIANO_RET_PLAYLIST_END;
93
} else if (strcmp ("QUICKMIX_NOT_PLAYABLE", matchStart) == 0) {
94
*ret = PIANO_RET_QUICKMIX_NOT_PLAYABLE;
95
} else if (strcmp ("REMOVING_TOO_MANY_SEEDS", matchStart) == 0) {
96
*ret = PIANO_RET_REMOVING_TOO_MANY_SEEDS;
99
printf (PACKAGE ": Unknown error %s in %s\n",
100
matchStart, valueStr);
105
} else if (strcmp ("faultCode", key) == 0) {
106
/* some errors can only be identified by looking at their id */
107
/* detect pandora's ip restriction */
108
if (strcmp ("12", valueStr) == 0) {
109
*ret = PIANO_RET_IP_REJECTED;
114
/* check whether pandora returned an error or not
115
* @param document root of xml doc
116
* @return _RET_OK or fault code (_RET_*)
118
static PianoReturn_t PianoXmlIsFault (ezxml_t xmlDoc) {
121
if ((xmlDoc = ezxml_child (xmlDoc, "fault")) != NULL) {
122
xmlDoc = ezxml_get (xmlDoc, "value", 0, "struct", -1);
123
PianoXmlStructParser (xmlDoc, PianoXmlIsFaultCb, &ret);
129
/* parses things like this:
137
* @param xml node named "struct" (or containing a similar structure)
138
* @param who wants to use this data? callback: content of <name> as
139
* string, content of <value> as xmlNode (may contain other nodes
140
* or text), additional data used by callback(); don't forget
141
* to *copy* data taken from <name> or <value> as they will be
143
* @param extra data for callback
145
static void PianoXmlStructParser (const ezxml_t structRoot,
146
void (*callback) (const char *, const ezxml_t, void *), void *data) {
147
ezxml_t curNode, keyNode, valueNode;
150
/* get all <member> nodes */
151
for (curNode = ezxml_child (structRoot, "member"); curNode; curNode = curNode->next) {
152
/* reset variables */
154
valueNode = keyNode = NULL;
156
keyNode = ezxml_child (curNode, "name");
157
if (keyNode != NULL) {
158
key = ezxml_txt (keyNode);
161
valueNode = ezxml_child (curNode, "value");
162
/* this will ignore empty <value /> nodes, but well... */
163
if (*key != '\0' && valueNode != NULL) {
164
(*callback) ((char *) key, valueNode, data);
169
/* create xml parser from string
170
* @param xml document
171
* @param returns document pointer (needed to free memory later)
172
* @param returns document root
173
* @return _OK or error
175
static PianoReturn_t PianoXmlInitDoc (char *xmlStr, ezxml_t *xmlDoc) {
178
if ((*xmlDoc = ezxml_parse_str (xmlStr, strlen (xmlStr))) == NULL) {
179
return PIANO_RET_XML_INVALID;
182
if ((ret = PianoXmlIsFault (*xmlDoc)) != PIANO_RET_OK) {
183
ezxml_free (*xmlDoc);
190
/* get text from <value> nodes; some of them have <boolean>, <string>
191
* or <int> subnodes, just ignore them
192
* @param xml node <value>
194
static char *PianoXmlGetNodeText (const ezxml_t node) {
197
retTxt = ezxml_txt (node);
198
/* no text => empty string */
199
if (*retTxt == '\0') {
200
retTxt = ezxml_txt (node->child);
205
/* structParser callback; writes userinfo to PianoUserInfo structure
206
* @param value identifier
208
* @param pointer to userinfo structure
211
static void PianoXmlParseUserinfoCb (const char *key, const ezxml_t value,
213
PianoUserInfo_t *user = data;
214
char *valueStr = PianoXmlGetNodeText (value);
216
if (strcmp ("webAuthToken", key) == 0) {
217
user->webAuthToken = strdup (valueStr);
218
} else if (strcmp ("authToken", key) == 0) {
219
user->authToken = strdup (valueStr);
220
} else if (strcmp ("listenerId", key) == 0) {
221
user->listenerId = strdup (valueStr);
225
static void PianoXmlParseStationsCb (const char *key, const ezxml_t value,
227
PianoStation_t *station = data;
228
char *valueStr = PianoXmlGetNodeText (value);
230
if (strcmp ("stationName", key) == 0) {
231
station->name = strdup (valueStr);
232
} else if (strcmp ("stationId", key) == 0) {
233
station->id = strdup (valueStr);
234
} else if (strcmp ("isQuickMix", key) == 0) {
235
station->isQuickMix = (strcmp (valueStr, "1") == 0);
236
} else if (strcmp ("isCreator", key) == 0) {
237
station->isCreator = (strcmp (valueStr, "1") == 0);
241
static void PianoXmlParsePlaylistCb (const char *key, const ezxml_t value,
243
PianoSong_t *song = data;
244
char *valueStr = PianoXmlGetNodeText (value);
246
if (strcmp ("audioURL", key) == 0) {
247
/* last 48 chars of audioUrl are encrypted, but they put the key
248
* into the door's lock... */
249
const char urlTailN = 48;
250
const size_t valueStrN = strlen (valueStr);
251
char *urlTail = NULL,
252
*urlTailCrypted = &valueStr[valueStrN - urlTailN];
254
/* don't try to decrypt if string is too short (=> invalid memory
256
if (valueStrN > urlTailN &&
257
(urlTail = PianoDecryptString (urlTailCrypted)) != NULL) {
258
if ((song->audioUrl = calloc (valueStrN + 1,
259
sizeof (*song->audioUrl))) != NULL) {
260
memcpy (song->audioUrl, valueStr, valueStrN - urlTailN);
261
/* FIXME: the key seems to be broken... so ignore 8 x 0x08
262
* postfix; urlTailN/2 because the encrypted hex string is now
264
memcpy (&song->audioUrl[valueStrN - urlTailN], urlTail,
269
} else if (strcmp ("artRadio", key) == 0) {
270
song->coverArt = strdup (valueStr);
271
} else if (strcmp ("artistSummary", key) == 0) {
272
song->artist = strdup (valueStr);
273
} else if (strcmp ("musicId", key) == 0) {
274
song->musicId = strdup (valueStr);
275
} else if (strcmp ("userSeed", key) == 0) {
276
song->userSeed = strdup (valueStr);
277
} else if (strcmp ("songTitle", key) == 0) {
278
song->title = strdup (valueStr);
279
} else if (strcmp ("rating", key) == 0) {
280
if (strcmp (valueStr, "1") == 0) {
281
song->rating = PIANO_RATE_LOVE;
283
song->rating = PIANO_RATE_NONE;
285
} else if (strcmp ("isPositive", key) == 0) {
286
if (strcmp (valueStr, "1") == 0) {
287
song->rating = PIANO_RATE_LOVE;
289
song->rating = PIANO_RATE_BAN;
291
} else if (strcmp ("stationId", key) == 0) {
292
song->stationId = strdup (valueStr);
293
} else if (strcmp ("albumTitle", key) == 0) {
294
song->album = strdup (valueStr);
295
} else if (strcmp ("fileGain", key) == 0) {
296
song->fileGain = atof (valueStr);
297
} else if (strcmp ("audioEncoding", key) == 0) {
298
if (strcmp (valueStr, "aacplus") == 0) {
299
song->audioFormat = PIANO_AF_AACPLUS;
300
} else if (strcmp (valueStr, "mp3") == 0) {
301
song->audioFormat = PIANO_AF_MP3;
302
} else if (strcmp (valueStr, "mp3-hifi") == 0) {
303
song->audioFormat = PIANO_AF_MP3_HI;
305
} else if (strcmp ("artistMusicId", key) == 0) {
306
song->artistMusicId = strdup (valueStr);
307
} else if (strcmp ("feedbackId", key) == 0) {
308
song->feedbackId = strdup (valueStr);
309
} else if (strcmp ("songDetailURL", key) == 0) {
310
song->detailUrl = strdup (valueStr);
311
} else if (strcmp ("trackToken", key) == 0) {
312
song->trackToken = strdup (valueStr);
316
/* parses userinfos sent by pandora as login response
317
* @param piano handle
318
* @param utf-8 string
319
* @return _RET_OK or error
321
PianoReturn_t PianoXmlParseUserinfo (PianoHandle_t *ph, char *xml) {
322
ezxml_t xmlDoc, structNode;
325
if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) {
329
/* <methodResponse> <params> <param> <value> <struct> */
330
structNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "struct", -1);
331
PianoXmlStructParser (structNode, PianoXmlParseUserinfoCb, &ph->user);
338
static void PianoXmlParseQuickMixStationsCb (const char *key, const ezxml_t value,
340
char ***retIds = data;
345
if (strcmp ("quickMixStationIds", key) == 0) {
346
for (curNode = ezxml_child (ezxml_get (value, "array", 0, "data", -1), "value");
347
curNode; curNode = curNode->next) {
350
if ((ids = calloc (idsN, sizeof (*ids))) == NULL) {
355
/* FIXME: memory leak (on failure) */
356
if ((ids = realloc (ids, idsN * sizeof (*ids))) == NULL) {
361
ids[idsN-1] = strdup (PianoXmlGetNodeText (curNode));
363
/* append NULL: list ends here */
365
/* FIXME: copy&waste */
367
if ((ids = calloc (idsN, sizeof (*ids))) == NULL) {
372
if ((ids = realloc (ids, idsN * sizeof (*ids))) == NULL) {
383
/* parse stations returned by pandora
384
* @param piano handle
385
* @param xml returned by pandora
386
* @return _RET_OK or error
388
PianoReturn_t PianoXmlParseStations (PianoHandle_t *ph, char *xml) {
389
ezxml_t xmlDoc, dataNode;
391
char **quickMixIds = NULL, **curQuickMixId = NULL;
393
if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) {
397
dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "array",
400
for (dataNode = ezxml_child (dataNode, "value"); dataNode;
401
dataNode = dataNode->next) {
402
PianoStation_t *tmpStation;
404
if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) {
406
return PIANO_RET_OUT_OF_MEMORY;
409
PianoXmlStructParser (ezxml_child (dataNode, "struct"),
410
PianoXmlParseStationsCb, tmpStation);
412
/* get stations selected for quickmix */
413
if (tmpStation->isQuickMix) {
414
PianoXmlStructParser (ezxml_child (dataNode, "struct"),
415
PianoXmlParseQuickMixStationsCb, &quickMixIds);
417
/* start new linked list or append */
418
if (ph->stations == NULL) {
419
ph->stations = tmpStation;
421
PianoStation_t *curStation = ph->stations;
422
while (curStation->next != NULL) {
423
curStation = curStation->next;
425
curStation->next = tmpStation;
428
/* set quickmix flags after all stations are read */
429
if (quickMixIds != NULL) {
430
curQuickMixId = quickMixIds;
431
while (*curQuickMixId != NULL) {
432
PianoStation_t *curStation = PianoFindStationById (ph->stations,
434
if (curStation != NULL) {
435
curStation->useQuickMix = 1;
437
free (*curQuickMixId);
448
/* parse "create station" answer (it returns a new station structure)
449
* @param piano handle
450
* @param xml document
451
* @return nothing yet
453
PianoReturn_t PianoXmlParseCreateStation (PianoHandle_t *ph, char *xml) {
454
ezxml_t xmlDoc, dataNode;
455
PianoStation_t *tmpStation;
458
if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) {
462
dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "struct", -1);
464
if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) {
466
return PIANO_RET_OUT_OF_MEMORY;
468
PianoXmlStructParser (dataNode, PianoXmlParseStationsCb, tmpStation);
469
/* FIXME: copy & waste */
470
/* start new linked list or append */
471
if (ph->stations == NULL) {
472
ph->stations = tmpStation;
474
PianoStation_t *curStation = ph->stations;
475
while (curStation->next != NULL) {
476
curStation = curStation->next;
478
curStation->next = tmpStation;
486
/* parse "add seed" answer, nearly the same as ParseCreateStation
487
* @param piano handle
488
* @param xml document
489
* @param update this station
491
PianoReturn_t PianoXmlParseAddSeed (PianoHandle_t *ph, char *xml,
492
PianoStation_t *station) {
493
ezxml_t xmlDoc, dataNode;
496
if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) {
500
dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "struct", -1);
501
PianoDestroyStation (station);
502
PianoXmlStructParser (dataNode, PianoXmlParseStationsCb, station);
509
static PianoReturn_t PianoXmlParsePlaylistStruct (ezxml_t xml,
510
PianoSong_t **retSong) {
511
PianoSong_t *playlist = *retSong, *tmpSong;
513
if ((tmpSong = calloc (1, sizeof (*tmpSong))) == NULL) {
514
return PIANO_RET_OUT_OF_MEMORY;
517
PianoXmlStructParser (ezxml_child (xml, "struct"), PianoXmlParsePlaylistCb,
519
/* begin linked list or append */
520
if (playlist == NULL) {
523
PianoSong_t *curSong = playlist;
524
while (curSong->next != NULL) {
525
curSong = curSong->next;
527
curSong->next = tmpSong;
535
/* parses playlist; used when searching too
536
* @param piano handle
537
* @param xml document
538
* @param return: playlist
540
PianoReturn_t PianoXmlParsePlaylist (PianoHandle_t *ph, char *xml,
541
PianoSong_t **retPlaylist) {
542
ezxml_t xmlDoc, dataNode;
543
PianoReturn_t ret = PIANO_RET_OK;
545
if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) {
549
dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "array",
552
for (dataNode = ezxml_child (dataNode, "value"); dataNode;
553
dataNode = dataNode->next) {
554
if ((ret = PianoXmlParsePlaylistStruct (dataNode, retPlaylist)) !=
565
/* check for exception only
567
* @return _OK or error
569
PianoReturn_t PianoXmlParseSimple (char *xml) {
573
if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) {
582
/* xml struct parser callback, used in PianoXmlParseSearchCb
584
static void PianoXmlParseSearchArtistCb (const char *key, const ezxml_t value,
586
PianoArtist_t *artist = data;
587
char *valueStr = PianoXmlGetNodeText (value);
589
if (strcmp ("artistName", key) == 0) {
590
artist->name = strdup (valueStr);
591
} else if (strcmp ("musicId", key) == 0) {
592
artist->musicId = strdup (valueStr);
596
/* callback for xml struct parser used in PianoXmlParseSearch, "switch" for
597
* PianoXmlParseSearchArtistCb and PianoXmlParsePlaylistCb
599
static void PianoXmlParseSearchCb (const char *key, const ezxml_t value,
601
PianoSearchResult_t *searchResult = data;
604
if (strcmp ("artists", key) == 0) {
605
/* skip <value><array><data> */
606
for (curNode = ezxml_child (ezxml_get (value, "array", 0, "data", -1), "value");
607
curNode; curNode = curNode->next) {
608
PianoArtist_t *artist;
610
if ((artist = calloc (1, sizeof (*artist))) == NULL) {
615
memset (artist, 0, sizeof (*artist));
617
PianoXmlStructParser (ezxml_child (curNode, "struct"),
618
PianoXmlParseSearchArtistCb, artist);
620
/* add result to linked list */
621
if (searchResult->artists == NULL) {
622
searchResult->artists = artist;
624
PianoArtist_t *curArtist = searchResult->artists;
625
while (curArtist->next != NULL) {
626
curArtist = curArtist->next;
628
curArtist->next = artist;
631
} else if (strcmp ("songs", key) == 0) {
632
for (curNode = ezxml_child (ezxml_get (value, "array", 0, "data", -1), "value");
633
curNode; curNode = curNode->next) {
634
if (PianoXmlParsePlaylistStruct (curNode, &searchResult->songs) !=
642
/* parse search result; searchResult is nulled before use
643
* @param xml document
644
* @param returns search result
645
* @return nothing yet
647
PianoReturn_t PianoXmlParseSearch (char *xml,
648
PianoSearchResult_t *searchResult) {
649
ezxml_t xmlDoc, dataNode;
652
if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) {
656
dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "struct", -1);
657
/* we need a "clean" search result (with null pointers) */
658
memset (searchResult, 0, sizeof (*searchResult));
659
PianoXmlStructParser (dataNode, PianoXmlParseSearchCb, searchResult);
666
/* FIXME: copy&waste (PianoXmlParseSearch)
668
PianoReturn_t PianoXmlParseSeedSuggestions (char *xml,
669
PianoSearchResult_t *searchResult) {
670
ezxml_t xmlDoc, dataNode;
673
if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) {
677
dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", -1);
678
/* we need a "clean" search result (with null pointers) */
679
memset (searchResult, 0, sizeof (*searchResult));
680
/* reuse seach result parser; structure is nearly the same */
681
PianoXmlParseSearchCb ("artists", dataNode, searchResult);
688
/* encode reserved xml chars
689
* TODO: remove and use ezxml_ampencode
691
* @return encoded string or NULL
693
char *PianoXmlEncodeString (const char *s) {
694
static const char *replacements[] = {"&&", "''", "\""",
695
"<<", ">>", NULL};
697
char *sOut, *sOutCurr, found;
699
if ((sOut = calloc (strlen (s) * 5 + 1, sizeof (*sOut))) == NULL) {
711
strcat (sOutCurr, (*r) + 1);
712
sOutCurr += strlen ((*r) + 1);
726
PianoReturn_t PianoXmlParseGenreExplorer (PianoHandle_t *ph, char *xml) {
727
ezxml_t xmlDoc, catNode;
730
if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) {
734
/* get all <member> nodes */
735
for (catNode = ezxml_child (xmlDoc, "category"); catNode;
736
catNode = catNode->next) {
737
PianoGenreCategory_t *tmpGenreCategory;
740
if ((tmpGenreCategory = calloc (1, sizeof (*tmpGenreCategory))) == NULL) {
742
return PIANO_RET_OUT_OF_MEMORY;
745
tmpGenreCategory->name = strdup (ezxml_attr (catNode, "categoryName"));
747
/* get genre subnodes */
748
for (genreNode = ezxml_child (catNode, "genre"); genreNode;
749
genreNode = genreNode->next) {
750
PianoGenre_t *tmpGenre;
752
if ((tmpGenre = calloc (1, sizeof (*tmpGenre))) == NULL) {
754
return PIANO_RET_OUT_OF_MEMORY;
757
/* get genre attributes */
758
tmpGenre->name = strdup (ezxml_attr (genreNode, "name"));
759
tmpGenre->musicId = strdup (ezxml_attr (genreNode, "musicId"));
762
if (tmpGenreCategory->genres == NULL) {
763
tmpGenreCategory->genres = tmpGenre;
765
PianoGenre_t *curGenre =
766
tmpGenreCategory->genres;
767
while (curGenre->next != NULL) {
768
curGenre = curGenre->next;
770
curGenre->next = tmpGenre;
773
/* append category */
774
if (ph->genreStations == NULL) {
775
ph->genreStations = tmpGenreCategory;
777
PianoGenreCategory_t *curCat = ph->genreStations;
778
while (curCat->next != NULL) {
779
curCat = curCat->next;
781
curCat->next = tmpGenreCategory;
790
/* dummy function, only checks for errors
792
* @return _OK or error
794
PianoReturn_t PianoXmlParseTranformStation (char *xml) {
798
if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) {
807
/* parses "why did you play ...?" answer
809
* @param returns the answer
810
* @return _OK or error
812
PianoReturn_t PianoXmlParseNarrative (char *xml, char **retNarrative) {
813
ezxml_t xmlDoc, dataNode;
816
if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) {
820
/* <methodResponse> <params> <param> <value> $textnode */
821
dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", -1);
822
*retNarrative = strdup (ezxml_txt (dataNode));
829
/* seed bag, required because seedId is not part of artist/song struct in
830
* pandora's xml response
832
struct PianoXmlParseSeedBag {
835
PianoArtist_t *artist;
836
PianoStation_t *station;
841
static void PianoXmlParseSeedCb (const char *key, const ezxml_t value,
843
struct PianoXmlParseSeedBag *bag = data;
845
assert (bag != NULL);
847
if (strcmp ("song", key) == 0) {
848
assert (bag->song == NULL);
850
if ((bag->song = calloc (1, sizeof (*bag->song))) == NULL) {
854
PianoXmlStructParser (ezxml_child (value, "struct"),
855
PianoXmlParsePlaylistCb, bag->song);
856
} else if (strcmp ("artist", key) == 0) {
857
assert (bag->artist == NULL);
859
if ((bag->artist = calloc (1, sizeof (*bag->artist))) == NULL) {
863
PianoXmlStructParser (ezxml_child (value, "struct"),
864
PianoXmlParseSearchArtistCb, bag->artist);
865
} else if (strcmp ("nonGenomeStation", key) == 0) {
866
/* genre stations are "non genome" station seeds */
867
assert (bag->station == NULL);
869
if ((bag->station = calloc (1, sizeof (*bag->station))) == NULL) {
873
PianoXmlStructParser (ezxml_child (value, "struct"),
874
PianoXmlParseStationsCb, bag->station);
875
} else if (strcmp ("seedId", key) == 0) {
876
char *valueStr = PianoXmlGetNodeText (value);
877
bag->seedId = strdup (valueStr);
881
/* parse getStation xml struct
883
static void PianoXmlParseGetStationInfoCb (const char *key, const ezxml_t value,
885
PianoStationInfo_t *info = data;
887
if (strcmp ("seeds", key) == 0) {
888
const ezxml_t dataNode = ezxml_get (value, "array", 0, "data", -1);
889
for (ezxml_t seedNode = ezxml_child (dataNode, "value"); seedNode;
890
seedNode = seedNode->next) {
891
struct PianoXmlParseSeedBag bag;
892
memset (&bag, 0, sizeof (bag));
894
PianoXmlStructParser (ezxml_child (seedNode, "struct"),
895
PianoXmlParseSeedCb, &bag);
897
assert (bag.song != NULL || bag.artist != NULL ||
898
bag.station != NULL);
900
if (bag.seedId == NULL) {
901
/* seeds without id are useless */
905
/* FIXME: copy&waste */
906
if (bag.song != NULL) {
907
bag.song->seedId = bag.seedId;
909
if (info->songSeeds == NULL) {
910
info->songSeeds = bag.song;
912
PianoSong_t *curSong = info->songSeeds;
913
while (curSong->next != NULL) {
914
curSong = curSong->next;
916
curSong->next = bag.song;
918
} else if (bag.artist != NULL) {
919
bag.artist->seedId = bag.seedId;
921
if (info->artistSeeds == NULL) {
922
info->artistSeeds = bag.artist;
924
PianoArtist_t *curSong = info->artistSeeds;
925
while (curSong->next != NULL) {
926
curSong = curSong->next;
928
curSong->next = bag.artist;
930
} else if (bag.station != NULL) {
931
bag.station->seedId = bag.seedId;
933
if (info->stationSeeds == NULL) {
934
info->stationSeeds = bag.station;
936
PianoStation_t *curStation = info->stationSeeds;
937
while (curStation->next != NULL) {
938
curStation = curStation->next;
940
curStation->next = bag.station;
946
} else if (strcmp ("feedback", key) == 0) {
947
const ezxml_t dataNode = ezxml_get (value, "array", 0, "data", -1);
948
for (ezxml_t feedbackNode = ezxml_child (dataNode, "value"); feedbackNode;
949
feedbackNode = feedbackNode->next) {
950
if (PianoXmlParsePlaylistStruct (feedbackNode, &info->feedback) !=
958
/* parse getStation response
960
PianoReturn_t PianoXmlParseGetStationInfo (char *xml,
961
PianoStationInfo_t *stationInfo) {
962
ezxml_t xmlDoc, dataNode;
965
if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) {
969
dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "struct", -1);
970
PianoXmlStructParser (dataNode, PianoXmlParseGetStationInfoCb, stationInfo);