aboutsummaryrefslogtreecommitdiff
path: root/Src/playlist
diff options
context:
space:
mode:
Diffstat (limited to 'Src/playlist')
-rw-r--r--Src/playlist/B4SLoader.cpp253
-rw-r--r--Src/playlist/B4SLoader.h46
-rw-r--r--Src/playlist/B4SWriter.cpp40
-rw-r--r--Src/playlist/B4SWriter.h24
-rw-r--r--Src/playlist/Handler.cpp210
-rw-r--r--Src/playlist/Handler.h32
-rw-r--r--Src/playlist/JSAPI2_Creator.cpp102
-rw-r--r--Src/playlist/JSAPI2_Creator.h31
-rw-r--r--Src/playlist/JSAPI2_Playlist.cpp328
-rw-r--r--Src/playlist/JSAPI2_Playlist.h51
-rw-r--r--Src/playlist/JSAPI2_Playlists.cpp221
-rw-r--r--Src/playlist/JSAPI2_Playlists.h30
-rw-r--r--Src/playlist/M3U8Writer.cpp39
-rw-r--r--Src/playlist/M3U8Writer.h21
-rw-r--r--Src/playlist/M3ULoader.cpp466
-rw-r--r--Src/playlist/M3ULoader.h31
-rw-r--r--Src/playlist/M3UWriter.cpp42
-rw-r--r--Src/playlist/M3UWriter.h23
-rw-r--r--Src/playlist/PLSLoader.cpp159
-rw-r--r--Src/playlist/PLSLoader.h22
-rw-r--r--Src/playlist/PLSWriter.cpp36
-rw-r--r--Src/playlist/PLSWriter.h21
-rw-r--r--Src/playlist/Playlist.cpp230
-rw-r--r--Src/playlist/Playlist.h48
-rw-r--r--Src/playlist/PlaylistCounter.cpp18
-rw-r--r--Src/playlist/PlaylistCounter.h21
-rw-r--r--Src/playlist/PlaylistManager.cpp630
-rw-r--r--Src/playlist/PlaylistManager.h42
-rw-r--r--Src/playlist/PlaylistWriter.h17
-rw-r--r--Src/playlist/Playlists.cpp746
-rw-r--r--Src/playlist/Playlists.h74
-rw-r--r--Src/playlist/PlaylistsXML.cpp149
-rw-r--r--Src/playlist/PlaylistsXML.h38
-rw-r--r--Src/playlist/SPlaylist.cpp270
-rw-r--r--Src/playlist/SPlaylist.h52
-rw-r--r--Src/playlist/SPlaylistManager.cpp117
-rw-r--r--Src/playlist/SPlaylistManager.h38
-rw-r--r--Src/playlist/SPlaylists.cpp157
-rw-r--r--Src/playlist/SPlaylists.h40
-rw-r--r--Src/playlist/SPlaylistsEnumerator.cpp184
-rw-r--r--Src/playlist/SPlaylistsEnumerator.h45
-rw-r--r--Src/playlist/ScriptObjectFactory.cpp67
-rw-r--r--Src/playlist/ScriptObjectFactory.h23
-rw-r--r--Src/playlist/ScriptObjectService.cpp42
-rw-r--r--Src/playlist/ScriptObjectService.h11
-rw-r--r--Src/playlist/XMLString.cpp42
-rw-r--r--Src/playlist/XMLString.h29
-rw-r--r--Src/playlist/_ifc_playlistentry.h49
-rw-r--r--Src/playlist/api__playlist.h58
-rw-r--r--Src/playlist/api_playlistmanager.h161
-rw-r--r--Src/playlist/api_playlists.h290
-rw-r--r--Src/playlist/factory_Handler.cpp97
-rw-r--r--Src/playlist/factory_Handler.h30
-rw-r--r--Src/playlist/factory_playlistmanager.cpp65
-rw-r--r--Src/playlist/factory_playlistmanager.h28
-rw-r--r--Src/playlist/factory_playlists.cpp63
-rw-r--r--Src/playlist/factory_playlists.h25
-rw-r--r--Src/playlist/ifc_playlist.h139
-rw-r--r--Src/playlist/ifc_playlistT.h48
-rw-r--r--Src/playlist/ifc_playlistdirectorycallback.h33
-rw-r--r--Src/playlist/ifc_playlistloader.h37
-rw-r--r--Src/playlist/ifc_playlistloadercallback.h96
-rw-r--r--Src/playlist/ifc_playlistloadercallbackT.h29
-rw-r--r--Src/playlist/ifc_plentryinfo.h28
-rw-r--r--Src/playlist/main.cpp134
-rw-r--r--Src/playlist/main.h26
-rw-r--r--Src/playlist/pl_entry.cpp336
-rw-r--r--Src/playlist/pl_entry.h61
-rw-r--r--Src/playlist/playlist.mi49
-rw-r--r--Src/playlist/playlist.rc86
-rw-r--r--Src/playlist/playlist.sln30
-rw-r--r--Src/playlist/playlist.vcxproj342
-rw-r--r--Src/playlist/playlist.vcxproj.filters299
-rw-r--r--Src/playlist/playlist.xcodeproj/project.pbxproj199
-rw-r--r--Src/playlist/plstring.cpp86
-rw-r--r--Src/playlist/plstring.h7
-rw-r--r--Src/playlist/resource.h29
-rw-r--r--Src/playlist/svc_playlisthandler.h110
-rw-r--r--Src/playlist/util.cpp77
-rw-r--r--Src/playlist/version.rc239
80 files changed, 8544 insertions, 0 deletions
diff --git a/Src/playlist/B4SLoader.cpp b/Src/playlist/B4SLoader.cpp
new file mode 100644
index 00000000..735cdfe8
--- /dev/null
+++ b/Src/playlist/B4SLoader.cpp
@@ -0,0 +1,253 @@
+#include "main.h"
+#include "B4SLoader.h"
+#include "../nu/AutoChar.h"
+#include "..\Components\wac_network\wac_network_http_receiver_api.h"
+#include <strsafe.h>
+
+B4SLoader::B4SLoader() : parser(0), parserFactory(0)
+{
+ parserFactory = WASABI_API_SVC->service_getServiceByGuid(obj_xmlGUID);
+ if (parserFactory)
+ parser = (obj_xml *)parserFactory->getInterface();
+
+ if (parser)
+ {
+ parser->xmlreader_registerCallback(L"WasabiXML\fplaylist\fentry\fname", &b4sXml.title);
+ parser->xmlreader_registerCallback(L"WinampXML\fplaylist\fentry\fname", &b4sXml.title);
+
+ parser->xmlreader_registerCallback(L"WasabiXML\fplaylist\fentry\flength", &b4sXml.length);
+ parser->xmlreader_registerCallback(L"WinampXML\fplaylist\fentry\flength", &b4sXml.length);
+
+ //parser->xmlreader_registerCallback(L"WasabiXML\fplaylist", this);
+ //parser->xmlreader_registerCallback(L"WinampXML\fplaylist", this);
+ parser->xmlreader_registerCallback(L"WasabiXML\fplaylist\fentry", &b4sXml);
+ parser->xmlreader_registerCallback(L"WinampXML\fplaylist\fentry", &b4sXml);
+ parser->xmlreader_open();
+ //parser->xmlreader_setEncoding(L"UTF-8");
+ }
+}
+
+B4SXML::B4SXML()
+{
+ filename[0]=0;
+ playlist = 0;
+}
+
+B4SLoader::~B4SLoader()
+{
+ if (parser)
+ {
+ parser->xmlreader_unregisterCallback(&b4sXml);
+ parser->xmlreader_unregisterCallback(&b4sXml.length);
+ parser->xmlreader_unregisterCallback(&b4sXml.title);
+ parser->xmlreader_close();
+ }
+
+ if (parserFactory && parser)
+ parserFactory->releaseInterface(parser);
+
+ parserFactory = 0;
+ parser = 0;
+}
+
+#define HTTP_BUFFER_SIZE 1024
+static int FeedXMLHTTP(api_httpreceiver *http, obj_xml *parser, bool *noData)
+{
+ char downloadedData[HTTP_BUFFER_SIZE] = {0};
+ int xmlResult = API_XML_SUCCESS;
+ int downloadSize = http->get_bytes(downloadedData, HTTP_BUFFER_SIZE);
+ if (downloadSize)
+ {
+ xmlResult = parser->xmlreader_feed((void *)downloadedData, downloadSize);
+ *noData=false;
+ }
+ else
+ *noData = true;
+
+ return xmlResult;
+}
+
+static void RunXMLDownload(api_httpreceiver *http, obj_xml *parser)
+{
+ int ret;
+ bool noData;
+ do
+ {
+ Sleep(50);
+ ret = http->run();
+ if (FeedXMLHTTP(http, parser, &noData) != API_XML_SUCCESS)
+ return ;
+ }
+ while (ret == HTTPRECEIVER_RUN_OK);
+
+ // finish off the data
+ do
+ {
+ if (FeedXMLHTTP(http, parser, &noData) != API_XML_SUCCESS)
+ return ;
+ } while (!noData);
+
+ parser->xmlreader_feed(0, 0);
+}
+
+void B4SXML::StartTag(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params)
+{
+ if (!_wcsicmp(xmltag, L"entry"))
+ {
+ const wchar_t *track = params->getItemValue(L"playstring");
+ if (!_wcsnicmp(track, L"file://", 7))
+ track+=7;
+ else if (!_wcsnicmp(track, L"file:", 5))
+ track+=5;
+ StringCchCopyW(filename, FILENAME_SIZE, track);
+ }
+}
+
+void B4SXML::EndTag(const wchar_t *xmlpath, const wchar_t *xmltag)
+{
+ if (!_wcsicmp(xmltag, L"entry") && filename[0])
+ {
+ int lengthSeconds=-1;
+ const wchar_t *trackLength = length.GetString();
+ if (trackLength && *trackLength)
+ lengthSeconds = _wtoi(trackLength) / 1000;
+
+ const wchar_t *trackTitle = title.GetString();
+ // TODO: deal with relative pathnames
+ if (trackTitle && *trackTitle)
+ playlist->OnFile(filename, trackTitle, lengthSeconds, 0); // TODO: extended info
+ else
+ playlist->OnFile(filename, 0, lengthSeconds, 0);// TODO: extended info
+
+ filename[0]=0;
+ length.Reset();
+ title.Reset();
+ }
+}
+#if 0 // TOOD: reimplement
+void B4SLoader::LoadURL(const char *url)
+{
+ if (!parser)
+ return ; // no sense in continuing if there's no parser available
+
+ api_httpreceiver *http = 0;
+ waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid(httpreceiverGUID);
+ if (sf)
+ http = (api_httpreceiver *)sf->getInterface();
+
+ if (!http)
+ return ;
+ http->AllowCompression();
+ http->open(API_DNS_AUTODNS, HTTP_BUFFER_SIZE, GetProxy());
+ SetUserAgent(http);
+ http->connect(url);
+ int ret;
+ bool first = true;
+
+ do
+ {
+ Sleep(50);
+ ret = http->run();
+ if (ret == -1) // connection failed
+ break;
+
+ // ---- check our reply code ----
+ int replycode = http->GetReplyCode();
+ switch (replycode)
+ {
+ case 0:
+ case 100:
+ break;
+ case 200:
+ {
+ RunXMLDownload(http, parser);
+ sf->releaseInterface(http);
+ return ;
+ }
+ break;
+ default:
+ sf->releaseInterface(http);
+ return ;
+ }
+ }
+ while (ret == HTTPRECEIVER_RUN_OK);
+ const char *er = http->geterrorstr();
+ sf->releaseInterface(http);
+ return ;
+}
+
+void B4SLoader::LoadFile(const char *filename)
+{
+ if (!parser)
+ return ; // no sense in continuing if there's no parser available
+
+ HANDLE file = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
+
+ if (file == INVALID_HANDLE_VALUE)
+ return ;
+
+ char data[1024] = {0};
+
+ while (true)
+ {
+ DWORD bytesRead = 0;
+ if (ReadFile(file, data, 1024, &bytesRead, NULL) && bytesRead)
+ {
+ parser->xmlreader_feed(data, bytesRead);
+ }
+ else
+ break;
+ }
+
+ CloseHandle(file);
+ parser->xmlreader_feed(0, 0);
+}
+#endif
+int B4SLoader::Load(const wchar_t *filename, ifc_playlistloadercallback *playlist)
+{
+ if (!parser)
+ return IFC_PLAYLISTLOADER_FAILED; // no sense in continuing if there's no parser available
+
+ b4sXml.playlist=playlist;
+
+ HANDLE file = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
+
+ if (file == INVALID_HANDLE_VALUE)
+ return IFC_PLAYLISTLOADER_FAILED;
+
+ while (true)
+ {
+ char data[1024] = {0};
+ DWORD bytesRead = 0;
+ if (ReadFile(file, data, 1024, &bytesRead, NULL) && bytesRead)
+ {
+ parser->xmlreader_feed(data, bytesRead);
+ }
+ else
+ break;
+ }
+
+ CloseHandle(file);
+ parser->xmlreader_feed(0, 0);
+ return IFC_PLAYLISTLOADER_SUCCESS;
+}
+
+#ifdef CBCLASS
+#undef CBCLASS
+#endif
+
+#define CBCLASS B4SXML
+START_DISPATCH;
+VCB(ONSTARTELEMENT, StartTag)
+VCB(ONENDELEMENT, EndTag)
+END_DISPATCH;
+
+#ifdef CBCLASS
+#undef CBCLASS
+#endif
+
+#define CBCLASS B4SLoader
+START_DISPATCH;
+CB(IFC_PLAYLISTLOADER_LOAD, Load)
+
+END_DISPATCH; \ No newline at end of file
diff --git a/Src/playlist/B4SLoader.h b/Src/playlist/B4SLoader.h
new file mode 100644
index 00000000..0c8e4bf5
--- /dev/null
+++ b/Src/playlist/B4SLoader.h
@@ -0,0 +1,46 @@
+#ifndef NULLSOFT_WINAMP_B4S_H
+#define NULLSOFT_WINAMP_B4S_H
+
+#include "../xml/obj_xml.h"
+#include "../xml/ifc_xmlreadercallback.h"
+#include "api__playlist.h"
+#include <api/service/waServiceFactory.h>
+#include "XMLString.h"
+#include "main.h"
+#include "ifc_playlistloader.h"
+
+class B4SXML : public ifc_xmlreadercallback
+{
+public:
+ B4SXML();
+ ifc_playlistloadercallback *playlist;
+ //private:
+
+ /* XML callbacks */
+ void StartTag(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params);
+ void EndTag(const wchar_t *xmlpath, const wchar_t *xmltag);
+
+ XMLString /*name, artist, */title/*, album*/, length; // wa2 only supports title and length in the playlist anyway
+ RECVS_DISPATCH;
+ wchar_t filename[FILENAME_SIZE];
+};
+
+class B4SLoader : public ifc_playlistloader
+{
+public:
+ B4SLoader();
+ ~B4SLoader();
+
+ void LoadFile(const char *filename);
+ void LoadURL(const char *url);
+
+ int Load(const wchar_t *filename, ifc_playlistloadercallback *playlist);
+
+ RECVS_DISPATCH;
+
+ obj_xml *parser;
+ waServiceFactory *parserFactory;
+ B4SXML b4sXml;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/B4SWriter.cpp b/Src/playlist/B4SWriter.cpp
new file mode 100644
index 00000000..a23a0b96
--- /dev/null
+++ b/Src/playlist/B4SWriter.cpp
@@ -0,0 +1,40 @@
+#include "B4SWriter.h"
+
+/*
+TODO: escape XML shit
+*/
+
+B4SWriter::B4SWriter() : fp(0)
+{
+}
+
+int B4SWriter::Open(const wchar_t *filename)
+{
+ fp = _wfopen(filename, L"wt");
+ if (!fp)
+ return 0;
+
+ fwprintf(fp, L"<playlist>\n");
+
+ return 1;
+}
+
+void B4SWriter::Write(const wchar_t *filename)
+{
+ fwprintf(fp, L"<entry playstring=\"%s\"/>\n", filename);
+}
+
+void B4SWriter::Write(const wchar_t *filename, const wchar_t *title, int length)
+{
+ fwprintf(fp, L"<entry playstring=\"%s\">\n", filename);
+ fwprintf(fp, L"<name>%s</name>\n", title);
+ fwprintf(fp, L"<length>%d</length>\n", length);
+ fwprintf(fp, L"</entry>\n");
+}
+
+void B4SWriter::Close()
+{
+ fputs("</playlist>", fp);
+ fclose(fp);
+
+} \ No newline at end of file
diff --git a/Src/playlist/B4SWriter.h b/Src/playlist/B4SWriter.h
new file mode 100644
index 00000000..ef2d656f
--- /dev/null
+++ b/Src/playlist/B4SWriter.h
@@ -0,0 +1,24 @@
+#ifndef NULLSOFT_PLAYLIST_B4SWRITER_H
+#define NULLSOFT_PLAYLIST_B4SWRITER_H
+
+#include <stdio.h>
+#include "PlaylistWriter.h"
+
+class B4SWriter : public PlaylistWriter
+{
+public:
+ B4SWriter();
+
+ int Open( const wchar_t *filename ) override;
+ void Write( const wchar_t *filename ) override;
+ void Write( const wchar_t *filename, const wchar_t *title, int length ) override;
+ void Write( const wchar_t *p_filename, const wchar_t *p_title, const wchar_t *p_extended_infos, int p_length ) override
+ {};
+
+ void Close() override;
+
+private:
+ FILE *fp;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/Handler.cpp b/Src/playlist/Handler.cpp
new file mode 100644
index 00000000..755f68fa
--- /dev/null
+++ b/Src/playlist/Handler.cpp
@@ -0,0 +1,210 @@
+#include "Handler.h"
+#include "resource.h"
+#include "M3ULoader.h"
+#include "PLSLoader.h"
+#include "B4SLoader.h"
+#include <shlwapi.h>
+
+void GetExtension(const wchar_t *filename, wchar_t *ext, size_t extCch)
+{
+ const wchar_t *s = PathFindExtensionW(filename);
+ if (!PathIsURLW(filename)
+ || (!wcsstr(s, L"?") && !wcsstr(s, L"&") && !wcsstr(s, L"=") && *s))
+ {
+ lstrcpynW(ext, s, (int)extCch);
+ return ;
+ }
+ // s is not a terribly good extension, let's try again
+ {
+ wchar_t *copy = _wcsdup(filename);
+ s = L"";
+ again:
+ {
+ wchar_t *p = StrRChrW(copy,NULL, L'?');
+ if (p)
+ {
+ *p = 0;
+ s = PathFindExtensionW(copy);
+ if (!*s) goto again;
+ }
+ lstrcpynW(ext, s, (int)extCch);
+ }
+ free(copy);
+ }
+}
+
+const wchar_t *M3UHandler::enumExtensions(size_t n)
+{
+ switch(n)
+ {
+ case 0:
+ return L"M3U";
+ case 1:
+ return L"M3U8";
+ default:
+ return 0;
+ }
+}
+
+int M3UHandler::SupportedFilename(const wchar_t *filename)
+{
+ wchar_t ext[16] = {0};
+ GetExtension(filename, ext, 16);
+
+ if (!lstrcmpiW(ext, L".M3U"))
+ return SVC_PLAYLISTHANDLER_SUCCESS;
+ if (!lstrcmpiW(ext, L".M3U8"))
+ return SVC_PLAYLISTHANDLER_SUCCESS;
+
+ return SVC_PLAYLISTHANDLER_FAILED;
+}
+
+ifc_playlistloader *M3UHandler::CreateLoader(const wchar_t *filename)
+{
+ return new M3ULoader;
+}
+
+void M3UHandler::ReleaseLoader(ifc_playlistloader *loader)
+{
+ M3ULoader *m3u;
+
+ m3u = static_cast<M3ULoader *>(loader);
+ delete m3u;
+}
+
+const wchar_t *M3UHandler::GetName()
+{
+ static wchar_t m3upl[64];
+ // no point re-loading this all of the time since it won't change once we've been loaded
+ return (!m3upl[0]?WASABI_API_LNGSTRINGW_BUF(IDS_M3U_PLAYLIST,m3upl,64):m3upl);
+}
+/* ----------------------------------------------------- */
+
+const wchar_t *PLSHandler::enumExtensions(size_t n)
+{
+ switch(n)
+ {
+ case 0:
+ return L"PLS";
+ default:
+ return 0;
+ }
+}
+
+int PLSHandler::SupportedFilename(const wchar_t *filename)
+{
+ wchar_t ext[16] = {0};
+ GetExtension(filename, ext, 16);
+
+ if (!lstrcmpiW(ext, L".PLS"))
+ return SVC_PLAYLISTHANDLER_SUCCESS;
+
+ // TODO: open file and sniff it for file signature
+ return SVC_PLAYLISTHANDLER_FAILED;
+}
+
+ifc_playlistloader *PLSHandler::CreateLoader(const wchar_t *filename)
+{
+ return new PLSLoader;
+}
+
+void PLSHandler::ReleaseLoader(ifc_playlistloader *loader)
+{
+ PLSLoader *pls;
+
+ pls = static_cast<PLSLoader *>(loader);
+ delete pls;
+}
+
+
+const wchar_t *PLSHandler::GetName()
+{
+ static wchar_t plspl[64];
+ // no point re-loading this all of the time since it won't change once we've been loaded
+ return (!plspl[0]?WASABI_API_LNGSTRINGW_BUF(IDS_PLS_PLAYLIST,plspl,64):plspl);
+}
+
+/* --- B4S --- */
+const wchar_t *B4SHandler::enumExtensions(size_t n)
+{
+ switch(n)
+ {
+ case 0:
+ return L"B4S";
+ //case 1:
+ //return L"BPL";
+ default:
+ return 0;
+ }
+}
+
+int B4SHandler::SupportedFilename(const wchar_t *filename)
+{
+ wchar_t ext[16] = {0};
+ GetExtension(filename, ext, 16);
+
+ if (!lstrcmpiW(ext, L".B4S"))
+ return SVC_PLAYLISTHANDLER_SUCCESS;
+ if (!lstrcmpiW(ext, L".BPL"))
+ return SVC_PLAYLISTHANDLER_SUCCESS;
+
+ // TODO: open file and sniff it for file signature
+ return SVC_PLAYLISTHANDLER_FAILED;
+}
+
+ifc_playlistloader *B4SHandler::CreateLoader(const wchar_t *filename)
+{
+ return new B4SLoader;
+}
+
+void B4SHandler::ReleaseLoader(ifc_playlistloader *loader)
+{
+ B4SLoader *pls;
+
+ pls = static_cast<B4SLoader *>(loader);
+ delete pls;
+}
+
+const wchar_t *B4SHandler::GetName()
+{
+ static wchar_t wa3pl[64];
+ // no point re-loading this all of the time since it won't change once we've been loaded
+ return (!wa3pl[0]?WASABI_API_LNGSTRINGW_BUF(IDS_WINAMP3_PLAYLIST,wa3pl,64):wa3pl);
+}
+
+
+#ifdef CBCLASS
+#undef CBCLASS
+#endif
+
+#define CBCLASS M3UHandler
+START_DISPATCH;
+CB(SVC_PLAYLISTHANDLER_ENUMEXTENSIONS, enumExtensions)
+CB(SVC_PLAYLISTHANDLER_SUPPORTFILENAME, SupportedFilename)
+CB(SVC_PLAYLISTHANDLER_CREATELOADER, CreateLoader)
+VCB(SVC_PLAYLISTHANDLER_RELEASELOADER, ReleaseLoader)
+CB(SVC_PLAYLISTHANDLER_GETNAME, GetName)
+CB(SVC_PLAYLISTHANDLER_HASWRITER, HasWriter)
+END_DISPATCH;
+#undef CBCLASS
+
+#define CBCLASS PLSHandler
+START_DISPATCH;
+CB(SVC_PLAYLISTHANDLER_ENUMEXTENSIONS, enumExtensions)
+CB(SVC_PLAYLISTHANDLER_SUPPORTFILENAME, SupportedFilename)
+CB(SVC_PLAYLISTHANDLER_CREATELOADER, CreateLoader)
+VCB(SVC_PLAYLISTHANDLER_RELEASELOADER, ReleaseLoader)
+CB(SVC_PLAYLISTHANDLER_GETNAME, GetName)
+CB(SVC_PLAYLISTHANDLER_HASWRITER, HasWriter)
+END_DISPATCH;
+#undef CBCLASS
+
+#define CBCLASS B4SHandler
+START_DISPATCH;
+CB(SVC_PLAYLISTHANDLER_ENUMEXTENSIONS, enumExtensions)
+CB(SVC_PLAYLISTHANDLER_SUPPORTFILENAME, SupportedFilename)
+CB(SVC_PLAYLISTHANDLER_CREATELOADER, CreateLoader)
+VCB(SVC_PLAYLISTHANDLER_RELEASELOADER, ReleaseLoader)
+CB(SVC_PLAYLISTHANDLER_GETNAME, GetName)
+CB(SVC_PLAYLISTHANDLER_HASWRITER, HasWriter)
+END_DISPATCH; \ No newline at end of file
diff --git a/Src/playlist/Handler.h b/Src/playlist/Handler.h
new file mode 100644
index 00000000..aabd8a7d
--- /dev/null
+++ b/Src/playlist/Handler.h
@@ -0,0 +1,32 @@
+#ifndef NULLSOFT_PLAYLISTS_HANDLER_H
+#define NULLSOFT_PLAYLISTS_HANDLER_H
+
+#include "svc_playlisthandler.h"
+#include <bfc/platform/types.h>
+
+#define DECLARE_HANDLER(className, IS_WRITER) class className ## Handler : public svc_playlisthandler {\
+public:\
+ const wchar_t *enumExtensions(size_t n); \
+ int SupportedFilename(const wchar_t *filename); \
+ ifc_playlistloader *CreateLoader(const wchar_t *filename);\
+ void ReleaseLoader(ifc_playlistloader *loader);\
+ const wchar_t *GetName(); \
+ int HasWriter() { return IS_WRITER; }\
+protected: RECVS_DISPATCH;}
+
+DECLARE_HANDLER(M3U, 1);
+DECLARE_HANDLER(PLS, 1);
+DECLARE_HANDLER(B4S, 0);
+
+// {8D031378-4209-4bfe-AC94-03C57C896214}
+static const GUID m3uHandlerGUID =
+{ 0x8d031378, 0x4209, 0x4bfe, { 0xac, 0x94, 0x3, 0xc5, 0x7c, 0x89, 0x62, 0x14 } };
+
+// {4FF33CC0-F82C-4550-8E4A-DDF90036BE85}
+static const GUID plsHandlerGUID =
+{ 0x4ff33cc0, 0xf82c, 0x4550, { 0x8e, 0x4a, 0xdd, 0xf9, 0x0, 0x36, 0xbe, 0x85 } };
+
+static const GUID b4sHandlerGUID =
+{ 0x6f62cbb8, 0x7e1f, 0x43eb, { 0xb3, 0xf6, 0x1, 0xc2, 0x60, 0x10, 0x29, 0xa3 } };
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/JSAPI2_Creator.cpp b/Src/playlist/JSAPI2_Creator.cpp
new file mode 100644
index 00000000..c621f07b
--- /dev/null
+++ b/Src/playlist/JSAPI2_Creator.cpp
@@ -0,0 +1,102 @@
+#include "JSAPI2_Creator.h"
+#include "JSAPI2_Playlists.h"
+#include "api__playlist.h"
+#include "main.h"
+#include "resource.h"
+
+IDispatch *JSAPI2_Creator::CreateAPI(const wchar_t *name, const wchar_t *key, JSAPI::ifc_info *info)
+{
+ if (!wcscmp(name, L"Playlists"))
+ return new JSAPI2::PlaylistsAPI(key, info);
+ else
+ return 0;
+}
+
+int JSAPI2_Creator::PromptForAuthorization(HWND parent, const wchar_t *group, const wchar_t *action, const wchar_t *authorization_key, JSAPI2::api_security::AuthorizationData *data)
+{
+ if (group && !wcscmp(group, L"playlists"))
+ {
+ const wchar_t *title_str = AGAVE_API_JSAPI2_SECURITY->GetAssociatedName(authorization_key);
+ if (action && !wcscmp(action, L"read"))
+ {
+ return AGAVE_API_JSAPI2_SECURITY->SecurityPrompt(parent, title_str, L"This service is requesting access to the playlists in your Library.", 0);
+ }
+ else if (action && !wcscmp(action, L"write"))
+ {
+ return AGAVE_API_JSAPI2_SECURITY->SecurityPrompt(parent, title_str, L"This service is trying modify one of the playlists in your Library.", 0);
+ }
+ }
+
+ return JSAPI2::svc_apicreator::AUTHORIZATION_UNDEFINED;
+}
+
+#define CBCLASS JSAPI2_Creator
+START_DISPATCH;
+CB(JSAPI2_SVC_APICREATOR_CREATEAPI, CreateAPI);
+CB(JSAPI2_SVC_APICREATOR_PROMPTFORAUTHORIZATION, PromptForAuthorization);
+END_DISPATCH;
+#undef CBCLASS
+
+static JSAPI2_Creator jsapi2_svc;
+static const char serviceName[] = "Playlist Javascript Objects";
+
+// {CF057176-A819-4bc5-8723-6C072BB28BAA}
+static const GUID jsapi2_factory_guid =
+{ 0xcf057176, 0xa819, 0x4bc5, { 0x87, 0x23, 0x6c, 0x7, 0x2b, 0xb2, 0x8b, 0xaa } };
+
+
+FOURCC JSAPI2Factory::GetServiceType()
+{
+ return jsapi2_svc.getServiceType();
+}
+
+const char *JSAPI2Factory::GetServiceName()
+{
+ return serviceName;
+}
+
+GUID JSAPI2Factory::GetGUID()
+{
+ return jsapi2_factory_guid;
+}
+
+void *JSAPI2Factory::GetInterface(int global_lock)
+{
+ // if (global_lock)
+ // WASABI_API_SVC->service_lock(this, (void *)ifc);
+ return &jsapi2_svc;
+}
+
+int JSAPI2Factory::SupportNonLockingInterface()
+{
+ return 1;
+}
+
+int JSAPI2Factory::ReleaseInterface(void *ifc)
+{
+ //WASABI_API_SVC->service_unlock(ifc);
+ return 1;
+}
+
+const char *JSAPI2Factory::GetTestString()
+{
+ return 0;
+}
+
+int JSAPI2Factory::ServiceNotify(int msg, int param1, int param2)
+{
+ return 1;
+}
+
+#define CBCLASS JSAPI2Factory
+START_DISPATCH;
+CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType)
+CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName)
+CB(WASERVICEFACTORY_GETGUID, GetGUID)
+CB(WASERVICEFACTORY_GETINTERFACE, GetInterface)
+CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface)
+CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface)
+CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString)
+CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/playlist/JSAPI2_Creator.h b/Src/playlist/JSAPI2_Creator.h
new file mode 100644
index 00000000..fd85a3f0
--- /dev/null
+++ b/Src/playlist/JSAPI2_Creator.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "../Winamp/JSAPI2_svc_apicreator.h"
+
+#include <api/service/waservicefactory.h>
+#include <api/service/services.h>
+
+class JSAPI2Factory : public waServiceFactory
+{
+public:
+ FOURCC GetServiceType();
+ const char *GetServiceName();
+ GUID GetGUID();
+ void *GetInterface(int global_lock);
+ int SupportNonLockingInterface();
+ int ReleaseInterface(void *ifc);
+ const char *GetTestString();
+ int ServiceNotify(int msg, int param1, int param2);
+
+protected:
+ RECVS_DISPATCH;
+};
+
+
+class JSAPI2_Creator : public JSAPI2::svc_apicreator
+{
+ IDispatch *CreateAPI(const wchar_t *name, const wchar_t *key, JSAPI::ifc_info *info);
+ int PromptForAuthorization(HWND parent, const wchar_t *group, const wchar_t *action, const wchar_t *authorization_key, JSAPI2::api_security::AuthorizationData *data);
+protected:
+ RECVS_DISPATCH;
+}; \ No newline at end of file
diff --git a/Src/playlist/JSAPI2_Playlist.cpp b/Src/playlist/JSAPI2_Playlist.cpp
new file mode 100644
index 00000000..924c4d70
--- /dev/null
+++ b/Src/playlist/JSAPI2_Playlist.cpp
@@ -0,0 +1,328 @@
+#include "JSAPI2_Playlist.h"
+#include "../Winamp/JSAPI.h"
+#include "api__playlist.h"
+#include "PlaylistManager.h"
+
+JSAPI2::PlaylistObject::PlaylistObject(const wchar_t *_key)
+{
+ key = _key;
+}
+
+#define DISP_TABLE \
+ CHECK_ID(Clear)\
+ CHECK_ID(AppendURL)\
+ CHECK_ID(GetItemFilename)\
+ CHECK_ID(GetItemTitle)\
+ CHECK_ID(GetItemLength)\
+ CHECK_ID(GetItemExtendedInfo)\
+ CHECK_ID(Reverse)\
+ CHECK_ID(SwapItems)\
+ CHECK_ID(Randomize)\
+ CHECK_ID(RemoveItem)\
+ CHECK_ID(SortByTitle)\
+ CHECK_ID(SortByFilename)\
+ CHECK_ID(SetItemFilename)\
+ CHECK_ID(SetItemTitle)\
+ CHECK_ID(SetItemLength)\
+ CHECK_ID(InsertURL)\
+ CHECK_ID(numitems)\
+
+
+#define CHECK_ID(str) JSAPI_DISP_ENUMIFY(str),
+enum {
+ DISP_TABLE
+};
+
+#undef CHECK_ID
+#define CHECK_ID(str) if (wcscmp(rgszNames[i], L## #str) == 0) { rgdispid[i] = JSAPI_DISP_ENUMIFY(str); continue; }
+HRESULT JSAPI2::PlaylistObject::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ bool unknowns = false;
+ for (unsigned int i = 0;i != cNames;i++)
+ {
+ DISP_TABLE
+
+ rgdispid[i] = DISPID_UNKNOWN;
+ unknowns = true;
+
+ }
+ if (unknowns)
+ return DISP_E_UNKNOWNNAME;
+ else
+ return S_OK;
+}
+
+HRESULT JSAPI2::PlaylistObject::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::PlaylistObject::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::PlaylistObject::Clear(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 0);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, VARIANT_TRUE);
+ playlist.Clear();
+ return S_OK;
+}
+
+HRESULT JSAPI2::PlaylistObject::AppendURL(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT_OPTIONAL(pdispparams, 1, 3);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 1, VT_BSTR, puArgErr);
+ JSAPI_VERIFY_PARAMTYPE_OPTIONAL(pdispparams, 2, VT_BSTR, puArgErr);
+ JSAPI_VERIFY_PARAMTYPE_OPTIONAL(pdispparams, 3, VT_I4, puArgErr);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, VARIANT_TRUE);
+ const wchar_t *filename = JSAPI_PARAM(pdispparams, 1).bstrVal;
+ const wchar_t *title = JSAPI_PARAM_OPTIONAL(pdispparams, 2, bstrVal, 0);
+ int length = JSAPI_PARAM_OPTIONAL(pdispparams, 3, lVal, -1);
+ playlist.AppendWithInfo(filename, title, length);
+ return S_OK;
+}
+
+HRESULT JSAPI2::PlaylistObject::InsertURL(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT_OPTIONAL(pdispparams, 2, 4);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 1, VT_I4, puArgErr);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 2, VT_BSTR, puArgErr);
+ JSAPI_VERIFY_PARAMTYPE_OPTIONAL(pdispparams, 3, VT_BSTR, puArgErr);
+ JSAPI_VERIFY_PARAMTYPE_OPTIONAL(pdispparams, 4, VT_I4, puArgErr);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, VARIANT_TRUE);
+ const wchar_t *filename = JSAPI_PARAM(pdispparams, 2).bstrVal;
+ const wchar_t *title = JSAPI_PARAM_OPTIONAL(pdispparams, 3, bstrVal, 0);
+ int length = JSAPI_PARAM_OPTIONAL(pdispparams, 4, lVal, -1);
+ playlist.Insert(JSAPI_PARAM(pdispparams, 1).lVal, filename, title, length);
+ return S_OK;
+}
+
+
+HRESULT JSAPI2::PlaylistObject::GetItemFilename(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 1);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 1, VT_I4, puArgErr);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BSTR);
+ JSAPI_SET_RESULT(pvarResult, bstrVal, SysAllocString(playlist.ItemName(JSAPI_PARAM(pdispparams, 1).lVal)));
+ return S_OK;
+}
+
+HRESULT JSAPI2::PlaylistObject::GetItemTitle(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 1);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 1, VT_I4, puArgErr);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BSTR);
+ JSAPI_SET_RESULT(pvarResult, bstrVal, SysAllocString(playlist.ItemTitle(JSAPI_PARAM(pdispparams, 1).lVal)));
+ return S_OK;
+}
+
+HRESULT JSAPI2::PlaylistObject::GetItemLength(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 1);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 1, VT_I4, puArgErr);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_I4);
+ JSAPI_SET_RESULT(pvarResult, lVal, playlist.GetItemLengthMilliseconds(JSAPI_PARAM(pdispparams, 1).lVal));
+ return S_OK;
+}
+
+HRESULT JSAPI2::PlaylistObject::GetItemExtendedInfo(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 2);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 1, VT_I4, puArgErr);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 2, VT_BSTR, puArgErr);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BSTR);
+ wchar_t metadata[1024]=L"";
+ playlist.GetItemExtendedInfo(JSAPI_PARAM(pdispparams, 1).lVal, JSAPI_PARAM(pdispparams, 2).bstrVal, metadata, sizeof(metadata)/sizeof(*metadata));
+ JSAPI_SET_RESULT(pvarResult, bstrVal, SysAllocString(metadata));
+ return S_OK;
+}
+
+HRESULT JSAPI2::PlaylistObject::Reverse(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 0);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, VARIANT_TRUE);
+ playlist.Reverse();
+ return S_OK;
+}
+
+
+HRESULT JSAPI2::PlaylistObject::SwapItems(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 2);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 1, VT_I4, puArgErr);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 2, VT_I4, puArgErr);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, VARIANT_TRUE);
+ playlist.Swap(JSAPI_PARAM(pdispparams, 1).lVal, JSAPI_PARAM(pdispparams, 2).lVal);
+ return S_OK;
+}
+
+HRESULT JSAPI2::PlaylistObject::Randomize(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 0);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, VARIANT_TRUE);
+ playlistManager.Randomize(&playlist);
+ return S_OK;
+}
+
+HRESULT JSAPI2::PlaylistObject::RemoveItem(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 1);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 1, VT_I4, puArgErr);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, VARIANT_TRUE);
+ playlist.Remove(JSAPI_PARAM(pdispparams, 1).lVal);
+ return S_OK;
+}
+
+HRESULT JSAPI2::PlaylistObject::SortByTitle(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 0);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, VARIANT_TRUE);
+ playlist.SortByTitle();
+ return S_OK;
+}
+
+HRESULT JSAPI2::PlaylistObject::SortByFilename(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 0);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, VARIANT_TRUE);
+ playlist.SortByFilename();
+ return S_OK;
+}
+
+HRESULT JSAPI2::PlaylistObject::SetItemFilename(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 2);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 1, VT_I4, puArgErr);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 2, VT_BSTR, puArgErr);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, VARIANT_TRUE);
+ playlist.SetItemFilename(JSAPI_PARAM(pdispparams, 1).lVal, JSAPI_PARAM(pdispparams, 2).bstrVal);
+ return S_OK;
+}
+
+HRESULT JSAPI2::PlaylistObject::SetItemTitle(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 2);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 1, VT_I4, puArgErr);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 2, VT_BSTR, puArgErr);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, VARIANT_TRUE);
+ playlist.SetItemTitle(JSAPI_PARAM(pdispparams, 1).lVal, JSAPI_PARAM(pdispparams, 2).bstrVal);
+ return S_OK;
+}
+
+HRESULT JSAPI2::PlaylistObject::SetItemLength(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 2);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 1, VT_I4, puArgErr);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 2, VT_I4, puArgErr);
+
+ JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
+ JSAPI_SET_RESULT(pvarResult, boolVal, VARIANT_TRUE);
+ playlist.SetItemLengthMilliseconds(JSAPI_PARAM(pdispparams, 1).lVal, JSAPI_PARAM(pdispparams, 2).lVal);
+ return S_OK;
+}
+
+HRESULT JSAPI2::PlaylistObject::numitems(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+// JSAPI_VERIFY_METHOD(wFlags);
+// JSAPI_VERIFY_PARAMCOUNT(pdispparams, 0);
+ if (wFlags & DISPATCH_PROPERTYGET)
+ {
+ JSAPI_INIT_RESULT(pvarResult, VT_I4);
+ JSAPI_SET_RESULT(pvarResult, lVal, (LONG)playlist.GetNumItems());
+ return S_OK;
+ }
+ else
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+
+#undef CHECK_ID
+#define CHECK_ID(str) case JSAPI_DISP_ENUMIFY(str): return str(wFlags, pdispparams, pvarResult, puArgErr);
+HRESULT JSAPI2::PlaylistObject::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ switch (dispid)
+ {
+ DISP_TABLE
+ }
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+STDMETHODIMP JSAPI2::PlaylistObject::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else if (IsEqualIID(riid, IID_PlaylistObject))
+ *ppvObject = (PlaylistObject *)this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+ULONG JSAPI2::PlaylistObject::AddRef(void)
+{
+ return this->_refCount.fetch_add( 1 );
+}
+
+
+ULONG JSAPI2::PlaylistObject::Release( void )
+{
+ std::size_t l_Ref = this->_refCount.fetch_sub( 1 );
+ if ( l_Ref == 0 )
+ delete this;
+
+ return l_Ref;
+}
diff --git a/Src/playlist/JSAPI2_Playlist.h b/Src/playlist/JSAPI2_Playlist.h
new file mode 100644
index 00000000..c6f946b7
--- /dev/null
+++ b/Src/playlist/JSAPI2_Playlist.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <ocidl.h>
+#include <atomic>
+
+#include "Playlist.h"
+
+namespace JSAPI2
+{
+ // {8535DB01-7630-45df-9429-2640A29B9468}
+ static const GUID IID_PlaylistObject =
+ { 0x8535db01, 0x7630, 0x45df, { 0x94, 0x29, 0x26, 0x40, 0xa2, 0x9b, 0x94, 0x68 } };
+
+
+ class PlaylistObject : public IDispatch
+ {
+ public:
+ PlaylistObject( const wchar_t *_key );
+ STDMETHOD( QueryInterface )( REFIID riid, PVOID *ppvObject );
+ STDMETHOD_( ULONG, AddRef )( void );
+ STDMETHOD_( ULONG, Release )( void );
+ // *** IDispatch Methods ***
+ STDMETHOD( GetIDsOfNames )( REFIID riid, OLECHAR FAR *FAR *rgszNames, unsigned int cNames, LCID lcid, DISPID FAR *rgdispid );
+ STDMETHOD( GetTypeInfo )( unsigned int itinfo, LCID lcid, ITypeInfo FAR *FAR *pptinfo );
+ STDMETHOD( GetTypeInfoCount )( unsigned int FAR *pctinfo );
+ STDMETHOD( Invoke )( DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR *pexecinfo, unsigned int FAR *puArgErr );
+
+ Playlist playlist;
+ private:
+ const wchar_t *key;
+ volatile std::atomic<std::size_t> _refCount = 1;
+
+ STDMETHOD( Clear )( WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr );
+ STDMETHOD( AppendURL )( WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr );
+ STDMETHOD( GetItemFilename )( WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr );
+ STDMETHOD( GetItemTitle )( WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr );
+ STDMETHOD( GetItemLength )( WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr );
+ STDMETHOD( GetItemExtendedInfo )( WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr );
+ STDMETHOD( Reverse )( WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr );
+ STDMETHOD( SwapItems )( WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr );
+ STDMETHOD( Randomize )( WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr );
+ STDMETHOD( RemoveItem )( WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr );
+ STDMETHOD( SortByTitle )( WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr );
+ STDMETHOD( SortByFilename )( WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr );
+ STDMETHOD( SetItemFilename )( WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr );
+ STDMETHOD( SetItemTitle )( WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr );
+ STDMETHOD( SetItemLength )( WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr );
+ STDMETHOD( InsertURL )( WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr );
+ STDMETHOD( numitems )( WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr );
+ };
+}
diff --git a/Src/playlist/JSAPI2_Playlists.cpp b/Src/playlist/JSAPI2_Playlists.cpp
new file mode 100644
index 00000000..c01a87f2
--- /dev/null
+++ b/Src/playlist/JSAPI2_Playlists.cpp
@@ -0,0 +1,221 @@
+#include "JSAPI2_Playlists.h"
+#include "../Winamp/JSAPI.h"
+#include "api__playlist.h"
+#include "../Winamp/JSAPI_ObjectArray.h"
+#include "Playlists.h"
+#include "JSAPI2_Playlist.h"
+#include "PlaylistManager.h"
+#include "../Winamp/JSAPI_CallbackParameters.h"
+
+extern Playlists playlists;
+
+JSAPI2::PlaylistsAPI::PlaylistsAPI(const wchar_t *_key, JSAPI::ifc_info *_info)
+{
+ info = _info;
+ key = _key;
+}
+
+#define DISP_TABLE \
+ CHECK_ID(GetPlaylists)\
+ CHECK_ID(OpenPlaylist)\
+ CHECK_ID(SavePlaylist)\
+
+#define CHECK_ID(str) JSAPI_DISP_ENUMIFY(str),
+enum {
+ DISP_TABLE
+};
+
+#undef CHECK_ID
+#define CHECK_ID(str) if (wcscmp(rgszNames[i], L## #str) == 0) { rgdispid[i] = JSAPI_DISP_ENUMIFY(str); continue; }
+HRESULT JSAPI2::PlaylistsAPI::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ bool unknowns = false;
+ for (unsigned int i = 0;i != cNames;i++)
+ {
+ DISP_TABLE
+
+ rgdispid[i] = DISPID_UNKNOWN;
+ unknowns = true;
+
+ }
+ if (unknowns)
+ return DISP_E_UNKNOWNNAME;
+ else
+ return S_OK;
+}
+
+HRESULT JSAPI2::PlaylistsAPI::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::PlaylistsAPI::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::PlaylistsAPI::GetPlaylists(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 0);
+
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_DISPATCH;
+ if (AGAVE_API_JSAPI2_SECURITY->GetActionAuthorization(L"playlists", L"read", key, info, JSAPI2::api_security::ACTION_PROMPT) == JSAPI2::api_security::ACTION_ALLOWED)
+ {
+ JSAPI::ObjectArray *objectArray = new JSAPI::ObjectArray;
+ playlists.Lock();
+
+ size_t count = playlists.GetCount();
+
+ for (size_t i=0;i!=count;i++)
+ {
+ const PlaylistInfo &info = playlists.GetPlaylistInfo(i);
+ JSAPI::CallbackParameters *playlistParams = new JSAPI::CallbackParameters;
+ playlistParams->AddString(L"filename", info.filename);
+ playlistParams->AddString(L"title", info.title);
+ wchar_t guid_str[40];
+ nsGUID::toCharW(info.guid, guid_str);
+ playlistParams->AddString(L"playlistId", guid_str);
+ playlistParams->AddLong(L"length", info.length);
+ playlistParams->AddLong(L"numitems", info.numItems);
+ objectArray->AddObject(playlistParams);
+ playlistParams->Release();
+ }
+ playlists.Unlock();
+ V_DISPATCH(pvarResult) = objectArray;
+ return S_OK;
+ }
+ else
+ {
+ V_DISPATCH(pvarResult) = 0;
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
+HRESULT JSAPI2::PlaylistsAPI::OpenPlaylist(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 1);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 1, VT_BSTR, puArgErr);
+
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_DISPATCH;
+ V_DISPATCH(pvarResult) = 0;
+ if (AGAVE_API_JSAPI2_SECURITY->GetActionAuthorization(L"playlists", L"read", key, info, JSAPI2::api_security::ACTION_PROMPT) == JSAPI2::api_security::ACTION_ALLOWED)
+ {
+ GUID playlist_guid = nsGUID::fromCharW(JSAPI_PARAM(pdispparams, 1).bstrVal);
+ playlists.Lock();
+ size_t index;
+ if (playlists.GetPosition(playlist_guid, &index) == API_PLAYLISTS_SUCCESS)
+ {
+ const wchar_t *filename = playlists.GetFilename(index);
+ if (filename)
+ {
+ PlaylistObject *playlist = new PlaylistObject(key);
+ if (playlistManager.Load(filename, &playlist->playlist) == PLAYLISTMANAGER_SUCCESS)
+ {
+ V_DISPATCH(pvarResult) = playlist;
+ }
+ else
+ {
+ delete playlist;
+ }
+ }
+ }
+ playlists.Unlock();
+ return S_OK;
+
+ }
+ else
+ {
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
+HRESULT JSAPI2::PlaylistsAPI::SavePlaylist(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 2);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 1, VT_BSTR, puArgErr);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 2, VT_DISPATCH, puArgErr);
+
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_BOOL;
+ V_BOOL(pvarResult) = FALSE;
+ if (AGAVE_API_JSAPI2_SECURITY->GetActionAuthorization(L"playlists", L"write", key, info, JSAPI2::api_security::ACTION_PROMPT) == JSAPI2::api_security::ACTION_ALLOWED)
+ {
+ GUID playlist_guid = nsGUID::fromCharW(JSAPI_PARAM(pdispparams, 1).bstrVal);
+ playlists.Lock();
+ size_t index;
+ if (playlists.GetPosition(playlist_guid, &index) == API_PLAYLISTS_SUCCESS)
+ {
+ const wchar_t *filename = playlists.GetFilename(index);
+ if (filename)
+ {
+ IDispatch *dispPlaylist = JSAPI_PARAM(pdispparams, 2).pdispVal;
+ PlaylistObject *playlist = 0;
+ dispPlaylist->QueryInterface(JSAPI2::IID_PlaylistObject, (void **)&playlist);
+ if (playlistManager.Save(filename, &(playlist->playlist)) == PLAYLISTMANAGER_SUCCESS)
+ V_BOOL(pvarResult) = TRUE;
+ }
+ }
+ playlists.Unlock();
+ return S_OK;
+
+ }
+ else
+ {
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
+#undef CHECK_ID
+#define CHECK_ID(str) case JSAPI_DISP_ENUMIFY(str): return str(wFlags, pdispparams, pvarResult, puArgErr);
+HRESULT JSAPI2::PlaylistsAPI::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ switch (dispid)
+ {
+ DISP_TABLE
+ }
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+STDMETHODIMP JSAPI2::PlaylistsAPI::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+ULONG JSAPI2::PlaylistsAPI::AddRef(void)
+{
+ return this->_refCount.fetch_add( 1 );
+}
+
+ULONG JSAPI2::PlaylistsAPI::Release( void )
+{
+ std::size_t l_Ref = this->_refCount.fetch_sub( 1 );
+ if ( l_Ref == 0 )
+ delete this;
+
+ return l_Ref;
+}
diff --git a/Src/playlist/JSAPI2_Playlists.h b/Src/playlist/JSAPI2_Playlists.h
new file mode 100644
index 00000000..0afd48f2
--- /dev/null
+++ b/Src/playlist/JSAPI2_Playlists.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <ocidl.h>
+#include <atomic>
+#include "../Winamp/JSAPI_Info.h"
+
+namespace JSAPI2
+{
+ class PlaylistsAPI : public IDispatch
+ {
+ public:
+ PlaylistsAPI(const wchar_t *_key, JSAPI::ifc_info *info);
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+ private:
+ const wchar_t *key;
+ volatile std::atomic<std::size_t> _refCount = 1;
+ JSAPI::ifc_info *info;
+
+ STDMETHOD (GetPlaylists)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr);
+ STDMETHOD (OpenPlaylist)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr);
+ STDMETHOD (SavePlaylist)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr);
+ };
+}
diff --git a/Src/playlist/M3U8Writer.cpp b/Src/playlist/M3U8Writer.cpp
new file mode 100644
index 00000000..72972216
--- /dev/null
+++ b/Src/playlist/M3U8Writer.cpp
@@ -0,0 +1,39 @@
+#include "M3U8Writer.h"
+#include "../nu/AutoChar.h"
+#include <bfc/platform/types.h>
+
+M3U8Writer::M3U8Writer() : fp(0)
+{
+}
+
+int M3U8Writer::Open(const wchar_t *filename)
+{
+ fp = _wfopen(filename, L"wt");
+ if (!fp)
+ return 0;
+
+ fputs( "\xEF\xBB\xBF", fp );
+ fprintf(fp,"#EXTM3U\n");
+
+ return 1;
+}
+
+void M3U8Writer::Write(const wchar_t *filename)
+{
+ fwprintf(fp,L"%s\n", filename);
+}
+
+void M3U8Writer::Write(const wchar_t *filename, const wchar_t *title, int length)
+{
+ fprintf( fp, "#EXTINF:%d,%s\n%s\n", length, (char *) AutoChar( title, CP_UTF8 ), (char *) AutoChar( filename, CP_UTF8 ) );
+}
+
+void M3U8Writer::Write( const wchar_t *p_filename, const wchar_t *p_title, const wchar_t *p_extended_infos, int p_length )
+{
+ fprintf( fp, "#EXTINF:%d %s,%s\n%s\n", p_length, (char *)AutoChar( p_extended_infos, CP_UTF8 ), (char *)AutoChar( p_title, CP_UTF8 ), (char *)AutoChar( p_filename, CP_UTF8 ) );
+}
+
+void M3U8Writer::Close()
+{
+ fclose(fp);
+} \ No newline at end of file
diff --git a/Src/playlist/M3U8Writer.h b/Src/playlist/M3U8Writer.h
new file mode 100644
index 00000000..7f251ef6
--- /dev/null
+++ b/Src/playlist/M3U8Writer.h
@@ -0,0 +1,21 @@
+#ifndef NULLSOFT_M3U8WRITERH
+#define NULLSOFT_M3U8WRITERH
+
+#include <stdio.h>
+#include "PlaylistWriter.h"
+class M3U8Writer : public PlaylistWriter
+{
+public:
+ M3U8Writer();
+
+ int Open( const wchar_t *filename ) override;
+ void Write( const wchar_t *filename ) override;
+ void Write( const wchar_t *filename, const wchar_t *title, int length ) override;
+ void Write( const wchar_t *p_filename, const wchar_t *p_title, const wchar_t* p_extended_infos, int p_length ) override;
+ void Close() override;
+
+private:
+ FILE *fp;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/M3ULoader.cpp b/Src/playlist/M3ULoader.cpp
new file mode 100644
index 00000000..ebab35da
--- /dev/null
+++ b/Src/playlist/M3ULoader.cpp
@@ -0,0 +1,466 @@
+#include <stdio.h>
+#include <shlwapi.h>
+#include <strsafe.h>
+#include <fstream>
+#include <string>
+
+#include "M3ULoader.h"
+#include "../nu/ns_wc.h"
+
+#include "../WAT/WAT.h"
+
+
+M3ULoader::M3ULoader() : _utf8( false )
+{
+ wideTitle[ 0 ] = wideFilename[ 0 ] = 0;
+}
+
+M3ULoader::~M3ULoader( void )
+{
+ //Close();
+}
+
+struct cmpWchar_t {
+ bool operator()(const wchar_t* a, const wchar_t* b) const {
+ return wcscmp(a, b) < 0;
+ }
+};
+
+class M3UInfo : public ifc_plentryinfo
+{
+public:
+ M3UInfo() {}
+ M3UInfo( wchar_t *_mediahash, wchar_t *_metahash, wchar_t *_cloud_id, wchar_t *_cloud_status, wchar_t *_cloud_devices )
+ {
+ _extended_infos.emplace( _wcsdup( _INFO_NAME_MEDIA_HASH ), _wcsdup( _mediahash) );
+ _extended_infos.emplace( _wcsdup( _INFO_NAME_META_HASH ), _wcsdup( _metahash ) );
+
+ _extended_infos.emplace( _wcsdup( _INFO_NAME_CLOUD_ID ), _wcsdup( _cloud_id ) );
+ _extended_infos.emplace( _wcsdup( _INFO_NAME_CLOUD_STATUS ), _wcsdup( _cloud_status ) );
+ _extended_infos.emplace( _wcsdup( _INFO_NAME_CLOUD_DEVICES ), _wcsdup( _cloud_devices ) );
+ }
+
+ ~M3UInfo()
+ {
+ for ( auto l_extended_infos_iterator = _extended_infos.begin(); l_extended_infos_iterator != _extended_infos.end(); ++l_extended_infos_iterator )
+ {
+ free( ( *l_extended_infos_iterator ).first );
+ free( ( *l_extended_infos_iterator ).second );
+ }
+
+ _extended_infos.clear();
+ }
+
+ void SetExtendedInfo( const wchar_t *p_parameter_name, const wchar_t *p_parameter_value )
+ {
+ _extended_infos.emplace( _wcsdup( p_parameter_name ), _wcsdup( p_parameter_value ) );
+ }
+
+ const wchar_t *GetExtendedInfo( wchar_t *parameter )
+ {
+ //for ( auto l_extended_infos_iterator = _extended_infos.begin(); l_extended_infos_iterator != _extended_infos.end(); ++l_extended_infos_iterator )
+ //{
+ // wchar_t *l_key = _wcsdup( ( *l_extended_infos_iterator ).first );
+ // if ( wcscmp( l_key, parameter ) == 0 )
+ // return _wcsdup( ( *l_extended_infos_iterator ).second );
+ //}
+
+ // OLD
+ //std::map<wchar_t *, wchar_t *>::iterator l_extended_infos_iterator = _extended_infos.find( parameter );
+
+ //if ( l_extended_infos_iterator != _extended_infos.end() )
+ // return _wcsdup( ( *l_extended_infos_iterator ).second );
+
+ auto it = _extended_infos.find(parameter);
+ if (_extended_infos.end() != it)
+ {
+ return it->second;
+ }
+
+ return 0;
+ }
+
+private:
+ RECVS_DISPATCH;
+
+ std::map<wchar_t *, wchar_t *, cmpWchar_t> _extended_infos;
+};
+
+#define CBCLASS M3UInfo
+START_DISPATCH;
+CB( IFC_PLENTRYINFO_GETEXTENDEDINFO, GetExtendedInfo )
+END_DISPATCH;
+#undef CBCLASS
+
+
+int M3ULoader::OnFileHelper( ifc_playlistloadercallback *playlist, const wchar_t *trackName, const wchar_t *title, int length, const wchar_t *rootPath, ifc_plentryinfo *extraInfo )
+{
+ if ( length == -1000 )
+ length = -1;
+
+ wcsncpy( wideFilename, trackName, FILENAME_SIZE );
+
+ int ret;
+
+ if ( wcsstr( wideFilename, L"://" ) || PathIsRootW( wideFilename ) )
+ {
+ ret = playlist->OnFile( wideFilename, title, length, extraInfo );
+ }
+ else
+ {
+ wchar_t fullPath[ MAX_PATH ] = { 0 };
+ if ( PathCombineW( fullPath, rootPath, wideFilename ) )
+ {
+ wchar_t canonicalizedPath[ MAX_PATH ] = { 0 };
+ PathCanonicalizeW( canonicalizedPath, fullPath );
+ ret = playlist->OnFile( canonicalizedPath, title, length, extraInfo );
+ }
+ else
+ {
+ ret = ifc_playlistloadercallback::LOAD_CONTINUE;
+ }
+ }
+
+ return ret;
+}
+
+static bool StringEnds( const wchar_t *a, const wchar_t *b )
+{
+ size_t aLen = wcslen( a );
+ size_t bLen = wcslen( b );
+
+ if ( aLen < bLen )
+ return false; // too short
+
+ if ( !_wcsicmp( a + aLen - bLen, b ) )
+ return true;
+
+ return false;
+}
+
+int M3ULoader::Load( const wchar_t *p_filename, ifc_playlistloadercallback *playlist )
+{
+ // TODO: download temp file if it's a URL
+ // TODO - WDP2-198
+
+ FILE *fp = _wfopen( p_filename, L"rt,ccs=UNICODE" );
+ if ( !fp )
+ return IFC_PLAYLISTLOADER_FAILED;
+
+ fseek( fp, 0, SEEK_END );
+ int size = ftell( fp );
+ fseek( fp, 0, SEEK_SET );
+
+ if ( size == -1 )
+ {
+ fclose( fp );
+ fp = 0;
+
+ return IFC_PLAYLISTLOADER_FAILED;
+ }
+
+ if ( StringEnds( p_filename, L".m3u8" ) )
+ _utf8 = true;
+
+ int ext = 0;
+
+ wchar_t *p;
+
+ const int l_linebuf_size = 2048;
+ wchar_t linebuf[ l_linebuf_size ] = { 0 };
+
+ wchar_t ext_title[ MAX_PATH ] = { 0 };
+
+ wchar_t ext_mediahash[ 128 ] = { 0 };
+ wchar_t ext_metahash[ 128 ] = { 0 };
+
+ wchar_t ext_cloud_id[ 128 ] = { 0 };
+ wchar_t ext_cloud_status[ 16 ] = { 0 };
+ wchar_t ext_cloud_devices[ 128 ] = { 0 };
+
+ int ext_len = -1;
+
+ wchar_t rootPath[ MAX_PATH ] = { 0 };
+ const wchar_t *callbackPath = playlist->GetBasePath();
+ if ( callbackPath )
+ StringCchCopyW( rootPath, MAX_PATH, callbackPath );
+ else
+ {
+ StringCchCopyW( rootPath, MAX_PATH, p_filename );
+ PathRemoveFileSpecW( rootPath );
+ }
+
+ unsigned char BOM[ 3 ] = { 0, 0, 0 };
+ if ( fread( BOM, 3, 1, fp ) == 1 && BOM[ 0 ] == 0xEF && BOM[ 1 ] == 0xBB && BOM[ 2 ] == 0xBF )
+ _utf8 = true;
+ else
+ fseek( fp, 0, SEEK_SET );
+
+
+ std::wstring l_separator = L"\" ";
+ std::wstring l_key_separator = L"=";
+
+
+ const wchar_t _ASF[] = L"ASF ";
+ const wchar_t _DIRECTIVE_EXTINF[] = L"#EXTINF:";
+ const wchar_t _DIRECTIVE_EXTM3U[] = L"#EXTM3U";
+ const wchar_t _DIRECTIVE_EXT_X_NS_CLOUD[] = L"#EXT-X-NS-CLOUD:";
+ const wchar_t _DIRECTIVE_UTF8[] = L"#UTF8";
+ const wchar_t _END_LINE[] = L"\r\n";
+
+ const int l_move_size = sizeof( wchar_t );
+
+
+ wa::strings::wa_string l_key_value_pair = "";
+ wa::strings::wa_string l_key = "";
+ wa::strings::wa_string l_value = "";
+
+ std::map<std::wstring, std::wstring> l_extended_infos;
+
+ while ( 1 )
+ {
+ if ( feof( fp ) )
+ break;
+
+ linebuf[ 0 ] = 0;
+ fgetws( linebuf, l_linebuf_size - 1, fp );
+
+ linebuf[ wcscspn( linebuf, _END_LINE ) ] = 0;
+ if ( wcslen( linebuf ) == 0 )
+ continue;
+
+ if ( ext == 0 && wcsstr( linebuf, _DIRECTIVE_EXTM3U ) )
+ {
+ ext = 1;
+
+ continue;
+ }
+
+ if ( !wcsncmp( linebuf, _DIRECTIVE_UTF8, 5 ) )
+ {
+ _utf8 = true;
+
+ continue;
+ }
+
+ p = linebuf;
+
+ while ( p && *p == ' ' || *p == '\t' )
+ p = CharNextW( p );
+
+ if ( *p != '#' && *p != '\n' && *p != '\r' && *p )
+ {
+ wchar_t buf[ 4096 ] = { 0 };
+
+ wchar_t *p2 = CharPrevW( linebuf, linebuf + wcslen( linebuf ) ); //GetLastCharacter(linebuf);
+ if ( p2 && *p2 == '\n' )
+ *p2 = 0;
+
+ if ( !wcsncmp( p, _ASF, 4 ) && wcslen( p ) > 4 )
+ p += 4;
+
+ if ( wcsncmp( p, L"\\\\", 2 ) && wcsncmp( p + 1, L":\\", 2 ) && wcsncmp( p + 1, L":/", 2 ) && !wcsstr( p, L"://" ) )
+ {
+ if ( p[ 0 ] == '\\' )
+ {
+ buf[ 0 ] = rootPath[ 0 ];
+ buf[ 1 ] = rootPath[ 1 ];
+
+ StringCchCopyW( buf + 2, 4093, p );
+
+ //buf[ wcslen( buf ) - 1 ] = 0;
+ buf[ wcscspn( buf, _END_LINE ) ] = 0;
+ p = buf;
+ }
+ }
+
+ int ret;
+
+ // generate extra info from the cloud specific values (if present)
+ M3UInfo info( ext_mediahash, ext_metahash, ext_cloud_id, ext_cloud_status, ext_cloud_devices );
+
+
+ if ( !l_extended_infos.empty() )
+ {
+ for ( auto l_extended_infos_iterator = l_extended_infos.begin(); l_extended_infos_iterator != l_extended_infos.end(); ++l_extended_infos_iterator )
+ {
+ info.SetExtendedInfo( ( *l_extended_infos_iterator ).first.c_str(), ( *l_extended_infos_iterator ).second.c_str() );
+ }
+ }
+
+ l_extended_infos.clear();
+
+ if ( ext_title[ 0 ] )
+ {
+ wcsncpy( wideTitle, ext_title, FILETITLE_SIZE );
+ ret = OnFileHelper( playlist, p, wideTitle, ext_len * 1000, rootPath, &info );
+ }
+ else
+ {
+ ret = OnFileHelper( playlist, p, 0, -1, rootPath, &info );
+ }
+
+ if ( ret != ifc_playlistloadercallback::LOAD_CONTINUE )
+ break;
+
+ ext_len = -1;
+ ext_title[ 0 ] = 0;
+ }
+ else
+ {
+ if ( ext && !wcsncmp( p, _DIRECTIVE_EXTINF, 8 ) )
+ {
+ p += 8;
+ ext_len = _wtoi( p );
+
+ int l_track_length = ext_len;
+ int l_digits = ( l_track_length < 0 ? 1 : 0 );
+ while ( l_track_length )
+ {
+ l_track_length /= 10;
+ ++l_digits;
+ }
+
+ p += l_digits;
+
+
+ if ( p && *p )
+ {
+ wchar_t *p2 = CharPrevW( p, p + wcslen( p ) ); // GetLastCharacter(p);
+ if ( p2 && *p2 == '\n' )
+ *p2 = 0;
+
+ while ( p && *p == ' ' )
+ p = CharNextW( p );
+
+ std::wstring l_string( p );
+
+ int l_pos = l_string.find_first_of( L"," );
+
+ if ( l_pos > 0 )
+ {
+ int l_key_separator_pos = 0;
+
+ wa::strings::wa_string l_line_trail( l_string.substr( 0, l_pos ) );
+
+ while ( !l_line_trail.empty() )
+ {
+ int l_separator_pos = l_line_trail.find( l_separator );
+
+ if ( l_separator_pos > 0 )
+ l_key_value_pair = l_line_trail.mid( 0, l_separator_pos + 1 );
+ else
+ l_key_value_pair = l_line_trail;
+
+
+ l_key_separator_pos = l_key_value_pair.find( l_key_separator );
+
+ l_key = l_key_value_pair.mid( 0, l_key_separator_pos );
+ l_value = l_key_value_pair.mid( l_key_separator_pos + 1, l_key_value_pair.lengthS() - l_key_separator_pos + 1 );
+
+ l_value.replaceAll( "\"", "" );
+
+ l_extended_infos.emplace( l_key.GetW(), l_value.GetW() );
+
+ if ( l_separator_pos > 0 )
+ l_line_trail = l_line_trail.mid( l_separator_pos + l_move_size, l_line_trail.lengthS() - l_separator_pos + 1 );
+ else
+ l_line_trail.clear();
+ }
+
+
+ l_string = l_string.substr( l_pos + 1, l_string.size() - l_pos );
+
+ StringCchCopyW( ext_title, MAX_PATH, l_string.c_str() );
+ }
+ else
+ StringCchCopyW( ext_title, MAX_PATH, CharNextW( p ) );
+ }
+ else
+ {
+ ext_len = -1;
+ ext_title[ 0 ] = 0;
+ }
+ }
+ // cloud specific playlist line for holding information about the entry
+ else if ( ext && !wcsncmp( p, _DIRECTIVE_EXT_X_NS_CLOUD, 16 ) )
+ {
+ p += 16;
+ wchar_t *pt = wcstok( p, L"," );
+ while ( pt != NULL )
+ {
+ int end = (int)wcscspn( pt, L"=" );
+
+ if ( !wcsncmp( pt, _INFO_NAME_MEDIA_HASH, end ) )
+ {
+ if ( ( lstrcpynW( ext_mediahash, pt + end + 1, 128 ) ) == NULL )
+ return IFC_PLAYLISTLOADER_FAILED;
+ }
+ else if ( !wcsncmp( pt, _INFO_NAME_META_HASH, end ) )
+ {
+ if ( ( lstrcpynW( ext_metahash, pt + end + 1, 128 ) ) == NULL )
+ return IFC_PLAYLISTLOADER_FAILED;
+ }
+ else if ( !wcsncmp( pt, _INFO_NAME_CLOUD_ID, end ) )
+ {
+ if ( ( lstrcpynW( ext_cloud_id, pt + end + 1, 128 ) ) == NULL )
+ return IFC_PLAYLISTLOADER_FAILED;
+ }
+ else if ( !wcsncmp( pt, _INFO_NAME_CLOUD_STATUS, end ) )
+ {
+ if ( ( lstrcpynW( ext_cloud_status, pt + end + 1, 16 ) ) == NULL )
+ return IFC_PLAYLISTLOADER_FAILED;
+ }
+ else if ( !wcsncmp( pt, _INFO_NAME_CLOUD_DEVICES, end ) )
+ {
+ wchar_t *p2 = pt + end + 1;
+ while ( p2 && *p2 != '\n' )
+ p2 = CharNextW( p2 );
+
+ if ( p2 && *p2 == '\n' )
+ *p2 = 0;
+
+ if ( ( lstrcpynW( ext_cloud_devices, pt + end + 1, 128 ) ) == NULL )
+ return IFC_PLAYLISTLOADER_FAILED;
+ }
+
+ pt = wcstok( NULL, L"," );
+ }
+ }
+ else
+ {
+ ext_len = -1;
+ ext_title[ 0 ] = 0;
+ ext_mediahash[ 0 ] = 0;
+ ext_metahash[ 0 ] = 0;
+ ext_cloud_id[ 0 ] = 0;
+ ext_cloud_status[ 0 ] = 0;
+ ext_cloud_devices[ 0 ] = 0;
+ }
+ }
+ }
+
+ if ( fp )
+ fclose( fp );
+
+ return IFC_PLAYLISTLOADER_SUCCESS;
+}
+
+
+#ifdef CBCLASS
+#undef CBCLASS
+#endif
+
+#define CBCLASS M3ULoader
+
+START_DISPATCH;
+CB( IFC_PLAYLISTLOADER_LOAD, Load )
+#if 0
+VCB( IFC_PLAYLISTLOADER_CLOSE, Close )
+CB( IFC_PLAYLISTLOADER_GETITEM, GetItem )
+CB( IFC_PLAYLISTLOADER_GETITEMTITLE, GetItemTitle )
+CB( IFC_PLAYLISTLOADER_GETITEMLENGTHMILLISECONDS, GetItemLengthMilliseconds )
+CB( IFC_PLAYLISTLOADER_GETITEMEXTENDEDINFO, GetItemExtendedInfo )
+CB( IFC_PLAYLISTLOADER_NEXTITEM, NextItem )
+#endif
+END_DISPATCH; \ No newline at end of file
diff --git a/Src/playlist/M3ULoader.h b/Src/playlist/M3ULoader.h
new file mode 100644
index 00000000..9dd93edf
--- /dev/null
+++ b/Src/playlist/M3ULoader.h
@@ -0,0 +1,31 @@
+#ifndef NULLSOFT_PLAYLIST_M3U_LOADER_H
+#define NULLSOFT_PLAYLIST_M3U_LOADER_H
+
+#include "ifc_playlistloader.h"
+#include "ifc_playlistloadercallback.h"
+#include <stdio.h>
+
+static wchar_t *_INFO_NAME_MEDIA_HASH = L"mediahash";
+static wchar_t *_INFO_NAME_META_HASH = L"metahash";
+static wchar_t *_INFO_NAME_CLOUD_ID = L"cloud_id";
+static wchar_t *_INFO_NAME_CLOUD_STATUS = L"cloud_status";
+static wchar_t *_INFO_NAME_CLOUD_DEVICES = L"cloud_devices";
+
+class M3ULoader : public ifc_playlistloader
+{
+public:
+ M3ULoader();
+ virtual ~M3ULoader( void );
+
+ int Load( const wchar_t *filename, ifc_playlistloadercallback *playlist );
+ int OnFileHelper( ifc_playlistloadercallback *playlist, const wchar_t *trackName, const wchar_t *title, int length, const wchar_t *rootPath, ifc_plentryinfo *extraInfo );
+
+
+protected:
+ RECVS_DISPATCH;
+
+ bool _utf8;
+ wchar_t wideFilename[ FILENAME_SIZE ];
+ wchar_t wideTitle[ FILETITLE_SIZE ];
+};
+#endif \ No newline at end of file
diff --git a/Src/playlist/M3UWriter.cpp b/Src/playlist/M3UWriter.cpp
new file mode 100644
index 00000000..07110c81
--- /dev/null
+++ b/Src/playlist/M3UWriter.cpp
@@ -0,0 +1,42 @@
+#include "M3UWriter.h"
+#include "../nu/AutoChar.h"
+
+M3UWriter::~M3UWriter()
+{
+ Close();
+}
+
+int M3UWriter::Open( const wchar_t *filename )
+{
+ if ( fp != NULL )
+ return 0;
+
+ fp = _wfopen( filename, L"wt" );
+ if ( !fp )
+ return 0;
+
+ fprintf( fp, "#EXTM3U\n" );
+
+ return 1;
+}
+
+void M3UWriter::Write( const wchar_t *filename )
+{
+ if ( fp != NULL )
+ fprintf( fp, "%s\n", (char *)AutoChar( filename ) );
+}
+
+void M3UWriter::Write( const wchar_t *filename, const wchar_t *title, int length )
+{
+ if ( fp != NULL )
+ fprintf( fp, "#EXTINF:%d,%s\n%s\n", length, (char *)AutoChar( title ), (char *)AutoChar( filename ) );
+}
+
+void M3UWriter::Close()
+{
+ if ( fp != NULL )
+ {
+ fclose( fp );
+ fp = NULL;
+ }
+} \ No newline at end of file
diff --git a/Src/playlist/M3UWriter.h b/Src/playlist/M3UWriter.h
new file mode 100644
index 00000000..1b53d646
--- /dev/null
+++ b/Src/playlist/M3UWriter.h
@@ -0,0 +1,23 @@
+#ifndef NULLSOFT_M3UWRITERH
+#define NULLSOFT_M3UWRITERH
+
+#include <stdio.h>
+#include "PlaylistWriter.h"
+class M3UWriter : public PlaylistWriter
+{
+public:
+ M3UWriter() {}
+ virtual ~M3UWriter();
+
+ int Open( const wchar_t *filename ) override;
+ void Write( const wchar_t *filename ) override;
+ void Write( const wchar_t *filename, const wchar_t *title, int length ) override;
+ void Write( const wchar_t *p_filename, const wchar_t *p_title, const wchar_t *p_extended_infos, int p_length ) override
+ {};
+ void Close() override;
+
+private:
+ FILE *fp = NULL;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/PLSLoader.cpp b/Src/playlist/PLSLoader.cpp
new file mode 100644
index 00000000..736b5240
--- /dev/null
+++ b/Src/playlist/PLSLoader.cpp
@@ -0,0 +1,159 @@
+#include "PLSLoader.h"
+
+#include "../nu/AutoChar.h"
+#include "../nu/AutoWide.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+
+
+class PLSInfo : public ifc_plentryinfo
+{
+public:
+ PLSInfo( const wchar_t *_filename, int _entryNum ) : filename( _filename ), entryNum( _entryNum )
+ {}
+
+ const wchar_t *GetExtendedInfo( const wchar_t *parameter )
+ {
+ static wchar_t data[ 1024 ];
+ wchar_t fieldbuf[ 100 ] = { 0 };
+
+ StringCchPrintfW( fieldbuf, 100, L"%s%d", parameter, entryNum );
+
+ GetPrivateProfileStringW( L"playlist", fieldbuf, L"", data, 1024, filename );
+
+ if ( data[ 0 ] )
+ return data;
+ else
+ return 0;
+ }
+
+private:
+ RECVS_DISPATCH;
+
+ const wchar_t *filename;
+ int entryNum;
+};
+
+#define CBCLASS PLSInfo
+START_DISPATCH;
+CB( IFC_PLENTRYINFO_GETEXTENDEDINFO, GetExtendedInfo )
+END_DISPATCH;
+#undef CBCLASS
+
+int PLSLoader::OnFileHelper( ifc_playlistloadercallback *playlist, const wchar_t *trackName, const wchar_t *title, int length, const wchar_t *rootPath, ifc_plentryinfo *extraInfo )
+{
+ if ( length == -1000 )
+ length = -1;
+
+ int ret;
+ if ( wcsstr( trackName, L"://" ) || PathIsRootW( trackName ) )
+ {
+ ret = playlist->OnFile( trackName, title, length, extraInfo );
+ }
+ else
+ {
+ wchar_t fullPath[ MAX_PATH ] = { 0 };
+ if ( PathCombineW( fullPath, rootPath, trackName ) )
+ {
+ wchar_t canonicalizedPath[ MAX_PATH ] = { 0 };
+ PathCanonicalizeW( canonicalizedPath, fullPath );
+ ret = playlist->OnFile( canonicalizedPath, title, length, extraInfo );
+ }
+ else
+ {
+ ret = ifc_playlistloadercallback::LOAD_CONTINUE;
+ }
+ }
+
+ return ret;
+}
+
+int PLSLoader::Load( const wchar_t *filename, ifc_playlistloadercallback *playlist )
+{
+ int x, numfiles;
+ int ext = 0;
+ char fieldbuf[ 100 ] = { 0 };
+ char fnbuf[ FILENAME_SIZE ] = { 0 };
+ char tmp[ MAX_PATH ] = { 0 };
+
+ wchar_t rootPath[ MAX_PATH ] = { 0 };
+ const wchar_t *callbackPath = playlist->GetBasePath();
+ if ( callbackPath )
+ lstrcpynW( rootPath, callbackPath, MAX_PATH );
+ else
+ {
+ lstrcpynW( rootPath, filename, MAX_PATH );
+ PathRemoveFileSpecW( rootPath );
+ }
+
+ tmp[ 0 ] = (char)rootPath[ 0 ];
+ tmp[ 1 ] = (char)rootPath[ 1 ];
+ tmp[ 2 ] = (char)rootPath[ 2 ];
+
+ AutoChar fn( filename );
+
+ numfiles = GetPrivateProfileIntA( "playlist", "NumberOfEntries", 0, fn );
+ ext = GetPrivateProfileIntA( "playlist", "Version", 1, fn );
+ if ( numfiles == 0 )
+ return IFC_PLAYLISTLOADER_FAILED;
+
+ for ( x = 1; x <= numfiles; x++ )
+ {
+ int flen = -1;
+ char ftitle[ FILETITLE_SIZE ] = "";
+ StringCchPrintfA( fieldbuf, 100, "File%d", x );
+ GetPrivateProfileStringA( "playlist", fieldbuf, "", fnbuf, FILENAME_SIZE, fn );
+ if ( ext )
+ {
+ StringCchPrintfA( fieldbuf, 100, "Title%d", x );
+ GetPrivateProfileStringA( "playlist", fieldbuf, "", ftitle, FILETITLE_SIZE, fn );
+ StringCchPrintfA( fieldbuf, 100, "Length%d", x );
+ flen = GetPrivateProfileIntA( "playlist", fieldbuf, -1, fn );
+ }
+
+ if ( *fnbuf )
+ {
+ char *p;
+ char buf[ 512 ] = { 0 };
+
+ p = fnbuf;
+
+ if ( strncmp( p, "\\\\", 2 ) && strncmp( p + 1, ":\\", 2 ) && !strstr( p, ":/" ) )
+ {
+ if ( p[ 0 ] == '\\' )
+ {
+ buf[ 0 ] = tmp[ 0 ];
+ buf[ 1 ] = tmp[ 1 ];
+ lstrcpynA( buf + 2, p, 510 );
+ buf[ 511 ] = 0;
+ p = buf;
+ }
+ }
+
+ PLSInfo info( filename, x );
+
+ int ret;
+ if ( ftitle[ 0 ] )
+ {
+ ret = OnFileHelper( playlist, AutoWide( p ), AutoWide( ftitle ), flen * 1000, rootPath, &info );
+ }
+ else
+ {
+ ret = OnFileHelper( playlist, AutoWide( p ), 0, -1, rootPath, &info );
+ }
+
+ if ( ret != ifc_playlistloadercallback::LOAD_CONTINUE )
+ {
+ break;
+ }
+ }
+ }
+
+ return IFC_PLAYLISTLOADER_SUCCESS;
+}
+
+#define CBCLASS PLSLoader
+START_DISPATCH;
+CB( IFC_PLAYLISTLOADER_LOAD, Load )
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/playlist/PLSLoader.h b/Src/playlist/PLSLoader.h
new file mode 100644
index 00000000..ebff25a0
--- /dev/null
+++ b/Src/playlist/PLSLoader.h
@@ -0,0 +1,22 @@
+#ifndef NULLSOFT_PLAYLIST_PLSLOADER_H
+#define NULLSOFT_PLAYLIST_PLSLOADER_H
+
+#include "ifc_playlistloader.h"
+#include <windows.h>
+
+class PLSLoader : public ifc_playlistloader
+{
+public:
+ PLSLoader() {}
+ virtual ~PLSLoader() {}
+
+ int Load( const wchar_t *filename, ifc_playlistloadercallback *playlist );
+
+protected:
+ RECVS_DISPATCH;
+
+private:
+ int OnFileHelper( ifc_playlistloadercallback *playlist, const wchar_t *trackName, const wchar_t *title, int length, const wchar_t *rootPath, ifc_plentryinfo *extraInfo );
+};
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/PLSWriter.cpp b/Src/playlist/PLSWriter.cpp
new file mode 100644
index 00000000..e3f35ed5
--- /dev/null
+++ b/Src/playlist/PLSWriter.cpp
@@ -0,0 +1,36 @@
+#include "PLSWriter.h"
+#include "../nu/AutoChar.h"
+
+PLSWriter::PLSWriter() : fp(0), numEntries(0)
+{
+}
+
+int PLSWriter::Open(const wchar_t *filename)
+{
+ fp = _wfopen(filename, L"wt");
+ if (!fp)
+ return 0;
+
+ fprintf(fp, "[playlist]\r\n");
+
+ return 1;
+}
+
+void PLSWriter::Write(const wchar_t *filename)
+{
+ fwprintf(fp, L"File%d=%s\r\n", (int)++numEntries, filename);
+}
+
+void PLSWriter::Write(const wchar_t *filename, const wchar_t *title, int length)
+{
+ Write(filename);
+ fwprintf(fp, L"Title%d=%s\r\n", (int)numEntries, title);
+ fprintf(fp, "Length%d=%d\r\n", (int)numEntries, length);
+}
+
+void PLSWriter::Close()
+{
+ fprintf(fp, "NumberOfEntries=%d\r\n", (int)numEntries);
+ fprintf(fp, "Version=2\r\n");
+ fclose(fp);
+} \ No newline at end of file
diff --git a/Src/playlist/PLSWriter.h b/Src/playlist/PLSWriter.h
new file mode 100644
index 00000000..868a3ac8
--- /dev/null
+++ b/Src/playlist/PLSWriter.h
@@ -0,0 +1,21 @@
+#ifndef NULLSOFT_PLSWRITERH
+#define NULLSOFT_PLSWRITERH
+
+#include <stdio.h>
+#include "PlaylistWriter.h"
+class PLSWriter : public PlaylistWriter
+{
+public:
+ PLSWriter();
+ int Open(const wchar_t *filename);
+ void Write(const wchar_t *filename);
+ void Write(const wchar_t *filename, const wchar_t *title, int length);
+ void Write( const wchar_t *p_filename, const wchar_t *p_title, const wchar_t *p_extended_infos, int p_length ) override
+ {};
+ void Close();
+private:
+ size_t numEntries;
+ FILE *fp;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/Playlist.cpp b/Src/playlist/Playlist.cpp
new file mode 100644
index 00000000..4c0dd564
--- /dev/null
+++ b/Src/playlist/Playlist.cpp
@@ -0,0 +1,230 @@
+#include "main.h"
+#include "Playlist.h"
+#include <algorithm>
+#include "../nu/AutoChar.h"
+#include "../Winamp/strutil.h"
+
+void Playlist::Clear()
+{
+ for ( pl_entry *entry : entries )
+ delete entry;
+
+ entries.clear();
+}
+
+int Playlist::OnFile( const wchar_t *p_filename, const wchar_t *p_title, int p_lengthInMS, ifc_plentryinfo *p_info )
+{
+ entries.push_back( new pl_entry( p_filename, p_title, p_lengthInMS, p_info ) );
+
+ return ifc_playlistloadercallback::LOAD_CONTINUE;
+}
+
+void Playlist::AppendWithInfo( const wchar_t *p_filename, const wchar_t *p_title, int p_lengthInMS )
+{
+ entries.push_back( new pl_entry( p_filename, p_title, p_lengthInMS ) );
+}
+
+void Playlist::Insert( size_t p_index, const wchar_t *p_filename, const wchar_t *p_title, int p_lengthInMS )
+{
+ entries.insert( entries.begin() + p_index, new pl_entry( p_filename, p_title, p_lengthInMS ) );
+}
+
+Playlist::~Playlist()
+{
+ Clear();
+}
+
+
+size_t Playlist::GetNumItems()
+{
+ return entries.size();
+}
+
+size_t Playlist::GetItem( size_t item, wchar_t *filename, size_t filenameCch )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->GetFilename( filename, filenameCch );
+}
+
+size_t Playlist::GetItemTitle( size_t item, wchar_t *title, size_t titleCch )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->GetTitle( title, titleCch );
+}
+
+const wchar_t *Playlist::ItemTitle( size_t item )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->filetitle;
+}
+
+const wchar_t *Playlist::ItemName( size_t item )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->filename;
+}
+
+int Playlist::GetItemLengthMilliseconds( size_t item )
+{
+ if ( item >= entries.size() )
+ return -1;
+
+ return entries[ item ]->GetLengthInMilliseconds();
+}
+
+size_t Playlist::GetItemExtendedInfo( size_t item, const wchar_t *metadata, wchar_t *info, size_t infoCch )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->GetExtendedInfo( metadata, info, infoCch );
+}
+
+int Playlist::Reverse()
+{
+ // TODO: keep a bool flag and just do size-item-1 every time a GetItem* function is called
+ std::reverse( entries.begin(), entries.end() );
+
+ return PLAYLIST_SUCCESS;
+}
+
+int Playlist::Swap( size_t item1, size_t item2 )
+{
+ std::swap( entries[ item1 ], entries[ item2 ] );
+
+ return PLAYLIST_SUCCESS;
+}
+
+class RandMod
+{
+public:
+ RandMod( int ( *_generator )( ) ) : generator( _generator ) {}
+ int operator ()( int n ) { return generator() % n; }
+ int ( *generator )( );
+};
+
+int Playlist::Randomize( int ( *generator )( ) )
+{
+ RandMod randMod( generator );
+ std::random_shuffle( entries.begin(), entries.end(), randMod );
+
+ return PLAYLIST_SUCCESS;
+}
+
+void Playlist::Remove( size_t item )
+{
+ if ( entries.size() > item )
+ entries.erase( entries.begin() + item );
+}
+
+void Playlist::SetItemFilename( size_t item, const wchar_t *filename )
+{
+ if ( item < entries.size() )
+ entries[ item ]->SetFilename( filename );
+}
+
+void Playlist::SetItemTitle( size_t item, const wchar_t *title )
+{
+ if ( item < entries.size() )
+ entries[ item ]->SetTitle( title );
+}
+
+void Playlist::SetItemLengthMilliseconds( size_t item, int length )
+{
+ if ( item < entries.size() )
+ entries[ item ]->SetLengthMilliseconds( length );
+}
+
+static bool PlayList_sortByTitle( pl_entry *&a, pl_entry *&b )
+{
+ int comp = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE /*|NORM_IGNOREKANATYPE*/ | NORM_IGNOREWIDTH, a->filetitle, -1, b->filetitle, -1 );
+
+ return comp == CSTR_LESS_THAN;
+ // TODO: grab this function from winamp - return CompareStringLogical(a.strTitle, b.strTitle)<0;
+}
+
+static bool PlayList_sortByFile( pl_entry *&a, pl_entry *&b ) //const void *a, const void *b)
+{
+ const wchar_t *file1 = PathFindFileNameW( a->filename );
+ const wchar_t *file2 = PathFindFileNameW( b->filename );
+
+ int comp = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | /*NORM_IGNOREKANATYPE |*/ NORM_IGNOREWIDTH, file1, -1, file2, -1 );
+
+ return comp == CSTR_LESS_THAN;
+ // TODO: grab this function from winamp - return FileCompareLogical(file1, file2)<0;
+}
+
+static bool PlayList_sortByDirectory( pl_entry *&a, pl_entry *&b ) // by dir, then by p_title
+{
+ const wchar_t *directory1 = a->filename;
+ const wchar_t *directory2 = b->filename;
+
+ const wchar_t *directoryEnd1 = scanstr_backcW( directory1, L"\\", 0 );
+ const wchar_t *directoryEnd2 = scanstr_backcW( directory2, L"\\", 0 );
+
+ int dirLen1 = (int)( directoryEnd1 - directory1 );
+ int dirLen2 = (int)( directoryEnd2 - directory2 );
+
+ if ( !dirLen1 && !dirLen2 ) // both in the current directory?
+ return PlayList_sortByFile( a, b ); // not optimized, because the function does another scanstr_back, but easy for now :)
+
+ if ( !dirLen1 ) // only the first dir is empty?
+ return true; // sort it first
+
+ if ( !dirLen2 ) // only the second dir empty?
+ return false; // empty dirs go first
+
+#if 0 // TODO: grab this function from winamp
+ int comp = FileCompareLogicalN( directory1, dirLen1, directory2, dirLen2 );
+ if ( comp == 0 )
+ return PlayList_sortByFile( a, b );
+ else
+ return comp < 0;
+#endif
+
+ int comp = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | /*NORM_IGNOREKANATYPE | */NORM_IGNOREWIDTH, directory1, dirLen1, directory2, dirLen2 );
+ if ( comp == CSTR_EQUAL ) // same dir
+ return PlayList_sortByFile( a, b ); // do second sort
+ else // different dirs
+ return comp == CSTR_LESS_THAN;
+}
+
+int Playlist::SortByTitle()
+{
+ std::sort( entries.begin(), entries.end(), PlayList_sortByTitle );
+
+ return 1;
+}
+
+int Playlist::SortByFilename()
+{
+ std::sort( entries.begin(), entries.end(), PlayList_sortByFile );
+
+ return 1;
+}
+
+int Playlist::SortByDirectory()
+{
+ std::sort( entries.begin(), entries.end(), PlayList_sortByDirectory );
+
+ return 1;
+}
+/*
+int Playlist::Move(size_t itemSrc, size_t itemDest)
+{
+ if (itemSrc < itemDest)
+ std::rotate(&entries[itemSrc], &entries[itemSrc], &entries[itemDest]);
+ else
+ if (itemSrc > itemDest)
+ std::rotate(&entries[itemDest], &entries[itemSrc], &entries[itemSrc]);
+ return 1;
+}*/
+
diff --git a/Src/playlist/Playlist.h b/Src/playlist/Playlist.h
new file mode 100644
index 00000000..0368b57c
--- /dev/null
+++ b/Src/playlist/Playlist.h
@@ -0,0 +1,48 @@
+#ifndef NULLSOFT_ML_PLAYLISTS_PLAYLIST_H
+#define NULLSOFT_ML_PLAYLISTS_PLAYLIST_H
+
+#include "ifc_playlist.h"
+
+#include <windows.h> // for MAX_PATH
+#include "pl_entry.h"
+#include "ifc_playlistT.h"
+#include "ifc_playlistloadercallbackT.h"
+#include <vector>
+
+
+class Playlist : public ifc_playlistloadercallbackT<Playlist>, public ifc_playlistT<Playlist>
+{
+public:
+ virtual ~Playlist();
+
+ void Clear();
+ int OnFile( const wchar_t *p_filename, const wchar_t *p_title, int p_lengthInMS, ifc_plentryinfo *p_info );
+ void AppendWithInfo( const wchar_t *p_filename, const wchar_t *p_title, int p_lengthInMS );
+ void Insert( size_t p_index, const wchar_t *p_filename, const wchar_t *p_title, int p_lengthInMS );
+
+ size_t GetNumItems();
+
+ size_t GetItem( size_t item, wchar_t *filename, size_t filenameCch );
+ size_t GetItemTitle( size_t item, wchar_t *title, size_t titleCch );
+ const wchar_t *ItemTitle( size_t item );
+ const wchar_t *ItemName( size_t item );
+ int GetItemLengthMilliseconds( size_t item ); // TODO: maybe microsecond for better resolution?
+ size_t GetItemExtendedInfo( size_t item, const wchar_t *metadata, wchar_t *info, size_t infoCch );
+
+ void SetItemFilename( size_t item, const wchar_t *filename );
+ void SetItemTitle( size_t item, const wchar_t *title );
+ void SetItemLengthMilliseconds( size_t item, int length );
+
+ int Reverse();
+ int Swap( size_t item1, size_t item2 );
+ int Randomize( int ( *generator )( ) );
+ void Remove( size_t item );
+ int SortByTitle();
+ int SortByFilename();
+ int SortByDirectory(); //sorts by directory and then by filename
+
+private:
+ typedef std::vector<pl_entry*> PlaylistEntries;
+ PlaylistEntries entries;
+};
+#endif \ No newline at end of file
diff --git a/Src/playlist/PlaylistCounter.cpp b/Src/playlist/PlaylistCounter.cpp
new file mode 100644
index 00000000..413c133b
--- /dev/null
+++ b/Src/playlist/PlaylistCounter.cpp
@@ -0,0 +1,18 @@
+#include "PlaylistCounter.h"
+
+int PlaylistCounter::OnFile( const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info )
+{
+ // TODO: recursive load?
+ ++count;
+ if ( lengthInMS > 0 )
+ length += lengthInMS;
+
+ return LOAD_CONTINUE;
+}
+
+
+#define CBCLASS PlaylistCounter
+START_DISPATCH;
+CB( IFC_PLAYLISTLOADERCALLBACK_ONFILE_RET, OnFile )
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/playlist/PlaylistCounter.h b/Src/playlist/PlaylistCounter.h
new file mode 100644
index 00000000..0d3fb72d
--- /dev/null
+++ b/Src/playlist/PlaylistCounter.h
@@ -0,0 +1,21 @@
+#ifndef NULLSOFT_PLAYLIST_PLAYLISTCOUNTER_H
+#define NULLSOFT_PLAYLIST_PLAYLISTCOUNTER_H
+
+#include "ifc_playlistloadercallback.h"
+
+class PlaylistCounter : public ifc_playlistloadercallback
+{
+public:
+ PlaylistCounter() {}
+
+ int OnFile( const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info );
+
+ size_t count = 0;
+ uint64_t length = 0;
+
+protected:
+ RECVS_DISPATCH;
+
+};
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/PlaylistManager.cpp b/Src/playlist/PlaylistManager.cpp
new file mode 100644
index 00000000..948048c8
--- /dev/null
+++ b/Src/playlist/PlaylistManager.cpp
@@ -0,0 +1,630 @@
+#include <strsafe.h>
+#include <shlwapi.h>
+#include <algorithm>
+
+
+#include "main.h"
+#include "resource.h"
+#include "PlaylistManager.h"
+#include "ifc_playlistloader.h"
+#include "M3ULoader.h"
+#include "M3UWriter.h"
+#include "PLSWriter.h"
+#include "M3U8Writer.h"
+#include "B4SWriter.h"
+#include "../nu/AutoChar.h"
+#include "Playlist.h"
+#include "../playlist/svc_playlisthandler.h"
+#include "../playlist/Handler.h"
+#include "../nu/AutoWide.h"
+#include "Playlist.h"
+#include "api/service/services.h"
+#include "api__playlist.h"
+#include "api/service/waservicefactory.h"
+#include "PlaylistCounter.h"
+#include "ifc_playlistloadercallback.h"
+#include "ifc_playlistdirectorycallback.h"
+
+#include "..\WAT\WAT.h"
+
+class NoRecurseCallback : public ifc_playlistdirectorycallback
+{
+public:
+ NoRecurseCallback( ifc_playlistdirectorycallback *_callback ) : callback( _callback ) {}
+ bool ShouldRecurse( const wchar_t *path ) { return false; }
+ bool ShouldLoad( const wchar_t *filename ) { return callback->ShouldLoad( filename ); }
+
+ ifc_playlistdirectorycallback *callback;
+
+protected:
+ RECVS_DISPATCH;
+};
+
+#define CBCLASS NoRecurseCallback
+START_DISPATCH;
+CB( IFC_PLAYLISTDIRECTORYCALLBACK_SHOULDRECURSE, ShouldRecurse )
+CB( IFC_PLAYLISTDIRECTORYCALLBACK_SHOULDLOAD, ShouldLoad )
+END_DISPATCH;
+#undef CBCLASS
+
+
+static void MakeRelativePathName( const wchar_t *filename, wchar_t *outFile, size_t cch, const wchar_t *path )
+{
+ wchar_t outPath[ MAX_PATH ] = { 0 };
+
+ int common = PathCommonPrefixW( path, filename, outPath );
+ if ( common && common == wcslen( path ) )
+ {
+ PathAddBackslashW( outPath );
+ const wchar_t *p = filename + wcslen( outPath );
+ lstrcpynW( outFile, p, (int)cch );
+ }
+ else if ( !PathIsUNCW( filename ) && PathIsSameRootW( filename, path ) )
+ {
+ if ( outFile[ 1 ] == ':' )
+ lstrcpynW( outFile, filename + 2, (int)cch );
+ }
+}
+
+static void PlayList_makerelative( const wchar_t *base, wchar_t *filename, size_t cch )
+{
+ MakeRelativePathName( filename, filename, cch, base );
+}
+
+
+PlaylistManager playlistManager;
+
+struct LoaderPair
+{
+ ifc_playlistloader *loader;
+ svc_playlisthandler *handler;
+};
+
+static LoaderPair CreateLoader( const wchar_t *filename )
+{
+ LoaderPair ret = { 0, 0 };
+ int n = 0;
+ waServiceFactory *sf = 0;
+
+ while ( sf = WASABI_API_SVC->service_enumService( WaSvc::PLAYLISTHANDLER, n++ ) )
+ {
+ svc_playlisthandler *handler = static_cast<svc_playlisthandler *>( sf->getInterface() );
+ if ( handler )
+ {
+ if ( handler->SupportedFilename( filename ) == SVC_PLAYLISTHANDLER_SUCCESS )
+ {
+ ret.loader = handler->CreateLoader( filename );
+ ret.handler = handler;
+ break;
+ }
+ else
+ {
+ sf->releaseInterface( handler );
+ }
+ }
+ }
+
+ // TODO: sniff file if no one claims it
+ return ret;
+}
+
+void DestroyLoader( LoaderPair &loader )
+{
+ loader.handler->ReleaseLoader( loader.loader );
+}
+
+// a simple loader...
+int PlaylistManager::Load( const wchar_t *filename, ifc_playlistloadercallback *playlist )
+{
+ LoaderPair loaderPair = CreateLoader( filename );
+ ifc_playlistloader *loader = loaderPair.loader;
+
+ if ( !loader )
+ return PLAYLISTMANAGER_LOAD_NO_LOADER; // failed to find a loader
+
+ // TODO: make our own ifc_playlistloadercallback, so we can handle nested playlists
+ int res = loader->Load( filename, playlist );
+ DestroyLoader( loaderPair );
+
+ if ( res != IFC_PLAYLISTLOADER_SUCCESS ) // TODO: switch on the error code and return a more specific error
+ return PLAYLISTMANAGER_LOAD_LOADER_OPEN_FAILED;
+
+ return PLAYLISTMANAGER_SUCCESS;
+}
+
+int PlaylistManager::LoadAs( const wchar_t *filename, const wchar_t *ext, ifc_playlistloadercallback *playlist )
+{
+ LoaderPair loaderPair = CreateLoader( ext );
+ ifc_playlistloader *loader = loaderPair.loader;
+
+ if ( !loader )
+ return PLAYLISTMANAGER_LOAD_NO_LOADER; // failed to find a loader
+
+ // TODO: make our own ifc_playlistloadercallback, so we can handle nested playlists
+ int res = loader->Load( filename, playlist );
+ DestroyLoader( loaderPair );
+
+ if ( res != IFC_PLAYLISTLOADER_SUCCESS ) // TODO: switch on the error code and return a more specific error
+ return PLAYLISTMANAGER_LOAD_LOADER_OPEN_FAILED;
+
+ return PLAYLISTMANAGER_SUCCESS;
+}
+
+int PlaylistManager::LoadFromDialog( const wchar_t *fns, ifc_playlistloadercallback *playlist )
+{
+ wchar_t buf[ MAX_PATH ] = { 0 };
+ const wchar_t *path = fns;
+ fns += wcslen( fns ) + 1;
+
+ while ( fns && *fns )
+ {
+ if ( *path )
+ PathCombineW( buf, path, fns );
+ else
+ StringCchCopyW( buf, MAX_PATH, fns );
+
+ if ( Load( buf, playlist ) != PLAYLISTMANAGER_SUCCESS )
+ {
+ if ( playlist->OnFile( buf, 0, -1, 0 ) != ifc_playlistloadercallback::LOAD_CONTINUE )
+ return PLAYLIST_SUCCESS;
+ }
+
+ fns += wcslen( fns ) + 1;
+ }
+ return PLAYLIST_SUCCESS;
+}
+
+int PlaylistManager::LoadFromANSIDialog( const char *fns, ifc_playlistloadercallback *playlist )
+{
+ char buf[ MAX_PATH ] = { 0 };
+ const char *path = fns;
+ fns += lstrlenA( fns ) + 1;
+
+ while ( fns && *fns )
+ {
+ if ( *path )
+ PathCombineA( buf, path, fns );
+ else
+ lstrcpynA( buf, fns, MAX_PATH );
+
+ AutoWide wideFn( buf );
+ if ( Load( wideFn, playlist ) != PLAYLISTMANAGER_SUCCESS )
+ {
+ if ( playlist->OnFile( wideFn, 0, -1, 0 ) != ifc_playlistloadercallback::LOAD_CONTINUE )
+ return PLAYLIST_SUCCESS;
+ }
+
+ fns += lstrlenA( fns ) + 1;
+ }
+ return PLAYLIST_SUCCESS;
+}
+
+
+int PlaylistManager::Save( const wchar_t *filename, ifc_playlist *playlist )
+{
+ const wchar_t *ext = PathFindExtensionW( filename );
+ PlaylistWriter *writer = 0;
+
+ if ( !lstrcmpiW( ext, L".M3U" ) )
+ writer = new M3UWriter;
+ else if ( !lstrcmpiW( ext, L".M3U8" ) )
+ writer = new M3U8Writer;
+ else if ( !lstrcmpiW( ext, L".PLS" ) )
+ writer = new PLSWriter;
+ else if ( !lstrcmpiW( ext, L".B4S" ) )
+ writer = new B4SWriter;
+ else
+ return PLAYLISTMANAGER_FAILED;
+
+ wchar_t base[ MAX_PATH ] = { 0 };
+ StringCchCopyW( base, MAX_PATH, filename );
+ PathRemoveFileSpecW( base );
+ PathRemoveBackslashW( base );
+
+ if ( !writer->Open( filename ) )
+ {
+ delete writer;
+
+ return PLAYLISTMANAGER_FAILED;
+ }
+
+ size_t numItems = playlist->GetNumItems();
+ wchar_t itemname[ FILENAME_SIZE ] = { 0 };
+ wchar_t title[ FILETITLE_SIZE ] = { 0 };
+ wchar_t cloud_info[ 512 ] = { 0 };
+ int length = 0;
+
+ wchar_t l_tvg_id[ 10 ] = { 0 };
+ wchar_t l_tvg_name[ FILETITLE_SIZE ] = { 0 };
+ wchar_t l_tvg_logo[ 512 ] = { 0 };
+ wchar_t l_group_title[ 64 ] = { 0 };
+
+ wchar_t l_ext[ 10 ] = { 0 };
+
+ wa::strings::wa_string l_extented_infos_line( "" );
+
+ for ( size_t i = 0; i != numItems; i++ )
+ {
+ if ( playlist->GetItem( i, itemname, FILENAME_SIZE ) )
+ {
+ //PlayList_makerelative( base, itemname, FILENAME_SIZE );
+
+ // this is used to preserve 'cloud' specific data in playlists
+ // and should only get a response from a cloud-based ml_playlist
+ if ( playlist->GetItemExtendedInfo( i, L"cloud", cloud_info, 512 ) )
+ {
+ writer->Write( cloud_info );
+ }
+
+
+ l_extented_infos_line.clear();
+
+ if ( playlist->GetItemExtendedInfo( i, L"tvg-name", l_tvg_name, FILETITLE_SIZE ) )
+ {
+ playlist->GetItemExtendedInfo( i, L"tvg-id", l_tvg_id, 10 );
+ playlist->GetItemExtendedInfo( i, L"tvg-logo", l_tvg_logo, 512 );
+ playlist->GetItemExtendedInfo( i, L"group-title", l_group_title, 64 );
+
+ l_extented_infos_line = L"tvg-id";
+ l_extented_infos_line.append( L"=\"" );
+ l_extented_infos_line.append( l_tvg_id );
+ l_extented_infos_line.append( L"\" " );
+
+ l_extented_infos_line.append( L"tvg-name" );
+ l_extented_infos_line.append( L"=\"" );
+ l_extented_infos_line.append( l_tvg_name );
+ l_extented_infos_line.append( L"\" " );
+
+ l_extented_infos_line.append( L"tvg-logo" );
+ l_extented_infos_line.append( L"=\"" );
+ l_extented_infos_line.append( l_tvg_logo );
+ l_extented_infos_line.append( L"\" " );
+
+ l_extented_infos_line.append( L"group-title" );
+ l_extented_infos_line.append( L"=\"" );
+ l_extented_infos_line.append( l_group_title );
+ l_extented_infos_line.append( L"\" " );
+ }
+
+ wa::strings::wa_string l_item_name( itemname );
+
+ if ( l_item_name.contains( "://" ) && playlist->GetItemExtendedInfo(i, L"ext", l_ext, 10) )
+ {
+ l_extented_infos_line = L"ext";
+ l_extented_infos_line.append( L"=\"" );
+ l_extented_infos_line.append( l_ext );
+ l_extented_infos_line.append( L"\"" );
+ }
+
+ if ( playlist->GetItemTitle( i, title, FILETITLE_SIZE ) )
+ {
+ length = playlist->GetItemLengthMilliseconds( i );
+
+ if ( l_extented_infos_line.empty() )
+ writer->Write( itemname, title, length / 1000 );
+ else
+ writer->Write( itemname, title, l_extented_infos_line.GetW().c_str(), length / 1000);
+ }
+ else
+ writer->Write( itemname );
+ }
+ }
+
+ writer->Close();
+ delete writer;
+
+ return PLAYLISTMANAGER_SUCCESS;
+}
+
+
+size_t PlaylistManager::Copy( const wchar_t *destFn, const wchar_t *srcFn )
+{
+ Playlist copy;
+
+ Load( srcFn, &copy );
+ Save( destFn, &copy );
+
+ return copy.GetNumItems();
+}
+
+
+size_t PlaylistManager::CountItems( const wchar_t *filename )
+{
+ LoaderPair loaderPair = CreateLoader( filename );
+ ifc_playlistloader *loader = loaderPair.loader;
+
+ if ( !loader )
+ return 0;
+
+ PlaylistCounter counter;
+ loader->Load( filename, &counter );
+
+ DestroyLoader( loaderPair );
+ return counter.count;
+}
+
+
+int PlaylistManager::GetLengthMilliseconds( const wchar_t *filename )
+{
+ LoaderPair loaderPair = CreateLoader( filename );
+ ifc_playlistloader *loader = loaderPair.loader;
+
+ if ( !loader )
+ return 0;
+
+ PlaylistCounter counter;
+ loader->Load( filename, &counter );
+ DestroyLoader( loaderPair );
+ return (int)counter.length;
+}
+
+uint64_t PlaylistManager::GetLongLengthMilliseconds( const wchar_t *filename )
+{
+ LoaderPair loaderPair = CreateLoader( filename );
+ ifc_playlistloader *loader = loaderPair.loader;
+
+ if ( !loader )
+ return 0;
+
+ PlaylistCounter counter;
+ loader->Load( filename, &counter );
+ DestroyLoader( loaderPair );
+ return counter.length;
+}
+
+
+void PlaylistManager::Randomize( ifc_playlist *playlist )
+{
+ if ( playlist->Randomize( warand ) == PLAYLIST_UNIMPLEMENTED )
+ {
+ // TODO: do it the hard way
+ }
+}
+
+void PlaylistManager::Reverse( ifc_playlist *playlist )
+{
+ if ( playlist->Reverse() == PLAYLIST_UNIMPLEMENTED )
+ {
+ // TODO: do it the hard way
+ }
+}
+
+
+void PlaylistManager::LoadDirectory( const wchar_t *directory, ifc_playlistloadercallback *callback, ifc_playlistdirectorycallback *dirCallback )
+{
+ WIN32_FIND_DATAW found = { 0 };
+ wchar_t filespec[ MAX_PATH ] = { 0 };
+ PathCombineW( filespec, directory, L"*.*" );
+
+ HANDLE i = FindFirstFileW( filespec, &found );
+ if ( i != INVALID_HANDLE_VALUE )
+ {
+ do
+ {
+ // if it's another folder, then we might want to recurse into it
+ if ( ( found.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) // if it's a directory
+ && wcscmp( found.cFileName, L"." ) && wcscmp( found.cFileName, L".." ) // but not . or ..
+ && ( !dirCallback || dirCallback->ShouldRecurse( found.cFileName ) ) ) // and we're allowed to recurse
+ {
+ if ( PathCombineW( filespec, directory, found.cFileName ) )
+ {
+ LoadDirectory( filespec, callback, dirCallback );
+ }
+ }
+
+ if ( !( found.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) )
+ {
+ const wchar_t *ext = PathFindExtensionW( found.cFileName );
+ if ( ext[ 0 ] )
+ {
+ if ( !_wcsicmp( ext, L".lnk" ) )
+ {
+ wchar_t thisf[ MAX_PATH ] = { 0 };
+ wchar_t temp2[ MAX_PATH ] = { 0 };
+ PathCombineW( temp2, directory, found.cFileName );
+ if ( ResolveShortCut( NULL, temp2, thisf ) && GetLongPathNameW( thisf, temp2, MAX_PATH ) && lstrcmpiW( temp2, directory ) )
+ {
+ WIN32_FIND_DATAW d2 = { 0 };
+ if ( IsUrl( temp2 ) && ( !dirCallback || dirCallback->ShouldLoad( temp2 ) ) )
+ {
+ if ( callback->OnFile( temp2, 0, -1, 0 ) != ifc_playlistloadercallback::LOAD_CONTINUE )
+ break;
+ }
+ else
+ {
+ HANDLE h2 = FindFirstFileW( temp2, &d2 );
+ if ( h2 != INVALID_HANDLE_VALUE )
+ {
+ if ( !( d2.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) )
+ {
+ if ( !dirCallback || dirCallback->ShouldLoad( temp2 ) )
+ {
+ if ( callback->OnFile( temp2, 0, -1, 0 ) != ifc_playlistloadercallback::LOAD_CONTINUE )
+ {
+ FindClose( h2 );
+ break;
+ }
+ }
+ }
+ else
+ {
+ // recursively load a shortcut w/o fear of infinite recursion
+ NoRecurseCallback noRecurse( dirCallback );
+ LoadDirectory( temp2, callback, &noRecurse );
+ }
+ FindClose( h2 );
+ }
+ }
+ }
+ }
+ else // !shortcut
+ {
+
+ if ( PathCombineW( filespec, directory, found.cFileName ) &&
+ ( !dirCallback || dirCallback->ShouldLoad( filespec ) ) )
+ {
+ if ( callback->OnFile( filespec, 0, -1, 0 ) != ifc_playlistloadercallback::LOAD_CONTINUE )
+ break;
+ }
+ }
+ }
+ }
+ } while ( FindNextFileW( i, &found ) );
+ FindClose( i );
+ }
+}
+
+bool PlaylistManager::CanLoad( const wchar_t *filename )
+{
+ int n = 0;
+ waServiceFactory *sf = 0;
+ while ( sf = WASABI_API_SVC->service_enumService( WaSvc::PLAYLISTHANDLER, n++ ) )
+ {
+ svc_playlisthandler *handler = static_cast<svc_playlisthandler *>( sf->getInterface() );
+ if ( handler )
+ {
+ if ( handler->SupportedFilename( filename ) == SVC_PLAYLISTHANDLER_SUCCESS )
+ {
+ sf->releaseInterface( handler );
+ return true;
+ }
+ else
+ {
+ sf->releaseInterface( handler );
+ }
+ }
+ }
+ return false;
+}
+
+void PlaylistManager::GetExtensionList( wchar_t *extensionList, size_t extensionListCch )
+{
+ extensionList[ 0 ] = 0;
+
+ bool first = true;
+
+ int n = 0, extListCch = (int)extensionListCch;
+ wchar_t *extList = extensionList;
+ waServiceFactory *sf = 0;
+ while ( sf = WASABI_API_SVC->service_enumService( WaSvc::PLAYLISTHANDLER, n++ ) )
+ {
+ svc_playlisthandler *handler = static_cast<svc_playlisthandler *>( sf->getInterface() );
+ if ( handler )
+ {
+ const wchar_t *ext = 0;
+ int k = 0;
+ while ( ext = handler->EnumerateExtensions( k++ ) )
+ {
+ if ( first )
+ StringCchCatExW( extensionList, extensionListCch, L"*.", &extensionList, &extensionListCch, 0 );
+ else
+ StringCchCatExW( extensionList, extensionListCch, L";*.", &extensionList, &extensionListCch, 0 );
+
+ first = false;
+
+ StringCchCatExW( extensionList, extensionListCch, ext, &extensionList, &extensionListCch, 0 );
+ }
+ sf->releaseInterface( handler );
+ }
+ }
+ CharUpperBuffW( extList, extListCch );
+}
+
+void PlaylistManager::GetFilterList( wchar_t *extensionList, size_t extensionListCch )
+{
+ extensionListCch--; // this needs to be DOUBLE null terminated, so we'll make sure there's room
+
+ StringCchCopyExW( extensionList, extensionListCch, WASABI_API_LNGSTRINGW( IDS_ALL_PLAYLIST_TYPES ), &extensionList, &extensionListCch, 0 );
+ extensionListCch--;
+ extensionList++;
+
+ GetExtensionList( extensionList, extensionListCch );
+
+ extensionListCch -= ( wcslen( extensionList ) + 1 );
+ extensionList += wcslen( extensionList ) + 1;
+
+ int n = 0;
+ waServiceFactory *sf = 0;
+ while ( sf = WASABI_API_SVC->service_enumService( WaSvc::PLAYLISTHANDLER, n++ ) )
+ {
+ svc_playlisthandler *handler = static_cast<svc_playlisthandler *>( sf->getInterface() );
+ if ( handler )
+ {
+ const wchar_t *name = handler->GetName();
+ if ( !name )
+ name = WASABI_API_LNGSTRINGW( IDS_PLAYLIST );
+
+ StringCchCopyExW( extensionList, extensionListCch, name, &extensionList, &extensionListCch, 0 );
+ extensionList++;
+ extensionListCch--;
+
+ bool first = true;
+ const wchar_t *ext = 0;
+ int k = 0;
+ while ( ext = handler->EnumerateExtensions( k++ ) )
+ {
+ if ( first )
+ StringCchCopyExW( extensionList, extensionListCch, L"*.", &extensionList, &extensionListCch, 0 );
+ else
+ StringCchCatExW( extensionList, extensionListCch, L";*.", &extensionList, &extensionListCch, 0 );
+
+ first = false;
+
+ StringCchCatExW( extensionList, extensionListCch, ext, &extensionList, &extensionListCch, 0 );
+ }
+ extensionList++;
+ extensionListCch--;
+
+ sf->releaseInterface( handler );
+ }
+ }
+
+ extensionList[ 0 ] = 0; // ok because we reserved the room for it above
+}
+
+const wchar_t *PlaylistManager::EnumExtensions( size_t num )
+{
+ int n = 0;
+ int total = 0;
+ waServiceFactory *sf = 0;
+ while ( sf = WASABI_API_SVC->service_enumService( WaSvc::PLAYLISTHANDLER, n++ ) )
+ {
+ svc_playlisthandler *handler = static_cast<svc_playlisthandler *>( sf->getInterface() );
+ if ( handler )
+ {
+ const wchar_t *ext = 0;
+ int k = 0;
+ while ( ext = handler->EnumerateExtensions( k++ ) )
+ {
+ if ( total++ == num )
+ return ext;
+ }
+
+ sf->releaseInterface( handler );
+ }
+ }
+
+ return 0;
+}
+
+
+#define CBCLASS PlaylistManager
+START_DISPATCH;
+CB( API_PLAYLISTMANAGER_LOAD, Load )
+CB( API_PLAYLISTMANAGER_LOADAS, LoadAs )
+CB( API_PLAYLISTMANAGER_LOADNULLDELIMITED, LoadFromDialog )
+CB( API_PLAYLISTMANAGER_LOADNULLDELIMITED_ANSI, LoadFromANSIDialog )
+CB( API_PLAYLISTMANAGER_SAVE, Save )
+CB( API_PLAYLISTMANAGER_COPY, Copy )
+CB( API_PLAYLISTMANAGER_COUNT, CountItems )
+CB( API_PLAYLISTMANAGER_GETLENGTH, GetLengthMilliseconds )
+CB( API_PLAYLISTMANAGER_GETLONGLENGTH, GetLongLengthMilliseconds )
+VCB( API_PLAYLISTMANAGER_RANDOMIZE, Randomize )
+VCB( API_PLAYLISTMANAGER_REVERSE, Reverse )
+VCB( API_PLAYLISTMANAGER_LOADDIRECTORY, LoadDirectory )
+CB( API_PLAYLISTMANAGER_CANLOAD, CanLoad )
+VCB( API_PLAYLISTMANAGER_GETEXTENSIONLIST, GetExtensionList )
+VCB( API_PLAYLISTMANAGER_GETFILTERLIST, GetFilterList )
+CB( API_PLAYLISTMANAGER_ENUMEXTENSION, EnumExtensions )
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/playlist/PlaylistManager.h b/Src/playlist/PlaylistManager.h
new file mode 100644
index 00000000..0ac53a35
--- /dev/null
+++ b/Src/playlist/PlaylistManager.h
@@ -0,0 +1,42 @@
+#ifndef NULLSOFT_ML_PLAYLISTS_PLAYLIST_MANAGER_H
+#define NULLSOFT_ML_PLAYLISTS_PLAYLIST_MANAGER_H
+
+#include "api_playlistmanager.h"
+
+class PlaylistManager : public api_playlistmanager
+{
+public:
+ int Load( const wchar_t *filename, ifc_playlistloadercallback *playlist );
+ int LoadAs( const wchar_t *filename, const wchar_t *ext, ifc_playlistloadercallback *playlist );
+ int LoadFromDialog( const wchar_t *fns, ifc_playlistloadercallback *playlist );
+ int LoadFromANSIDialog( const char *fns, ifc_playlistloadercallback *playlist );
+
+ int Save( const wchar_t *filename, ifc_playlist *playlist );
+
+ size_t Copy( const wchar_t *destFn, const wchar_t *srcFn ); // returns number of items copied
+
+ size_t CountItems( const wchar_t *filename );
+
+ int GetLengthMilliseconds( const wchar_t *filename );
+ uint64_t GetLongLengthMilliseconds( const wchar_t *filename );
+
+ void Randomize( ifc_playlist *playlist );
+ void Reverse( ifc_playlist *playlist );
+
+ void LoadDirectory( const wchar_t *directory, ifc_playlistloadercallback *callback, ifc_playlistdirectorycallback *dirCallback );
+
+ bool CanLoad( const wchar_t *filename );
+
+ void GetExtensionList( wchar_t *extensionList, size_t extensionListCch );
+ void GetFilterList( wchar_t *extensionList, size_t extensionListCch );
+
+ const wchar_t *EnumExtensions( size_t num );
+
+protected:
+ RECVS_DISPATCH;
+
+};
+
+extern PlaylistManager playlistManager;
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/PlaylistWriter.h b/Src/playlist/PlaylistWriter.h
new file mode 100644
index 00000000..74d4b9f0
--- /dev/null
+++ b/Src/playlist/PlaylistWriter.h
@@ -0,0 +1,17 @@
+#ifndef NULLSOFT_PLAYLIST_PLAYLISTWRITER_H
+#define NULLSOFT_PLAYLIST_PLAYLISTWRITER_H
+
+// probably not the final interface, so we won't dispatch it yet
+
+class PlaylistWriter
+{
+public:
+ virtual ~PlaylistWriter() {}
+ virtual int Open( const wchar_t *filename ) = 0;
+ virtual void Write( const wchar_t *filename ) = 0;
+ virtual void Write( const wchar_t *filename, const wchar_t *title, int length ) = 0;
+ virtual void Write( const wchar_t *filename, const wchar_t *title, const wchar_t *p_extended_infos, int length ) = 0;
+ virtual void Close() = 0;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/Playlists.cpp b/Src/playlist/Playlists.cpp
new file mode 100644
index 00000000..71fd1a0e
--- /dev/null
+++ b/Src/playlist/Playlists.cpp
@@ -0,0 +1,746 @@
+#include <algorithm>
+#include "playlists.h"
+#include "api__playlist.h"
+#include "PlaylistsXML.h"
+#include <shlwapi.h>
+#include <limits.h>
+#include <strsafe.h>
+#pragma comment(lib, "Rpcrt4")
+using namespace Nullsoft::Utility;
+
+/*
+benski> Notes to maintainers
+be sure to call DelayLoad() before doing anything.
+This is mainly done because the XML parsing service isn't guaranteed to be registered before this service.
+It also improves load time.
+*/
+
+/* --------------------------------------------- */
+
+PlaylistInfo::PlaylistInfo()
+{
+ filename[0] = 0;
+ title[0] = 0;
+ length = 0;
+ numItems = 0;
+ iTunesID = 0;
+ cloud = 0;
+
+ UuidCreate(&guid);
+}
+
+PlaylistInfo::PlaylistInfo( const wchar_t *_filename, const wchar_t *_title, GUID playlist_guid )
+{
+ StringCbCopyW( filename, sizeof( filename ), _filename );
+ if ( _title )
+ StringCbCopyW( title, sizeof( title ), _title );
+ else
+ title[ 0 ] = 0;
+
+ length = 0;
+ numItems = 0;
+
+ if ( playlist_guid == INVALID_GUID )
+ UuidCreate( &guid );
+ else
+ guid = playlist_guid;
+
+ iTunesID = 0;
+ cloud = 0;
+}
+
+PlaylistInfo::PlaylistInfo( const PlaylistInfo &copy )
+{
+ StringCbCopyW( filename, sizeof( filename ), copy.filename );
+ StringCbCopyW( title, sizeof( title ), copy.title );
+
+ length = copy.length;
+ numItems = copy.numItems;
+ guid = copy.guid;
+ iTunesID = copy.iTunesID;
+ cloud = copy.cloud;
+}
+
+/* --------------------------------------------- */
+Playlists::Playlists()
+{
+ iterator = 0;
+ triedLoaded = false;
+ loaded = false;
+ dirty = false;
+}
+
+bool Playlists::DelayLoad()
+{
+ if ( triedLoaded )
+ return loaded;
+
+ PlaylistsXML loader( this );
+
+ const wchar_t *g_path = WASABI_API_APP->path_getUserSettingsPath();
+ wchar_t playlistsFilename[ MAX_PATH ] = { 0 };
+ wchar_t oldPlaylistsFilename[ MAX_PATH ] = { 0 };
+ wchar_t newPlaylistsFolder[ MAX_PATH ] = { 0 };
+
+ PathCombineW( playlistsFilename, g_path, L"plugins" );
+ PathAppendW( playlistsFilename, L"ml" );
+ PathAppendW( playlistsFilename, L"playlists" );
+ CreateDirectoryW( playlistsFilename, NULL );
+ lstrcpynW( newPlaylistsFolder, playlistsFilename, MAX_PATH );
+ PathAppendW( playlistsFilename, L"playlists.xml" );
+
+ PathCombineW( oldPlaylistsFilename, g_path, L"plugins" );
+ PathAppendW( oldPlaylistsFilename, L"ml" );
+ PathAppendW( oldPlaylistsFilename, L"playlists.xml" );
+
+ bool migrated = false;
+ if ( PathFileExistsW( oldPlaylistsFilename ) && !PathFileExistsW( playlistsFilename ) )
+ {
+ if ( MoveFileW( oldPlaylistsFilename, playlistsFilename ) )
+ {
+ migrated = true;
+ PathRemoveFileSpecW( oldPlaylistsFilename );
+ }
+ }
+
+ switch ( loader.LoadFile( playlistsFilename ) )
+ {
+ case PLAYLISTSXML_SUCCESS:
+ loaded = true;
+ triedLoaded = true;
+ if ( AGAVE_API_STATS )
+ AGAVE_API_STATS->SetStat( api_stats::PLAYLIST_COUNT, (int)playlists.size() );
+
+ if ( playlists.size() && migrated )
+ {
+ for ( PlaylistInfo l_playlist : playlists )
+ {
+ wchar_t path[ MAX_PATH ] = { 0 }, file[ MAX_PATH ] = { 0 };
+ lstrcpynW( file, l_playlist.filename, MAX_PATH );
+ PathStripPathW( file );
+ PathCombineW( path, oldPlaylistsFilename, file );
+ if ( PathFileExistsW( path ) )
+ {
+ wchar_t new_path[ MAX_PATH ] = { 0 };
+ PathCombineW( new_path, newPlaylistsFolder, file );
+ MoveFileW( path, new_path );
+ }
+ }
+ dirty = true;
+ Flush();
+ }
+
+ break;
+ case PLAYLISTSXML_NO_PARSER:
+ // if there's XML parser, we'll try again on the off-chance it eventually gets loaded (we might still be in the midst of loading the w5s/wac components)
+ break;
+ default:
+ loaded = true;
+ triedLoaded = true;
+ break;
+ }
+
+ return loaded;
+}
+
+void Playlists::Lock()
+{
+ playlistsGuard.Lock();
+}
+
+void Playlists::Unlock()
+{
+ playlistsGuard.Unlock();
+}
+
+size_t Playlists::GetIterator()
+{
+ return iterator;
+}
+
+static void WriteEscaped( FILE *fp, const wchar_t *str )
+{
+ // TODO: for speed optimization,
+ // we should wait until we hit a special character
+ // and write out everything else so before it,
+ // like how ASX loader does it
+ while ( str && *str )
+ {
+ switch ( *str )
+ {
+ case L'&':
+ fputws( L"&amp;", fp );
+ break;
+ case L'>':
+ fputws( L"&gt;", fp );
+ break;
+ case L'<':
+ fputws( L"&lt;", fp );
+ break;
+ case L'\'':
+ fputws( L"&apos;", fp );
+ break;
+ case L'\"':
+ fputws( L"&quot;", fp );
+ break;
+ default:
+ fputwc( *str, fp );
+ break;
+ }
+
+ // write out the whole UTF-16 character
+ wchar_t *next = CharNextW( str );
+ while ( ++str != next )
+ fputwc( *str, fp );
+ }
+}
+
+bool TitleSortAsc( PlaylistInfo &item1, PlaylistInfo &item2 )
+{
+ int comp = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | NORM_IGNOREWIDTH, item1.title, -1, item2.title, -1 );
+
+ return comp == CSTR_LESS_THAN;
+}
+
+bool TitleSortDesc( PlaylistInfo &item1, PlaylistInfo &item2 )
+{
+ int comp = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | NORM_IGNOREWIDTH, item1.title, -1, item2.title, -1 );
+
+ return comp == CSTR_GREATER_THAN;
+}
+
+bool NumberOfEntrySortAsc( PlaylistInfo &item1, PlaylistInfo &item2 )
+{
+ return !!( item1.numItems < item2.numItems );
+}
+
+bool NumberOfEntrySortDesc( PlaylistInfo &item1, PlaylistInfo &item2 )
+{
+ return !!( item1.numItems > item2.numItems );
+}
+
+int Playlists::Sort( size_t sort_type )
+{
+ if ( !DelayLoad() )
+ return 0;
+
+ int sorted = 1;
+
+ switch ( sort_type )
+ {
+ case SORT_TITLE_ASCENDING:
+ std::sort( playlists.begin(), playlists.end(), TitleSortAsc );
+ break;
+ case SORT_TITLE_DESCENDING:
+ std::sort( playlists.begin(), playlists.end(), TitleSortDesc );
+ break;
+ case SORT_NUMBER_ASCENDING:
+ std::sort( playlists.begin(), playlists.end(), NumberOfEntrySortAsc );
+ break;
+ case SORT_NUMBER_DESCENDING:
+ std::sort( playlists.begin(), playlists.end(), NumberOfEntrySortDesc );
+ break;
+ default:
+ sorted = 0;
+ break;
+ }
+
+ dirty = true;
+
+ if ( sorted )
+ Flush();
+
+ return sorted;
+}
+
+void Playlists::Flush()
+{
+ AutoLockT<Playlists> lock( this );
+
+ if ( !triedLoaded && !loaded ) // if the playlists.xml file was never even attempted to be loaded, don't overwrite
+ return;
+
+ if ( !dirty ) // if we've not seen any changes then no need to re-save
+ return;
+
+ const wchar_t *g_path = WASABI_API_APP->path_getUserSettingsPath();
+
+ wchar_t rootPath[ MAX_PATH ] = { 0 };
+ wchar_t playlistsBackupFilename[ MAX_PATH ] = { 0 };
+ wchar_t playlistsDestination[ MAX_PATH ] = { 0 };
+
+ PathCombineW( rootPath, g_path, L"plugins" );
+ CreateDirectoryW( rootPath, NULL );
+ PathAppendW( rootPath, L"ml" );
+ CreateDirectoryW( rootPath, NULL );
+ PathAppendW( rootPath, L"playlists" );
+ CreateDirectoryW( rootPath, NULL );
+
+ int g_path_size = wcslen( rootPath );
+ PathCombineW( playlistsBackupFilename, rootPath, L"playlists.xml.backup" );
+ PathCombineW( playlistsDestination, rootPath, L"playlists.xml" );
+
+ CopyFileW( playlistsDestination, playlistsBackupFilename, FALSE );
+
+ FILE *fp = _wfopen( playlistsDestination, L"wb" );
+ if ( !fp ) // bah
+ {
+ dirty = false;
+ return;
+ }
+
+ fseek( fp, 0, SEEK_SET );
+ fputwc( L'\xFEFF', fp );
+ fwprintf( fp, L"<?xml version=\"1.0\" encoding=\"UTF-16\"?>" );
+ fwprintf( fp, L"<playlists playlists=\"%u\">", (unsigned int)playlists.size() );
+
+ if ( AGAVE_API_STATS )
+ AGAVE_API_STATS->SetStat( api_stats::PLAYLIST_COUNT, (int)playlists.size() );
+
+ for ( PlaylistInfo &l_play_list_info : playlists )
+ {
+ fputws( L"<playlist filename=\"", fp );
+ const wchar_t *fn = l_play_list_info.filename;
+
+ if ( !_wcsnicmp( rootPath, fn, g_path_size ) )
+ {
+ fn += g_path_size;
+
+ if ( *fn == L'\\' )
+ ++fn;
+ }
+
+ WriteEscaped( fp, fn );
+
+ fputws( L"\" title=\"", fp );
+ WriteEscaped( fp, l_play_list_info.title );
+
+ GUID guid = l_play_list_info.guid;
+
+ fwprintf( fp, L"\" id=\"{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\"",
+ (int)guid.Data1, (int)guid.Data2, (int)guid.Data3,
+ (int)guid.Data4[ 0 ], (int)guid.Data4[ 1 ],
+ (int)guid.Data4[ 2 ], (int)guid.Data4[ 3 ],
+ (int)guid.Data4[ 4 ], (int)guid.Data4[ 5 ],
+ (int)guid.Data4[ 6 ], (int)guid.Data4[ 7 ] );
+
+ fwprintf( fp, L" songs=\"%u\" seconds=\"%u\"", l_play_list_info.numItems, l_play_list_info.length );
+
+ if ( l_play_list_info.iTunesID )
+ fwprintf( fp, L" iTunesID=\"%I64u\"", l_play_list_info.iTunesID );
+
+ if ( l_play_list_info.cloud )
+ fwprintf( fp, L" cloud=\"1\"" );
+
+ fwprintf( fp, L"/>" );
+ }
+
+ fwprintf( fp, L"</playlists>" );
+ fclose( fp );
+ dirty = false;
+}
+
+size_t Playlists::GetCount()
+{
+ DelayLoad();
+
+ return playlists.size();
+}
+
+const wchar_t *Playlists::GetFilename( size_t index )
+{
+ if ( !DelayLoad() || index >= playlists.size() )
+ return 0;
+
+ return playlists[ index ].filename;
+}
+
+const wchar_t *Playlists::GetName( size_t index )
+{
+ if ( !DelayLoad() || index >= playlists.size() )
+ return 0;
+
+ return playlists[ index ].title;
+}
+
+GUID Playlists::GetGUID( size_t index )
+{
+ if ( !DelayLoad() || index >= playlists.size() )
+ return INVALID_GUID;
+
+ return playlists[ index ].guid;
+}
+
+int Playlists::GetPosition( GUID playlist_guid, size_t *index )
+{
+ if ( !DelayLoad() )
+ return API_PLAYLISTS_UNABLE_TO_LOAD_PLAYLISTS;
+
+ size_t indexCount = 0;
+ for ( PlaylistInfo &l_play_list_info : playlists )
+ {
+ if ( l_play_list_info.guid == playlist_guid )
+ {
+ *index = indexCount;
+ return API_PLAYLISTS_SUCCESS;
+ }
+
+ ++indexCount;
+ }
+
+ return API_PLAYLISTS_FAILURE;
+}
+
+template <class val_t>
+static int GetWithSize( void *data, size_t dataLen, val_t value )
+{
+ switch ( dataLen )
+ {
+ case 1:
+ {
+ if ( value > _UI8_MAX ) // check for overflow
+ return API_PLAYLISTS_BAD_SIZE;
+
+ *(uint8_t *)data = (uint8_t)value;
+
+ return API_PLAYLISTS_SUCCESS;
+ }
+ case 2:
+ {
+ if ( value > _UI16_MAX ) // check for overflow
+ return API_PLAYLISTS_BAD_SIZE;
+
+ *(uint16_t *)data = (uint16_t)value;
+
+ return API_PLAYLISTS_SUCCESS;
+ }
+ case 4:
+ {
+ if ( value > _UI32_MAX )
+ return API_PLAYLISTS_BAD_SIZE;
+
+ *(uint32_t *)data = (uint32_t)value;
+
+ return API_PLAYLISTS_SUCCESS;
+ }
+ case 8:
+ {
+ if ( value > _UI64_MAX )
+ return API_PLAYLISTS_BAD_SIZE;
+
+ *(uint64_t *)data = (uint64_t)value;
+
+ return API_PLAYLISTS_SUCCESS;
+ }
+ }
+
+ return API_PLAYLISTS_BAD_SIZE;
+}
+
+template <class val_t>
+static int SetWithSize( void *data, size_t dataLen, val_t *value )
+{
+ switch ( dataLen )
+ {
+ case 1:
+ {
+ *value = ( val_t ) * (uint8_t *)data;
+ return API_PLAYLISTS_SUCCESS;
+ }
+ case 2:
+ {
+ *value = ( val_t ) * (uint16_t *)data;
+ return API_PLAYLISTS_SUCCESS;
+ }
+ case 4:
+ {
+ *value = ( val_t ) * (uint32_t *)data;
+ return API_PLAYLISTS_SUCCESS;
+ }
+ case 8:
+ {
+ *value = ( val_t ) * (uint64_t *)data;
+ return API_PLAYLISTS_SUCCESS;
+ }
+ }
+
+ return API_PLAYLISTS_BAD_SIZE;
+}
+
+int Playlists::GetInfo( size_t index, GUID info, void *data, size_t dataLen )
+{
+ if ( !DelayLoad() )
+ return API_PLAYLISTS_UNABLE_TO_LOAD_PLAYLISTS;
+
+ if ( index >= playlists.size() )
+ return API_PLAYLISTS_INVALID_INDEX;
+
+ if ( info == api_playlists_itemCount )
+ return GetWithSize( data, dataLen, playlists[ index ].numItems );
+ else if ( info == api_playlists_totalTime )
+ return GetWithSize( data, dataLen, playlists[ index ].length );
+ else if ( info == api_playlists_iTunesID )
+ return GetWithSize( data, dataLen, playlists[ index ].iTunesID );
+ else if ( info == api_playlists_cloud )
+ return GetWithSize( data, dataLen, playlists[ index ].cloud );
+
+ return API_PLAYLISTS_UNKNOWN_INFO_GUID;
+}
+
+int Playlists::MoveBefore( size_t index1, size_t index2 )
+{
+ if ( !DelayLoad() )
+ return API_PLAYLISTS_UNABLE_TO_LOAD_PLAYLISTS;
+
+ if ( index1 >= playlists.size() )
+ return API_PLAYLISTS_INVALID_INDEX;
+
+ PlaylistInfo copy = playlists[ index1 ];
+ if ( index2 >= playlists.size() )
+ {
+ playlists.push_back( copy );
+ playlists.erase(playlists.begin() + index1 );
+ }
+ else
+ {
+ playlists.insert(playlists.begin() + index2, copy );
+ if ( index1 >= index2 )
+ index1++;
+ playlists.erase(playlists.begin() + index1 );
+ }
+
+ dirty = true;
+ ++iterator;
+
+ return API_PLAYLISTS_SUCCESS;
+}
+
+size_t Playlists::AddPlaylist( const wchar_t *filename, const wchar_t *playlistName, GUID playlist_guid )
+{
+ if ( !DelayLoad() )
+ return -1;
+
+ AutoLockT<Playlists> lock( this );
+
+ if ( playlist_guid != INVALID_GUID )
+ {
+ for ( size_t index = 0; index < playlists.size(); index++ )
+ {
+ if ( playlists[ index ].guid == playlist_guid )
+ {
+ if ( lstrcmpiW( playlists[ index ].title, playlistName ) )
+ {
+ dirty = true;
+ StringCbCopyW( playlists[ index ].title, sizeof( playlists[ index ].title ), playlistName );
+ WASABI_API_SYSCB->syscb_issueCallback( api_playlists::SYSCALLBACK, api_playlists::PLAYLIST_RENAMED, index, 0 );
+ }
+
+ return -2;
+ }
+ }
+ }
+
+ size_t newIndex = AddPlaylist_NoCallback( filename, playlistName, playlist_guid );
+ WASABI_API_SYSCB->syscb_issueCallback( api_playlists::SYSCALLBACK, api_playlists::PLAYLIST_ADDED, newIndex, 0 );
+
+ return newIndex;
+}
+
+size_t Playlists::AddCloudPlaylist( const wchar_t *filename, const wchar_t *playlistName, GUID playlist_guid )
+{
+ if ( !DelayLoad() )
+ return -1;
+
+ AutoLockT<Playlists> lock( this );
+
+ if ( playlist_guid != INVALID_GUID )
+ {
+ for ( size_t index = 0; index < playlists.size(); index++ )
+ {
+ if ( playlists[ index ].guid == playlist_guid )
+ {
+ // we make sure that this playlist has a 'cloud' flag
+ // as without it, our detection of thing isn't ideal.
+ if ( !playlists[ index ].cloud )
+ playlists[ index ].cloud = 1;
+
+ if ( lstrcmpW( playlists[ index ].title, playlistName ) )
+ {
+ dirty = true;
+ StringCbCopyW( playlists[ index ].title, sizeof( playlists[ index ].title ), playlistName );
+ WASABI_API_SYSCB->syscb_issueCallback( api_playlists::SYSCALLBACK, api_playlists::PLAYLIST_RENAMED, index, 0 );
+ }
+
+ return -2;
+ }
+ }
+ }
+
+ size_t newIndex = AddPlaylist_NoCallback( filename, playlistName, playlist_guid );
+ int cloud = 1;
+ SetInfo( newIndex, api_playlists_cloud, &cloud, sizeof( cloud ) );
+ WASABI_API_SYSCB->syscb_issueCallback( api_playlists::SYSCALLBACK, api_playlists::PLAYLIST_ADDED, newIndex, 0 );
+
+ return newIndex;
+}
+
+size_t Playlists::AddPlaylist_internal( const wchar_t *filename, const wchar_t *playlistName, GUID playlist_guid, size_t numItems, size_t length, uint64_t iTunesID, size_t cloud )
+{
+ PlaylistInfo newPlaylist( filename, playlistName, playlist_guid );
+ newPlaylist.numItems = (int)numItems;
+ newPlaylist.length = (int)length;
+ newPlaylist.iTunesID = iTunesID;
+ newPlaylist.cloud = (int)cloud;
+
+ playlists.push_back( newPlaylist );
+ size_t newIndex = playlists.size() - 1;
+
+ return newIndex;
+}
+
+size_t Playlists::AddPlaylist_NoCallback( const wchar_t *filename, const wchar_t *playlistName, GUID playlist_guid )
+{
+ AutoLockT<Playlists> lock( this );
+ PlaylistInfo newPlaylist( filename, playlistName, playlist_guid );
+
+ dirty = true;
+ playlists.push_back( newPlaylist );
+
+ ++iterator;
+
+ size_t newIndex = playlists.size() - 1;
+
+ return newIndex;
+}
+
+int Playlists::SetGUID( size_t index, GUID playlist_guid )
+{
+ if ( !DelayLoad() )
+ return API_PLAYLISTS_UNABLE_TO_LOAD_PLAYLISTS;
+
+ if ( index >= playlists.size() )
+ return API_PLAYLISTS_INVALID_INDEX;
+
+ dirty = true;
+ playlists[ index ].guid = playlist_guid;
+
+ return API_PLAYLISTS_SUCCESS;
+}
+
+int Playlists::RenamePlaylist( size_t index, const wchar_t *name )
+{
+ if ( !DelayLoad() )
+ return API_PLAYLISTS_UNABLE_TO_LOAD_PLAYLISTS;
+
+ if ( index >= playlists.size() )
+ return API_PLAYLISTS_INVALID_INDEX;
+
+ dirty = true;
+
+ if ( lstrcmpW( playlists[ index ].title, name ) )
+ {
+ StringCbCopyW( playlists[ index ].title, sizeof( playlists[ index ].title ), name );
+ WASABI_API_SYSCB->syscb_issueCallback( api_playlists::SYSCALLBACK, api_playlists::PLAYLIST_RENAMED, index, 0 );
+ }
+
+ return API_PLAYLISTS_SUCCESS;
+}
+
+int Playlists::MovePlaylist( size_t index, const wchar_t *filename )
+{
+ if ( !DelayLoad() )
+ return API_PLAYLISTS_UNABLE_TO_LOAD_PLAYLISTS;
+
+ if ( index >= playlists.size() )
+ return API_PLAYLISTS_INVALID_INDEX;
+
+ dirty = true;
+ StringCbCopyW( playlists[ index ].filename, sizeof( playlists[ index ].filename ), filename );
+ iterator++;
+
+ return API_PLAYLISTS_SUCCESS;
+}
+
+int Playlists::SetInfo( size_t index, GUID info, void *data, size_t dataLen )
+{
+ if ( !DelayLoad() )
+ return API_PLAYLISTS_UNABLE_TO_LOAD_PLAYLISTS;
+
+ if ( index >= playlists.size() )
+ return API_PLAYLISTS_INVALID_INDEX;
+
+ dirty = true;
+ if ( info == api_playlists_itemCount )
+ return SetWithSize( data, dataLen, &playlists[ index ].numItems );
+ else if ( info == api_playlists_totalTime )
+ return SetWithSize( data, dataLen, &playlists[ index ].length );
+ else if ( info == api_playlists_iTunesID )
+ return SetWithSize( data, dataLen, &playlists[ index ].iTunesID );
+ else if ( info == api_playlists_cloud )
+ return SetWithSize( data, dataLen, &playlists[ index ].cloud );
+
+ dirty = false;
+
+ return API_PLAYLISTS_UNKNOWN_INFO_GUID;
+}
+
+int Playlists::RemovePlaylist( size_t index )
+{
+ if ( !DelayLoad() )
+ return API_PLAYLISTS_UNABLE_TO_LOAD_PLAYLISTS;
+
+ if ( index >= playlists.size() )
+ return API_PLAYLISTS_INVALID_INDEX;
+
+ dirty = true;
+ WASABI_API_SYSCB->syscb_issueCallback( api_playlists::SYSCALLBACK, api_playlists::PLAYLIST_REMOVED_PRE, index, 0 );
+ playlists.erase(playlists.begin() + index );
+ iterator++;
+ WASABI_API_SYSCB->syscb_issueCallback( api_playlists::SYSCALLBACK, api_playlists::PLAYLIST_REMOVED_POST, index, 0 );
+
+ return API_PLAYLISTS_SUCCESS;
+}
+
+int Playlists::ClearPlaylists()
+{
+ AutoLockT<Playlists> lock( this );
+
+ if ( !DelayLoad() )
+ return API_PLAYLISTS_UNABLE_TO_LOAD_PLAYLISTS;
+
+ dirty = true;
+ playlists.clear();
+
+ return API_PLAYLISTS_SUCCESS;
+}
+
+const PlaylistInfo &Playlists::GetPlaylistInfo(size_t i)
+{
+ return playlists[i];
+}
+
+#define CBCLASS Playlists
+START_DISPATCH;
+VCB( API_PLAYLISTS_LOCK, Lock );
+VCB( API_PLAYLISTS_UNLOCK, Unlock );
+CB( API_PLAYLISTS_GETITERATOR, GetIterator );
+VCB( API_PLAYLISTS_FLUSH, Flush );
+CB( API_PLAYLISTS_GETCOUNT, GetCount );
+CB( API_PLAYLISTS_GETFILENAME, GetFilename );
+CB( API_PLAYLISTS_GETNAME, GetName );
+CB( API_PLAYLISTS_GETGUID, GetGUID );
+CB( API_PLAYLISTS_GETPOSITION, GetPosition );
+CB( API_PLAYLISTS_GETINFO, GetInfo );
+CB( API_PLAYLISTS_MOVEBEFORE, MoveBefore );
+CB( API_PLAYLISTS_ADDPLAYLIST, AddPlaylist );
+CB( API_PLAYLISTS_ADDPLAYLISTNOCB, AddPlaylist_NoCallback );
+CB( API_PLAYLISTS_ADDCLOUDPLAYLIST, AddCloudPlaylist );
+CB( API_PLAYLISTS_SETGUID, SetGUID );
+CB( API_PLAYLISTS_RENAMEPLAYLIST, RenamePlaylist );
+CB( API_PLAYLISTS_MOVEPLAYLIST, MovePlaylist );
+CB( API_PLAYLISTS_SETINFO, SetInfo );
+CB( API_PLAYLISTS_REMOVEPLAYLIST, RemovePlaylist );
+CB( API_PLAYLISTS_CLEARPLAYLISTS, ClearPlaylists );
+CB( API_PLAYLISTS_SORT, Sort );
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/playlist/Playlists.h b/Src/playlist/Playlists.h
new file mode 100644
index 00000000..7b873b2d
--- /dev/null
+++ b/Src/playlist/Playlists.h
@@ -0,0 +1,74 @@
+#ifndef NULLSOFT_PLAYLIST_PLAYLISTS_H
+#define NULLSOFT_PLAYLIST_PLAYLISTS_H
+
+#include "api_playlists.h"
+#include <vector>
+#include "../nu/AutoLock.h"
+
+class PlaylistInfo
+{
+public:
+ PlaylistInfo();
+ PlaylistInfo(const wchar_t *_filename, const wchar_t *_title, GUID playlist_guid = INVALID_GUID);
+ PlaylistInfo(const PlaylistInfo &copy);
+ wchar_t filename[MAX_PATH];
+ wchar_t title[1024];
+ int length; // in seconds
+ int numItems;
+ GUID guid;
+ uint64_t iTunesID; // this is used by ml_impex
+ int cloud; // this is used by ml_playlists
+};
+
+class Playlists : public api_playlists
+{
+public:
+ Playlists();
+ bool DelayLoad();
+
+ // api_playlists implementations
+ void Lock();
+ void Unlock();
+
+ size_t GetIterator();
+ int Sort(size_t sort_type);
+ void Flush();
+
+ // get information about playlists
+ size_t GetCount(); // returns number of playlists
+ const wchar_t *GetFilename(size_t index); // returns API_PLAYLISTS_SUCCESS or API_PLAYLISTS_FAILURE. only valid until you Unlock()
+ const wchar_t *GetName(size_t index); // returns API_PLAYLISTS_SUCCESS or API_PLAYLISTS_FAILURE. only valid until you Unlock()
+ GUID GetGUID(size_t index); // retrieves a unique ID which identifies this playlist
+ int GetPosition(GUID playlist_guid, size_t *index); // retrieves the index where a particular playlist ID lives. returns API_PLAYLISTS_SUCCESS or API_PLAYLISTS_FAILURE
+ int GetInfo(size_t index, GUID info, void *data, size_t dataLen); // This is for getting "extra" data, see list of GUIDs below. returns API_PLAYLISTS_SUCCESS or API_PLAYLISTS_FAILURE
+
+ // manipulating playlists
+ int MoveBefore(size_t index1, size_t index2); // moves playlist at position index1 to before index2. setting index2 to anything larger than GetCount() moves to end
+ size_t AddPlaylist(const wchar_t *filename, const wchar_t *playlistName, GUID playlist_guid = INVALID_GUID); // adds a new playlist, returns new index. Generates a GUID if you don't pass third parameter.
+ size_t AddPlaylist_NoCallback(const wchar_t *filename, const wchar_t *playlistName, GUID playlist_guid = INVALID_GUID);
+ size_t AddCloudPlaylist(const wchar_t *filename, const wchar_t *playlistName, GUID playlist_guid = INVALID_GUID); // adds a new playlist, returns new index. Generates a GUID if you don't pass third parameter.
+ int SetGUID(size_t index, GUID playlist_guid); // sets (overrides) a playlist ID. Don't use unless you have some very specific need
+ int RenamePlaylist(size_t index, const wchar_t *name);
+ int MovePlaylist(size_t index, const wchar_t *filename); // sets a new filename. NOTE: IT'S UP TO YOU TO PHYSICALLY MOVE/RENAME/CREATE THE NEW FILENAME.
+ int SetInfo(size_t index, GUID info, void *data, size_t dataLen); // returns API_PLAYLISTS_SUCCESS or API_PLAYLISTS_FAILURE
+ int RemovePlaylist(size_t index); // removes a particular playlist
+ int ClearPlaylists(); // [*] clears the entire playlist. Use at your own risk :)
+
+ size_t AddPlaylist_internal(const wchar_t *filename, const wchar_t *playlistName, GUID playlist_guid, size_t numItems, size_t length, uint64_t iTunesID, size_t cloud);
+
+ // for SPlaylists to use
+ const PlaylistInfo &GetPlaylistInfo(size_t i);
+private:
+ bool loaded; // whether or not playlists.xml has been loaded
+ bool triedLoaded; // whether or not we tried to load
+ bool dirty; // whether a flush should save on Flush()
+ size_t iterator; // a counter to help clients determine if the list data has changed
+ typedef std::vector<PlaylistInfo> PlaylistsList;
+ PlaylistsList playlists;
+
+ Nullsoft::Utility::LockGuard playlistsGuard;
+
+ RECVS_DISPATCH;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/PlaylistsXML.cpp b/Src/playlist/PlaylistsXML.cpp
new file mode 100644
index 00000000..5db3b8dd
--- /dev/null
+++ b/Src/playlist/PlaylistsXML.cpp
@@ -0,0 +1,149 @@
+#include "PlaylistsXML.h"
+#include "api__playlist.h"
+#include "../nu/AutoLock.h"
+#include "Playlists.h"
+#include <shlwapi.h>
+#include <stdint.h>
+
+using namespace Nullsoft::Utility;
+
+void PlaylistsXML::StartTag( const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params )
+{
+ const wchar_t *filename = params->getItemValue( L"filename" );
+ const wchar_t *title = params->getItemValue( L"title" );
+ const wchar_t *countString = params->getItemValue( L"songs" );
+
+ size_t numItems = 0;
+
+ if ( countString && *countString )
+ numItems = _wtoi( countString );
+
+ const wchar_t *lengthString = params->getItemValue( L"seconds" );
+ size_t length = 0;
+ if ( lengthString && *lengthString )
+ length = _wtoi( lengthString );
+
+ const wchar_t *iTunesIDString = params->getItemValue( L"iTunesID" );
+ uint64_t iTunesID = 0;
+ if ( iTunesIDString && *iTunesIDString )
+ iTunesID = _wtoi64( iTunesIDString );
+
+ const wchar_t *cloudString = params->getItemValue( L"cloud" );
+ size_t cloud = 0;
+ if ( cloudString && *cloudString )
+ cloud = _wtoi( cloudString );
+
+ // parse GUID
+ GUID guid = INVALID_GUID;
+ const wchar_t *guidString = params->getItemValue( L"id" );
+ if ( guidString && *guidString )
+ {
+ int Data1, Data2, Data3;
+ int Data4[ 8 ];
+
+ int n = swscanf( guidString, L" { %08x - %04x - %04x - %02x%02x - %02x%02x%02x%02x%02x%02x } ",
+ &Data1, &Data2, &Data3, Data4 + 0, Data4 + 1,
+ Data4 + 2, Data4 + 3, Data4 + 4, Data4 + 5, Data4 + 6, Data4 + 7 );
+
+ if ( n == 11 ) // GUID was
+ {
+ // Cross assign all the values
+ guid.Data1 = Data1;
+ guid.Data2 = Data2;
+ guid.Data3 = Data3;
+ guid.Data4[ 0 ] = Data4[ 0 ];
+ guid.Data4[ 1 ] = Data4[ 1 ];
+ guid.Data4[ 2 ] = Data4[ 2 ];
+ guid.Data4[ 3 ] = Data4[ 3 ];
+ guid.Data4[ 4 ] = Data4[ 4 ];
+ guid.Data4[ 5 ] = Data4[ 5 ];
+ guid.Data4[ 6 ] = Data4[ 6 ];
+ guid.Data4[ 7 ] = Data4[ 7 ];
+ }
+ }
+
+ if ( PathIsFileSpecW( filename ) )
+ {
+ wchar_t playlistFilename[ MAX_PATH ] = { 0 };
+ PathCombineW( playlistFilename, rootPath, filename );
+ playlists->AddPlaylist_internal( playlistFilename, title, guid, numItems, length, iTunesID, cloud );
+ }
+ else
+ {
+ playlists->AddPlaylist_internal( filename, title, guid, numItems, length, iTunesID, cloud );
+ }
+}
+
+int PlaylistsXML::LoadFile( const wchar_t *filename )
+{
+ if ( !parser )
+ return PLAYLISTSXML_NO_PARSER; // no sense in continuing if there's no parser available
+
+ HANDLE file = CreateFileW( filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL );
+
+ if ( file == INVALID_HANDLE_VALUE )
+ return PLAYLISTSXML_NO_FILE;
+
+ while ( true )
+ {
+ int8_t data[ 1024 ] = { 0 };
+ DWORD bytesRead = 0;
+ if ( ReadFile( file, data, 1024, &bytesRead, NULL ) && bytesRead )
+ {
+ if ( parser->xmlreader_feed( data, bytesRead ) != API_XML_SUCCESS )
+ {
+ CloseHandle( file );
+ return PLAYLISTSXML_XML_PARSE_ERROR;
+ }
+ }
+ else
+ break;
+ }
+
+ CloseHandle( file );
+ parser->xmlreader_feed( 0, 0 );
+
+ return PLAYLISTSXML_SUCCESS;
+}
+
+PlaylistsXML::PlaylistsXML( Playlists *_playlists ) : playlists( _playlists )
+{
+ const wchar_t *g_path = WASABI_API_APP->path_getUserSettingsPath();
+ PathCombineW( rootPath, g_path, L"plugins\\ml\\playlists" );
+
+ playlists->AddRef();
+
+ parserFactory = WASABI_API_SVC->service_getServiceByGuid( obj_xmlGUID );
+ if ( parserFactory )
+ parser = (obj_xml *)parserFactory->getInterface();
+
+ if ( parser )
+ {
+ parser->xmlreader_setCaseSensitive();
+ parser->xmlreader_registerCallback( L"Winamp:Playlists\fplaylist", this );
+ parser->xmlreader_registerCallback( L"playlists\fplaylist", this );
+ parser->xmlreader_open();
+ }
+}
+
+PlaylistsXML::~PlaylistsXML()
+{
+ playlists->Release();
+ if ( parser )
+ {
+ parser->xmlreader_unregisterCallback( this );
+ parser->xmlreader_close();
+ }
+
+ if ( parserFactory && parser )
+ parserFactory->releaseInterface( parser );
+
+ parserFactory = 0;
+ parser = 0;
+}
+
+#define CBCLASS PlaylistsXML
+START_DISPATCH;
+VCB( ONSTARTELEMENT, StartTag )
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/playlist/PlaylistsXML.h b/Src/playlist/PlaylistsXML.h
new file mode 100644
index 00000000..8207c653
--- /dev/null
+++ b/Src/playlist/PlaylistsXML.h
@@ -0,0 +1,38 @@
+#ifndef NULLSOFT_AGAVE_PLAYLISTSXML_H
+#define NULLSOFT_AGAVE_PLAYLISTSXML_H
+
+#include "../xml/obj_xml.h"
+#include "../xml/ifc_xmlreadercallback.h"
+#include <api/service/waServiceFactory.h>
+class Playlists;
+
+enum
+{
+ PLAYLISTSXML_SUCCESS = 0,
+ PLAYLISTSXML_FAILURE = 1,
+ PLAYLISTSXML_NO_PARSER = 2,
+ PLAYLISTSXML_NO_FILE = 3,
+ PLAYLISTSXML_XML_PARSE_ERROR = 4,
+};
+
+class PlaylistsXML : public ifc_xmlreadercallback
+{
+public:
+ PlaylistsXML( Playlists *_playlists );
+ ~PlaylistsXML();
+
+ int LoadFile( const wchar_t *filename );
+
+private:
+ RECVS_DISPATCH;
+ /* XML callbacks */
+ void StartTag( const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params );
+
+ obj_xml *parser = 0;
+ waServiceFactory *parserFactory = 0;
+ Playlists *playlists = 0;
+
+ wchar_t rootPath[ MAX_PATH ];
+};
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/SPlaylist.cpp b/Src/playlist/SPlaylist.cpp
new file mode 100644
index 00000000..0777840d
--- /dev/null
+++ b/Src/playlist/SPlaylist.cpp
@@ -0,0 +1,270 @@
+#include "SPlaylist.h"
+#include "api__playlist.h"
+#include "PlaylistManager.h"
+
+extern ScriptObjectController *script_root;
+extern PlaylistScriptController playlistController;
+
+
+// -- Functions table -------------------------------------
+function_descriptor_struct PlaylistScriptController::exportedFunction[] = {
+ {L"Clear", 0, (void*)SPlaylist::script_vcpu_Clear },
+ {L"GetNumItems", 0, (void*)SPlaylist::script_vcpu_GetNumItems },
+ {L"GetItem", 1, (void*)SPlaylist::script_vcpu_GetItem },
+ {L"GetItemTitle", 1, (void*)SPlaylist::script_vcpu_GetItemTitle },
+ {L"GetItemLength", 1, (void*)SPlaylist::script_vcpu_GetItemLength },
+ {L"GetItemExtendedInfo", 2, (void*)SPlaylist::script_vcpu_GetItemExtendedInfo },
+ {L"Reverse", 0, (void*)SPlaylist::script_vcpu_Reverse },
+ {L"Swap", 2, (void*)SPlaylist::script_vcpu_Swap },
+ {L"Randomize", 0, (void*)SPlaylist::script_vcpu_Randomize },
+ {L"Remove", 1, (void*)SPlaylist::script_vcpu_Remove },
+ {L"SortByTitle", 0, (void*)SPlaylist::script_vcpu_SortByTitle },
+ {L"SortByFilename", 0, (void*)SPlaylist::script_vcpu_SortByFilename },
+ /* TODO:
+ {L"Append", 3, (void*)SPlaylist::script_vcpu_Append },
+ {L"Insert", 4, (void*)SPlaylist::script_vcpu_Insert },
+ {L"SetItemFilename", 2, (void*)SPlaylist::script_vcpu_SetItemFilename },
+ {L"SetItemTitle", 2, (void*)SPlaylist::script_vcpu_SetItemTitle },
+ {L"SetItemLengthMilliseconds", 2, (void*)SPlaylist::script_vcpu_SetItemLengthMilliseconds },
+ {L"SortByDirectory", 0, (void*)SPlaylist::script_vcpu_SortByDirectory },
+ */
+
+};
+// --------------------------------------------------------
+
+const wchar_t *PlaylistScriptController::getClassName()
+{
+ return L"Playlist";
+}
+
+const wchar_t *PlaylistScriptController::getAncestorClassName()
+{
+ return L"Object";
+}
+
+ScriptObjectController *PlaylistScriptController::getAncestorController()
+{
+ return script_root;
+}
+
+ScriptObject *PlaylistScriptController::instantiate()
+{
+ SPlaylist *xd = new SPlaylist;
+
+ ASSERT( xd != NULL );
+
+ return xd->getScriptObject();
+}
+
+void PlaylistScriptController::destroy( ScriptObject *o )
+{
+ SPlaylist *xd = static_cast<SPlaylist *>( o->vcpu_getInterface( makiPlaylistGUID ) );
+
+ ASSERT( xd != NULL );
+
+ delete xd;
+}
+
+void *PlaylistScriptController::encapsulate( ScriptObject *o )
+{
+ return NULL;
+}
+
+void PlaylistScriptController::deencapsulate( void *o )
+{}
+
+int PlaylistScriptController::getNumFunctions()
+{
+ return sizeof( exportedFunction ) / sizeof( function_descriptor_struct );
+}
+
+const function_descriptor_struct *PlaylistScriptController::getExportedFunctions()
+{
+ return exportedFunction;
+}
+
+GUID PlaylistScriptController::getClassGuid()
+{
+ return makiPlaylistGUID;
+}
+
+
+/* ---- */
+SPlaylist::SPlaylist()
+{
+ getScriptObject()->vcpu_setInterface( makiPlaylistGUID, static_cast<SPlaylist *>( this ) );
+ getScriptObject()->vcpu_setClassName( L"Playlist" );
+ getScriptObject()->vcpu_setController( &playlistController );
+}
+
+scriptVar SPlaylist::script_vcpu_Clear( SCRIPT_FUNCTION_PARAMS, ScriptObject *o )
+{
+ SCRIPT_FUNCTION_INIT;
+ SPlaylist *playlist = static_cast<SPlaylist *>( o->vcpu_getInterface( makiPlaylistGUID ) );
+ if ( playlist )
+ {
+ playlist->playlist.Clear();
+ }
+ RETURN_SCRIPT_VOID
+}
+
+scriptVar SPlaylist::script_vcpu_GetNumItems( SCRIPT_FUNCTION_PARAMS, ScriptObject *o )
+{
+ SCRIPT_FUNCTION_INIT;
+ SPlaylist *playlist = static_cast<SPlaylist *>( o->vcpu_getInterface( makiPlaylistGUID ) );
+ if ( playlist )
+ {
+ size_t count = playlist->playlist.GetNumItems();
+
+ return MAKE_SCRIPT_INT( (int)count );
+ }
+ RETURN_SCRIPT_ZERO
+}
+
+static wchar_t splaylist_string_return[ 1024 ];
+static wchar_t *splaylist_string_empty = L"";
+
+scriptVar SPlaylist::script_vcpu_GetItem( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar itemNumber )
+{
+ SCRIPT_FUNCTION_INIT;
+
+ SPlaylist *playlist = static_cast<SPlaylist *>( o->vcpu_getInterface( makiPlaylistGUID ) );
+ if ( playlist )
+ {
+ int item = GET_SCRIPT_INT( itemNumber );
+ size_t cch = playlist->playlist.GetItem( item, splaylist_string_return, sizeof( splaylist_string_return ) / sizeof( *splaylist_string_return ) );
+
+ if ( cch == 0 )
+ return MAKE_SCRIPT_STRING( splaylist_string_empty );
+ else
+ return MAKE_SCRIPT_STRING( splaylist_string_return );
+ }
+
+ return MAKE_SCRIPT_STRING( splaylist_string_empty );
+}
+
+scriptVar SPlaylist::script_vcpu_GetItemTitle( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar itemNumber )
+{
+ SCRIPT_FUNCTION_INIT;
+
+ SPlaylist *playlist = static_cast<SPlaylist *>( o->vcpu_getInterface( makiPlaylistGUID ) );
+ if ( playlist )
+ {
+ int item = GET_SCRIPT_INT( itemNumber );
+ size_t cch = playlist->playlist.GetItemTitle( item, splaylist_string_return, sizeof( splaylist_string_return ) / sizeof( *splaylist_string_return ) );
+
+ if ( cch == 0 )
+ return MAKE_SCRIPT_STRING( splaylist_string_empty );
+ else
+ return MAKE_SCRIPT_STRING( splaylist_string_return );
+ }
+
+ return MAKE_SCRIPT_STRING( splaylist_string_empty );
+}
+
+scriptVar SPlaylist::script_vcpu_GetItemLength( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar itemNumber )
+{
+ SCRIPT_FUNCTION_INIT;
+
+ SPlaylist *playlist = static_cast<SPlaylist *>( o->vcpu_getInterface( makiPlaylistGUID ) );
+ if ( playlist )
+ {
+ int item = GET_SCRIPT_INT( itemNumber );
+ int length = playlist->playlist.GetItemLengthMilliseconds( item );
+
+ return MAKE_SCRIPT_INT( length );
+ }
+
+ return MAKE_SCRIPT_INT( -1 );
+}
+
+scriptVar SPlaylist::script_vcpu_GetItemExtendedInfo( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar itemNumber, scriptVar metadata )
+{
+ SCRIPT_FUNCTION_INIT;
+
+ SPlaylist *playlist = static_cast<SPlaylist *>( o->vcpu_getInterface( makiPlaylistGUID ) );
+ if ( playlist )
+ {
+ int item = GET_SCRIPT_INT( itemNumber );
+ const wchar_t *tag = GET_SCRIPT_STRING( metadata );
+ size_t cch = playlist->playlist.GetItemExtendedInfo( item, tag, splaylist_string_return, sizeof( splaylist_string_return ) / sizeof( *splaylist_string_return ) );
+
+ if ( cch == 0 )
+ return MAKE_SCRIPT_STRING( splaylist_string_empty );
+ else
+ return MAKE_SCRIPT_STRING( splaylist_string_return );
+ }
+
+ return MAKE_SCRIPT_STRING( splaylist_string_empty );
+}
+
+scriptVar SPlaylist::script_vcpu_Reverse( SCRIPT_FUNCTION_PARAMS, ScriptObject *o )
+{
+ SCRIPT_FUNCTION_INIT;
+ SPlaylist *playlist = static_cast<SPlaylist *>( o->vcpu_getInterface( makiPlaylistGUID ) );
+ if ( playlist )
+ {
+ playlist->playlist.Reverse();
+ }
+ RETURN_SCRIPT_VOID
+}
+
+scriptVar SPlaylist::script_vcpu_Swap( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar position1, scriptVar position2 )
+{
+ SCRIPT_FUNCTION_INIT;
+ SPlaylist *playlist = static_cast<SPlaylist *>( o->vcpu_getInterface( makiPlaylistGUID ) );
+ if ( playlist )
+ {
+ int item1 = GET_SCRIPT_INT( position1 );
+ int item2 = GET_SCRIPT_INT( position2 );
+
+ playlist->playlist.Swap( item1, item2 );
+ }
+ RETURN_SCRIPT_VOID
+}
+
+scriptVar SPlaylist::script_vcpu_Randomize( SCRIPT_FUNCTION_PARAMS, ScriptObject *o )
+{
+ SCRIPT_FUNCTION_INIT;
+ SPlaylist *playlist = static_cast<SPlaylist *>( o->vcpu_getInterface( makiPlaylistGUID ) );
+ if ( playlist )
+ {
+ playlistManager.Randomize( &playlist->playlist );
+ }
+ RETURN_SCRIPT_VOID
+}
+
+
+scriptVar SPlaylist::script_vcpu_Remove( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar itemIndex )
+{
+ SCRIPT_FUNCTION_INIT;
+ SPlaylist *playlist = static_cast<SPlaylist *>( o->vcpu_getInterface( makiPlaylistGUID ) );
+ if ( playlist )
+ {
+ int item = GET_SCRIPT_INT( itemIndex );
+ playlist->playlist.Remove( item );
+ }
+ RETURN_SCRIPT_VOID
+}
+
+scriptVar SPlaylist::script_vcpu_SortByTitle( SCRIPT_FUNCTION_PARAMS, ScriptObject *o )
+{
+ SCRIPT_FUNCTION_INIT;
+ SPlaylist *playlist = static_cast<SPlaylist *>( o->vcpu_getInterface( makiPlaylistGUID ) );
+ if ( playlist )
+ {
+ playlist->playlist.SortByTitle();
+ }
+ RETURN_SCRIPT_VOID
+}
+
+scriptVar SPlaylist::script_vcpu_SortByFilename( SCRIPT_FUNCTION_PARAMS, ScriptObject *o )
+{
+ SCRIPT_FUNCTION_INIT;
+ SPlaylist *playlist = static_cast<SPlaylist *>( o->vcpu_getInterface( makiPlaylistGUID ) );
+ if ( playlist )
+ {
+ playlist->playlist.SortByFilename();
+ }
+ RETURN_SCRIPT_VOID
+}
diff --git a/Src/playlist/SPlaylist.h b/Src/playlist/SPlaylist.h
new file mode 100644
index 00000000..8689b1ec
--- /dev/null
+++ b/Src/playlist/SPlaylist.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include "api/script/objcontroller.h"
+#include "api/script/objects/rootobj.h"
+#include "Playlist.h"
+
+class PlaylistScriptController : public ScriptObjectControllerI
+{
+public:
+ virtual const wchar_t *getClassName();
+ virtual const wchar_t *getAncestorClassName();
+ virtual ScriptObjectController *getAncestorController();
+ virtual int getNumFunctions();
+ virtual const function_descriptor_struct *getExportedFunctions();
+ virtual GUID getClassGuid();
+ virtual ScriptObject *instantiate();
+ virtual void destroy( ScriptObject *o );
+ virtual void *encapsulate( ScriptObject *o );
+ virtual void deencapsulate( void *o );
+
+private:
+ static function_descriptor_struct exportedFunction[];
+};
+
+class SPlaylist : public RootObjectInstance
+{
+public:
+ SPlaylist();
+
+ /* ifc_playlist wrapper */
+ static scriptVar script_vcpu_Clear( SCRIPT_FUNCTION_PARAMS, ScriptObject *o );
+ static scriptVar script_vcpu_GetNumItems( SCRIPT_FUNCTION_PARAMS, ScriptObject *o );
+ static scriptVar script_vcpu_GetItem( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar itemNumber );
+ static scriptVar script_vcpu_GetItemTitle( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar itemNumber );
+ static scriptVar script_vcpu_GetItemLength( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar itemNumber );
+ static scriptVar script_vcpu_GetItemExtendedInfo( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar itemNumber, scriptVar metadata );
+ static scriptVar script_vcpu_Reverse( SCRIPT_FUNCTION_PARAMS, ScriptObject *o );
+ static scriptVar script_vcpu_Swap( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar position1, scriptVar position2 );
+ static scriptVar script_vcpu_Randomize( SCRIPT_FUNCTION_PARAMS, ScriptObject *o );
+ static scriptVar script_vcpu_Remove( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar itemIndex );
+ static scriptVar script_vcpu_SortByTitle( SCRIPT_FUNCTION_PARAMS, ScriptObject *o );
+ static scriptVar script_vcpu_SortByFilename( SCRIPT_FUNCTION_PARAMS, ScriptObject *o );
+
+ /* extra functions */
+//private:
+ Playlist playlist;
+};
+
+
+// {632883FC-159F-4330-B193-CFD62CA47EC1}
+static const GUID makiPlaylistGUID =
+{ 0x632883fc, 0x159f, 0x4330, { 0xb1, 0x93, 0xcf, 0xd6, 0x2c, 0xa4, 0x7e, 0xc1 } };
diff --git a/Src/playlist/SPlaylistManager.cpp b/Src/playlist/SPlaylistManager.cpp
new file mode 100644
index 00000000..896039d4
--- /dev/null
+++ b/Src/playlist/SPlaylistManager.cpp
@@ -0,0 +1,117 @@
+#include "SPlaylistManager.h"
+#include "SPlaylist.h"
+#include "api__playlist.h"
+#include "PlaylistManager.h"
+
+extern ScriptObjectController *script_root;
+extern PlaylistManagerScriptController playlistManagerController;
+
+// {C6207729-2600-4bb8-B562-2E0BC04E4416}
+static const GUID makePlaylistManagerGUID =
+{ 0xc6207729, 0x2600, 0x4bb8, { 0xb5, 0x62, 0x2e, 0xb, 0xc0, 0x4e, 0x44, 0x16 } };
+
+
+// -- Functions table -------------------------------------
+function_descriptor_struct PlaylistManagerScriptController::exportedFunction[] = {
+ {L"OpenPlaylist", 1, (void*)SPlaylistManager::script_vcpu_OpenPlaylist },
+ {L"SavePlaylist", 2, (void*)SPlaylistManager::script_vcpu_SavePlaylist },
+};
+// --------------------------------------------------------
+
+const wchar_t *PlaylistManagerScriptController::getClassName() {
+ return L"PlaylistManager";
+}
+
+const wchar_t *PlaylistManagerScriptController::getAncestorClassName() {
+ return L"Object";
+}
+
+ScriptObjectController *PlaylistManagerScriptController::getAncestorController()
+{
+ return script_root;
+}
+
+int PlaylistManagerScriptController::getInstantiable()
+{
+ return 0;
+}
+
+int PlaylistManagerScriptController::getReferenceable()
+{
+ return 0;
+}
+
+ScriptObject *PlaylistManagerScriptController::instantiate() {
+ SPlaylistManager *xd = new SPlaylistManager;
+ ASSERT(xd != NULL);
+ return xd->getScriptObject();
+}
+
+void PlaylistManagerScriptController::destroy(ScriptObject *o) {
+ SPlaylistManager *xd = static_cast<SPlaylistManager *>(o->vcpu_getInterface(makePlaylistManagerGUID));
+ ASSERT(xd != NULL);
+ delete xd;
+}
+
+void *PlaylistManagerScriptController::encapsulate(ScriptObject *o) {
+ return NULL;
+}
+
+void PlaylistManagerScriptController::deencapsulate(void *o) {
+}
+
+int PlaylistManagerScriptController::getNumFunctions() {
+ return sizeof(exportedFunction) / sizeof(function_descriptor_struct);
+}
+
+const function_descriptor_struct *PlaylistManagerScriptController::getExportedFunctions() {
+ return exportedFunction;
+}
+
+GUID PlaylistManagerScriptController::getClassGuid() {
+ return makePlaylistManagerGUID;
+}
+
+/* --- */
+
+SPlaylistManager::SPlaylistManager()
+{
+ getScriptObject()->vcpu_setInterface(makePlaylistManagerGUID, static_cast<SPlaylistManager *>(this));
+ getScriptObject()->vcpu_setClassName(L"PlaylistManager");
+ getScriptObject()->vcpu_setController(&playlistManagerController);
+}
+
+scriptVar SPlaylistManager::script_vcpu_OpenPlaylist(SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar scriptFilename)
+{
+ SCRIPT_FUNCTION_INIT;
+ const wchar_t *filename = GET_SCRIPT_STRING(scriptFilename);
+ if (filename && *filename)
+ {
+ SPlaylist *playlist = new SPlaylist;
+ if (playlistManager.Load(filename, &playlist->playlist) == PLAYLISTMANAGER_SUCCESS)
+ {
+ return MAKE_SCRIPT_OBJECT(playlist->getScriptObject());
+ }
+ else
+ {
+ delete playlist;
+ return MAKE_SCRIPT_OBJECT(0);
+ }
+ }
+
+ return MAKE_SCRIPT_OBJECT(0);
+}
+
+scriptVar SPlaylistManager::script_vcpu_SavePlaylist(SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar scriptFilename, scriptVar scriptPlaylist)
+{
+ SCRIPT_FUNCTION_INIT;
+ const wchar_t *filename = GET_SCRIPT_STRING(scriptFilename);
+ SPlaylist *playlist = static_cast<SPlaylist *>(GET_SCRIPT_OBJECT_AS(scriptPlaylist, makiPlaylistGUID));
+ if (filename && *filename && playlist)
+ {
+ playlistManager.Save(filename, &playlist->playlist);
+ }
+
+ RETURN_SCRIPT_VOID;
+}
+
diff --git a/Src/playlist/SPlaylistManager.h b/Src/playlist/SPlaylistManager.h
new file mode 100644
index 00000000..4917deb5
--- /dev/null
+++ b/Src/playlist/SPlaylistManager.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <api/script/objcontroller.h>
+#include <api/script/objects/rootobj.h>
+
+class PlaylistManagerScriptController : public ScriptObjectControllerI
+{
+public:
+ const wchar_t *getClassName();
+ const wchar_t *getAncestorClassName();
+ ScriptObjectController *getAncestorController();
+ int getNumFunctions();
+ const function_descriptor_struct *getExportedFunctions();
+ GUID getClassGuid();
+
+ ScriptObject *instantiate();
+ void destroy( ScriptObject *o );
+
+ void *encapsulate( ScriptObject *o );
+ void deencapsulate( void *o );
+
+ int getInstantiable();
+ int getReferenceable();
+
+private:
+ static function_descriptor_struct exportedFunction[];
+};
+
+class SPlaylistManager : public RootObjectInstance
+{
+public:
+ SPlaylistManager();
+
+ static scriptVar script_vcpu_OpenPlaylist( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar filename );
+ static scriptVar script_vcpu_SavePlaylist( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar filename, scriptVar playlist );
+
+private:
+}; \ No newline at end of file
diff --git a/Src/playlist/SPlaylists.cpp b/Src/playlist/SPlaylists.cpp
new file mode 100644
index 00000000..953b8ed2
--- /dev/null
+++ b/Src/playlist/SPlaylists.cpp
@@ -0,0 +1,157 @@
+#include "SPlaylists.h"
+#include "api__playlist.h"
+#include "Playlists.h"
+#include "SPlaylistsEnumerator.h"
+#include "SPlaylist.h"
+#include "PlaylistManager.h"
+
+extern Playlists playlists;
+
+extern ScriptObjectController *script_root;
+extern PlaylistsScriptController playlistsController;
+
+// {5829EE15-3648-4c6e-B2FE-8736CBBF39DB}
+static const GUID makiPlaylistsGUID =
+{ 0x5829ee15, 0x3648, 0x4c6e, { 0xb2, 0xfe, 0x87, 0x36, 0xcb, 0xbf, 0x39, 0xdb } };
+
+// -- Functions table -------------------------------------
+function_descriptor_struct PlaylistsScriptController::exportedFunction[] = {
+ {L"GetEnumerator", 0, (void*)SPlaylists::script_vcpu_GetEnumerator },
+ {L"OpenPlaylist", 1, (void*)SPlaylists::script_vcpu_OpenPlaylist },
+ {L"SavePlaylist", 2, (void*)SPlaylists::script_vcpu_SavePlaylist },
+};
+// --------------------------------------------------------
+
+const wchar_t *PlaylistsScriptController::getClassName()
+{
+ return L"Playlists";
+}
+
+const wchar_t *PlaylistsScriptController::getAncestorClassName()
+{
+ return L"Object";
+}
+
+ScriptObjectController *PlaylistsScriptController::getAncestorController()
+{
+ return script_root;
+}
+
+int PlaylistsScriptController::getInstantiable()
+{
+ return 0;
+}
+
+int PlaylistsScriptController::getReferenceable()
+{
+ return 0;
+}
+
+ScriptObject *PlaylistsScriptController::instantiate()
+{
+ SPlaylists *xd = new SPlaylists;
+ ASSERT(xd != NULL);
+ return xd->getScriptObject();
+}
+
+void PlaylistsScriptController::destroy(ScriptObject *o)
+{
+ SPlaylists *xd = static_cast<SPlaylists *>(o->vcpu_getInterface(makiPlaylistsGUID));
+ ASSERT(xd != NULL);
+ delete xd;
+}
+
+void *PlaylistsScriptController::encapsulate(ScriptObject *o)
+{
+ return NULL;
+}
+
+void PlaylistsScriptController::deencapsulate(void *o)
+{}
+
+int PlaylistsScriptController::getNumFunctions()
+{
+ return sizeof(exportedFunction) / sizeof(function_descriptor_struct);
+}
+
+const function_descriptor_struct *PlaylistsScriptController::getExportedFunctions()
+{
+ return exportedFunction;
+}
+
+GUID PlaylistsScriptController::getClassGuid()
+{
+ return makiPlaylistsGUID;
+}
+
+/* --- */
+
+SPlaylists::SPlaylists()
+{
+ getScriptObject()->vcpu_setInterface(makiPlaylistsGUID, static_cast<SPlaylists *>(this));
+ getScriptObject()->vcpu_setClassName(L"Playlists");
+ getScriptObject()->vcpu_setController(&playlistsController);
+}
+
+scriptVar SPlaylists::script_vcpu_GetEnumerator(SCRIPT_FUNCTION_PARAMS, ScriptObject *o)
+{
+ SCRIPT_FUNCTION_INIT;
+ SPlaylistsEnumerator *enumerator = new SPlaylistsEnumerator();
+ playlists.Lock();
+ size_t count = playlists.GetCount();
+ enumerator->Reserve(count);
+ for (size_t i=0;i!=count;i++)
+ {
+ const PlaylistInfo &info = playlists.GetPlaylistInfo(i);
+ enumerator->AppendPlaylist(info);
+ }
+ playlists.Unlock();
+ return MAKE_SCRIPT_OBJECT(enumerator->getScriptObject());
+}
+
+scriptVar SPlaylists::script_vcpu_OpenPlaylist( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar scriptPlaylistGUID )
+{
+ SCRIPT_FUNCTION_INIT;
+ GUID playlist_guid = nsGUID::fromCharW( GET_SCRIPT_STRING( scriptPlaylistGUID ) );
+ SPlaylist *playlist = 0;
+
+ playlists.Lock();
+ size_t index;
+ if ( playlists.GetPosition( playlist_guid, &index ) == API_PLAYLISTS_SUCCESS )
+ {
+ const wchar_t *filename = playlists.GetFilename( index );
+ if ( filename )
+ {
+ playlist = new SPlaylist;
+ if ( playlistManager.Load( filename, &playlist->playlist ) != PLAYLISTMANAGER_SUCCESS )
+ {
+ delete playlist;
+ playlist = 0;
+ }
+ }
+ }
+ playlists.Unlock();
+
+ return MAKE_SCRIPT_OBJECT( playlist ? playlist->getScriptObject() : 0 );
+}
+
+scriptVar SPlaylists::script_vcpu_SavePlaylist( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar scriptPlaylistGUID, scriptVar scriptPlaylist )
+{
+ SCRIPT_FUNCTION_INIT;
+ GUID playlist_guid = nsGUID::fromCharW( GET_SCRIPT_STRING( scriptPlaylistGUID ) );
+ SPlaylist *playlist = static_cast<SPlaylist *>( GET_SCRIPT_OBJECT_AS( scriptPlaylist, makiPlaylistGUID ) );
+ if ( playlist )
+ {
+ playlists.Lock();
+ size_t index;
+ if ( playlists.GetPosition( playlist_guid, &index ) == API_PLAYLISTS_SUCCESS )
+ {
+ const wchar_t *filename = playlists.GetFilename( index );
+ if ( filename )
+ playlistManager.Save( filename, &playlist->playlist );
+ }
+ playlists.Unlock();
+ }
+
+ return MAKE_SCRIPT_OBJECT( playlist ? playlist->getScriptObject() : 0 );
+}
diff --git a/Src/playlist/SPlaylists.h b/Src/playlist/SPlaylists.h
new file mode 100644
index 00000000..53d55c49
--- /dev/null
+++ b/Src/playlist/SPlaylists.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <api/script/objcontroller.h>
+#include <api/script/objects/rootobj.h>
+
+class PlaylistsScriptController : public ScriptObjectControllerI
+{
+public:
+ const wchar_t *getClassName();
+ const wchar_t *getAncestorClassName();
+ ScriptObjectController *getAncestorController();
+ int getNumFunctions();
+ const function_descriptor_struct *getExportedFunctions();
+ GUID getClassGuid();
+
+ ScriptObject *instantiate();
+ void destroy( ScriptObject *o );
+
+ void *encapsulate( ScriptObject *o );
+ void deencapsulate( void *o );
+
+ int getInstantiable();
+ int getReferenceable();
+
+private:
+ static function_descriptor_struct exportedFunction[];
+};
+
+class SPlaylists : public RootObjectInstance
+{
+public:
+ SPlaylists();
+
+ static scriptVar script_vcpu_GetEnumerator( SCRIPT_FUNCTION_PARAMS, ScriptObject *o );
+ static scriptVar script_vcpu_OpenPlaylist( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar scriptPlaylistGUID );
+ static scriptVar script_vcpu_SavePlaylist( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar scriptPlaylistGUID, scriptVar scriptPlaylist );
+
+private:
+
+}; \ No newline at end of file
diff --git a/Src/playlist/SPlaylistsEnumerator.cpp b/Src/playlist/SPlaylistsEnumerator.cpp
new file mode 100644
index 00000000..376c64f2
--- /dev/null
+++ b/Src/playlist/SPlaylistsEnumerator.cpp
@@ -0,0 +1,184 @@
+#include "SPlaylistsEnumerator.h"
+#include "api__playlist.h"
+#include <bfc/nsguid.h>
+#include "Playlists.h"
+
+extern ScriptObjectController *script_root;
+extern PlaylistsEnumeratorScriptController playlistsEnumeratorController;
+
+// {C18F8E50-2C81-4001-9F46-FD942B07ECCD}
+static const GUID makiPlaylistsEnumeratorGUID =
+{ 0xc18f8e50, 0x2c81, 0x4001, { 0x9f, 0x46, 0xfd, 0x94, 0x2b, 0x7, 0xec, 0xcd } };
+
+// -- Functions table -------------------------------------
+function_descriptor_struct PlaylistsEnumeratorScriptController::exportedFunction[] = {
+ {L"GetCount", 0, (void *)SPlaylistsEnumerator::script_vcpu_GetCount },
+ {L"GetFilename", 1, (void *)SPlaylistsEnumerator::script_vcpu_GetFilename },
+ {L"GetTitle", 1, (void *)SPlaylistsEnumerator::script_vcpu_GetTitle },
+ {L"GetLength", 1, (void *)SPlaylistsEnumerator::script_vcpu_GetLength },
+ {L"GetNumItems", 1, (void *)SPlaylistsEnumerator::script_vcpu_GetNumItems },
+ {L"GetGUID", 1, (void *)SPlaylistsEnumerator::script_vcpu_GetGUID },
+
+};
+// --------------------------------------------------------
+
+const wchar_t *PlaylistsEnumeratorScriptController::getClassName()
+{
+ return L"PlaylistsEnumerator";
+}
+
+const wchar_t *PlaylistsEnumeratorScriptController::getAncestorClassName()
+{
+ return L"Object";
+}
+
+ScriptObjectController *PlaylistsEnumeratorScriptController::getAncestorController()
+{
+ return script_root;
+}
+
+ScriptObject *PlaylistsEnumeratorScriptController::instantiate()
+{
+ SPlaylistsEnumerator *xd = new SPlaylistsEnumerator;
+ ASSERT(xd != NULL);
+ return xd->getScriptObject();
+}
+
+void PlaylistsEnumeratorScriptController::destroy( ScriptObject *o )
+{
+ SPlaylistsEnumerator *xd = static_cast<SPlaylistsEnumerator *>( o->vcpu_getInterface( makiPlaylistsEnumeratorGUID ) );
+ ASSERT( xd != NULL );
+ delete xd;
+}
+
+void *PlaylistsEnumeratorScriptController::encapsulate( ScriptObject *o )
+{
+ return NULL;
+}
+
+void PlaylistsEnumeratorScriptController::deencapsulate( void *o )
+{}
+
+int PlaylistsEnumeratorScriptController::getNumFunctions()
+{
+ return sizeof( exportedFunction ) / sizeof( function_descriptor_struct );
+}
+
+const function_descriptor_struct *PlaylistsEnumeratorScriptController::getExportedFunctions()
+{
+ return exportedFunction;
+}
+
+GUID PlaylistsEnumeratorScriptController::getClassGuid()
+{
+ return makiPlaylistsEnumeratorGUID;
+}
+
+/* --- */
+
+SPlaylistsEnumerator::SPlaylistsEnumerator()
+{
+ getScriptObject()->vcpu_setInterface( makiPlaylistsEnumeratorGUID, static_cast<SPlaylistsEnumerator *>( this ) );
+ getScriptObject()->vcpu_setClassName( L"PlaylistsEnumerator" );
+ getScriptObject()->vcpu_setController( &playlistsEnumeratorController );
+}
+
+SPlaylistsEnumerator::~SPlaylistsEnumerator()
+{
+ //info.deleteAll();
+ for (auto obj : info)
+ {
+ delete obj;
+ }
+ info.clear();
+}
+
+scriptVar SPlaylistsEnumerator::script_vcpu_GetCount( SCRIPT_FUNCTION_PARAMS, ScriptObject *o )
+{
+ SCRIPT_FUNCTION_INIT;
+ SPlaylistsEnumerator *enumerator = static_cast<SPlaylistsEnumerator *>( o->vcpu_getInterface( makiPlaylistsEnumeratorGUID ) );
+ if ( enumerator )
+ {
+ return MAKE_SCRIPT_INT( (int)enumerator->info.size() );
+ }
+
+ return MAKE_SCRIPT_INT( 0 );
+}
+
+static const wchar_t *static_splaylistsenumerator_empty_string = L"";
+scriptVar SPlaylistsEnumerator::script_vcpu_GetFilename( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar playlistNumber )
+{
+ SCRIPT_FUNCTION_INIT;
+ SPlaylistsEnumerator *enumerator = static_cast<SPlaylistsEnumerator *>( o->vcpu_getInterface( makiPlaylistsEnumeratorGUID ) );
+ size_t i = GET_SCRIPT_INT( playlistNumber );
+ if ( enumerator && i < enumerator->info.size() )
+ {
+ return MAKE_SCRIPT_STRING( enumerator->info[ i ]->filename );
+ }
+
+ return MAKE_SCRIPT_STRING( static_splaylistsenumerator_empty_string );
+}
+
+scriptVar SPlaylistsEnumerator::script_vcpu_GetTitle( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar playlistNumber )
+{
+ SCRIPT_FUNCTION_INIT;
+ SPlaylistsEnumerator *enumerator = static_cast<SPlaylistsEnumerator *>( o->vcpu_getInterface( makiPlaylistsEnumeratorGUID ) );
+ size_t i = GET_SCRIPT_INT( playlistNumber );
+ if ( enumerator && i < enumerator->info.size() )
+ {
+ return MAKE_SCRIPT_STRING( enumerator->info[ i ]->title );
+ }
+
+ return MAKE_SCRIPT_STRING( static_splaylistsenumerator_empty_string );
+}
+
+scriptVar SPlaylistsEnumerator::script_vcpu_GetLength( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar playlistNumber )
+{
+ SCRIPT_FUNCTION_INIT;
+ SPlaylistsEnumerator *enumerator = static_cast<SPlaylistsEnumerator *>( o->vcpu_getInterface( makiPlaylistsEnumeratorGUID ) );
+ size_t i = GET_SCRIPT_INT( playlistNumber );
+ if ( enumerator && i < enumerator->info.size() )
+ {
+ return MAKE_SCRIPT_INT( enumerator->info[ i ]->length );
+ }
+
+ return MAKE_SCRIPT_INT( -1 );
+}
+
+scriptVar SPlaylistsEnumerator::script_vcpu_GetNumItems( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar playlistNumber )
+{
+ SCRIPT_FUNCTION_INIT;
+ SPlaylistsEnumerator *enumerator = static_cast<SPlaylistsEnumerator *>( o->vcpu_getInterface( makiPlaylistsEnumeratorGUID ) );
+ size_t i = GET_SCRIPT_INT( playlistNumber );
+ if ( enumerator && i < enumerator->info.size() )
+ {
+ return MAKE_SCRIPT_INT( enumerator->info[ i ]->numItems );
+ }
+
+ return MAKE_SCRIPT_INT( 0 );
+}
+
+static wchar_t static_splaylistsenumerator_guid_string[39];
+scriptVar SPlaylistsEnumerator::script_vcpu_GetGUID( SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar playlistNumber )
+{
+ SCRIPT_FUNCTION_INIT;
+ SPlaylistsEnumerator *enumerator = static_cast<SPlaylistsEnumerator *>( o->vcpu_getInterface( makiPlaylistsEnumeratorGUID ) );
+ size_t i = GET_SCRIPT_INT( playlistNumber );
+ if ( enumerator && i < enumerator->info.size() )
+ {
+ nsGUID::toCharW( enumerator->info[ i ]->guid, static_splaylistsenumerator_guid_string );
+ return MAKE_SCRIPT_STRING( static_splaylistsenumerator_guid_string );
+ }
+
+ return MAKE_SCRIPT_STRING( static_splaylistsenumerator_empty_string );
+}
+
+void SPlaylistsEnumerator::Reserve( size_t count )
+{
+ info.reserve( count );
+}
+
+void SPlaylistsEnumerator::AppendPlaylist( const PlaylistInfo &newPlaylist )
+{
+ info.push_back( new PlaylistInfo( newPlaylist ) );
+} \ No newline at end of file
diff --git a/Src/playlist/SPlaylistsEnumerator.h b/Src/playlist/SPlaylistsEnumerator.h
new file mode 100644
index 00000000..9ad12a96
--- /dev/null
+++ b/Src/playlist/SPlaylistsEnumerator.h
@@ -0,0 +1,45 @@
+
+#pragma once
+
+#include <api/script/objcontroller.h>
+#include <api/script/objects/rootobj.h>
+#include "Playlists.h"
+#include <vector>
+
+class PlaylistsEnumeratorScriptController : public ScriptObjectControllerI
+{
+ public:
+ const wchar_t *getClassName();
+ const wchar_t *getAncestorClassName();
+ ScriptObjectController *getAncestorController();
+ int getNumFunctions();
+ const function_descriptor_struct *getExportedFunctions();
+ GUID getClassGuid();
+ ScriptObject *instantiate();
+ void destroy(ScriptObject *o);
+ void *encapsulate(ScriptObject *o);
+ void deencapsulate(void *o);
+
+ private:
+ static function_descriptor_struct exportedFunction[];
+};
+
+class SPlaylistsEnumerator : public RootObjectInstance
+{
+public:
+ SPlaylistsEnumerator();
+ ~SPlaylistsEnumerator();
+
+ void Reserve(size_t count);
+ void AppendPlaylist(const PlaylistInfo &newPlaylist);
+
+ static scriptVar script_vcpu_GetCount(SCRIPT_FUNCTION_PARAMS, ScriptObject *o);
+ static scriptVar script_vcpu_GetFilename(SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar itemNumber);
+ static scriptVar script_vcpu_GetTitle(SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar itemNumber);
+ static scriptVar script_vcpu_GetLength(SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar itemNumber);
+ static scriptVar script_vcpu_GetNumItems(SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar itemNumber);
+ static scriptVar script_vcpu_GetGUID(SCRIPT_FUNCTION_PARAMS, ScriptObject *o, scriptVar itemNumber);
+
+private:
+ std::vector<PlaylistInfo*> info;
+}; \ No newline at end of file
diff --git a/Src/playlist/ScriptObjectFactory.cpp b/Src/playlist/ScriptObjectFactory.cpp
new file mode 100644
index 00000000..5e947b68
--- /dev/null
+++ b/Src/playlist/ScriptObjectFactory.cpp
@@ -0,0 +1,67 @@
+#include "ScriptObjectFactory.h"
+#include "api__playlist.h"
+#include "ScriptObjectService.h"
+
+ScriptObjectService svc;
+static const char serviceName[] = "Playlist Maki Objects";
+
+// {2406A46C-9740-4af7-A0EB-A544AAB8F00C}
+static const GUID playlist_script_object_guid =
+{ 0x2406a46c, 0x9740, 0x4af7, { 0xa0, 0xeb, 0xa5, 0x44, 0xaa, 0xb8, 0xf0, 0xc } };
+
+
+FOURCC ScriptObjectFactory::GetServiceType()
+{
+ return svc.getServiceType();
+}
+
+const char *ScriptObjectFactory::GetServiceName()
+{
+ return serviceName;
+}
+
+GUID ScriptObjectFactory::GetGUID()
+{
+ return playlist_script_object_guid;
+}
+
+void *ScriptObjectFactory::GetInterface(int global_lock)
+{
+// if (global_lock)
+// WASABI_API_SVC->service_lock(this, (void *)ifc);
+ return &svc;
+}
+
+int ScriptObjectFactory::SupportNonLockingInterface()
+{
+ return 1;
+}
+
+int ScriptObjectFactory::ReleaseInterface(void *ifc)
+{
+ //WASABI_API_SVC->service_unlock(ifc);
+ return 1;
+}
+
+const char *ScriptObjectFactory::GetTestString()
+{
+ return 0;
+}
+
+int ScriptObjectFactory::ServiceNotify(int msg, int param1, int param2)
+{
+ return 1;
+}
+
+#define CBCLASS ScriptObjectFactory
+START_DISPATCH;
+CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType)
+CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName)
+CB(WASERVICEFACTORY_GETGUID, GetGUID)
+CB(WASERVICEFACTORY_GETINTERFACE, GetInterface)
+CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface)
+CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface)
+CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString)
+CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/playlist/ScriptObjectFactory.h b/Src/playlist/ScriptObjectFactory.h
new file mode 100644
index 00000000..8209082e
--- /dev/null
+++ b/Src/playlist/ScriptObjectFactory.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "api__playlist.h"
+#include <api/service/waservicefactory.h>
+#include <api/service/services.h>
+
+class ScriptObjectFactory : public waServiceFactory
+{
+public:
+ FOURCC GetServiceType();
+ const char *GetServiceName();
+ GUID GetGUID();
+ void *GetInterface(int global_lock);
+ int SupportNonLockingInterface();
+ int ReleaseInterface(void *ifc);
+ const char *GetTestString();
+ int ServiceNotify(int msg, int param1, int param2);
+
+protected:
+ RECVS_DISPATCH;
+};
+
+
diff --git a/Src/playlist/ScriptObjectService.cpp b/Src/playlist/ScriptObjectService.cpp
new file mode 100644
index 00000000..01c64410
--- /dev/null
+++ b/Src/playlist/ScriptObjectService.cpp
@@ -0,0 +1,42 @@
+#include "ScriptObjectService.h"
+#include <api/script/objects/rootobjcontroller.h>
+#include "SPlaylist.h"
+#include "SPlaylists.h"
+#include "SPlaylistsEnumerator.h"
+#include "SPlaylistManager.h"
+
+ScriptObjectController *script_root=0;
+PlaylistScriptController playlistController;
+PlaylistsScriptController playlistsController;
+PlaylistsEnumeratorScriptController playlistsEnumeratorController;
+PlaylistManagerScriptController playlistManagerController;
+
+ScriptObjectController *ScriptObjectService::getController(int n)
+{
+ switch (n)
+ {
+ case 0:
+ return &playlistController;
+ case 1:
+ return &playlistsController;
+ case 2:
+ return &playlistsEnumeratorController;
+ case 3:
+ return &playlistManagerController;
+ }
+
+ return 0;
+}
+
+
+void ScriptObjectService::onRegisterClasses(ScriptObjectController *rootController)
+{
+ script_root = rootController;
+}
+
+#define CBCLASS ScriptObjectService
+START_DISPATCH;
+ CB(GETCONTROLLER, getController);
+ VCB(ONREGISTER, onRegisterClasses);
+END_DISPATCH;
+#undef CBCLASS
diff --git a/Src/playlist/ScriptObjectService.h b/Src/playlist/ScriptObjectService.h
new file mode 100644
index 00000000..872657ac
--- /dev/null
+++ b/Src/playlist/ScriptObjectService.h
@@ -0,0 +1,11 @@
+#pragma once
+#include <api/service/svcs/svc_scriptobj.h>
+
+class ScriptObjectService : public svc_scriptObject
+{
+public:
+ ScriptObjectController *getController(int n);
+ void onRegisterClasses(ScriptObjectController *rootController);
+protected:
+ RECVS_DISPATCH;
+};
diff --git a/Src/playlist/XMLString.cpp b/Src/playlist/XMLString.cpp
new file mode 100644
index 00000000..ea4d0cbf
--- /dev/null
+++ b/Src/playlist/XMLString.cpp
@@ -0,0 +1,42 @@
+#include "main.h"
+#include "XMLString.h"
+#include "../nu/strsafe.h"
+
+XMLString::XMLString()
+{
+ data[ 0 ] = 0;
+}
+
+void XMLString::Reset()
+{
+ data[ 0 ] = 0;
+}
+
+const wchar_t *XMLString::GetString()
+{
+ return data;
+}
+
+void XMLString::StartTag( const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params )
+{
+ data[ 0 ] = 0;
+}
+
+
+void XMLString::TextHandler( const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *str )
+{
+ StringCchCatW( data, 256, str );
+}
+
+
+void XMLString::ManualSet( const wchar_t *string )
+{
+ StringCchCatW( data, 256, string );
+}
+
+#define CBCLASS XMLString
+START_DISPATCH;
+VCB( ONSTARTELEMENT, StartTag )
+VCB( ONCHARDATA, TextHandler )
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/playlist/XMLString.h b/Src/playlist/XMLString.h
new file mode 100644
index 00000000..b80380eb
--- /dev/null
+++ b/Src/playlist/XMLString.h
@@ -0,0 +1,29 @@
+#ifndef NULLSOFT_WINAMP_XMLSTRING_H
+#define NULLSOFT_WINAMP_XMLSTRING_H
+
+
+#include "../xml/ifc_xmlreadercallback.h"
+/*
+this one is an xml callback that just saves the last encountered string
+*/
+
+
+class XMLString : public ifc_xmlreadercallback
+{
+public:
+ XMLString();
+ void Reset();
+ const wchar_t *GetString();
+ void ManualSet(const wchar_t *string);
+private:
+ /* XML callbacks */
+ void StartTag(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params);
+ void EndTag(const wchar_t *xmlpath, const wchar_t *xmltag);
+ void TextHandler(const wchar_t *xmlpath, const wchar_t *xmltag, const wchar_t *str);
+
+ wchar_t data[256]; // for now, we'll make it dynamic later
+
+ RECVS_DISPATCH;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/_ifc_playlistentry.h b/Src/playlist/_ifc_playlistentry.h
new file mode 100644
index 00000000..71dee76f
--- /dev/null
+++ b/Src/playlist/_ifc_playlistentry.h
@@ -0,0 +1,49 @@
+#ifndef NULLSOFT_IFC_PLAYLIST_ENTRY_H
+#define NULLSOFT_IFC_PLAYLIST_ENTRY_H
+
+#include <bfc/dispatch.h>
+
+class ifc_playlistentry : public Dispatchable
+{
+protected:
+ ifc_playlistentry() {}
+ ~ifc_playlistentry() {}
+
+
+public:
+ DISPATCH_CODES
+ {
+ IFC_PLAYLISTENTRY_GETFILENAME = 10,
+ IFC_PLAYLISTENTRY_GETTITLE = 20,
+ IFC_PLAYLISTENTRY_GETLENGTHMS = 30,
+ IFC_PLAYLISTENTRY_GETEXTENDEDINFO = 40,
+ };
+
+
+ size_t GetFilename( wchar_t *filename, size_t filenameCch );
+ size_t GetTitle( wchar_t *title, size_t titleCch );
+ int GetLengthInMilliseconds();
+ size_t GetExtendedInfo( const wchar_t *metadata, wchar_t *info, size_t infoCch );
+};
+
+inline size_t ifc_playlistentry::GetFilename( wchar_t *filename, size_t filenameCch )
+{
+ return _call( IFC_PLAYLISTENTRY_GETFILENAME, (size_t)0, filename, filenameCch );
+}
+
+inline size_t ifc_playlistentry::GetTitle( wchar_t *title, size_t titleCch )
+{
+ return _call( IFC_PLAYLISTENTRY_GETTITLE, (size_t)0, title, titleCch );
+}
+
+inline int ifc_playlistentry::GetLengthInMilliseconds()
+{
+ return _call( IFC_PLAYLISTENTRY_GETLENGTHMS, (int)-1 );
+}
+
+inline size_t ifc_playlistentry::GetExtendedInfo( const wchar_t *metadata, wchar_t *info, size_t infoCch )
+{
+ return _call( IFC_PLAYLISTENTRY_GETEXTENDEDINFO, (size_t)0, metadata, info, infoCch );
+}
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/api__playlist.h b/Src/playlist/api__playlist.h
new file mode 100644
index 00000000..eae25572
--- /dev/null
+++ b/Src/playlist/api__playlist.h
@@ -0,0 +1,58 @@
+#ifndef NULLSOFT_PLAYLIST_API_H
+#define NULLSOFT_PLAYLIST_API_H
+
+#include <api/service/api_service.h>
+
+#include <api/application/api_application.h>
+#define WASABI_API_APP applicationApi
+
+#include <api/syscb/api_syscb.h>
+#define WASABI_API_SYSCB sysCallbackApi
+
+#ifndef ML_PLG_EXPORTS
+#include "../Agave/Config/api_config.h"
+#endif
+
+#include <api/script/api_maki.h>
+#define WASABI_API_MAKI makiApi
+
+#include "../Winamp/JSAPI2_api_security.h"
+extern JSAPI2::api_security *jsapi2_security;
+#define AGAVE_API_JSAPI2_SECURITY jsapi2_security
+
+#include "../Winamp/api_stats.h"
+extern api_stats *statsApi;
+#define AGAVE_API_STATS statsApi
+
+#include <api/service/waservicefactory.h>
+
+template <class api_t>
+class QuickService
+{
+public:
+ QuickService( GUID p_serviceGUID )
+ {
+ _sf = WASABI_API_SVC->service_getServiceByGuid( p_serviceGUID );
+ if ( _sf )
+ _api = (api_t *)_sf->getInterface();
+ }
+ ~QuickService()
+ {
+ _sf->releaseInterface( _api );
+ _api = 0;
+ }
+ bool OK()
+ {
+ return _api != 0;
+ }
+ api_t *operator ->()
+ {
+ return _api;
+ }
+ waServiceFactory *_sf = NULL;
+ api_t *_api = 0;
+};
+
+#include "../Agave/Language/api_language.h"
+
+#endif // !NULLSOFT_PLAYLIST_API_H \ No newline at end of file
diff --git a/Src/playlist/api_playlistmanager.h b/Src/playlist/api_playlistmanager.h
new file mode 100644
index 00000000..f04df555
--- /dev/null
+++ b/Src/playlist/api_playlistmanager.h
@@ -0,0 +1,161 @@
+#ifndef NULLSOFT_API_PLAYLISTMANAGER_H
+#define NULLSOFT_API_PLAYLISTMANAGER_H
+
+#include <bfc/dispatch.h>
+
+class ifc_playlistloadercallback;
+class ifc_playlist;
+class ifc_playlistdirectorycallback;
+enum
+{
+ PLAYLISTMANAGER_SUCCESS = 0,
+
+ PLAYLISTMANAGER_FAILED = 1,
+ PLAYLISTMANAGER_LOAD_NO_LOADER = 1,
+ PLAYLISTMANAGER_LOAD_LOADER_OPEN_FAILED = 2,
+};
+
+class api_playlistmanager : public Dispatchable
+{
+protected:
+ api_playlistmanager() {}
+ ~api_playlistmanager() {}
+
+public:
+ int Load( const wchar_t *filename, ifc_playlistloadercallback *playlist );
+ int LoadAs( const wchar_t *filename, const wchar_t *ext, ifc_playlistloadercallback *playlist ); // call with ext in the format ".pls"
+ int LoadFromDialog( const wchar_t *fns, ifc_playlistloadercallback *playlist );
+ int LoadFromANSIDialog( const char *fns, ifc_playlistloadercallback *playlist );
+
+ int Save( const wchar_t *filename, ifc_playlist *playlist );
+
+ size_t Copy( const wchar_t *destFn, const wchar_t *srcFn ); // returns number of items copied
+
+ size_t CountItems( const wchar_t *filename );
+
+ int GetLengthMilliseconds( const wchar_t *filename );
+ uint64_t GetLongLengthMilliseconds( const wchar_t *filename );
+
+ void Randomize( ifc_playlist *playlist );
+ void Reverse( ifc_playlist *playlist );
+
+ void LoadDirectory( const wchar_t *directory, ifc_playlistloadercallback *callback, ifc_playlistdirectorycallback *dirCallback );
+
+ bool CanLoad( const wchar_t *filename );
+
+ void GetExtensionList( wchar_t *extensionList, size_t extensionListCch );
+ void GetFilterList( wchar_t *extensionList, size_t extensionListCch );
+
+ const wchar_t *EnumExtension( size_t num );
+
+public:
+ DISPATCH_CODES
+ {
+ API_PLAYLISTMANAGER_LOAD = 10,
+ API_PLAYLISTMANAGER_LOADNULLDELIMITED = 11,
+ API_PLAYLISTMANAGER_LOADNULLDELIMITED_ANSI = 12,
+ API_PLAYLISTMANAGER_LOADAS = 13,
+ API_PLAYLISTMANAGER_SAVE = 20,
+ API_PLAYLISTMANAGER_COPY = 30,
+ API_PLAYLISTMANAGER_COUNT = 40,
+ API_PLAYLISTMANAGER_GETLENGTH = 50,
+ API_PLAYLISTMANAGER_GETLONGLENGTH = 51,
+ API_PLAYLISTMANAGER_LOADDIRECTORY = 60,
+ API_PLAYLISTMANAGER_RANDOMIZE = 100,
+ API_PLAYLISTMANAGER_REVERSE = 110,
+ API_PLAYLISTMANAGER_CANLOAD = 120,
+ API_PLAYLISTMANAGER_GETEXTENSIONLIST = 130,
+ API_PLAYLISTMANAGER_GETFILTERLIST = 140,
+ API_PLAYLISTMANAGER_ENUMEXTENSION = 150,
+ };
+};
+
+inline void api_playlistmanager::GetFilterList( wchar_t *extensionList, size_t extensionListCch )
+{
+ extensionList[ 0 ] = 0; // just in case no one implements it
+ extensionList[ 1 ] = 0;
+
+ _voidcall( API_PLAYLISTMANAGER_GETFILTERLIST, extensionList, extensionListCch );
+}
+
+inline int api_playlistmanager::LoadAs( const wchar_t *filename, const wchar_t *ext, ifc_playlistloadercallback *playlist )
+{
+ return _call( API_PLAYLISTMANAGER_LOADAS, (int)PLAYLISTMANAGER_FAILED, filename, ext, playlist );
+}
+
+inline void api_playlistmanager::GetExtensionList( wchar_t *extensionList, size_t extensionListCch )
+{
+ extensionList[ 0 ] = 0; // just in case no one implements it
+ _voidcall( API_PLAYLISTMANAGER_GETEXTENSIONLIST, extensionList, extensionListCch );
+}
+
+inline int api_playlistmanager::Load( const wchar_t *filename, ifc_playlistloadercallback *playlist )
+{
+ return _call( API_PLAYLISTMANAGER_LOAD, (int)PLAYLISTMANAGER_FAILED, filename, playlist );
+}
+
+inline int api_playlistmanager::LoadFromDialog( const wchar_t *filename, ifc_playlistloadercallback *playlist )
+{
+ return _call( API_PLAYLISTMANAGER_LOADNULLDELIMITED, (int)PLAYLISTMANAGER_FAILED, filename, playlist );
+}
+
+inline int api_playlistmanager::LoadFromANSIDialog( const char *filename, ifc_playlistloadercallback *playlist )
+{
+ return _call( API_PLAYLISTMANAGER_LOADNULLDELIMITED_ANSI, (int)PLAYLISTMANAGER_FAILED, filename, playlist );
+}
+
+inline int api_playlistmanager::Save( const wchar_t *filename, ifc_playlist *playlist )
+{
+ return _call( API_PLAYLISTMANAGER_SAVE, (int)PLAYLISTMANAGER_FAILED, filename, playlist );
+}
+
+inline size_t api_playlistmanager::Copy( const wchar_t *destFn, const wchar_t *srcFn )
+{
+ return _call( API_PLAYLISTMANAGER_COPY, (size_t)0, destFn, srcFn );
+}
+
+inline size_t api_playlistmanager::CountItems( const wchar_t *filename )
+{
+ return _call( API_PLAYLISTMANAGER_COUNT, (size_t)0, filename );
+}
+
+inline int api_playlistmanager::GetLengthMilliseconds( const wchar_t *filename )
+{
+ return _call( API_PLAYLISTMANAGER_GETLENGTH, (int)0, filename );
+}
+
+inline uint64_t api_playlistmanager::GetLongLengthMilliseconds( const wchar_t *filename )
+{
+ return _call( API_PLAYLISTMANAGER_GETLONGLENGTH, (uint64_t)0, filename );
+}
+
+inline void api_playlistmanager::Randomize( ifc_playlist *playlist )
+{
+ _voidcall( API_PLAYLISTMANAGER_RANDOMIZE, playlist );
+}
+
+inline void api_playlistmanager::Reverse( ifc_playlist *playlist )
+{
+ _voidcall( API_PLAYLISTMANAGER_REVERSE, playlist );
+}
+
+inline void api_playlistmanager::LoadDirectory( const wchar_t *directory, ifc_playlistloadercallback *callback, ifc_playlistdirectorycallback *dirCallback )
+{
+ _voidcall( API_PLAYLISTMANAGER_LOADDIRECTORY, directory, callback, dirCallback );
+}
+
+inline bool api_playlistmanager::CanLoad( const wchar_t *filename )
+{
+ return _call( API_PLAYLISTMANAGER_CANLOAD, (bool)true, filename );
+}
+
+inline const wchar_t *api_playlistmanager::EnumExtension( size_t num )
+{
+ return _call( API_PLAYLISTMANAGER_ENUMEXTENSION, (const wchar_t *)0, num );
+}
+
+// {C5618774-7177-43aa-9906-933C9F40EBDC}
+static const GUID api_playlistmanagerGUID =
+{ 0xc5618774, 0x7177, 0x43aa, { 0x99, 0x6, 0x93, 0x3c, 0x9f, 0x40, 0xeb, 0xdc } };
+
+#endif
diff --git a/Src/playlist/api_playlists.h b/Src/playlist/api_playlists.h
new file mode 100644
index 00000000..6e205b0f
--- /dev/null
+++ b/Src/playlist/api_playlists.h
@@ -0,0 +1,290 @@
+#ifndef NULLSOFT_PLAYLIST_API_PLAYLISTS_H
+#define NULLSOFT_PLAYLIST_API_PLAYLISTS_H
+
+#include <bfc/dispatch.h>
+#include <bfc/platform/guid.h>
+#include <bfc/std_mkncc.h>
+// manages Winamp's master list of playlists
+
+/* Important note to users of this API:
+ This API does not actually parse or in any way read the contents of the playlist files themselves.
+ It only manages the master "list" of playlists, used in e.g. ml_playlists.
+
+ --- important ---
+ This also means that some information retrieved through this API can be inaccurate,
+ such as playlist item length or total time. These values are provided as a cache,
+ to speed up display of UI. They are not to be relied on for determining how many items
+ are actually in the playlist. Don't use this value to allocate memory for data structures,
+ unless it's just an initial guess and you allow for realloc'ing if the count is higher.
+ -----------------
+
+ It is recommended (but not required) that you call SetInfo to update calculated values,
+ such as playlist item length, whenever you parse the playlist and have accurate information.
+
+ If you need playlist parsing, use api_playlistmanager.
+
+ This API is thread-safe, as long as you properly call Lock/Unlock
+ Methods which don't require external locking are marked with [*]
+ Note that these methods still lock internally
+ */
+
+enum
+{
+ API_PLAYLISTS_SUCCESS = 0,
+ API_PLAYLISTS_FAILURE = 1, // general purpose failure
+ API_PLAYLISTS_UNKNOWN_INFO_GUID = 2, // bad GUID passed to Set/GetInfo
+ API_PLAYLISTS_UNABLE_TO_LOAD_PLAYLISTS = 3, // take that variable name, FORTRAN77!
+ API_PLAYLISTS_INVALID_INDEX = 4, // index you passed was out of range
+ API_PLAYLISTS_BAD_SIZE = 5, // bad dataLen passed to Set/GetInfo
+};
+
+class api_playlists : public Dispatchable
+{
+protected:
+ api_playlists() {}
+ virtual ~api_playlists() {}
+
+public:
+ // call these to lock the list of playlists so no one changes in the middle of an operation. be careful with this!
+ // you can use AutoLockT<api_playlists> to help you out
+ // indices are only valid between these two calls. call GetGUID() if you need session-persistent identifiers
+ void Lock();
+ void Unlock();
+
+ size_t GetIterator();
+ /* this value changes each time a modification is made that would invalidate indices previously retrieved.
+ It does not change when information is changed
+ Use it to test if your index is out of date.
+ example:
+ size_t playlistIterator = playlists->GetIterator();
+ playlists->GetPosition(myGUID, &index);
+ // ... do a bunch of stuff
+ if (playlistIterator != playlists->GetIterator())
+ playlists->GetPosition(myGUID, &index);
+
+ This is meant as a tool to aid implementations that want to cache indices to prevent too many GetPosition() lookups
+ you don't need this function for casual usage of the API
+ */
+
+ int Sort( size_t sort_type );
+
+ void Flush(); // [*] flushes playlists to disk. avoid usage - mainly useful when some program is parsing the playlists.xml file externally
+
+ // get information about playlists
+ size_t GetCount(); // returns number of playlists
+
+ const wchar_t *GetFilename( size_t index ); // returns API_PLAYLISTS_SUCCESS or API_PLAYLISTS_FAILURE. only valid until you Unlock()
+ const wchar_t *GetName( size_t index ); // returns API_PLAYLISTS_SUCCESS or API_PLAYLISTS_FAILURE. only valid until you Unlock()
+
+ GUID GetGUID( size_t index ); // retrieves a unique ID which identifies this playlist
+
+ int GetPosition( GUID playlist_guid, size_t *index ); // retrieves the index where a particular playlist ID lives. returns API_PLAYLISTS_SUCCESS or API_PLAYLISTS_FAILURE
+ int GetInfo( size_t index, GUID info, void *data, size_t dataLen ); // This is for getting "extra" data, see list of GUIDs below. returns API_PLAYLISTS_SUCCESS or API_PLAYLISTS_FAILURE
+
+ // manipulating playlists
+ // at this time, it is not recommended that you use this API. It is reserved for ml_playlists.
+ int MoveBefore( size_t index1, size_t index2 ); // moves playlist at position index1 to before index2. setting index2 to anything larger than GetCount() moves to end
+
+ size_t AddPlaylist( const wchar_t *filename, const wchar_t *playlistName, GUID playlist_guid = INVALID_GUID ); // [*] adds a new playlist, returns new index. Generates a GUID if you don't pass third parameter. returns (size_t)-1 on error and (size_t)-2 if already exists
+ // note: AddPlaylist locks internally, but you need to lock externally if you want to trust the return value
+
+ size_t AddCloudPlaylist( const wchar_t *filename, const wchar_t *playlistName, GUID playlist_guid = INVALID_GUID ); // [*] adds a new playlist, returns new index. Generates a GUID if you don't pass third parameter. returns (size_t)-1 on error and (size_t)-2 if already exists
+ // note: AddCloudPlaylist locks internally, but you need to lock externally if you want to trust the return value
+
+ size_t AddPlaylist_NoCallback( const wchar_t *filename, const wchar_t *playlistName, GUID playlist_guid = INVALID_GUID );
+ // same as AddPlaylist, but doesn't do a syscallback, use when you want to make a few SetInfo calls,
+ // when you are done, call WASABI_API_SYSCB->syscb_issueCallback(api_playlists::SYSCALLBACK, api_playlists::PLAYLIST_REMOVED_POST, index, 0); yourself
+
+ int SetGUID( size_t index, GUID playlist_guid ); // sets (overrides) a playlist ID. Don't use unless you have some very specific need
+ int RenamePlaylist( size_t index, const wchar_t *name );
+ int MovePlaylist( size_t index, const wchar_t *filename ); // sets a new filename. NOTE: IT'S UP TO YOU TO PHYSICALLY MOVE/RENAME/CREATE THE NEW FILENAME.
+ int SetInfo( size_t index, GUID info, void *data, size_t dataLen ); // returns API_PLAYLISTS_SUCCESS or API_PLAYLISTS_FAILURE
+ int RemovePlaylist( size_t index ); // removes a particular playlist
+ int ClearPlaylists(); // [*] clears the entire list of playlists. Use at your own risk :)
+
+ /*
+ callbacks. these are sent through api_syscb.
+ callbacks are typically done within the api_playlists Lock,
+ so be careful not to deadlock by waiting on another thread that is also using api_playlists
+ TODO
+ probably want move, remove, add
+ need to think through adding, though. Someone might add a playlist and then SetInfo, so don't want to call syscb too early
+ */
+
+ enum
+ {
+ SYSCALLBACK = MK4CC( 'p', 'l', 'a', 'y' ),
+ PLAYLIST_ADDED = 10, // param1 = index
+ PLAYLIST_REMOVED_PRE = 20, // param1 = index, called BEFORE it's removed internally, so you can still query for data (Get* function calls)
+ PLAYLIST_REMOVED_POST = 30, // no parameters, called after it's removed internally
+ PLAYLIST_RENAMED = 40, // param1 = index
+ /* These two callbacks are made by you (not api_playlists)
+ * pass some unique ID as param2 (e.g. some function pointer or pointer to a global variable)
+ * so that you can identify your own callbacks if you also listen for these events
+ */
+ PLAYLIST_SAVED = 50, // param1 = index. You should send this when you save a playlist. Surround with Lock()/Unlock() so that the index is valid
+ PLAYLIST_FLUSH_REQUEST = 60, // param1 = index. Call before you load a playlist to request anyone who might be currently modifying the same playlist to save
+ };
+
+ enum
+ {
+ SORT_TITLE_ASCENDING,
+ SORT_TITLE_DESCENDING,
+ SORT_NUMBER_ASCENDING,
+ SORT_NUMBER_DESCENDING,
+ };
+
+ DISPATCH_CODES
+ {
+ API_PLAYLISTS_LOCK = 10,
+ API_PLAYLISTS_UNLOCK = 20,
+ API_PLAYLISTS_GETITERATOR = 30,
+ API_PLAYLISTS_FLUSH = 40,
+ API_PLAYLISTS_GETCOUNT = 50,
+ API_PLAYLISTS_GETFILENAME = 60,
+ API_PLAYLISTS_GETNAME = 70,
+ API_PLAYLISTS_GETGUID = 80,
+ API_PLAYLISTS_GETPOSITION = 90,
+ API_PLAYLISTS_GETINFO = 100,
+ API_PLAYLISTS_MOVEBEFORE = 110,
+ API_PLAYLISTS_ADDPLAYLIST = 120,
+ API_PLAYLISTS_ADDPLAYLISTNOCB = 121,
+ API_PLAYLISTS_ADDCLOUDPLAYLIST = 122,
+ API_PLAYLISTS_SETGUID = 130,
+ API_PLAYLISTS_RENAMEPLAYLIST = 140,
+ API_PLAYLISTS_MOVEPLAYLIST = 150,
+ API_PLAYLISTS_SETINFO = 160,
+ API_PLAYLISTS_REMOVEPLAYLIST = 170,
+ API_PLAYLISTS_CLEARPLAYLISTS = 180,
+ API_PLAYLISTS_SORT = 190,
+ };
+};
+
+// Info GUIDS
+
+// {C4FAD6CE-DA38-47b0-AAA9-E966D8E8E7C5}
+static const GUID api_playlists_itemCount =
+{ 0xc4fad6ce, 0xda38, 0x47b0, { 0xaa, 0xa9, 0xe9, 0x66, 0xd8, 0xe8, 0xe7, 0xc5 } };
+
+// {D4E0E000-A3F5-4f18-ADA5-F2BA40689593}
+static const GUID api_playlists_totalTime =
+{ 0xd4e0e000, 0xa3f5, 0x4f18, { 0xad, 0xa5, 0xf2, 0xba, 0x40, 0x68, 0x95, 0x93 } };
+
+// {F6E1AB19-6931-4cc9-BCBA-4B40DE2A959F}
+static const GUID api_playlists_iTunesID =
+{ 0xf6e1ab19, 0x6931, 0x4cc9, { 0xbc, 0xba, 0x4b, 0x40, 0xde, 0x2a, 0x95, 0x9f } };
+
+// {B83AD244-7CD3-4a24-B2C5-41F42CA37F14}
+static const GUID api_playlists_cloud =
+{ 0xb83ad244, 0x7cd3, 0x4a24, { 0xb2, 0xc5, 0x41, 0xf4, 0x2c, 0xa3, 0x7f, 0x14 } };
+
+
+inline void api_playlists::Lock()
+{
+ _voidcall( API_PLAYLISTS_LOCK );
+}
+inline void api_playlists::Unlock()
+{
+ _voidcall( API_PLAYLISTS_UNLOCK );
+}
+
+inline size_t api_playlists::GetIterator()
+{
+ return _call( API_PLAYLISTS_GETITERATOR, 0 );
+}
+
+inline void api_playlists::Flush()
+{
+ _voidcall( API_PLAYLISTS_FLUSH );
+}
+
+inline int api_playlists::Sort( size_t sort_type )
+{
+ return _call( API_PLAYLISTS_SORT, 0, sort_type );
+}
+
+inline size_t api_playlists::GetCount()
+{
+ return _call( API_PLAYLISTS_GETCOUNT, 0 );
+}
+
+inline const wchar_t *api_playlists::GetFilename( size_t index )
+{
+ return _call( API_PLAYLISTS_GETFILENAME, (const wchar_t *)0, index );
+}
+
+inline const wchar_t *api_playlists::GetName( size_t index )
+{
+ return _call( API_PLAYLISTS_GETNAME, (const wchar_t *)0, index );
+}
+
+inline GUID api_playlists::GetGUID( size_t index )
+{
+ return _call( API_PLAYLISTS_GETGUID, INVALID_GUID, index );
+}
+
+inline int api_playlists::GetPosition( GUID playlist_guid, size_t *index )
+{
+ return _call( API_PLAYLISTS_GETPOSITION, API_PLAYLISTS_FAILURE, playlist_guid, index );
+}
+
+inline int api_playlists::GetInfo( size_t index, GUID info, void *data, size_t dataLen )
+{
+ return _call( API_PLAYLISTS_GETINFO, API_PLAYLISTS_FAILURE, index, info, data, dataLen );
+}
+
+inline int api_playlists::MoveBefore( size_t index1, size_t index2 )
+{
+ return _call( API_PLAYLISTS_MOVEBEFORE, API_PLAYLISTS_FAILURE, index1, index2 );
+}
+
+inline size_t api_playlists::AddPlaylist( const wchar_t *filename, const wchar_t *playlistName, GUID playlist_guid )
+{
+ return _call( API_PLAYLISTS_ADDPLAYLIST, (size_t)-1, filename, playlistName, playlist_guid );
+}
+
+inline size_t api_playlists::AddPlaylist_NoCallback( const wchar_t *filename, const wchar_t *playlistName, GUID playlist_guid )
+{
+ return _call( API_PLAYLISTS_ADDPLAYLISTNOCB, (size_t)-1, filename, playlistName, playlist_guid );
+}
+
+inline size_t api_playlists::AddCloudPlaylist( const wchar_t *filename, const wchar_t *playlistName, GUID playlist_guid )
+{
+ return _call( API_PLAYLISTS_ADDCLOUDPLAYLIST, (size_t)-1, filename, playlistName, playlist_guid );
+}
+
+inline int api_playlists::SetGUID( size_t index, GUID playlist_guid )
+{
+ return _call( API_PLAYLISTS_SETGUID, API_PLAYLISTS_FAILURE, index, playlist_guid );
+}
+
+inline int api_playlists::RenamePlaylist( size_t index, const wchar_t *name )
+{
+ return _call( API_PLAYLISTS_RENAMEPLAYLIST, API_PLAYLISTS_FAILURE, index, name );
+}
+
+inline int api_playlists::MovePlaylist( size_t index, const wchar_t *filename )
+{
+ return _call( API_PLAYLISTS_MOVEPLAYLIST, API_PLAYLISTS_FAILURE, index, filename );
+}
+
+inline int api_playlists::SetInfo( size_t index, GUID info, void *data, size_t dataLen )
+{
+ return _call( API_PLAYLISTS_SETINFO, API_PLAYLISTS_FAILURE, index, info, data, dataLen );
+}
+
+inline int api_playlists::RemovePlaylist( size_t index )
+{
+ return _call( API_PLAYLISTS_REMOVEPLAYLIST, API_PLAYLISTS_FAILURE, index );
+}
+
+inline int api_playlists::ClearPlaylists()
+{
+ return _call( API_PLAYLISTS_CLEARPLAYLISTS, API_PLAYLISTS_FAILURE );
+}
+
+// {2DC3C390-D9B8-4a49-B230-EF240ADDDCDB}
+static const GUID api_playlistsGUID =
+{ 0x2dc3c390, 0xd9b8, 0x4a49, { 0xb2, 0x30, 0xef, 0x24, 0xa, 0xdd, 0xdc, 0xdb } };
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/factory_Handler.cpp b/Src/playlist/factory_Handler.cpp
new file mode 100644
index 00000000..199dd1ad
--- /dev/null
+++ b/Src/playlist/factory_Handler.cpp
@@ -0,0 +1,97 @@
+#include "api__playlist.h"
+#include "factory_Handler.h"
+#include "handler.h"
+#include "api/service/services.h"
+
+M3UHandler m3uHandler;
+PLSHandler plsHandler;
+B4SHandler b4sHandler;
+
+static const char m3uServiceName[] = "M3U Playlist Handler";
+static const char plsServiceName[] = "PLS Playlist Handler";
+
+#define DEFINE_HANDLER_FACTORY(CLASSNAME, className) const char *CLASSNAME ## HandlerFactory::GetServiceName() { return #CLASSNAME ## "Playlist Handler"; }\
+ GUID CLASSNAME ## HandlerFactory::GetGUID(){ return className ## HandlerGUID;}\
+ void *CLASSNAME ## HandlerFactory::GetInterface(int global_lock){ return &className ## Handler;}
+
+DEFINE_HANDLER_FACTORY(M3U, m3u);
+DEFINE_HANDLER_FACTORY(PLS, pls);
+DEFINE_HANDLER_FACTORY(B4S, b4s);
+
+/* --------------------------------------------------------------------- */
+FOURCC CommonHandlerFactory::GetServiceType()
+{
+ return WaSvc::PLAYLISTHANDLER;
+}
+
+int CommonHandlerFactory::SupportNonLockingInterface()
+{
+ return 1;
+}
+
+int CommonHandlerFactory::ReleaseInterface(void *ifc)
+{
+ return 1;
+}
+
+const char *CommonHandlerFactory::GetTestString()
+{
+ return NULL;
+}
+
+int CommonHandlerFactory::ServiceNotify(int msg, int param1, int param2)
+{
+ return 1;
+}
+
+#ifdef CBCLASS
+#undef CBCLASS
+#endif
+
+#define CBCLASS M3UHandlerFactory
+START_DISPATCH;
+CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName)
+CB(WASERVICEFACTORY_GETGUID, GetGUID)
+CB(WASERVICEFACTORY_GETINTERFACE, GetInterface)
+#undef CBCLASS
+#define CBCLASS CommonHandlerFactory
+CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType)
+CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface)
+CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface)
+CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString)
+CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify)
+END_DISPATCH;
+
+#undef CBCLASS
+#define CBCLASS PLSHandlerFactory
+START_DISPATCH;
+CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName)
+CB(WASERVICEFACTORY_GETGUID, GetGUID)
+CB(WASERVICEFACTORY_GETINTERFACE, GetInterface)
+#undef CBCLASS
+#define CBCLASS CommonHandlerFactory
+CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType)
+CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface)
+CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface)
+CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString)
+CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify)
+END_DISPATCH;
+
+
+
+#undef CBCLASS
+#define CBCLASS B4SHandlerFactory
+START_DISPATCH;
+CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName)
+CB(WASERVICEFACTORY_GETGUID, GetGUID)
+CB(WASERVICEFACTORY_GETINTERFACE, GetInterface)
+#undef CBCLASS
+#define CBCLASS CommonHandlerFactory
+CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType)
+CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface)
+CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface)
+CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString)
+CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify)
+END_DISPATCH;
+
+
diff --git a/Src/playlist/factory_Handler.h b/Src/playlist/factory_Handler.h
new file mode 100644
index 00000000..c952e233
--- /dev/null
+++ b/Src/playlist/factory_Handler.h
@@ -0,0 +1,30 @@
+#ifndef NULLSOFT_FACTORY_HANDLER_H
+#define NULLSOFT_FACTORY_HANDLER_H
+
+#include <api/service/waservicefactory.h>
+#include <api/service/services.h>
+
+class CommonHandlerFactory : public waServiceFactory
+{
+public:
+
+ FOURCC GetServiceType();
+ int SupportNonLockingInterface();
+ int ReleaseInterface(void *ifc);
+ const char *GetTestString();
+ int ServiceNotify(int msg, int param1, int param2);
+
+};
+
+#define DECLARE_HANDLER_FACTORY(CLASSNAME) class CLASSNAME : public CommonHandlerFactory {\
+public:\
+ const char *GetServiceName();\
+ GUID GetGUID();\
+ void *GetInterface(int global_lock);\
+protected:\
+ RECVS_DISPATCH;}
+
+DECLARE_HANDLER_FACTORY(M3UHandlerFactory);
+DECLARE_HANDLER_FACTORY(PLSHandlerFactory);
+DECLARE_HANDLER_FACTORY(B4SHandlerFactory);
+#endif \ No newline at end of file
diff --git a/Src/playlist/factory_playlistmanager.cpp b/Src/playlist/factory_playlistmanager.cpp
new file mode 100644
index 00000000..6de39fb8
--- /dev/null
+++ b/Src/playlist/factory_playlistmanager.cpp
@@ -0,0 +1,65 @@
+#include "main.h"
+#include "api__playlist.h"
+#include "factory_playlistmanager.h"
+#include "PlaylistManager.h"
+
+static const char serviceName[] = "Playlist Manager";
+
+FOURCC PlaylistManagerFactory::GetServiceType()
+{
+ return WaSvc::UNIQUE;
+}
+
+const char *PlaylistManagerFactory::GetServiceName()
+{
+ return serviceName;
+}
+
+GUID PlaylistManagerFactory::GetGUID()
+{
+ return api_playlistmanagerGUID;
+}
+
+void *PlaylistManagerFactory::GetInterface(int global_lock)
+{
+// if (global_lock)
+// WASABI_API_SVC->service_lock(this, (void *)ifc);
+ return &playlistManager;
+}
+
+int PlaylistManagerFactory::SupportNonLockingInterface()
+{
+ return 1;
+}
+
+int PlaylistManagerFactory::ReleaseInterface(void *ifc)
+{
+ //WASABI_API_SVC->service_unlock(ifc);
+ return 1;
+}
+
+const char *PlaylistManagerFactory::GetTestString()
+{
+ return 0;
+}
+
+int PlaylistManagerFactory::ServiceNotify(int msg, int param1, int param2)
+{
+ return 1;
+}
+
+#ifdef CBCLASS
+#undef CBCLASS
+#endif
+
+#define CBCLASS PlaylistManagerFactory
+START_DISPATCH;
+CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType)
+CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName)
+CB(WASERVICEFACTORY_GETGUID, GetGUID)
+CB(WASERVICEFACTORY_GETINTERFACE, GetInterface)
+CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface)
+CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface)
+CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString)
+CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify)
+END_DISPATCH;
diff --git a/Src/playlist/factory_playlistmanager.h b/Src/playlist/factory_playlistmanager.h
new file mode 100644
index 00000000..50dc893c
--- /dev/null
+++ b/Src/playlist/factory_playlistmanager.h
@@ -0,0 +1,28 @@
+#ifndef NULLSOFT_FACTORY_PLAYLISTMANAGER_H
+#define NULLSOFT_FACTORY_PLAYLISTMANAGER_H
+
+#include "api__playlist.h"
+#include "api/service/waservicefactory.h"
+#include "api/service/services.h"
+
+class PlaylistManagerFactory : public waServiceFactory
+{
+public:
+ FOURCC GetServiceType();
+ const char *GetServiceName();
+ GUID GetGUID();
+ void *GetInterface( int global_lock );
+
+ int SupportNonLockingInterface();
+ int ReleaseInterface( void *ifc );
+
+ const char *GetTestString();
+
+ int ServiceNotify( int msg, int param1, int param2 );
+
+protected:
+ RECVS_DISPATCH;
+};
+
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/factory_playlists.cpp b/Src/playlist/factory_playlists.cpp
new file mode 100644
index 00000000..841bac6d
--- /dev/null
+++ b/Src/playlist/factory_playlists.cpp
@@ -0,0 +1,63 @@
+#include "main.h"
+#include "api__playlist.h"
+#include "factory_playlists.h"
+#include "Playlists.h"
+
+Playlists playlists;
+static const char serviceName[] = "Playlists";
+
+FOURCC PlaylistsFactory::GetServiceType()
+{
+ return WaSvc::UNIQUE;
+}
+
+const char *PlaylistsFactory::GetServiceName()
+{
+ return serviceName;
+}
+
+GUID PlaylistsFactory::GetGUID()
+{
+ return api_playlistsGUID;
+}
+
+void *PlaylistsFactory::GetInterface(int global_lock)
+{
+// if (global_lock)
+// WASABI_API_SVC->service_lock(this, (void *)ifc);
+ return &playlists;
+}
+
+int PlaylistsFactory::SupportNonLockingInterface()
+{
+ return 1;
+}
+
+int PlaylistsFactory::ReleaseInterface(void *ifc)
+{
+ //WASABI_API_SVC->service_unlock(ifc);
+ return 1;
+}
+
+const char *PlaylistsFactory::GetTestString()
+{
+ return 0;
+}
+
+int PlaylistsFactory::ServiceNotify(int msg, int param1, int param2)
+{
+ return 1;
+}
+
+#define CBCLASS PlaylistsFactory
+START_DISPATCH;
+CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType)
+CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName)
+CB(WASERVICEFACTORY_GETGUID, GetGUID)
+CB(WASERVICEFACTORY_GETINTERFACE, GetInterface)
+CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface)
+CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface)
+CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString)
+CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify)
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/playlist/factory_playlists.h b/Src/playlist/factory_playlists.h
new file mode 100644
index 00000000..d31d16da
--- /dev/null
+++ b/Src/playlist/factory_playlists.h
@@ -0,0 +1,25 @@
+#ifndef NULLSOFT_FACTORY_PLAYLISTS_H
+#define NULLSOFT_FACTORY_PLAYLISTS_H
+
+#include "api__playlist.h"
+#include "api/service/waservicefactory.h"
+#include "api/service/services.h"
+
+class PlaylistsFactory : public waServiceFactory
+{
+public:
+ FOURCC GetServiceType();
+ const char *GetServiceName();
+ GUID GetGUID();
+ void *GetInterface( int global_lock );
+ int SupportNonLockingInterface();
+ int ReleaseInterface( void *ifc );
+ const char *GetTestString();
+ int ServiceNotify( int msg, int param1, int param2 );
+
+protected:
+ RECVS_DISPATCH;
+};
+
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/ifc_playlist.h b/Src/playlist/ifc_playlist.h
new file mode 100644
index 00000000..656623c3
--- /dev/null
+++ b/Src/playlist/ifc_playlist.h
@@ -0,0 +1,139 @@
+#ifndef NULLSOFT_IFC_PLAYLIST_H
+#define NULLSOFT_IFC_PLAYLIST_H
+
+#include "bfc/dispatch.h"
+#include "bfc/platform/types.h"
+
+enum
+{
+ PLAYLIST_SUCCESS = 0,
+ PLAYLIST_UNIMPLEMENTED = 1,
+};
+
+class ifc_playlist : public Dispatchable
+{
+protected:
+ ifc_playlist() {}
+ ~ifc_playlist() {}
+
+public:
+ DISPATCH_CODES
+ {
+ IFC_PLAYLIST_CLEAR = 10,
+ //IFC_PLAYLIST_APPENDWITHINFO = 20,
+ //IFC_PLAYLIST_APPEND = 30,
+ IFC_PLAYLIST_GETNUMITEMS = 40,
+ IFC_PLAYLIST_GETITEM = 50,
+ IFC_PLAYLIST_GETITEMTITLE = 60,
+ IFC_PLAYLIST_GETITEMLENGTHMILLISECONDS = 70,
+ IFC_PLAYLIST_GETITEMEXTENDEDINFO = 80,
+ IFC_PLAYLIST_REVERSE = 90,
+ IFC_PLAYLIST_SWAP = 100,
+ IFC_PLAYLIST_RANDOMIZE = 110,
+ IFC_PLAYLIST_REMOVE = 120,
+ IFC_PLAYLIST_SORTBYTITLE = 130,
+ IFC_PLAYLIST_SORTBYFILENAME = 140,
+ IFC_PLAYLIST_SORTBYDIRECTORY = 150,
+ };
+
+ void Clear();
+ //void AppendWithInfo(const wchar_t *filename, const char *title, int lengthInMS);
+ //void Append(const wchar_t *filename);
+
+ size_t GetNumItems();
+
+ size_t GetItem( size_t item, wchar_t *filename, size_t filenameCch );
+
+ size_t GetItemTitle( size_t item, wchar_t *title, size_t titleCch );
+
+ int GetItemLengthMilliseconds( size_t item ); // TODO: maybe microsecond for better resolution?
+
+ size_t GetItemExtendedInfo( size_t item, const wchar_t *metadata, wchar_t *info, size_t infoCch );
+
+ int Reverse(); // optional, return 1 to indicate that you did the reversal (otherwise, caller must perform manually)
+ int Swap( size_t item1, size_t item2 );
+ int Randomize( int ( *generator )( ) ); // optional, return 1 to indicate that you did the randomization (otherwise, caller must perform manually)
+ void Remove( size_t item );
+
+ int SortByTitle(); // optional, return 1 to indicate that you did the sort (otherwise, caller must perform manually)
+ int SortByFilename(); // optional, return 1 to indicate that you did the sort (otherwise, caller must perform manually)
+ int SortByDirectory();
+};
+
+inline void ifc_playlist::Clear()
+{
+ _voidcall( IFC_PLAYLIST_CLEAR );
+}
+/*
+inline void ifc_playlist::AppendWithInfo(const wchar_t *filename, const char *title, int lengthInMS)
+{
+_voidcall(IFC_PLAYLIST_APPENDWITHINFO, filename, title, lengthInMS);
+}
+*/
+/*
+inline void ifc_playlist::Append(const wchar_t *filename)
+{
+_voidcall(IFC_PLAYLIST_APPEND, filename);
+}*/
+
+inline size_t ifc_playlist::GetNumItems()
+{
+ return _call( IFC_PLAYLIST_GETNUMITEMS, (size_t)0 );
+}
+
+inline size_t ifc_playlist::GetItem( size_t item, wchar_t *filename, size_t filenameCch )
+{
+ return _call( IFC_PLAYLIST_GETITEM, (size_t)0, item, filename, filenameCch );
+}
+
+inline size_t ifc_playlist::GetItemTitle( size_t item, wchar_t *title, size_t titleCch )
+{
+ return _call( IFC_PLAYLIST_GETITEMTITLE, (size_t)0, item, title, titleCch );
+}
+
+inline int ifc_playlist::GetItemLengthMilliseconds( size_t item )
+{
+ return _call( IFC_PLAYLIST_GETITEMLENGTHMILLISECONDS, (int)-1, item );
+}
+
+inline size_t ifc_playlist::GetItemExtendedInfo( size_t item, const wchar_t *metadata, wchar_t *info, size_t infoCch )
+{
+ return _call( IFC_PLAYLIST_GETITEMEXTENDEDINFO, (size_t)0, item, metadata, info, infoCch );
+}
+
+inline int ifc_playlist::Reverse()
+{
+ return _call( IFC_PLAYLIST_REVERSE, (int)PLAYLIST_UNIMPLEMENTED );
+}
+
+inline int ifc_playlist::Swap( size_t item1, size_t item2 )
+{
+ return _call( IFC_PLAYLIST_SWAP, (int)PLAYLIST_UNIMPLEMENTED, item1, item2 );
+}
+
+inline int ifc_playlist::Randomize( int ( *generator )( ) )
+{
+ return _call( IFC_PLAYLIST_RANDOMIZE, (int)PLAYLIST_UNIMPLEMENTED, generator );
+}
+
+inline void ifc_playlist::Remove( size_t item )
+{
+ _voidcall( IFC_PLAYLIST_REMOVE, item );
+}
+
+inline int ifc_playlist::SortByTitle()
+{
+ return _call( IFC_PLAYLIST_SORTBYTITLE, (int)0 );
+}
+
+inline int ifc_playlist::SortByFilename()
+{
+ return _call( IFC_PLAYLIST_SORTBYFILENAME, (int)0 );
+}
+
+inline int ifc_playlist::SortByDirectory()
+{
+ return _call( IFC_PLAYLIST_SORTBYDIRECTORY, (int)0 );
+}
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/ifc_playlistT.h b/Src/playlist/ifc_playlistT.h
new file mode 100644
index 00000000..7b9d96d5
--- /dev/null
+++ b/Src/playlist/ifc_playlistT.h
@@ -0,0 +1,48 @@
+#pragma once
+#include "ifc_playlist.h"
+template <class T>
+class ifc_playlistT : public ifc_playlist
+{
+protected:
+ ifc_playlistT() {}
+ ~ifc_playlistT() {}
+
+ void Clear();
+ //void AppendWithInfo(const wchar_t *filename, const char *title, int lengthInMS);
+ //void Append(const wchar_t *filename);
+
+ size_t GetNumItems() { return 0; }
+ size_t GetItem( size_t item, wchar_t *filename, size_t filenameCch ) { return 0; }
+ size_t GetItemTitle( size_t item, wchar_t *title, size_t titleCch ) { return 0; }
+ int GetItemLengthMilliseconds( size_t item ) { return -1; }
+ size_t GetItemExtendedInfo( size_t item, const wchar_t *metadata, wchar_t *info, size_t infoCch ) { return 0; }
+ int Reverse() { return PLAYLIST_UNIMPLEMENTED; }
+ int Swap( size_t item1, size_t item2 ) { return PLAYLIST_UNIMPLEMENTED; }
+ int Randomize( int ( *generator )( ) ) { return PLAYLIST_UNIMPLEMENTED; }
+ void Remove( size_t item ) {}
+ int SortByTitle() { return 0; }
+ int SortByFilename() { return 0; }
+ int SortByDirectory() { return 0; }
+
+#define CBCLASS T
+#define CBCLASST ifc_playlistT<T>
+ START_DISPATCH_INLINE;
+ VCBT( IFC_PLAYLIST_CLEAR, Clear )
+ //M_VCB( IFC_PLAYLIST_APPENDWITHINFO, AppendWithInfo)
+ //M_VCB( IFC_PLAYLIST_APPEND, Append)
+ CBT( IFC_PLAYLIST_GETNUMITEMS, GetNumItems )
+ CBT( IFC_PLAYLIST_GETITEM, GetItem )
+ CBT( IFC_PLAYLIST_GETITEMTITLE, GetItemTitle )
+ CBT( IFC_PLAYLIST_GETITEMLENGTHMILLISECONDS, GetItemLengthMilliseconds )
+ CBT( IFC_PLAYLIST_GETITEMEXTENDEDINFO, GetItemExtendedInfo )
+ CBT( IFC_PLAYLIST_REVERSE, Reverse )
+ CBT( IFC_PLAYLIST_SWAP, Swap )
+ CBT( IFC_PLAYLIST_RANDOMIZE, Randomize )
+ VCBT( IFC_PLAYLIST_REMOVE, Remove )
+ CBT( IFC_PLAYLIST_SORTBYTITLE, SortByTitle )
+ CBT( IFC_PLAYLIST_SORTBYFILENAME, SortByFilename )
+ CBT( IFC_PLAYLIST_SORTBYDIRECTORY, SortByDirectory )
+ END_DISPATCH;
+#undef CBCLASS
+#undef CBCLASST
+};
diff --git a/Src/playlist/ifc_playlistdirectorycallback.h b/Src/playlist/ifc_playlistdirectorycallback.h
new file mode 100644
index 00000000..1e8a09bd
--- /dev/null
+++ b/Src/playlist/ifc_playlistdirectorycallback.h
@@ -0,0 +1,33 @@
+#ifndef NULLSOFT_IFC_PLAYLISTDIRECTORYCALLBACK_H
+#define NULLSOFT_IFC_PLAYLISTDIRECTORYCALLBACK_H
+
+#include <bfc/dispatch.h>
+#include <bfc/platform/types.h>
+
+class ifc_playlistdirectorycallback : public Dispatchable
+{
+protected:
+ ifc_playlistdirectorycallback() {}
+ ~ifc_playlistdirectorycallback() {}
+
+public:
+ bool ShouldRecurse(const wchar_t *path);
+ bool ShouldLoad(const wchar_t *filename);
+
+ DISPATCH_CODES
+ {
+ IFC_PLAYLISTDIRECTORYCALLBACK_SHOULDRECURSE = 10,
+ IFC_PLAYLISTDIRECTORYCALLBACK_SHOULDLOAD = 20,
+ };
+};
+
+inline bool ifc_playlistdirectorycallback::ShouldRecurse(const wchar_t *path)
+{
+ return _call(IFC_PLAYLISTDIRECTORYCALLBACK_SHOULDRECURSE, (bool)false, path);
+}
+
+inline bool ifc_playlistdirectorycallback::ShouldLoad(const wchar_t *filename)
+{
+ return _call(IFC_PLAYLISTDIRECTORYCALLBACK_SHOULDLOAD, (bool)true, filename);
+}
+#endif \ No newline at end of file
diff --git a/Src/playlist/ifc_playlistloader.h b/Src/playlist/ifc_playlistloader.h
new file mode 100644
index 00000000..d1b6fba4
--- /dev/null
+++ b/Src/playlist/ifc_playlistloader.h
@@ -0,0 +1,37 @@
+#ifndef NULLSOFT_IFC_PLAYLISTLOADER_H
+#define NULLSOFT_IFC_PLAYLISTLOADER_H
+
+#include <bfc/dispatch.h>
+#include <wchar.h>
+#include "ifc_playlistloadercallback.h"
+
+enum
+{
+ IFC_PLAYLISTLOADER_SUCCESS = 0,
+ IFC_PLAYLISTLOADER_FAILED = 1,
+
+ IFC_PLAYLISTLOADER_NEXTITEM_EOF = 1,
+};
+
+class ifc_playlistloader : public Dispatchable
+{
+protected:
+ ifc_playlistloader() {}
+ ~ifc_playlistloader() {}
+
+public:
+ int Load( const wchar_t *filename, ifc_playlistloadercallback *playlist );
+
+ DISPATCH_CODES
+ {
+ IFC_PLAYLISTLOADER_LOAD = 10,
+ };
+
+};
+
+inline int ifc_playlistloader::Load( const wchar_t *filename, ifc_playlistloadercallback *playlist )
+{
+ return _call( IFC_PLAYLISTLOADER_LOAD, (int)IFC_PLAYLISTLOADER_FAILED, filename, playlist );
+}
+
+#endif
diff --git a/Src/playlist/ifc_playlistloadercallback.h b/Src/playlist/ifc_playlistloadercallback.h
new file mode 100644
index 00000000..561fa749
--- /dev/null
+++ b/Src/playlist/ifc_playlistloadercallback.h
@@ -0,0 +1,96 @@
+#ifndef NULLSOFT_IFC_PLAYLISTLOADERCALLBACK_H
+#define NULLSOFT_IFC_PLAYLISTLOADERCALLBACK_H
+
+#include <bfc/dispatch.h>
+#include <bfc/platform/types.h>
+#include "ifc_plentryinfo.h"
+
+#ifndef FILENAME_SIZE
+#define FILENAME_SIZE (MAX_PATH * 4)
+#endif
+
+#ifndef FILETITLE_SIZE
+#define FILETITLE_SIZE 400
+#endif
+
+class ifc_playlistinfo; // TODO
+class ifc_playlistloadercallback : public Dispatchable
+{
+protected:
+ ifc_playlistloadercallback() {}
+ ~ifc_playlistloadercallback() {}
+
+public:
+ // return 0 to continue enumeration, or 1 to quit
+
+ // title will be NULL if no title found, length will be -1
+ int OnFile( const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info );
+ void OnFileOld( const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info );
+
+ // numEntries is just a hint, there is no gaurantee. 0 means "don't know"
+ int OnPlaylistInfo( const wchar_t *playlistName, size_t numEntries, ifc_plentryinfo *info );
+ void OnPlaylistInfoOld( const wchar_t *playlistName, size_t numEntries, ifc_plentryinfo *info );
+
+ const wchar_t *GetBasePath(); // return 0 to use playlist file path as base (or just don't implement)
+
+ DISPATCH_CODES
+ {
+ IFC_PLAYLISTLOADERCALLBACK_ONFILE = 10,
+ IFC_PLAYLISTLOADERCALLBACK_ONFILE_RET = 11,
+ IFC_PLAYLISTLOADERCALLBACK_ONPLAYLISTINFO = 20,
+ IFC_PLAYLISTLOADERCALLBACK_ONPLAYLISTINFO_RET = 21,
+ IFC_PLAYLISTLOADERCALLBACK_GETBASEPATH = 30,
+ };
+ enum
+ {
+ LOAD_CONTINUE = 0,
+ LOAD_ABORT = 1,
+ };
+};
+
+inline void ifc_playlistloadercallback::OnFileOld( const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info )
+{
+ _voidcall( IFC_PLAYLISTLOADERCALLBACK_ONFILE, filename, title, lengthInMS, info );
+}
+
+inline int ifc_playlistloadercallback::OnFile( const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info )
+{
+ void *params[ 4 ] = { &filename, &title, &lengthInMS, &info };
+ int retval;
+
+ if ( _dispatch( IFC_PLAYLISTLOADERCALLBACK_ONFILE_RET, &retval, params, 4 ) == 0 )
+ {
+ _dispatch( IFC_PLAYLISTLOADERCALLBACK_ONFILE, 0, params, 4 );
+
+ return LOAD_CONTINUE;
+ }
+
+ return retval;
+}
+
+inline void ifc_playlistloadercallback::OnPlaylistInfoOld( const wchar_t *playlistName, size_t numEntries, ifc_plentryinfo *info )
+{
+ _voidcall( IFC_PLAYLISTLOADERCALLBACK_ONPLAYLISTINFO, playlistName, numEntries, info );
+}
+
+inline int ifc_playlistloadercallback::OnPlaylistInfo( const wchar_t *playlistName, size_t numEntries, ifc_plentryinfo *info )
+{
+ void *params[ 3 ] = { &playlistName, &numEntries, &info };
+ int retval;
+
+ if ( _dispatch( IFC_PLAYLISTLOADERCALLBACK_ONPLAYLISTINFO_RET, &retval, params, 3 ) == 0 )
+ {
+ _dispatch( IFC_PLAYLISTLOADERCALLBACK_ONPLAYLISTINFO, 0, params, 3 );
+
+ return LOAD_CONTINUE;
+ }
+
+ return retval;
+}
+
+inline const wchar_t *ifc_playlistloadercallback::GetBasePath()
+{
+ return _call( IFC_PLAYLISTLOADERCALLBACK_GETBASEPATH, (const wchar_t *)0 );
+}
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/ifc_playlistloadercallbackT.h b/Src/playlist/ifc_playlistloadercallbackT.h
new file mode 100644
index 00000000..7ee6366f
--- /dev/null
+++ b/Src/playlist/ifc_playlistloadercallbackT.h
@@ -0,0 +1,29 @@
+#pragma once
+#include "ifc_playlistloadercallback.h"
+
+template <class T>
+class ifc_playlistloadercallbackT : public ifc_playlistloadercallback
+{
+protected:
+ ifc_playlistloadercallbackT() {}
+ ~ifc_playlistloadercallbackT() {}
+protected:
+ // return 0 to continue enumeration, or 1 to quit
+
+ // title will be NULL if no title found, length will be -1
+ int OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info) { return LOAD_ABORT; }
+ // numEntries is just a hint, there is no gaurantee. 0 means "don't know"
+ int OnPlaylistInfo(const wchar_t *playlistName, size_t numEntries, ifc_plentryinfo *info) { return LOAD_ABORT; }
+ // return 0 to use playlist file path as base (or just don't implement)
+ const wchar_t *GetBasePath() { return 0; }
+
+#define CBCLASS T
+#define CBCLASST ifc_playlistloadercallbackT<T>
+ START_DISPATCH_INLINE;
+ CBT(IFC_PLAYLISTLOADERCALLBACK_ONFILE_RET, OnFile);
+ CBT(IFC_PLAYLISTLOADERCALLBACK_ONPLAYLISTINFO_RET, OnPlaylistInfo);
+ CBT(IFC_PLAYLISTLOADERCALLBACK_GETBASEPATH, GetBasePath);
+ END_DISPATCH;
+#undef CBCLASS
+#undef CBCLASST
+};
diff --git a/Src/playlist/ifc_plentryinfo.h b/Src/playlist/ifc_plentryinfo.h
new file mode 100644
index 00000000..f8c767a9
--- /dev/null
+++ b/Src/playlist/ifc_plentryinfo.h
@@ -0,0 +1,28 @@
+#ifndef NULLSOFT_IFC_PLENTRYINFO_H
+#define NULLSOFT_IFC_PLENTRYINFO_H
+
+#include <bfc/dispatch.h>
+#include <bfc/platform/types.h>
+
+class ifc_plentryinfo : public Dispatchable
+{
+protected:
+ ifc_plentryinfo() {}
+ ~ifc_plentryinfo() {}
+
+public:
+ // TODO: you can't guarantee that this wchar_t * pointer will last, make a copy before calling again!
+ const wchar_t *GetExtendedInfo( const wchar_t *parameter );
+
+ DISPATCH_CODES
+ {
+ IFC_PLENTRYINFO_GETEXTENDEDINFO = 10,
+ };
+};
+
+inline const wchar_t *ifc_plentryinfo::GetExtendedInfo( const wchar_t *parameter )
+{
+ return _call( IFC_PLENTRYINFO_GETEXTENDEDINFO, (const wchar_t *)0, parameter );
+}
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/main.cpp b/Src/playlist/main.cpp
new file mode 100644
index 00000000..4d7f9fbd
--- /dev/null
+++ b/Src/playlist/main.cpp
@@ -0,0 +1,134 @@
+#include "api__playlist.h"
+#include "main.h"
+
+#include "factory_Handler.h"
+#include "factory_playlistmanager.h"
+#include "factory_playlists.h"
+#include "../Winamp/api_random.h"
+#include "Playlists.h"
+#include "plstring.h"
+#include "ScriptObjectFactory.h"
+#include "../nu/ServiceWatcher.h"
+#include "JSAPI2_Creator.h"
+
+extern Playlists playlists;
+int (*warand)(void) = 0;
+
+M3UHandlerFactory m3uHandlerFactory;
+PLSHandlerFactory plsHandlerFactory;
+B4SHandlerFactory b4sHandlerFactory;
+PlaylistManagerFactory playlistManagerFactory;
+PlaylistsFactory playlistsFactory;
+ScriptObjectFactory scriptObjectFactory;
+JSAPI2Factory jsapi2Factory;
+ServiceWatcher serviceWatcher;
+PlaylistComponent playlistComponent;
+
+api_service *WASABI_API_SVC = 0;
+api_application *WASABI_API_APP = 0;
+api_config *AGAVE_API_CONFIG = 0;
+api_syscb *WASABI_API_SYSCB = 0;
+api_maki *WASABI_API_MAKI = 0;
+JSAPI2::api_security *AGAVE_API_JSAPI2_SECURITY = 0;
+api_stats *AGAVE_API_STATS = 0;
+
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+
+template <class api_t>
+api_t *GetService( GUID serviceGUID )
+{
+ waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid( serviceGUID );
+ if ( sf )
+ return (api_t *)sf->getInterface();
+ else
+ return 0;
+
+}
+
+inline void ReleaseService( GUID serviceGUID, void *service )
+{
+ if ( service )
+ {
+ waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid( serviceGUID );
+ if ( sf )
+ sf->releaseInterface( service );
+ }
+}
+
+void PlaylistComponent::RegisterServices( api_service *service )
+{
+ WASABI_API_SVC = service;
+
+ warand = QuickService<api_random>( randomApiGUID )->GetFunction();
+
+ WASABI_API_APP = GetService<api_application>( applicationApiServiceGuid );
+ WASABI_API_SYSCB = GetService<api_syscb>( syscbApiServiceGuid );
+ AGAVE_API_CONFIG = GetService<api_config>( AgaveConfigGUID );
+ AGAVE_API_JSAPI2_SECURITY = GetService<JSAPI2::api_security>( JSAPI2::api_securityGUID );
+ AGAVE_API_STATS = GetService<api_stats>( AnonymousStatsGUID );
+
+ serviceWatcher.WatchWith( WASABI_API_SVC );
+ serviceWatcher.WatchFor( &WASABI_API_MAKI, makiApiServiceGuid );
+
+ // need to get WASABI_API_APP first
+ plstring_init();
+
+ WASABI_API_SVC->service_register( &m3uHandlerFactory );
+ WASABI_API_SVC->service_register( &plsHandlerFactory );
+ WASABI_API_SVC->service_register( &b4sHandlerFactory );
+ WASABI_API_SVC->service_register( &playlistManagerFactory );
+ WASABI_API_SVC->service_register( &playlistsFactory );
+ WASABI_API_SVC->service_register( &scriptObjectFactory );
+ WASABI_API_SVC->service_register( &jsapi2Factory );
+
+ WASABI_API_LNG = GetService<api_language>( languageApiGUID );
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG( hModule, playlistLangGUID );
+
+ // register for service callbacks in case any of these don't exist yet
+ WASABI_API_SYSCB->syscb_registerCallback( &serviceWatcher );
+}
+
+int PlaylistComponent::RegisterServicesSafeModeOk()
+{
+ return 1;
+}
+
+void PlaylistComponent::DeregisterServices( api_service *service )
+{
+ playlists.Flush();
+
+ service->service_deregister( &playlistsFactory );
+ service->service_deregister( &playlistManagerFactory );
+ service->service_deregister( &m3uHandlerFactory );
+ service->service_deregister( &plsHandlerFactory );
+ service->service_deregister( &b4sHandlerFactory );
+ service->service_deregister( &scriptObjectFactory );
+ service->service_deregister( &jsapi2Factory );
+
+ serviceWatcher.StopWatching();
+ serviceWatcher.Clear();
+
+ ReleaseService( makiApiServiceGuid, WASABI_API_MAKI );
+ ReleaseService( applicationApiServiceGuid, WASABI_API_APP );
+ ReleaseService( AgaveConfigGUID, AGAVE_API_CONFIG );
+ ReleaseService( syscbApiServiceGuid, WASABI_API_SYSCB );
+ ReleaseService( languageApiGUID, WASABI_API_LNG );
+ ReleaseService( JSAPI2::api_securityGUID, AGAVE_API_JSAPI2_SECURITY );
+ ReleaseService( AnonymousStatsGUID, AGAVE_API_STATS );
+}
+
+extern "C" __declspec(dllexport) ifc_wa5component *GetWinamp5SystemComponent()
+{
+ return &playlistComponent;
+}
+
+#define CBCLASS PlaylistComponent
+START_DISPATCH;
+VCB( API_WA5COMPONENT_REGISTERSERVICES, RegisterServices )
+CB( API_WA5COMPONENT_REGISTERSERVICES_SAFE_MODE, RegisterServicesSafeModeOk )
+VCB( API_WA5COMPONENT_DEREEGISTERSERVICES, DeregisterServices )
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/playlist/main.h b/Src/playlist/main.h
new file mode 100644
index 00000000..62b60334
--- /dev/null
+++ b/Src/playlist/main.h
@@ -0,0 +1,26 @@
+#ifndef NULLSOFT_PLAYLIST_MAIN_H
+#define NULLSOFT_PLAYLIST_MAIN_H
+
+#include <windows.h>
+#include <shlwapi.h>
+#include "..\Components\wac_network\wac_network_http_receiver_api.h"
+
+extern int (*warand)(void);
+HRESULT ResolveShortCut(HWND hwnd, LPCWSTR pszShortcutFile, LPWSTR pszPath);
+bool IsUrl(const wchar_t *url);
+void SetUserAgent(api_httpreceiver *http);
+const char *GetProxy();
+
+#include "../Agave/Component/ifc_wa5component.h"
+
+class PlaylistComponent : public ifc_wa5component
+{
+public:
+ void RegisterServices(api_service *service);
+ int RegisterServicesSafeModeOk();
+ void DeregisterServices(api_service *service);
+protected:
+ RECVS_DISPATCH;
+};
+extern PlaylistComponent playlistComponent;
+#endif \ No newline at end of file
diff --git a/Src/playlist/pl_entry.cpp b/Src/playlist/pl_entry.cpp
new file mode 100644
index 00000000..5f164654
--- /dev/null
+++ b/Src/playlist/pl_entry.cpp
@@ -0,0 +1,336 @@
+#include "main.h"
+#include "pl_entry.h"
+#include "plstring.h"
+#include <wchar.h>
+#include "../nu/strsafe.h"
+
+#include <iostream>
+
+static const wchar_t *_INFO_NAME_MEDIA_HASH = L"mediahash";
+static const wchar_t *_INFO_NAME_META_HASH = L"metahash";
+static const wchar_t *_INFO_NAME_CLOUD_ID = L"cloud_id";
+static const wchar_t *_INFO_NAME_CLOUD_STATUS = L"cloud_status";
+static const wchar_t *_INFO_NAME_CLOUD_DEVICES = L"cloud_devices";
+
+pl_entry::pl_entry( const wchar_t *p_filename, const wchar_t *p_title, int p_length_ms )
+{
+ SetFilename( p_filename );
+ SetTitle( p_title );
+ SetLengthMilliseconds( p_length_ms );
+}
+
+pl_entry::pl_entry( const wchar_t *p_filename, const wchar_t *p_title, int p_length_ms, int p_size )
+{
+ SetFilename( p_filename );
+ SetTitle( p_title );
+ SetLengthMilliseconds( p_length_ms );
+ SetSizeBytes( p_size );
+}
+
+pl_entry::pl_entry( const wchar_t *p_filename, const wchar_t *p_title, int p_length_ms, ifc_plentryinfo *p_info )
+{
+ SetFilename( p_filename );
+ SetTitle( p_title );
+ SetLengthMilliseconds( p_length_ms );
+
+ if ( p_info )
+ {
+ SetMediahash( p_info->GetExtendedInfo( _INFO_NAME_MEDIA_HASH ) );
+ SetMetahash( p_info->GetExtendedInfo( _INFO_NAME_META_HASH ) );
+ SetCloudID( p_info->GetExtendedInfo( _INFO_NAME_CLOUD_ID ) );
+ SetCloudStatus( p_info->GetExtendedInfo( _INFO_NAME_CLOUD_STATUS ) );
+ SetCloudDevices( p_info->GetExtendedInfo( _INFO_NAME_CLOUD_DEVICES ) );
+
+ const wchar_t *l_tvg_name = p_info->GetExtendedInfo( L"tvg-name" );
+
+ if ( l_tvg_name && wcslen( l_tvg_name ) > 0 )
+ {
+ _extended_infos.emplace( L"tvg-name", l_tvg_name );
+
+ const wchar_t *l_tvg_id = p_info->GetExtendedInfo( L"tvg-id" );
+ if ( l_tvg_id && *l_tvg_id )
+ _extended_infos.emplace( L"tvg-id", l_tvg_id );
+
+ const wchar_t *l_tvg_logo = p_info->GetExtendedInfo( L"tvg-logo" );
+ if ( l_tvg_logo && *l_tvg_logo )
+ _extended_infos.emplace( L"tvg-logo", l_tvg_logo );
+
+ const wchar_t *l_tvg_title = p_info->GetExtendedInfo( L"tvg-title" );
+ if ( l_tvg_title && *l_tvg_title )
+ _extended_infos.emplace( L"group-title", l_tvg_title );
+ }
+
+ const wchar_t *l_ext = p_info->GetExtendedInfo( L"ext" );
+
+ if ( l_ext && wcslen( l_ext ) )
+ _extended_infos.emplace( L"ext", l_ext );
+ }
+}
+
+pl_entry::pl_entry( const wchar_t *p_filename, const wchar_t *p_title, int p_length_ms, int p_size, ifc_plentryinfo *p_info )
+{
+ SetFilename( p_filename );
+ SetTitle( p_title );
+ SetLengthMilliseconds( p_length_ms );
+ SetSizeBytes( p_size );
+
+ if ( p_info )
+ {
+ SetMediahash( p_info->GetExtendedInfo( _INFO_NAME_MEDIA_HASH ) );
+ SetMetahash( p_info->GetExtendedInfo( _INFO_NAME_META_HASH ) );
+ SetCloudID( p_info->GetExtendedInfo( _INFO_NAME_CLOUD_ID ) );
+ SetCloudStatus( p_info->GetExtendedInfo( _INFO_NAME_CLOUD_STATUS ) );
+ SetCloudDevices( p_info->GetExtendedInfo( _INFO_NAME_CLOUD_DEVICES ) );
+
+ const wchar_t *l_tvg_name = p_info->GetExtendedInfo( L"tvg-name" );
+
+ if ( l_tvg_name && wcslen( l_tvg_name ) > 0 )
+ {
+ _extended_infos.emplace( L"tvg-id", p_info->GetExtendedInfo( L"tvg-id" ) );
+ _extended_infos.emplace( L"tvg-name", l_tvg_name );
+ _extended_infos.emplace( L"tvg-logo", p_info->GetExtendedInfo( L"tvg-logo" ) );
+ _extended_infos.emplace( L"group-title", p_info->GetExtendedInfo( L"group-title" ) );
+ }
+ }
+}
+
+pl_entry::pl_entry( const wchar_t *p_filename, const wchar_t *p_title, int p_length_ms,
+ const wchar_t *mediahash, const wchar_t *metahash,
+ const wchar_t *cloud_id, const wchar_t *cloud_status,
+ const wchar_t *cloud_devices )
+{
+ SetFilename( p_filename );
+ SetTitle( p_title );
+ SetLengthMilliseconds( p_length_ms );
+
+ SetMediahash( mediahash );
+ SetMetahash( metahash );
+ SetCloudID( cloud_id );
+ SetCloudStatus( cloud_status );
+ SetCloudDevices( cloud_devices );
+}
+
+pl_entry::~pl_entry()
+{
+ plstring_release( filename );
+ plstring_release( filetitle );
+ plstring_release( mediahash );
+ plstring_release( metahash );
+ plstring_release( cloud_id );
+ plstring_release( cloud_status );
+ plstring_release( cloud_devices );
+}
+
+
+size_t pl_entry::GetFilename( wchar_t *p_filename, size_t filenameCch )
+{
+ if ( !this->filename )
+ return 0;
+
+ if ( !p_filename )
+ return wcslen( this->filename );
+
+ if ( !this->filename[ 0 ] )
+ return 0;
+
+ StringCchCopyW( p_filename, filenameCch, this->filename );
+
+ return 1;
+}
+
+size_t pl_entry::GetTitle( wchar_t *title, size_t titleCch )
+{
+ if ( !this->filetitle )
+ return 0;
+
+ if ( !title )
+ return wcslen( this->filetitle );
+
+ if ( !this->filetitle[ 0 ] )
+ return 0;
+
+ StringCchCopyW( title, titleCch, this->filetitle );
+
+ return 1;
+}
+
+int pl_entry::GetLengthInMilliseconds()
+{
+ return this->length;
+}
+
+int pl_entry::GetSizeInBytes()
+{
+ return this->size;
+}
+
+size_t pl_entry::GetExtendedInfo( const wchar_t *metadata, wchar_t *info, size_t infoCch )
+{
+ if ( cloud_id )
+ {
+ if ( !_wcsnicmp( _INFO_NAME_MEDIA_HASH, metadata, 9 ) && mediahash )
+ {
+ lstrcpynW( info, mediahash, (int)infoCch );
+ return 1;
+ }
+ else if ( !_wcsnicmp( _INFO_NAME_META_HASH, metadata, 8 ) && metahash )
+ {
+ lstrcpynW( info, metahash, (int)infoCch );
+ return 1;
+ }
+ else if ( !_wcsnicmp( _INFO_NAME_CLOUD_ID, metadata, 8 ) && cloud_id )
+ {
+ lstrcpynW( info, cloud_id, (int)infoCch );
+ return 1;
+ }
+ else if ( !_wcsnicmp( _INFO_NAME_CLOUD_STATUS, metadata, 12 ) && cloud_status )
+ {
+ lstrcpynW( info, cloud_status, (int)infoCch );
+ return 1;
+ }
+ else if ( !_wcsnicmp( _INFO_NAME_CLOUD_DEVICES, metadata, 13 ) && cloud_devices )
+ {
+ lstrcpynW( info, cloud_devices, (int)infoCch );
+ return 1;
+ }
+ else if ( !_wcsnicmp( metadata, L"cloud", 5 ) )
+ {
+ if ( _wtoi( cloud_id ) > 0 )
+ {
+ StringCchPrintfW( info, infoCch, L"#EXT-X-NS-CLOUD:mediahash=%s,metahash=%s,cloud_id=%s,cloud_status=%s,cloud_devices=%s",
+ ( mediahash && *mediahash ? mediahash : L"" ),
+ ( metahash && *metahash ? metahash : L"" ), cloud_id,
+ ( cloud_status && *cloud_status ? cloud_status : L"" ),
+ ( cloud_devices && *cloud_devices ? cloud_devices : L"" ) );
+ return 1;
+ }
+ }
+ else
+ {
+ auto l_extended_infos_iterator = _extended_infos.find( metadata );
+
+ if ( l_extended_infos_iterator != _extended_infos.end() )
+ {
+ lstrcpynW( info, ( *l_extended_infos_iterator ).second.c_str(), (int)infoCch);
+ return 1;
+ }
+ }
+ }
+
+ if ( !this->_extended_infos.empty() )
+ {
+ for ( std::map<std::wstring, std::wstring>::iterator l_extented_infos_iterator = _extended_infos.begin(); l_extented_infos_iterator != _extended_infos.end(); l_extented_infos_iterator++ )
+ {
+ const std::wstring &l_parameter_name = ( *l_extented_infos_iterator ).first;
+
+ if ( l_parameter_name.compare( metadata ) == 0 )
+ {
+ lstrcpynW( info, ( *l_extented_infos_iterator ).second.c_str(), (int)infoCch);
+ return 1;
+ }
+ }
+ }
+
+
+ return 0;
+}
+
+
+void pl_entry::SetFilename( const wchar_t *p_filename )
+{
+ plstring_release( this->filename );
+
+ if ( p_filename && p_filename[ 0 ] )
+ {
+ this->filename = plstring_wcsdup( p_filename );
+
+ if ( wcslen( p_filename ) > 4 )
+ _is_local_file = wcsncmp( this->filename, L"http", 4 ) != 0;
+ }
+ else
+ this->filename = 0;
+}
+
+void pl_entry::SetTitle( const wchar_t *title )
+{
+ plstring_release( this->filetitle );
+
+ if ( title && title[ 0 ] )
+ {
+ const wchar_t *t = L" \t\n\r\f\v";
+ std::wstring l_title( title );
+
+ l_title.erase( 0, l_title.find_first_not_of( t ) );
+
+ this->filetitle = plstring_wcsdup( l_title.c_str() );
+ this->cached = true;
+ }
+ else
+ this->filetitle = 0;
+}
+
+void pl_entry::SetLengthMilliseconds( int length )
+{
+ if ( length <= 0 )
+ this->length = -1000;
+ else
+ this->length = length;
+}
+
+void pl_entry::SetMediahash( const wchar_t *mediahash )
+{
+ plstring_release( this->mediahash );
+
+ if ( mediahash && mediahash[ 0 ] )
+ this->mediahash = plstring_wcsdup( mediahash );
+ else
+ this->mediahash = 0;
+}
+
+void pl_entry::SetSizeBytes( int size )
+{
+ if ( size <= 0 )
+ this->size = 0;
+ else
+ this->size = size;
+}
+
+void pl_entry::SetMetahash( const wchar_t *metahash )
+{
+ plstring_release( this->metahash );
+
+ if ( metahash && metahash[ 0 ] )
+ this->metahash = plstring_wcsdup( metahash );
+ else
+ this->metahash = 0;
+}
+
+void pl_entry::SetCloudID( const wchar_t *cloud_id )
+{
+ plstring_release( this->cloud_id );
+
+ if ( cloud_id && cloud_id[ 0 ] && _wtoi( cloud_id ) > 0 )
+ this->cloud_id = plstring_wcsdup( cloud_id );
+ else
+ this->cloud_id = 0;
+}
+
+void pl_entry::SetCloudStatus( const wchar_t *cloud_status )
+{
+ plstring_release( this->cloud_status );
+
+ if ( cloud_status && cloud_status[ 0 ] && _wtoi( cloud_status ) >= 0 )
+ this->cloud_status = plstring_wcsdup( cloud_status );
+ else
+ this->cloud_status = 0;
+}
+
+void pl_entry::SetCloudDevices( const wchar_t *cloud_devices )
+{
+ plstring_release( this->cloud_devices );
+
+ if ( cloud_devices && cloud_devices[ 0 ] )
+ this->cloud_devices = plstring_wcsdup( cloud_devices );
+ else
+ this->cloud_devices = 0;
+}
diff --git a/Src/playlist/pl_entry.h b/Src/playlist/pl_entry.h
new file mode 100644
index 00000000..852b584d
--- /dev/null
+++ b/Src/playlist/pl_entry.h
@@ -0,0 +1,61 @@
+#ifndef NULLSOFT_ML_PLAYLISTS_PL_ENTRY_H
+#define NULLSOFT_ML_PLAYLISTS_PL_ENTRY_H
+
+#include "ifc_plentryinfo.h"
+#include <windows.h>
+#include <map>
+#include <iostream>
+
+
+class pl_entry
+{
+public:
+ pl_entry() {}
+ pl_entry( const wchar_t *p_filename, const wchar_t *p_title, int p_length_ms );
+ pl_entry( const wchar_t *p_filename, const wchar_t *p_title, int p_length_ms, int p_size );
+ pl_entry( const wchar_t *p_filename, const wchar_t *p_title, int p_length_ms, ifc_plentryinfo *p_info );
+ pl_entry( const wchar_t *p_filename, const wchar_t *p_title, int p_length_ms, int p_size, ifc_plentryinfo *p_info );
+ pl_entry( const wchar_t *p_filename, const wchar_t *p_title, int p_length_ms,
+ const wchar_t *mediahash, const wchar_t *metahash,
+ const wchar_t *cloud_id, const wchar_t *cloud_status,
+ const wchar_t *cloud_devices );
+ ~pl_entry();
+
+ size_t GetFilename( wchar_t *p_filename, size_t filenameCch );
+ size_t GetTitle( wchar_t *title, size_t titleCch );
+ int GetLengthInMilliseconds();
+ int GetSizeInBytes();
+ size_t GetExtendedInfo( const wchar_t *metadata, wchar_t *info, size_t infoCch );
+
+ void SetFilename( const wchar_t *p_filename );
+ void SetTitle( const wchar_t *title );
+ void SetLengthMilliseconds( int length );
+ void SetSizeBytes( int size );
+
+ void SetMediahash( const wchar_t *mediahash );
+ void SetMetahash( const wchar_t *metahash );
+ void SetCloudID( const wchar_t *cloud_id );
+ void SetCloudStatus( const wchar_t *cloud_status );
+ void SetCloudDevices( const wchar_t *cloud_devices );
+
+ bool isLocal() const { return _is_local_file; }
+
+ wchar_t *filename = 0;
+ wchar_t *filetitle = 0;
+
+ wchar_t *mediahash = 0;
+ wchar_t *metahash = 0;
+ wchar_t *cloud_id = 0;
+ wchar_t *cloud_status = 0;
+ wchar_t *cloud_devices = 0;
+
+ std::map<std::wstring, std::wstring> _extended_infos;
+
+ int length = -1;
+ int size = 0;
+
+ bool cached = false;
+ bool _is_local_file = false;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/playlist.mi b/Src/playlist/playlist.mi
new file mode 100644
index 00000000..c82862be
--- /dev/null
+++ b/Src/playlist/playlist.mi
@@ -0,0 +1,49 @@
+#ifndef __PLAYLIST_MI
+#define __PLAYLIST_MI
+
+extern class @{632883FC-159F-4330-B193-CFD62CA47EC1}@ Object &Playlist;
+extern class @{5829EE15-3648-4c6e-B2FE-8736CBBF39DB}@ Object _predecl Playlists;
+extern class @{C18F8E50-2C81-4001-9F46-FD942B07ECCD}@ Object &PlaylistsEnumerator;
+extern class @{C6207729-2600-4bb8-B562-2E0BC04E4416}@ Object _predecl PlaylistManager;
+
+/* ===== Playlist ===== */
+extern Playlist.Clear();
+extern int Playlist.GetNumItems();
+/*
+Retrieve the filename for some item in the playlist
+*/
+extern String Playlist.GetItem(int itemNumber);
+extern String Playlist.GetItemTitle(int itemNumber);
+extern int Playlist.GetItemLength(int itemNumber);
+extern String Playlist.GetItemExtendedInfo(int itemNumber, String metadata);
+extern Playlist.Reverse();
+extern Playlist.Swap(int item1, int item2);
+extern Playlist.Randomize();
+extern Playlist.Remove(int itemNumber);
+extern Playlist.SortByTitle();
+extern Playlist.SortByFilename();
+
+/* ===== Playlists ===== */
+
+extern PlaylistsEnumerator Playlists.GetEnumerator();
+extern Playlist Playlists.OpenPlaylist(String playlistGUID);
+extern Playlists.SavePlaylist(String playlistGUID, Playlist playlist_to_save);
+
+/* ===== PlaylistsEnumerator ===== */
+
+/* returns the number of playlists in the enumerator object */
+extern int PlaylistsEnumerator.GetCount();
+extern String PlaylistsEnumerator.GetFilename(int playlistNumber);
+extern String PlaylistsEnumerator.GetTitle(int playlistNumber);
+extern int PlaylistsEnumerator.GetLength(int playlistNumber);
+/*
+returns number of items in one of the playlists
+*/
+extern int PlaylistsEnumerator.GetNumItems(int playlistNumber);
+extern String PlaylistsEnumerator.GetGUID(int playlistNumber);
+
+/* ===== Playlist Manager ===== */
+extern Playlist PlaylistManager.OpenPlaylist(String filename);
+extern PlaylistManager.SavePlaylist(String filename, Playlist playlist_to_save);
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/playlist.rc b/Src/playlist/playlist.rc
new file mode 100644
index 00000000..909ed680
--- /dev/null
+++ b/Src/playlist/playlist.rc
@@ -0,0 +1,86 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""afxres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "#include ""version.rc2""\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ 65535 "{9E398E5F-EDEC-4dd8-A40D-E29B385A88C0}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_M3U_PLAYLIST "M3U Playlist"
+ IDS_PLS_PLAYLIST "PLS Playlist"
+ IDS_WINAMP3_PLAYLIST "Winamp3 Playlist"
+ IDS_ALL_PLAYLIST_TYPES "All Playlist Types"
+ IDS_PLAYLIST "Playlist"
+ IDS_MPCPL_PLAYLIST "Media Player Classic Playlist (MPCPL)"
+ IDS_FPL_PLAYLIST "Foobar2000 Playlist (FPL)"
+ IDS_XSPF_PLAYLIST "XML Shareable Playlist Format"
+END
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+#include "version.rc2"
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/Src/playlist/playlist.sln b/Src/playlist/playlist.sln
new file mode 100644
index 00000000..54a86262
--- /dev/null
+++ b/Src/playlist/playlist.sln
@@ -0,0 +1,30 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29509.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "playlist", "playlist.vcxproj", "{B2C0F048-C7FA-4864-B5B3-75E69458BA9E}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Debug|x64 = Debug|x64
+ Release|Win32 = Release|Win32
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B2C0F048-C7FA-4864-B5B3-75E69458BA9E}.Debug|Win32.ActiveCfg = Debug|Win32
+ {B2C0F048-C7FA-4864-B5B3-75E69458BA9E}.Debug|Win32.Build.0 = Debug|Win32
+ {B2C0F048-C7FA-4864-B5B3-75E69458BA9E}.Release|Win32.ActiveCfg = Release|Win32
+ {B2C0F048-C7FA-4864-B5B3-75E69458BA9E}.Release|Win32.Build.0 = Release|Win32
+ {B2C0F048-C7FA-4864-B5B3-75E69458BA9E}.Debug|x64.ActiveCfg = Debug|x64
+ {B2C0F048-C7FA-4864-B5B3-75E69458BA9E}.Debug|x64.Build.0 = Debug|x64
+ {B2C0F048-C7FA-4864-B5B3-75E69458BA9E}.Release|x64.ActiveCfg = Release|x64
+ {B2C0F048-C7FA-4864-B5B3-75E69458BA9E}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {3815C7C8-CBE1-4F34-B885-16D73AD02A60}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/playlist/playlist.vcxproj b/Src/playlist/playlist.vcxproj
new file mode 100644
index 00000000..f0bfa185
--- /dev/null
+++ b/Src/playlist/playlist.vcxproj
@@ -0,0 +1,342 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{B2C0F048-C7FA-4864-B5B3-75E69458BA9E}</ProjectGuid>
+ <RootNamespace>playlist</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <TargetExt>.w5s</TargetExt>
+ <IncludePath>$(IncludePath)</IncludePath>
+ <LibraryPath>$(LibraryPath)</LibraryPath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <TargetExt>.w5s</TargetExt>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <TargetExt>.w5s</TargetExt>
+ <IncludePath>$(IncludePath)</IncludePath>
+ <LibraryPath>$(LibraryPath)</LibraryPath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <TargetExt>.w5s</TargetExt>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg">
+ <VcpkgEnabled>false</VcpkgEnabled>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>.;..;../Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;PLAYLIST_EXPORTS;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;_CRT_NON_CONFORMING_WCSTOK;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(ProjectDir)x86_Debug\$(ProjectName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\System\
+xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\System\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\System\'</Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>.;..;../Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN64;_DEBUG;_WINDOWS;_USRDLL;PLAYLIST_EXPORTS;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;_CRT_NON_CONFORMING_WCSTOK;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(ProjectDir)x64_Debug\$(ProjectName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\System\
+xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\System\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\System\'</Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..;../Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;PLAYLIST_EXPORTS;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;_CRT_NON_CONFORMING_WCSTOK;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <DelayLoadDLLs>ole32.dll;rpcrt4.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(ProjectDir)x86_Release\$(ProjectName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\System\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\System\'</Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..;../Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;PLAYLIST_EXPORTS;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;_CRT_NON_CONFORMING_WCSTOK;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <DelayLoadDLLs>ole32.dll;rpcrt4.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(ProjectDir)x64_Release\$(ProjectName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\System\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\System\'</Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\nu\ServiceWatcher.cpp" />
+ <ClCompile Include="..\Wasabi\api\script\objcontroller.cpp" />
+ <ClCompile Include="..\Wasabi\api\script\objects\rootobj.cpp" />
+ <ClCompile Include="..\Wasabi\api\script\objects\rootobjcbx.cpp" />
+ <ClCompile Include="..\Wasabi\api\script\scriptobji.cpp" />
+ <ClCompile Include="..\Wasabi\api\script\scriptobjx.cpp" />
+ <ClCompile Include="..\Wasabi\bfc\assert.cpp" />
+ <ClCompile Include="..\Wasabi\bfc\foreach.cpp" />
+ <ClCompile Include="..\Wasabi\bfc\memblock.cpp" />
+ <ClCompile Include="..\Wasabi\bfc\nsguid.cpp" />
+ <ClCompile Include="..\Wasabi\bfc\parse\PathParseW.cpp" />
+ <ClCompile Include="..\Wasabi\bfc\std_string.cpp" />
+ <ClCompile Include="..\Wasabi\bfc\string\string.cpp" />
+ <ClCompile Include="..\Wasabi\bfc\string\StringW.cpp" />
+ <ClCompile Include="..\Wasabi\bfc\wasabi_std.cpp" />
+ <ClCompile Include="..\Winamp\JSAPI_CallbackParameters.cpp" />
+ <ClCompile Include="..\Winamp\JSAPI_ObjectArray.cpp" />
+ <ClCompile Include="..\Winamp\strutil.cpp" />
+ <ClCompile Include="B4SLoader.cpp" />
+ <ClCompile Include="B4SWriter.cpp" />
+ <ClCompile Include="factory_Handler.cpp" />
+ <ClCompile Include="factory_playlistmanager.cpp" />
+ <ClCompile Include="factory_playlists.cpp" />
+ <ClCompile Include="Handler.cpp" />
+ <ClCompile Include="JSAPI2_Creator.cpp" />
+ <ClCompile Include="JSAPI2_Playlist.cpp" />
+ <ClCompile Include="JSAPI2_Playlists.cpp" />
+ <ClCompile Include="M3U8Writer.cpp" />
+ <ClCompile Include="M3ULoader.cpp" />
+ <ClCompile Include="M3UWriter.cpp" />
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="Playlist.cpp" />
+ <ClCompile Include="PlaylistCounter.cpp" />
+ <ClCompile Include="PlaylistManager.cpp" />
+ <ClCompile Include="Playlists.cpp" />
+ <ClCompile Include="PlaylistsXML.cpp" />
+ <ClCompile Include="PLSLoader.cpp" />
+ <ClCompile Include="plstring.cpp" />
+ <ClCompile Include="PLSWriter.cpp" />
+ <ClCompile Include="pl_entry.cpp" />
+ <ClCompile Include="ScriptObjectFactory.cpp" />
+ <ClCompile Include="ScriptObjectService.cpp" />
+ <ClCompile Include="SPlaylist.cpp" />
+ <ClCompile Include="SPlaylistManager.cpp" />
+ <ClCompile Include="SPlaylists.cpp" />
+ <ClCompile Include="SPlaylistsEnumerator.cpp" />
+ <ClCompile Include="util.cpp" />
+ <ClCompile Include="XMLString.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\Winamp\JSAPI_ObjectArray.h" />
+ <ClInclude Include="..\Winamp\strutil.h" />
+ <ClInclude Include="api__playlist.h" />
+ <ClInclude Include="api_playlistmanager.h" />
+ <ClInclude Include="api_playlists.h" />
+ <ClInclude Include="B4SLoader.h" />
+ <ClInclude Include="B4SWriter.h" />
+ <ClInclude Include="factory_Handler.h" />
+ <ClInclude Include="factory_playlistmanager.h" />
+ <ClInclude Include="factory_playlists.h" />
+ <ClInclude Include="Handler.h" />
+ <ClInclude Include="ifc_playlist.h" />
+ <ClInclude Include="ifc_playlistloader.h" />
+ <ClInclude Include="JSAPI2_Creator.h" />
+ <ClInclude Include="JSAPI2_Playlist.h" />
+ <ClInclude Include="JSAPI2_Playlists.h" />
+ <ClInclude Include="M3U8Writer.h" />
+ <ClInclude Include="M3ULoader.h" />
+ <ClInclude Include="M3UWriter.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="PlaylistCounter.h" />
+ <ClInclude Include="PlaylistManager.h" />
+ <ClInclude Include="Playlists.h" />
+ <ClInclude Include="PlaylistsXML.h" />
+ <ClInclude Include="PlaylistWriter.h" />
+ <ClInclude Include="PLSLoader.h" />
+ <ClInclude Include="plstring.h" />
+ <ClInclude Include="PLSWriter.h" />
+ <ClInclude Include="pl_entry.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="ScriptObjectFactory.h" />
+ <ClInclude Include="ScriptObjectService.h" />
+ <ClInclude Include="SPlaylist.h" />
+ <ClInclude Include="SPlaylistManager.h" />
+ <ClInclude Include="SPlaylists.h" />
+ <ClInclude Include="SPlaylistsEnumerator.h" />
+ <ClInclude Include="svc_playlisthandler.h" />
+ <ClInclude Include="XMLString.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="playlist.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ <ProjectReference Include="..\WAT\WAT.vcxproj">
+ <Project>{c5714908-a71f-4644-bd95-aad8ee7914da}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/Src/playlist/playlist.vcxproj.filters b/Src/playlist/playlist.vcxproj.filters
new file mode 100644
index 00000000..3445ab88
--- /dev/null
+++ b/Src/playlist/playlist.vcxproj.filters
@@ -0,0 +1,299 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="B4SLoader.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="B4SWriter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="factory_Handler.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="factory_playlistmanager.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="factory_playlists.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Handler.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="JSAPI2_Creator.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="JSAPI2_Playlist.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="JSAPI2_Playlists.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="M3U8Writer.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="M3UWriter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="M3ULoader.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="pl_entry.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Playlist.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PlaylistCounter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PlaylistManager.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Playlists.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PlaylistsXML.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PLSLoader.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="plstring.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PLSWriter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ScriptObjectFactory.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ScriptObjectService.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="SPlaylist.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="SPlaylistManager.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="SPlaylists.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="SPlaylistsEnumerator.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="util.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="XMLString.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Wasabi\bfc\assert.cpp">
+ <Filter>Source Files\Wasabi\bfc</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Wasabi\bfc\foreach.cpp">
+ <Filter>Source Files\Wasabi\bfc</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Winamp\JSAPI_CallbackParameters.cpp">
+ <Filter>Source Files\Winamp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Winamp\JSAPI_ObjectArray.cpp">
+ <Filter>Source Files\Winamp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Wasabi\bfc\memblock.cpp">
+ <Filter>Source Files\Wasabi\bfc</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Wasabi\bfc\nsguid.cpp">
+ <Filter>Source Files\Wasabi\bfc</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Wasabi\api\script\objcontroller.cpp">
+ <Filter>Source Files\Wasabi\api</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Wasabi\bfc\parse\PathParseW.cpp">
+ <Filter>Source Files\Wasabi\bfc</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Wasabi\api\script\objects\rootobj.cpp">
+ <Filter>Source Files\Wasabi\api</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Wasabi\api\script\objects\rootobjcbx.cpp">
+ <Filter>Source Files\Wasabi\api</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Wasabi\api\script\scriptobji.cpp">
+ <Filter>Source Files\Wasabi\api</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Wasabi\api\script\scriptobjx.cpp">
+ <Filter>Source Files\Wasabi\api</Filter>
+ </ClCompile>
+ <ClCompile Include="..\nu\ServiceWatcher.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Wasabi\bfc\std_string.cpp">
+ <Filter>Source Files\Wasabi\bfc</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Wasabi\bfc\string\StringW.cpp">
+ <Filter>Source Files\Wasabi\bfc</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Wasabi\bfc\string\string.cpp">
+ <Filter>Source Files\Wasabi\bfc</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Winamp\strutil.cpp">
+ <Filter>Source Files\Winamp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Wasabi\bfc\wasabi_std.cpp">
+ <Filter>Source Files\Wasabi\bfc</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="api__playlist.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="api_playlistmanager.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="api_playlists.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="B4SLoader.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="B4SWriter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="factory_Handler.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="factory_playlistmanager.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="factory_playlists.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Handler.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ifc_playlist.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ifc_playlistloader.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\Winamp\JSAPI_ObjectArray.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="JSAPI2_Creator.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="JSAPI2_Playlist.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="JSAPI2_Playlists.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="M3U8Writer.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="M3ULoader.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="M3UWriter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="pl_entry.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PlaylistCounter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PlaylistManager.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Playlists.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PlaylistsXML.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PlaylistWriter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PLSLoader.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="plstring.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PLSWriter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ScriptObjectFactory.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ScriptObjectService.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="SPlaylist.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="SPlaylistManager.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="SPlaylists.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="SPlaylistsEnumerator.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="svc_playlisthandler.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="XMLString.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\Winamp\strutil.h">
+ <Filter>Header Files\Winamp</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{e091476c-aae2-467b-9b96-08b12796cbcb}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{b035c0ae-5ec9-481d-a5ce-02d790985bc9}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{3c7b3569-6627-448b-a320-57a5fc880fe1}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\Wasabi">
+ <UniqueIdentifier>{158dd0e1-252e-49be-8630-9d3b31313f79}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\Wasabi\bfc">
+ <UniqueIdentifier>{9b816713-5c43-4613-9a32-54c9413d76fd}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\Winamp">
+ <UniqueIdentifier>{95d29037-d9f4-4431-8b6d-9312dcbdc38e}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\Wasabi\api">
+ <UniqueIdentifier>{93f96976-a073-46d0-822d-c7da3c1c8286}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\nu">
+ <UniqueIdentifier>{01f330ce-f18e-43d9-9b79-b8def37065b1}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\Winamp">
+ <UniqueIdentifier>{31801414-58c9-4a09-bd1a-2a71d7255072}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="playlist.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/playlist/playlist.xcodeproj/project.pbxproj b/Src/playlist/playlist.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..a0618eff
--- /dev/null
+++ b/Src/playlist/playlist.xcodeproj/project.pbxproj
@@ -0,0 +1,199 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 42;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 0C19117B0BC1D80B005443D9 /* PlaylistCounter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C19117A0BC1D80B005443D9 /* PlaylistCounter.cpp */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 0C19117A0BC1D80B005443D9 /* PlaylistCounter.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = PlaylistCounter.cpp; sourceTree = "<group>"; };
+ D2AAC0630554660B00DB518D /* playlist.w5s */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = playlist.w5s; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ D289988505E68E00004EDB86 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 08FB7794FE84155DC02AAC07 /* playlist */ = {
+ isa = PBXGroup;
+ children = (
+ 08FB7795FE84155DC02AAC07 /* Source */,
+ 1AB674ADFE9D54B511CA2CBB /* Products */,
+ );
+ name = playlist;
+ sourceTree = "<group>";
+ };
+ 08FB7795FE84155DC02AAC07 /* Source */ = {
+ isa = PBXGroup;
+ children = (
+ 0C19117A0BC1D80B005443D9 /* PlaylistCounter.cpp */,
+ 0C1911740BC1D456005443D9 /* PLS */,
+ );
+ name = Source;
+ sourceTree = "<group>";
+ };
+ 0C1911740BC1D456005443D9 /* PLS */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = PLS;
+ sourceTree = "<group>";
+ };
+ 1AB674ADFE9D54B511CA2CBB /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ D2AAC0630554660B00DB518D /* playlist.w5s */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ D2AAC0600554660B00DB518D /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+ D2AAC0620554660B00DB518D /* playlist */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 1DEB914A08733D8E0010E9CD /* Build configuration list for PBXNativeTarget "playlist" */;
+ buildPhases = (
+ D2AAC0600554660B00DB518D /* Headers */,
+ D2AAC0610554660B00DB518D /* Sources */,
+ D289988505E68E00004EDB86 /* Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = playlist;
+ productName = playlist;
+ productReference = D2AAC0630554660B00DB518D /* playlist.w5s */;
+ productType = "com.apple.product-type.library.dynamic";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 08FB7793FE84155DC02AAC07 /* Project object */ = {
+ isa = PBXProject;
+ buildConfigurationList = 1DEB914E08733D8E0010E9CD /* Build configuration list for PBXProject "playlist" */;
+ hasScannedForEncodings = 1;
+ mainGroup = 08FB7794FE84155DC02AAC07 /* playlist */;
+ projectDirPath = "";
+ targets = (
+ D2AAC0620554660B00DB518D /* playlist */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXSourcesBuildPhase section */
+ D2AAC0610554660B00DB518D /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 0C19117B0BC1D80B005443D9 /* PlaylistCounter.cpp in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 1DEB914B08733D8E0010E9CD /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ COPY_PHASE_STRIP = NO;
+ EXECUTABLE_EXTENSION = w5s;
+ EXECUTABLE_PREFIX = "";
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_ENABLE_FIX_AND_CONTINUE = YES;
+ GCC_MODEL_TUNING = G5;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ HEADER_SEARCH_PATHS = ../Wasabi;
+ INSTALL_PATH = /usr/local/lib;
+ PRODUCT_NAME = playlist;
+ ZERO_LINK = YES;
+ };
+ name = Debug;
+ };
+ 1DEB914C08733D8E0010E9CD /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ARCHS = (
+ ppc,
+ i386,
+ );
+ EXECUTABLE_EXTENSION = w5s;
+ EXECUTABLE_PREFIX = "";
+ GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
+ GCC_MODEL_TUNING = G5;
+ HEADER_SEARCH_PATHS = ../Wasabi;
+ INSTALL_PATH = /usr/local/lib;
+ PRODUCT_NAME = playlist;
+ };
+ name = Release;
+ };
+ 1DEB914F08733D8E0010E9CD /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ PREBINDING = NO;
+ SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk;
+ };
+ name = Debug;
+ };
+ 1DEB915008733D8E0010E9CD /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ PREBINDING = NO;
+ SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 1DEB914A08733D8E0010E9CD /* Build configuration list for PBXNativeTarget "playlist" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1DEB914B08733D8E0010E9CD /* Debug */,
+ 1DEB914C08733D8E0010E9CD /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 1DEB914E08733D8E0010E9CD /* Build configuration list for PBXProject "playlist" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1DEB914F08733D8E0010E9CD /* Debug */,
+ 1DEB915008733D8E0010E9CD /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 08FB7793FE84155DC02AAC07 /* Project object */;
+}
diff --git a/Src/playlist/plstring.cpp b/Src/playlist/plstring.cpp
new file mode 100644
index 00000000..49c09110
--- /dev/null
+++ b/Src/playlist/plstring.cpp
@@ -0,0 +1,86 @@
+#include "plstring.h"
+#include "api__playlist.h"
+#include <shlwapi.h>
+#include <stdint.h>
+
+static wchar_t *_plstring_wcsdup( const wchar_t *str )
+{
+ if ( !str )
+ return 0;
+
+ size_t len = wcslen( str );
+ size_t *self = (size_t *)calloc( ( len + 1 ) * sizeof( wchar_t ), sizeof( size_t ) );
+ *self = 1;
+
+ wchar_t *new_str = (wchar_t *)( ( (int8_t *)self ) + sizeof( size_t ) );
+ memcpy( new_str, str, ( len + 1 ) * sizeof( wchar_t ) );
+
+ return new_str;
+}
+
+static wchar_t *_plstring_malloc( size_t str_size )
+{
+ size_t *self = (size_t *)calloc( ( str_size ), sizeof( size_t ) );
+ *self = 1;
+
+ wchar_t *new_str = (wchar_t *)( ( (int8_t *)self ) + sizeof( size_t ) );
+
+ return new_str;
+}
+
+static void _plstring_release( wchar_t *str )
+{
+ if ( str )
+ {
+ size_t *self = (size_t *)( ( (int8_t *)str ) - sizeof( size_t ) );
+ ( *self )--;
+
+ if ( *self == 0 )
+ free( self );
+ }
+}
+
+static void _plstring_retain( wchar_t *str )
+{
+ if ( str )
+ {
+ size_t *self = (size_t *)( ( (int8_t *)str ) - sizeof( size_t ) );
+ ( *self )++;
+ }
+}
+
+wchar_t *( *plstring_wcsdup )( const wchar_t *str ) = _plstring_wcsdup;
+wchar_t *( *plstring_malloc )( size_t str_size ) = _plstring_malloc;
+void ( *plstring_release )( wchar_t *str ) = _plstring_release;
+void ( *plstring_retain )( wchar_t *str ) = _plstring_retain;
+
+static bool ndestring_tried_load = false;
+
+void plstring_init()
+{
+ if ( !ndestring_tried_load )
+ {
+ wchar_t path[ MAX_PATH ] = { 0 };
+ const wchar_t *PROGDIR = WASABI_API_APP->path_getAppPath();
+
+ PathCombineW( path, PROGDIR, L"winamp.exe" );
+ HMODULE ndelib = LoadLibraryW( path );
+ if ( ndelib )
+ {
+ FARPROC ndestring_wcsdup = GetProcAddress( ndelib, "plstring_wcsdup" );
+ FARPROC ndestring_malloc = GetProcAddress( ndelib, "plstring_malloc" );
+ FARPROC ndestring_release = GetProcAddress( ndelib, "plstring_release" );
+ FARPROC ndestring_retain = GetProcAddress( ndelib, "plstring_retain" );
+
+ if ( ndestring_wcsdup && ndestring_malloc && ndestring_release && ndestring_retain )
+ {
+ *(FARPROC *)&plstring_wcsdup = *(FARPROC *)ndestring_wcsdup;
+ *(FARPROC *)&plstring_malloc = *(FARPROC *)ndestring_malloc;
+ *(FARPROC *)&plstring_release = *(FARPROC *)ndestring_release;
+ *(FARPROC *)&plstring_retain = *(FARPROC *)ndestring_retain;
+ }
+ }
+
+ ndestring_tried_load = true;
+ }
+} \ No newline at end of file
diff --git a/Src/playlist/plstring.h b/Src/playlist/plstring.h
new file mode 100644
index 00000000..50dbc433
--- /dev/null
+++ b/Src/playlist/plstring.h
@@ -0,0 +1,7 @@
+#pragma once
+#include <bfc/platform/types.h>
+extern wchar_t *(*plstring_wcsdup)(const wchar_t *str);
+extern wchar_t *(*plstring_malloc)(size_t str_size);
+extern void (*plstring_release)(wchar_t *str);
+extern void (*plstring_retain)(wchar_t *str);
+void plstring_init(); \ No newline at end of file
diff --git a/Src/playlist/resource.h b/Src/playlist/resource.h
new file mode 100644
index 00000000..e27843b3
--- /dev/null
+++ b/Src/playlist/resource.h
@@ -0,0 +1,29 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by playlist.rc
+//
+#define IDS_STRING0 1
+#define IDS_M3U_PLAYLIST 1
+#define IDS_PLS_PLAYLIST 2
+#define IDS_WINAMP3_PLAYLIST 3
+#define IDS_ALL_PLAYLIST_TYPES 4
+#define IDS_PLAYLIST 5
+#define IDS_MPCPL_PLAYLIST 6
+#define IDS_FPL_PLAYLIST 7
+#define IDS_XSPF_PLAYLIST 8
+#define IDC_ALLOW_ALWAYS 1001
+#define IDC_ALLOW 1002
+#define IDC_BUTTON3 1003
+#define IDC_DENY 1003
+#define IDC_CHECK1 1004
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 103
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1005
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/playlist/svc_playlisthandler.h b/Src/playlist/svc_playlisthandler.h
new file mode 100644
index 00000000..8dc44bc1
--- /dev/null
+++ b/Src/playlist/svc_playlisthandler.h
@@ -0,0 +1,110 @@
+#ifndef NULLSOFT_SVC_PLAYLISTHANDLER_H
+#define NULLSOFT_SVC_PLAYLISTHANDLER_H
+
+#include <bfc/dispatch.h>
+#include <bfc/platform/types.h>
+#include "ifc_playlistloader.h"
+#include <stdint.h>
+
+enum
+{
+ SVC_PLAYLISTHANDLER_SUCCESS = 0,
+ SVC_PLAYLISTHANDLER_FAILED = 1,
+};
+class svc_playlisthandler : public Dispatchable
+{
+protected:
+ svc_playlisthandler() {}
+ ~svc_playlisthandler() {}
+
+public:
+ static FOURCC getServiceType() { return svc_playlisthandler::SERVICETYPE; }
+ const wchar_t *EnumerateExtensions(size_t n); // returns 0 when it's done
+ const char *EnumerateMIMETypes(size_t n); // returns 0 when it's done, returns char * to match HTTP specs
+ const wchar_t *GetName(); // returns a name suitable for display to user of this playlist form (e.g. PLS Playlist)
+ int SupportedFilename(const wchar_t *filename); // returns SUCCESS and FAILED, so be careful ...
+ int SupportedMIMEType(const char *filename); // returns SUCCESS and FAILED, so be careful ...
+ ifc_playlistloader *CreateLoader(const wchar_t *filename);
+ void ReleaseLoader(ifc_playlistloader *loader);
+ int HasWriter(); // returns 1 if writing is supported
+ //ifc_playlistwriter CreateWriter(const wchar_t *writer);
+ //void ReleaseWriter(ifc_playlistwriter *writer);
+
+ size_t SniffSizeRequired(); // return number of bytes required for detection on an unknown file
+ bool IsOurs(const int8_t *data, size_t sizeBytes);
+
+public:
+ DISPATCH_CODES
+ {
+ SVC_PLAYLISTHANDLER_ENUMEXTENSIONS = 10,
+ SVC_PLAYLISTHANDLER_ENUMMIMETYPES = 20,
+ SVC_PLAYLISTHANDLER_SUPPORTFILENAME= 30,
+ SVC_PLAYLISTHANDLER_SUPPORTMIME= 40,
+ SVC_PLAYLISTHANDLER_CREATELOADER = 50,
+ SVC_PLAYLISTHANDLER_RELEASELOADER = 60,
+ SVC_PLAYLISTHANDLER_CREATEWRITER= 70,
+ SVC_PLAYLISTHANDLER_RELEASEWRITER= 80,
+ SVC_PLAYLISTHANDLER_SNIFFSIZE=90,
+ SVC_PLAYLISTHANDLER_SNIFF=100,
+ SVC_PLAYLISTHANDLER_GETNAME=110,
+ SVC_PLAYLISTHANDLER_HASWRITER=120,
+ };
+
+ enum
+ {
+ SERVICETYPE = MK3CC('p','l','h')
+ };
+
+};
+
+inline const wchar_t *svc_playlisthandler::GetName()
+{
+ return _call(SVC_PLAYLISTHANDLER_GETNAME, (const wchar_t *)0);
+}
+
+inline const wchar_t *svc_playlisthandler::EnumerateExtensions(size_t n)
+{
+ return _call(SVC_PLAYLISTHANDLER_ENUMEXTENSIONS, (const wchar_t *)0, n);
+};
+
+inline const char *svc_playlisthandler::EnumerateMIMETypes(size_t n)
+{
+ return _call(SVC_PLAYLISTHANDLER_ENUMMIMETYPES, (const char *)0, n);
+}
+
+inline int svc_playlisthandler::SupportedFilename(const wchar_t *filename)
+{
+ return _call(SVC_PLAYLISTHANDLER_SUPPORTFILENAME, (int)SVC_PLAYLISTHANDLER_FAILED, filename);
+}
+
+inline int svc_playlisthandler::SupportedMIMEType(const char *filename)
+{
+ return _call(SVC_PLAYLISTHANDLER_SUPPORTMIME, (int)SVC_PLAYLISTHANDLER_FAILED, filename);
+}
+
+inline ifc_playlistloader *svc_playlisthandler::CreateLoader(const wchar_t *filename)
+{
+ return _call(SVC_PLAYLISTHANDLER_CREATELOADER, (ifc_playlistloader *)0, filename);
+}
+
+inline void svc_playlisthandler::ReleaseLoader(ifc_playlistloader *loader)
+{
+ _voidcall(SVC_PLAYLISTHANDLER_RELEASELOADER, loader);
+}
+
+inline size_t svc_playlisthandler::SniffSizeRequired()
+{
+ return _call(SVC_PLAYLISTHANDLER_SNIFFSIZE, (size_t)0);
+}
+
+inline bool svc_playlisthandler::IsOurs(const int8_t *data, size_t sizeBytes)
+{
+ return _call(SVC_PLAYLISTHANDLER_SNIFF, (bool)false, data, sizeBytes);
+}
+
+inline int svc_playlisthandler::HasWriter()
+{
+ return _call(SVC_PLAYLISTHANDLER_HASWRITER, (int)0);
+}
+
+#endif \ No newline at end of file
diff --git a/Src/playlist/util.cpp b/Src/playlist/util.cpp
new file mode 100644
index 00000000..debd3be0
--- /dev/null
+++ b/Src/playlist/util.cpp
@@ -0,0 +1,77 @@
+#include <shlobj.h>
+#include <strsafe.h>
+
+#include "main.h"
+#include "api__playlist.h"
+#include "../nu/ns_wc.h"
+
+HRESULT ResolveShortCut(HWND hwnd, LPCWSTR pszShortcutFile, LPWSTR pszPath)
+{
+ IShellLinkW *psl = 0;
+ WIN32_FIND_DATAW wfd = {0};
+
+ *pszPath = 0; // assume failure
+
+ HRESULT hres = CoCreateInstance( CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (void **)&psl );
+ if ( SUCCEEDED( hres ) )
+ {
+ IPersistFile *ppf = 0;
+ hres = psl->QueryInterface( &ppf ); // OLE 2! Yay! --YO
+ if ( SUCCEEDED( hres ) )
+ {
+ hres = ppf->Load( pszShortcutFile, STGM_READ );
+ if ( SUCCEEDED( hres ) )
+ {
+ hres = psl->Resolve( hwnd, SLR_ANY_MATCH );
+ if ( SUCCEEDED( hres ) )
+ {
+ wchar_t szGotPath[ MAX_PATH ] = { 0 };
+ StringCchCopyW( szGotPath, MAX_PATH, pszShortcutFile );
+ hres = psl->GetPath( szGotPath, MAX_PATH, &wfd, SLGP_SHORTPATH );
+ StringCchCopyW( pszPath, MAX_PATH, szGotPath );
+ }
+ }
+
+ ppf->Release();
+ }
+
+ psl->Release();
+ }
+
+ return SUCCEEDED(hres);
+}
+
+bool IsUrl(const wchar_t *url)
+{
+ return !!wcsstr(url, L"://");
+}
+
+void SetUserAgent(api_httpreceiver *http)
+{
+ char agent[256] = {0};
+ StringCchPrintfA(agent, 256, "User-Agent: %S/%S", WASABI_API_APP->main_getAppName(), WASABI_API_APP->main_getVersionNumString());
+ http->addheader(agent);
+}
+
+const char *GetProxy()
+{
+ static char proxy[ 256 ] = "";
+ // {C0A565DC-0CFE-405a-A27C-468B0C8A3A5C}
+ const GUID internetConfigGroupGUID =
+ { 0xc0a565dc, 0xcfe, 0x405a, { 0xa2, 0x7c, 0x46, 0x8b, 0xc, 0x8a, 0x3a, 0x5c } };
+ ifc_configgroup *group = AGAVE_API_CONFIG->GetGroup( internetConfigGroupGUID );
+ if ( group )
+ {
+ ifc_configitem *item = group->GetItem( L"Proxy" );
+ if ( item )
+ {
+ const wchar_t *wideProxy = item->GetString();
+ if ( wideProxy )
+ {
+ WideCharToMultiByteSZ( CP_ACP, 0, wideProxy, -1, proxy, 256, 0, 0 );
+ }
+ }
+ }
+
+ return proxy;
+} \ No newline at end of file
diff --git a/Src/playlist/version.rc2 b/Src/playlist/version.rc2
new file mode 100644
index 00000000..e4d8ea6f
--- /dev/null
+++ b/Src/playlist/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION WINAMP_PRODUCTVER
+ PRODUCTVERSION WINAMP_PRODUCTVER
+ FILEFLAGSMASK 0x17L
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "CompanyName", "Winamp SA"
+ VALUE "FileDescription", "Winamp 5.x System Component"
+ VALUE "FileVersion", STR_WINAMP_PRODUCTVER
+ VALUE "InternalName", "playlist.w5s"
+ VALUE "LegalCopyright", "Copyright © 2005-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "playlist.w5s"
+ VALUE "ProductName", "Winamp Playlists Core Service"
+ VALUE "ProductVersion", STR_WINAMP_PRODUCTVER
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END