#include "XSPFLoader.h" #include "../xml/obj_xml.h" #include "../xml/ifc_xmlreadercallback.h" #include "api__xspf.h" #include "../winamp/wa_ipc.h" #include #include #include // tries to retrieve the media library API (if it's not already retrieved bool HasMediaLibraryAPI() { // TODO: should critical section this if (!AGAVE_API_MLDB) { waServiceFactory *mldbFactory = WASABI_API_SVC->service_getServiceByGuid(mldbApiGuid); if (mldbFactory) AGAVE_API_MLDB = (api_mldb *)mldbFactory->getInterface(); // retrieve a pointer to the API object } return !!AGAVE_API_MLDB; } static bool HasTagzAPI() { // TODO: should critical section this if (!AGAVE_API_TAGZ) { waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid(tagzGUID); if (sf) AGAVE_API_TAGZ = (api_tagz *)sf->getInterface(); // retrieve a pointer to the API object } return !!AGAVE_API_TAGZ; } const wchar_t* ATFString() { // TODO: should critical section this if (!WASABI_API_APP) { waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid(applicationApiServiceGuid); if (sf) WASABI_API_APP = (api_application *)sf->getInterface(); // retrieve a pointer to the API object } if (WASABI_API_APP) { const wchar_t *atf = WASABI_API_APP->getATFString(); if (atf && *atf) return atf; } return L"%artist% - %title%"; } // go check out XSPFLoader::Load before decyphering this class class XSPFLoaderCallback : public ifc_xmlreadercallback { public: XSPFLoaderCallback(ifc_playlistloadercallback *_playlist); ~XSPFLoaderCallback(); void xmlReaderOnStartElementCallback(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params); void xmlReaderOnEndElementCallback(const wchar_t *xmlpath, const wchar_t *xmltag); void xmlReaderOnCharacterDataCallback(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *str); protected: RECVS_DISPATCH; private: enum { ELEMENT_NONE=-1, ELEMENT_LOCATION=0, ELEMENT_IDENTIFIER, ELEMENT_TITLE, ELEMENT_CREATOR, ELEMENT_ALBUM, ELEMENT_TRACKNUM, NUM_ELEMENTS, }; wchar_t *elements[NUM_ELEMENTS]; int element; ifc_playlistloadercallback *playlist; }; XSPFLoaderCallback::XSPFLoaderCallback(ifc_playlistloadercallback *_playlist) { element = ELEMENT_NONE; for (int i=0;i 3) { if (*value == L'%') { *query++ = L'%'; *query++ = L'%'; query_len-=2;} else if (*value == L'\"') { *query++ = L'%'; *query++ = L'2'; *query++ = L'2'; query_len-=3;} else if (*value == L'\'') { *query++ = L'%'; *query++ = L'2'; *query++ = L'7'; query_len-=3;} else if (*value == L'[') { *query++ = L'%'; *query++ = L'5'; *query++ = L'B'; query_len-=3;} else if (*value == L']') { *query++ = L'%'; *query++ = L'5'; *query++ = L'D'; query_len-=3;} else if (*value == L'(') { *query++ = L'%'; *query++ = L'2'; *query++ = L'8'; query_len-=3;} else if (*value == L')') { *query++ = L'%'; *query++ = L'2'; *query++ = L'9'; query_len-=3;} else if (*value == L'#') { *query++ = L'%'; *query++ = L'2'; *query++ = L'3'; query_len-=3;} else { *query++ = *value; query_len--; } value++; } *query = 0; } class ItemRecordTagProvider : public ifc_tagprovider { public: ItemRecordTagProvider(itemRecordW *item) : t(item) {} wchar_t *GetTag(const wchar_t *name, ifc_tagparams *parameters); void FreeTag(wchar_t *Tag); protected: RECVS_DISPATCH; private: itemRecordW *t; }; #define CBCLASS ItemRecordTagProvider START_DISPATCH; CB(IFC_TAGPROVIDER_GET_TAG, GetTag); VCB(IFC_TAGPROVIDER_FREE_TAG, FreeTag); END_DISPATCH; #undef CBCLASS wchar_t *ItemRecordTagProvider::GetTag(const wchar_t *tag, ifc_tagparams *parameters) { bool copy=false; wchar_t buf[128]={0}; wchar_t *value = NULL; if (!_wcsicmp(tag, L"artist")) value = t->artist; else if (!_wcsicmp(tag, L"album")) value = t->album; else if (!_wcsicmp(tag, L"filename")) value = t->filename; else if (!_wcsicmp(tag, L"title")) value = t->title; else if (!_wcsicmp(tag, L"year")) { if (t->year > 0) { StringCchPrintfW(buf, 128, L"%04d", t->year); value = buf; } } else if (!_wcsicmp(tag, L"genre")) value = t->genre; else if (!_wcsicmp(tag, L"comment")) value = t->comment; else if (!_wcsicmp(tag, L"tracknumber") || !_wcsicmp(tag, L"track")) { if (t->track > 0) { if (t->tracks > 0) StringCchPrintfW(buf, 128, L"%02d/%02d", t->track, t->tracks); else StringCchPrintfW(buf, 128, L"%02d", t->track); value = buf; } } else if (!_wcsicmp(tag, L"disc")) { if (t->disc > 0) { if (t->discs > 0) StringCchPrintfW(buf, 128, L"%d/%d", t->disc, t->discs); else StringCchPrintfW(buf, 128, L"%d", t->disc); value = buf; } } else if (!_wcsicmp(tag, L"rating")) { if (t->rating > 0) { StringCchPrintfW(buf, 128, L"%d", t->rating); value = buf; } } else if (!_wcsicmp(tag, L"playcount")) { if (t->playcount > 0) { StringCchPrintfW(buf, 128, L"%d", t->playcount); value = buf; } } else if (!_wcsicmp(tag, L"bitrate")) { if (t->bitrate > 0) { StringCchPrintfW(buf, 128, L"%d", t->bitrate); value = buf; } } else if (!_wcsicmp(tag, L"bpm")) { if (t->bpm > 0) { StringCchPrintfW(buf, 128, L"%d", t->bpm); value = buf; } } else if (!_wcsicmp(tag, L"albumartist")) value = t->albumartist; else if (!_wcsicmp(tag, L"publisher")) value = t->publisher; else if (!_wcsicmp(tag, L"composer")) value = t->composer; else if (!_wcsicmp(tag, L"replaygain_album_gain")) value = t->replaygain_album_gain; else if (!_wcsicmp(tag, L"replaygain_track_gain")) value = t->replaygain_track_gain; else if (!_wcsicmp(tag, L"GracenoteFileID")) { copy=true; value = getRecordExtendedItem(t, L"GracenoteFileID"); } else if (!_wcsicmp(tag, L"GracenoteExtData")) { copy=true; value = getRecordExtendedItem(t, L"GracenoteExtData"); } else return 0; if (!value) return 0; else { if (copy || value == buf) return AGAVE_API_MLDB->DuplicateString(value); else { AGAVE_API_MLDB->RetainString(value); return value; } } } void ItemRecordTagProvider::FreeTag(wchar_t *tag) { AGAVE_API_MLDB->ReleaseString(tag); } // this gets called for every closing tag void XSPFLoaderCallback::xmlReaderOnEndElementCallback(const wchar_t *xmlpath, const wchar_t *xmltag) { if (!wcscmp(xmlpath, L"playlist\ftrackList\ftrack")) { // end of track info if (elements[ELEMENT_LOCATION] // if we have a location && (PathIsURL(elements[ELEMENT_LOCATION]) // and it's a URL || GetFileAttributes(elements[ELEMENT_LOCATION]) != INVALID_FILE_ATTRIBUTES)) // or a file that exists { // location field seemed OK so go ahead and add // TODO try using the length if available... playlist->OnFile(elements[ELEMENT_LOCATION], 0, -1, 0); } else if (HasMediaLibraryAPI()) // we don't have a good location so let's hit the media library { // let's build a query out of what we have bool needs_and=false; wchar_t query[2048]={0}; wchar_t *query_end=query; size_t query_size=2048; struct { const wchar_t *field_name; int field_value; } fields[] = { {L"artist", ELEMENT_CREATOR}, {L"title", ELEMENT_TITLE}, }; for(size_t i=0;i!=sizeof(fields)/sizeof(fields[0]);i++) { if (elements[fields[i].field_value]) { if (needs_and) StringCchCopyEx(query_end, query_size, L" AND ", &query_end, &query_size, 0); StringCchPrintfEx(query_end, query_size, &query_end, &query_size, 0, L"(%s == \"", fields[i].field_name); QueryStringAdd(elements[fields[i].field_value], query_end, query_size); StringCchCopyEx(query_end, query_size, L"\")", &query_end, &query_size, 0); needs_and=true; } } if (query[0]) { itemRecordListW *results = AGAVE_API_MLDB->QueryLimit(query, 1); if (results && results->Size >= 1 && results->Items[0].filename) { if (HasTagzAPI()) { wchar_t title[2048]={0}; ItemRecordTagProvider provider(&results->Items[0]); AGAVE_API_TAGZ->format(ATFString(), title, 2048, &provider, 0); int length = -1; if (results->Items[0].length > 0) length=results->Items[0].length*1000; playlist->OnFile(results->Items[0].filename, title, length, 0); } else { int length = -1; if (results->Items[0].length > 0) length=results->Items[0].length*1000; playlist->OnFile(results->Items[0].filename, 0, length, 0); } } AGAVE_API_MLDB->FreeRecordList(results); } } for (int i=0;ixmlreader_feed(data, bytesRead) != API_XML_SUCCESS) { CloseHandle(file); return IFC_PLAYLISTLOADER_FAILED; } } else break; } CloseHandle(file); if (parser->xmlreader_feed(0, 0) != API_XML_SUCCESS) return IFC_PLAYLISTLOADER_FAILED; return IFC_PLAYLISTLOADER_SUCCESS; } // this get called by Winamp // you a method in the passed playlist loader callback object // for each item in the playlist int XSPFLoader::Load(const wchar_t *filename, ifc_playlistloadercallback *playlist) { // first thing we'll need is an XML parser // we'll have to do the wasabi dance to get it // first, get the service factory for creating XML objects waServiceFactory *xmlFactory = WASABI_API_SVC->service_getServiceByGuid(obj_xmlGUID); if (xmlFactory) { obj_xml *parser = (obj_xml *)xmlFactory->getInterface(); // create an XML parser if (parser) { // ok now we can get down to biz'nes XSPFLoaderCallback xmlCallback(playlist); parser->xmlreader_registerCallback(L"*", &xmlCallback); parser->xmlreader_setCaseSensitive(); parser->xmlreader_open(); int ret = LoadFile(parser, filename); parser->xmlreader_unregisterCallback(&xmlCallback); parser->xmlreader_close(); xmlFactory->releaseInterface(parser); // destroy the XML parser via the service factory return ret; } } return IFC_PLAYLISTLOADER_FAILED; } // Define the dispatch table #define CBCLASS XSPFLoader START_DISPATCH; CB(IFC_PLAYLISTLOADER_LOAD, Load) END_DISPATCH; #undef CBCLASS