XSPFLoader.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. #include "XSPFLoader.h"
  2. #include "../xml/obj_xml.h"
  3. #include "../xml/ifc_xmlreadercallback.h"
  4. #include "api__xspf.h"
  5. #include "../winamp/wa_ipc.h"
  6. #include <api/service/waservicefactory.h>
  7. #include <shlwapi.h>
  8. #include <strsafe.h>
  9. // tries to retrieve the media library API (if it's not already retrieved
  10. bool HasMediaLibraryAPI()
  11. {
  12. // TODO: should critical section this
  13. if (!AGAVE_API_MLDB)
  14. {
  15. waServiceFactory *mldbFactory = WASABI_API_SVC->service_getServiceByGuid(mldbApiGuid);
  16. if (mldbFactory)
  17. AGAVE_API_MLDB = (api_mldb *)mldbFactory->getInterface(); // retrieve a pointer to the API object
  18. }
  19. return !!AGAVE_API_MLDB;
  20. }
  21. static bool HasTagzAPI()
  22. {
  23. // TODO: should critical section this
  24. if (!AGAVE_API_TAGZ)
  25. {
  26. waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid(tagzGUID);
  27. if (sf)
  28. AGAVE_API_TAGZ = (api_tagz *)sf->getInterface(); // retrieve a pointer to the API object
  29. }
  30. return !!AGAVE_API_TAGZ;
  31. }
  32. const wchar_t* ATFString()
  33. {
  34. // TODO: should critical section this
  35. if (!WASABI_API_APP)
  36. {
  37. waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid(applicationApiServiceGuid);
  38. if (sf)
  39. WASABI_API_APP = (api_application *)sf->getInterface(); // retrieve a pointer to the API object
  40. }
  41. if (WASABI_API_APP)
  42. {
  43. const wchar_t *atf = WASABI_API_APP->getATFString();
  44. if (atf && *atf) return atf;
  45. }
  46. return L"%artist% - %title%";
  47. }
  48. // go check out XSPFLoader::Load before decyphering this class
  49. class XSPFLoaderCallback : public ifc_xmlreadercallback
  50. {
  51. public:
  52. XSPFLoaderCallback(ifc_playlistloadercallback *_playlist);
  53. ~XSPFLoaderCallback();
  54. void xmlReaderOnStartElementCallback(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params);
  55. void xmlReaderOnEndElementCallback(const wchar_t *xmlpath, const wchar_t *xmltag);
  56. void xmlReaderOnCharacterDataCallback(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *str);
  57. protected:
  58. RECVS_DISPATCH;
  59. private:
  60. enum
  61. {
  62. ELEMENT_NONE=-1,
  63. ELEMENT_LOCATION=0,
  64. ELEMENT_IDENTIFIER,
  65. ELEMENT_TITLE,
  66. ELEMENT_CREATOR,
  67. ELEMENT_ALBUM,
  68. ELEMENT_TRACKNUM,
  69. NUM_ELEMENTS,
  70. };
  71. wchar_t *elements[NUM_ELEMENTS];
  72. int element;
  73. ifc_playlistloadercallback *playlist;
  74. };
  75. XSPFLoaderCallback::XSPFLoaderCallback(ifc_playlistloadercallback *_playlist)
  76. {
  77. element = ELEMENT_NONE;
  78. for (int i=0;i<NUM_ELEMENTS;i++)
  79. elements[i]=0;
  80. playlist = _playlist;
  81. }
  82. XSPFLoaderCallback::~XSPFLoaderCallback()
  83. {
  84. for (int i=0;i<NUM_ELEMENTS;i++)
  85. {
  86. free(elements[i]);
  87. elements[i]=0;
  88. }
  89. }
  90. // this gets called for every opening tag
  91. // xmlpath is the full XML path up to this point, delimited with \f (form feed)
  92. // xmltag is the actual opening tag
  93. // params is an object which lets you query for the attribtues in the tag
  94. void XSPFLoaderCallback::xmlReaderOnStartElementCallback(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params)
  95. {
  96. if (!wcscmp(xmlpath, L"playlist\ftrackList\ftrack\flocation"))
  97. element=ELEMENT_LOCATION;
  98. else if (!wcscmp(xmlpath, L"playlist\ftrackList\ftrack\fidentifier"))
  99. element=ELEMENT_IDENTIFIER;
  100. else if (!wcscmp(xmlpath, L"playlist\ftrackList\ftrack\ftitle"))
  101. element=ELEMENT_TITLE;
  102. else if (!wcscmp(xmlpath, L"playlist\ftrackList\ftrack\fcreator"))
  103. element=ELEMENT_CREATOR;
  104. else if (!wcscmp(xmlpath, L"playlist\ftrackList\ftrack\falbum"))
  105. element=ELEMENT_ALBUM;
  106. else if (!wcscmp(xmlpath, L"playlist\ftrackList\ftrack\ftracknum"))
  107. element=ELEMENT_TRACKNUM;
  108. else
  109. element=ELEMENT_NONE;
  110. }
  111. static void QueryStringAdd(const wchar_t *value, wchar_t *&query, size_t &query_len)
  112. {
  113. if (!value || !*value) return;
  114. while (value && *value && query_len > 3) {
  115. if (*value == L'%') { *query++ = L'%'; *query++ = L'%'; query_len-=2;}
  116. else if (*value == L'\"') { *query++ = L'%'; *query++ = L'2'; *query++ = L'2'; query_len-=3;}
  117. else if (*value == L'\'') { *query++ = L'%'; *query++ = L'2'; *query++ = L'7'; query_len-=3;}
  118. else if (*value == L'[') { *query++ = L'%'; *query++ = L'5'; *query++ = L'B'; query_len-=3;}
  119. else if (*value == L']') { *query++ = L'%'; *query++ = L'5'; *query++ = L'D'; query_len-=3;}
  120. else if (*value == L'(') { *query++ = L'%'; *query++ = L'2'; *query++ = L'8'; query_len-=3;}
  121. else if (*value == L')') { *query++ = L'%'; *query++ = L'2'; *query++ = L'9'; query_len-=3;}
  122. else if (*value == L'#') { *query++ = L'%'; *query++ = L'2'; *query++ = L'3'; query_len-=3;}
  123. else { *query++ = *value; query_len--; }
  124. value++;
  125. }
  126. *query = 0;
  127. }
  128. class ItemRecordTagProvider : public ifc_tagprovider
  129. {
  130. public:
  131. ItemRecordTagProvider(itemRecordW *item) : t(item) {}
  132. wchar_t *GetTag(const wchar_t *name, ifc_tagparams *parameters);
  133. void FreeTag(wchar_t *Tag);
  134. protected:
  135. RECVS_DISPATCH;
  136. private:
  137. itemRecordW *t;
  138. };
  139. #define CBCLASS ItemRecordTagProvider
  140. START_DISPATCH;
  141. CB(IFC_TAGPROVIDER_GET_TAG, GetTag);
  142. VCB(IFC_TAGPROVIDER_FREE_TAG, FreeTag);
  143. END_DISPATCH;
  144. #undef CBCLASS
  145. wchar_t *ItemRecordTagProvider::GetTag(const wchar_t *tag, ifc_tagparams *parameters)
  146. {
  147. bool copy=false;
  148. wchar_t buf[128]={0};
  149. wchar_t *value = NULL;
  150. if (!_wcsicmp(tag, L"artist")) value = t->artist;
  151. else if (!_wcsicmp(tag, L"album")) value = t->album;
  152. else if (!_wcsicmp(tag, L"filename")) value = t->filename;
  153. else if (!_wcsicmp(tag, L"title")) value = t->title;
  154. else if (!_wcsicmp(tag, L"year"))
  155. {
  156. if (t->year > 0)
  157. {
  158. StringCchPrintfW(buf, 128, L"%04d", t->year);
  159. value = buf;
  160. }
  161. }
  162. else if (!_wcsicmp(tag, L"genre")) value = t->genre;
  163. else if (!_wcsicmp(tag, L"comment")) value = t->comment;
  164. else if (!_wcsicmp(tag, L"tracknumber") || !_wcsicmp(tag, L"track"))
  165. {
  166. if (t->track > 0)
  167. {
  168. if (t->tracks > 0)
  169. StringCchPrintfW(buf, 128, L"%02d/%02d", t->track, t->tracks);
  170. else
  171. StringCchPrintfW(buf, 128, L"%02d", t->track);
  172. value = buf;
  173. }
  174. }
  175. else if (!_wcsicmp(tag, L"disc"))
  176. {
  177. if (t->disc > 0)
  178. {
  179. if (t->discs > 0)
  180. StringCchPrintfW(buf, 128, L"%d/%d", t->disc, t->discs);
  181. else
  182. StringCchPrintfW(buf, 128, L"%d", t->disc);
  183. value = buf;
  184. }
  185. }
  186. else if (!_wcsicmp(tag, L"rating"))
  187. {
  188. if (t->rating > 0)
  189. {
  190. StringCchPrintfW(buf, 128, L"%d", t->rating);
  191. value = buf;
  192. }
  193. }
  194. else if (!_wcsicmp(tag, L"playcount"))
  195. {
  196. if (t->playcount > 0)
  197. {
  198. StringCchPrintfW(buf, 128, L"%d", t->playcount);
  199. value = buf;
  200. }
  201. }
  202. else if (!_wcsicmp(tag, L"bitrate"))
  203. {
  204. if (t->bitrate > 0)
  205. {
  206. StringCchPrintfW(buf, 128, L"%d", t->bitrate);
  207. value = buf;
  208. }
  209. }
  210. else if (!_wcsicmp(tag, L"bpm"))
  211. {
  212. if (t->bpm > 0)
  213. {
  214. StringCchPrintfW(buf, 128, L"%d", t->bpm);
  215. value = buf;
  216. }
  217. }
  218. else if (!_wcsicmp(tag, L"albumartist")) value = t->albumartist;
  219. else if (!_wcsicmp(tag, L"publisher")) value = t->publisher;
  220. else if (!_wcsicmp(tag, L"composer")) value = t->composer;
  221. else if (!_wcsicmp(tag, L"replaygain_album_gain")) value = t->replaygain_album_gain;
  222. else if (!_wcsicmp(tag, L"replaygain_track_gain")) value = t->replaygain_track_gain;
  223. else if (!_wcsicmp(tag, L"GracenoteFileID"))
  224. {
  225. copy=true;
  226. value = getRecordExtendedItem(t, L"GracenoteFileID");
  227. }
  228. else if (!_wcsicmp(tag, L"GracenoteExtData"))
  229. {
  230. copy=true;
  231. value = getRecordExtendedItem(t, L"GracenoteExtData");
  232. }
  233. else
  234. return 0;
  235. if (!value)
  236. return 0;
  237. else
  238. {
  239. if (copy || value == buf)
  240. return AGAVE_API_MLDB->DuplicateString(value);
  241. else
  242. {
  243. AGAVE_API_MLDB->RetainString(value);
  244. return value;
  245. }
  246. }
  247. }
  248. void ItemRecordTagProvider::FreeTag(wchar_t *tag)
  249. {
  250. AGAVE_API_MLDB->ReleaseString(tag);
  251. }
  252. // this gets called for every closing tag
  253. void XSPFLoaderCallback::xmlReaderOnEndElementCallback(const wchar_t *xmlpath, const wchar_t *xmltag)
  254. {
  255. if (!wcscmp(xmlpath, L"playlist\ftrackList\ftrack"))
  256. {
  257. // end of track info
  258. if (elements[ELEMENT_LOCATION] // if we have a location
  259. && (PathIsURL(elements[ELEMENT_LOCATION]) // and it's a URL
  260. || GetFileAttributes(elements[ELEMENT_LOCATION]) != INVALID_FILE_ATTRIBUTES)) // or a file that exists
  261. {
  262. // location field seemed OK so go ahead and add
  263. // TODO try using the length if available...
  264. playlist->OnFile(elements[ELEMENT_LOCATION], 0, -1, 0);
  265. }
  266. else if (HasMediaLibraryAPI()) // we don't have a good location so let's hit the media library
  267. {
  268. // let's build a query out of what we have
  269. bool needs_and=false;
  270. wchar_t query[2048]={0};
  271. wchar_t *query_end=query;
  272. size_t query_size=2048;
  273. struct {
  274. const wchar_t *field_name;
  275. int field_value;
  276. } fields[] =
  277. {
  278. {L"artist", ELEMENT_CREATOR},
  279. {L"title", ELEMENT_TITLE},
  280. };
  281. for(size_t i=0;i!=sizeof(fields)/sizeof(fields[0]);i++)
  282. {
  283. if (elements[fields[i].field_value])
  284. {
  285. if (needs_and)
  286. StringCchCopyEx(query_end, query_size, L" AND ", &query_end, &query_size, 0);
  287. StringCchPrintfEx(query_end, query_size, &query_end, &query_size, 0, L"(%s == \"", fields[i].field_name);
  288. QueryStringAdd(elements[fields[i].field_value], query_end, query_size);
  289. StringCchCopyEx(query_end, query_size, L"\")", &query_end, &query_size, 0);
  290. needs_and=true;
  291. }
  292. }
  293. if (query[0])
  294. {
  295. itemRecordListW *results = AGAVE_API_MLDB->QueryLimit(query, 1);
  296. if (results && results->Size >= 1 && results->Items[0].filename)
  297. {
  298. if (HasTagzAPI())
  299. {
  300. wchar_t title[2048]={0};
  301. ItemRecordTagProvider provider(&results->Items[0]);
  302. AGAVE_API_TAGZ->format(ATFString(), title, 2048, &provider, 0);
  303. int length = -1;
  304. if (results->Items[0].length > 0)
  305. length=results->Items[0].length*1000;
  306. playlist->OnFile(results->Items[0].filename, title, length, 0);
  307. }
  308. else
  309. {
  310. int length = -1;
  311. if (results->Items[0].length > 0)
  312. length=results->Items[0].length*1000;
  313. playlist->OnFile(results->Items[0].filename, 0, length, 0);
  314. }
  315. }
  316. AGAVE_API_MLDB->FreeRecordList(results);
  317. }
  318. }
  319. for (int i=0;i<NUM_ELEMENTS;i++)
  320. {
  321. free(elements[i]);
  322. elements[i]=0;
  323. }
  324. }
  325. else if (!wcscmp(xmlpath, L"playlist\ftrackList\ftrack\flocation"))
  326. element=ELEMENT_NONE;
  327. else if (!wcscmp(xmlpath, L"playlist\ftrackList\ftrack\fidentifier"))
  328. element=ELEMENT_NONE;
  329. else if (!wcscmp(xmlpath, L"playlist\ftrackList\ftrack\ftitle"))
  330. element=ELEMENT_NONE;
  331. else if (!wcscmp(xmlpath, L"playlist\ftrackList\ftrack\fcreator"))
  332. element=ELEMENT_NONE;
  333. else if (!wcscmp(xmlpath, L"playlist\ftrackList\ftrack\falbum"))
  334. element=ELEMENT_NONE;
  335. else if (!wcscmp(xmlpath, L"playlist\ftrackList\ftrack\ftracknum"))
  336. element=ELEMENT_NONE;
  337. }
  338. void XSPFLoaderCallback::xmlReaderOnCharacterDataCallback(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *str)
  339. {
  340. if (element!=ELEMENT_NONE)
  341. {
  342. // this certainly isn't the most memory efficient way to do this but it'll work for now :)
  343. if (elements[element] == 0)
  344. {
  345. elements[element] = wcsdup(str);
  346. }
  347. else
  348. {
  349. size_t elementlen = wcslen(elements[element]);
  350. size_t incominglen = wcslen(str);
  351. wchar_t *&newstr = elements[element], *oldstr = newstr;
  352. newstr = (wchar_t *)realloc(newstr, sizeof(wchar_t)*(elementlen+incominglen+1));
  353. if (newstr != NULL)
  354. {
  355. memcpy(newstr+elementlen, str, incominglen*sizeof(wchar_t));
  356. newstr[elementlen+incominglen] = 0;
  357. }
  358. }
  359. }
  360. }
  361. #define CBCLASS XSPFLoaderCallback
  362. START_DISPATCH;
  363. VCB(ONSTARTELEMENT, xmlReaderOnStartElementCallback)
  364. VCB(ONENDELEMENT, xmlReaderOnEndElementCallback)
  365. VCB(ONCHARDATA, xmlReaderOnCharacterDataCallback)
  366. END_DISPATCH;
  367. #undef CBCLASS
  368. /* ---------------------------- */
  369. static int LoadFile(obj_xml *parser, const wchar_t *filename)
  370. {
  371. HANDLE file = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
  372. if (file == INVALID_HANDLE_VALUE)
  373. return IFC_PLAYLISTLOADER_FAILED;
  374. while (true)
  375. {
  376. char data[1024] = {0};
  377. DWORD bytesRead = 0;
  378. if (ReadFile(file, data, 1024, &bytesRead, NULL) && bytesRead)
  379. {
  380. if (parser->xmlreader_feed(data, bytesRead) != API_XML_SUCCESS)
  381. {
  382. CloseHandle(file);
  383. return IFC_PLAYLISTLOADER_FAILED;
  384. }
  385. }
  386. else
  387. break;
  388. }
  389. CloseHandle(file);
  390. if (parser->xmlreader_feed(0, 0) != API_XML_SUCCESS)
  391. return IFC_PLAYLISTLOADER_FAILED;
  392. return IFC_PLAYLISTLOADER_SUCCESS;
  393. }
  394. // this get called by Winamp
  395. // you a method in the passed playlist loader callback object
  396. // for each item in the playlist
  397. int XSPFLoader::Load(const wchar_t *filename, ifc_playlistloadercallback *playlist)
  398. {
  399. // first thing we'll need is an XML parser
  400. // we'll have to do the wasabi dance to get it
  401. // first, get the service factory for creating XML objects
  402. waServiceFactory *xmlFactory = WASABI_API_SVC->service_getServiceByGuid(obj_xmlGUID);
  403. if (xmlFactory)
  404. {
  405. obj_xml *parser = (obj_xml *)xmlFactory->getInterface(); // create an XML parser
  406. if (parser)
  407. {
  408. // ok now we can get down to biz'nes
  409. XSPFLoaderCallback xmlCallback(playlist);
  410. parser->xmlreader_registerCallback(L"*", &xmlCallback);
  411. parser->xmlreader_setCaseSensitive();
  412. parser->xmlreader_open();
  413. int ret = LoadFile(parser, filename);
  414. parser->xmlreader_unregisterCallback(&xmlCallback);
  415. parser->xmlreader_close();
  416. xmlFactory->releaseInterface(parser); // destroy the XML parser via the service factory
  417. return ret;
  418. }
  419. }
  420. return IFC_PLAYLISTLOADER_FAILED;
  421. }
  422. // Define the dispatch table
  423. #define CBCLASS XSPFLoader
  424. START_DISPATCH;
  425. CB(IFC_PLAYLISTLOADER_LOAD, Load)
  426. END_DISPATCH;
  427. #undef CBCLASS