aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Library/ml_downloads
diff options
context:
space:
mode:
authorJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
committerJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
commit20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/Plugins/Library/ml_downloads
parent537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff)
downloadwinamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz
Initial community commit
Diffstat (limited to 'Src/Plugins/Library/ml_downloads')
-rw-r--r--Src/Plugins/Library/ml_downloads/AtomParse.h51
-rw-r--r--Src/Plugins/Library/ml_downloads/DESIGN.txt12
-rw-r--r--Src/Plugins/Library/ml_downloads/Defaults.cpp43
-rw-r--r--Src/Plugins/Library/ml_downloads/Defaults.h24
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadManager.cpp1
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadStatus.cpp158
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadStatus.h41
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadThread.cpp65
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadThread.h27
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadViewCallback.cpp125
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadViewCallback.h37
-rw-r--r--Src/Plugins/Library/ml_downloads/Downloaded.cpp141
-rw-r--r--Src/Plugins/Library/ml_downloads/Downloaded.h86
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadsDialog.cpp1657
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadsDialog.h11
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadsParse.cpp179
-rw-r--r--Src/Plugins/Library/ml_downloads/DownloadsParse.h13
-rw-r--r--Src/Plugins/Library/ml_downloads/Main.cpp387
-rw-r--r--Src/Plugins/Library/ml_downloads/Main.h43
-rw-r--r--Src/Plugins/Library/ml_downloads/MessageProcessor.cpp7
-rw-r--r--Src/Plugins/Library/ml_downloads/MessageProcessor.h35
-rw-r--r--Src/Plugins/Library/ml_downloads/ParseUtil.cpp43
-rw-r--r--Src/Plugins/Library/ml_downloads/ParseUtil.h8
-rw-r--r--Src/Plugins/Library/ml_downloads/Preferences.cpp77
-rw-r--r--Src/Plugins/Library/ml_downloads/Preferences.h6
-rw-r--r--Src/Plugins/Library/ml_downloads/RFCDate.cpp216
-rw-r--r--Src/Plugins/Library/ml_downloads/RFCDate.h7
-rw-r--r--Src/Plugins/Library/ml_downloads/TODO.txt51
-rw-r--r--Src/Plugins/Library/ml_downloads/Util.h64
-rw-r--r--Src/Plugins/Library/ml_downloads/XMLWriter.cpp125
-rw-r--r--Src/Plugins/Library/ml_downloads/XMLWriter.h6
-rw-r--r--Src/Plugins/Library/ml_downloads/api__ml_downloads.h11
-rw-r--r--Src/Plugins/Library/ml_downloads/date.c1
-rw-r--r--Src/Plugins/Library/ml_downloads/date.h20
-rw-r--r--Src/Plugins/Library/ml_downloads/db.cpp150
-rw-r--r--Src/Plugins/Library/ml_downloads/errors.h15
-rw-r--r--Src/Plugins/Library/ml_downloads/layout.cpp215
-rw-r--r--Src/Plugins/Library/ml_downloads/layout.h43
-rw-r--r--Src/Plugins/Library/ml_downloads/ml_downloads.rc249
-rw-r--r--Src/Plugins/Library/ml_downloads/ml_downloads.sln93
-rw-r--r--Src/Plugins/Library/ml_downloads/ml_downloads.vcxproj370
-rw-r--r--Src/Plugins/Library/ml_downloads/ml_downloads.vcxproj.filters215
-rw-r--r--Src/Plugins/Library/ml_downloads/png.rc63
-rw-r--r--Src/Plugins/Library/ml_downloads/resource.h120
-rw-r--r--Src/Plugins/Library/ml_downloads/resources/downloadIcon.bmpbin0 -> 1334 bytes
-rw-r--r--Src/Plugins/Library/ml_downloads/resources/downloadIcon.pngbin0 -> 367 bytes
-rw-r--r--Src/Plugins/Library/ml_downloads/resources/downloadIcon1.bmpbin0 -> 1334 bytes
-rw-r--r--Src/Plugins/Library/ml_downloads/resources/downloadIcon2.bmpbin0 -> 1334 bytes
-rw-r--r--Src/Plugins/Library/ml_downloads/resources/downloadIcon3.bmpbin0 -> 1334 bytes
-rw-r--r--Src/Plugins/Library/ml_downloads/util.cpp255
-rw-r--r--Src/Plugins/Library/ml_downloads/version.rc239
51 files changed, 5605 insertions, 0 deletions
diff --git a/Src/Plugins/Library/ml_downloads/AtomParse.h b/Src/Plugins/Library/ml_downloads/AtomParse.h
new file mode 100644
index 00000000..ab76a1f9
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/AtomParse.h
@@ -0,0 +1,51 @@
+#ifndef NULLSOFT_ATOMPARSEH
+#define NULLSOFT_ATOMPARSEH
+#if 0
+#include "RFCDate.h"
+#include "XMLNode.h"
+#include "Feeds.h"
+#include "../nu/AutoChar.h"
+#include "ChannelSync.h"
+
+void ReadAtomItem(XMLNode *item, Channel &channel)
+{
+}
+
+void ReadAtomChannel(XMLNode *node, Channel &newChannel)
+{
+ XMLNode *curNode = 0;
+ curNode = node->Get(L"title");
+ if (curNode)
+ newChanneltitle = curNode->content;
+
+ curNode = node->Get(L"subtitle");
+ if (curNode)
+ newChannel.description = curNode->content;
+
+ XMLNode::NodeList &links = node->GetList(L"link");
+ XMLNode::NodeList::iterator linkItr;
+ for (linkItr=links.begin();linkItr!=links.end();linkItr++)
+ {
+ if ((*linkItr)->properties[L"rel"].empty()
+ || (*linkItr)->properties[L"rel"]== L"alternate")
+ newChannel.link = (*linkItr)->properties[L"href"];
+ }
+
+ XMLNode::NodeList &entries = node->GetList(L"entry");
+ XMLNode::NodeList::iterator entryItr;
+ for (entryItr=entries.begin();entryItr!=entries.end();entryItr++)
+ {
+ }
+}
+
+void ReadAtom(XMLNode *atom, ChannelSync *sync)
+{
+ sync->BeginChannelSync();
+ Channel newChannel;
+ ReadAtomChannel(atom, newChannel);
+ sync->NewChannel(newChannel);
+
+ sync->EndChannelSync();
+}
+#endif
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/DESIGN.txt b/Src/Plugins/Library/ml_downloads/DESIGN.txt
new file mode 100644
index 00000000..47148235
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DESIGN.txt
@@ -0,0 +1,12 @@
+
+
+
+
+major components:
+
+Cloud
+-----
+
+The cloud is the object which is responsible for doing period updates on RSS feeds.
+It automatically scans the feeds. You can manually refresh via the Refresh(string url) method
+
diff --git a/Src/Plugins/Library/ml_downloads/Defaults.cpp b/Src/Plugins/Library/ml_downloads/Defaults.cpp
new file mode 100644
index 00000000..0478d92b
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/Defaults.cpp
@@ -0,0 +1,43 @@
+#include "main.h"
+#include "Defaults.h"
+#include <shlobj.h>
+wchar_t defaultDownloadPath[MAX_PATH] = {0};
+bool needToMakePodcastsView=true;
+
+static BOOL UtilGetSpecialFolderPath( HWND hwnd, wchar_t *path, int folder )
+{
+ ITEMIDLIST *pidl; // Shell Item ID List ptr
+ IMalloc *imalloc; // Shell IMalloc interface ptr
+ BOOL result; // Return value
+
+ if ( SHGetSpecialFolderLocation( hwnd, folder, &pidl ) != NOERROR )
+ return FALSE;
+
+ result = SHGetPathFromIDList( pidl, path );
+
+ if ( SHGetMalloc( &imalloc ) == NOERROR )
+ {
+ imalloc->Free( pidl );
+ imalloc->Release();
+ }
+
+ return result;
+}
+
+static void BuildDefaultDownloadPath( HWND hwnd )
+{
+ wchar_t defaultPath[ MAX_PATH ] = L"";
+ if ( !UtilGetSpecialFolderPath( hwnd, defaultPath, CSIDL_MYMUSIC ) )
+ UtilGetSpecialFolderPath( hwnd, defaultPath, CSIDL_PERSONAL );
+
+ lstrcpyn( defaultDownloadPath, defaultPath, MAX_PATH );
+}
+
+/*
+Requires an HWND because some of the shell functions require one. Theoretically it could be NULL
+*/
+void BuildDefaults( HWND hwnd )
+{
+ BuildDefaultDownloadPath( hwnd );
+}
+
diff --git a/Src/Plugins/Library/ml_downloads/Defaults.h b/Src/Plugins/Library/ml_downloads/Defaults.h
new file mode 100644
index 00000000..52afbd60
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/Defaults.h
@@ -0,0 +1,24 @@
+#ifndef NULLSOFT_DEFAULTSH
+#define NULLSOFT_DEFAULTSH
+#include <windows.h>
+
+extern wchar_t defaultDownloadPath[MAX_PATH];
+
+#define DOWNLOADSSOURCEWIDTHDEFAULT 200
+#define DOWNLOADSTITLEWIDTHDEFAULT 200
+#define DOWNLOADSPROGRESSWIDTHDEFAULT 100
+#define DOWNLOADSDATEWIDTHDEFAULTS 100
+#define DOWNLOADSSIZEWIDTHDEFAULTS 100
+#define DOWNLOADSPATHWIDTHDEFAULTS 200
+
+extern int downloadsSourceWidth,
+ downloadsTitleWidth,
+ downloadsProgressWidth,
+ downloadsPathWidth,
+ downloadsSizeWidth,
+ downloadsDateWidth;
+
+extern bool needToMakePodcastsView;
+
+void BuildDefaults(HWND);
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/DownloadManager.cpp b/Src/Plugins/Library/ml_downloads/DownloadManager.cpp
new file mode 100644
index 00000000..61f734b2
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadManager.cpp
@@ -0,0 +1 @@
+#include "main.h" \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/DownloadStatus.cpp b/Src/Plugins/Library/ml_downloads/DownloadStatus.cpp
new file mode 100644
index 00000000..fb11a773
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadStatus.cpp
@@ -0,0 +1,158 @@
+#include "main.h"
+#include "api__ml_downloads.h"
+#include "DownloadStatus.h"
+#include "DownloadsDialog.h"
+
+#include <strsafe.h>
+
+DownloadStatus downloadStatus;
+
+using namespace Nullsoft::Utility;
+
+DownloadStatus::Status::Status()
+{
+ Init();
+}
+
+DownloadStatus::Status::Status( size_t _downloaded, size_t _maxSize, const wchar_t *_source, const wchar_t *_title, const wchar_t *_path )
+{
+ Init();
+
+ downloaded = _downloaded;
+ maxSize = _maxSize;
+ source = _wcsdup( _source );
+ title = _wcsdup( _title );
+ path = _wcsdup( _path );
+}
+
+const DownloadStatus::Status &DownloadStatus::Status::operator =( const DownloadStatus::Status &copy )
+{
+ Reset();
+ Init();
+
+ downloaded = copy.downloaded;
+ maxSize = copy.maxSize;
+ source = _wcsdup( copy.source );
+ title = _wcsdup( copy.title );
+ path = _wcsdup( copy.path );
+ killswitch = copy.killswitch;
+
+ return *this;
+}
+
+DownloadStatus::Status::~Status()
+{
+ Reset();
+}
+
+void DownloadStatus::Status::Init()
+{
+ downloaded = 0;
+ maxSize = 0;
+ killswitch = 0;
+ source = 0;
+ title = 0;
+ path = 0;
+}
+
+void DownloadStatus::Status::Reset()
+{
+ if ( source )
+ {
+ free( source );
+ source = 0;
+ }
+
+ if ( title )
+ {
+ free( title );
+ title = 0;
+ }
+
+ if ( path )
+ {
+ free( path );
+ path = 0;
+ }
+}
+
+void DownloadStatus::AddDownloadThread(DownloadToken token, const wchar_t *source, const wchar_t *title, const wchar_t *path)
+{
+ {
+ AutoLock lock(statusLock);
+ downloads[token] = Status(0,0,source,title,path);
+ DownloadsUpdated(downloads[token],token);
+ }
+
+ static pluginMessage p = {ML_MSG_DOWNLOADS_VIEW_POSITION, 0, 0, 0};
+ PostMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&p, ML_IPC_SEND_PLUGIN_MESSAGE);
+}
+
+void DownloadStatus::DownloadThreadDone(DownloadToken token)
+{
+ {
+ AutoLock lock(statusLock);
+ downloads.erase(token);
+ }
+
+ static pluginMessage p = {ML_MSG_DOWNLOADS_VIEW_POSITION, 0, 0, 0};
+ PostMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&p, ML_IPC_SEND_PLUGIN_MESSAGE);
+}
+
+bool DownloadStatus::UpdateStatus(DownloadToken token, size_t downloaded, size_t maxSize)
+{
+ AutoLock lock(statusLock);
+ downloads[token].downloaded = downloaded;
+ downloads[token].maxSize = maxSize;
+
+ return !!downloads[token].killswitch;
+}
+
+bool DownloadStatus::CurrentlyDownloading()
+{
+ AutoLock lock(statusLock);
+ return !downloads.empty();
+}
+
+void DownloadStatus::GetStatusString( wchar_t *status, size_t len )
+{
+ AutoLock lock( statusLock );
+ Downloads::iterator itr;
+ size_t bytesDownloaded = 0, bytesTotal = 0, numDownloads = 0;
+ bool unknownTotal = false;
+ for ( itr = downloads.begin(); itr != downloads.end(); itr++ )
+ {
+ Status &dlstatus = itr->second;
+ if ( dlstatus.maxSize )
+ {
+ numDownloads++;
+ bytesDownloaded += dlstatus.downloaded;
+ bytesTotal += dlstatus.maxSize;
+ }
+ else // don't have a max size
+ {
+ if ( dlstatus.downloaded ) // if we've downloaded some then we just don't know the total
+ {
+ unknownTotal = true;
+ numDownloads++;
+ bytesDownloaded += dlstatus.downloaded;
+ }
+ }
+ }
+
+ if ( 0 == numDownloads )
+ {
+ status[ 0 ] = L'\0';
+ }
+ else
+ {
+ wchar_t buf[ 128 ] = { 0 };
+ if ( unknownTotal || bytesTotal == 0 )
+ StringCchPrintf( status, len, WASABI_API_LNGSTRINGW( IDS_DOWNLOADING_COMPLETE ), numDownloads, numDownloads, WASABI_API_LNG->FormattedSizeString( buf, ARRAYSIZE( buf ), bytesDownloaded ) );
+ else
+ {
+ wchar_t buf2[ 128 ] = { 0 };
+ StringCchPrintf( status, len, WASABI_API_LNGSTRINGW( IDS_DOWNLOADING_PROGRESS ), numDownloads, WASABI_API_LNG->FormattedSizeString( buf, ARRAYSIZE( buf ), bytesDownloaded ), WASABI_API_LNG->FormattedSizeString( buf2, ARRAYSIZE( buf2 ), bytesTotal ), MulDiv( (int)bytesDownloaded, 100, (int)bytesTotal ) );
+ }
+ }
+}
diff --git a/Src/Plugins/Library/ml_downloads/DownloadStatus.h b/Src/Plugins/Library/ml_downloads/DownloadStatus.h
new file mode 100644
index 00000000..8e8eedbb
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadStatus.h
@@ -0,0 +1,41 @@
+#ifndef NULLSOFT_DOWNLOADSTATUSH
+#define NULLSOFT_DOWNLOADSTATUSH
+
+#include "../nu/AutoLock.h"
+#include <map>
+
+
+class DownloadStatus
+{
+public:
+ class Status
+ {
+ public:
+ Status();
+ ~Status();
+ const Status &operator =(const Status &copy);
+ Status(size_t _downloaded, size_t _maxSize, const wchar_t *source, const wchar_t *title, const wchar_t *path);
+ size_t downloaded, maxSize;
+
+ int killswitch;
+ wchar_t *source;
+ wchar_t *title;
+ wchar_t *path;
+ private:
+
+ void Init();
+ void Reset();
+ };
+
+ void AddDownloadThread(DownloadToken token, const wchar_t *source, const wchar_t *title, const wchar_t *path);
+ void DownloadThreadDone(DownloadToken token);
+ bool UpdateStatus(DownloadToken token, size_t downloaded, size_t maxSize);
+ bool CurrentlyDownloading();
+ void GetStatusString(wchar_t *status, size_t len);
+ typedef std::map<DownloadToken, Status> Downloads;
+ Downloads downloads;
+ Nullsoft::Utility::LockGuard statusLock;
+};
+
+extern DownloadStatus downloadStatus;
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/DownloadThread.cpp b/Src/Plugins/Library/ml_downloads/DownloadThread.cpp
new file mode 100644
index 00000000..563c9d3d
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadThread.cpp
@@ -0,0 +1,65 @@
+#include "Main.h"
+
+#pragma warning(disable:4786)
+
+#include "DownloadThread.h"
+#include "api__ml_downloads.h"
+#include <api/service/waServiceFactory.h>
+#include "errors.h"
+#include <strsafe.h>
+
+DownloadThread::DownloadThread() : parser( 0 ), parserFactory( 0 )
+{
+ parserFactory = plugin.service->service_getServiceByGuid( obj_xmlGUID );
+ if ( parserFactory )
+ parser = (obj_xml *)parserFactory->getInterface();
+
+ if ( parser )
+ {
+ parser->xmlreader_setCaseSensitive();
+ parser->xmlreader_registerCallback( L"*", &xmlDOM );
+ parser->xmlreader_open();
+ }
+}
+
+DownloadThread::~DownloadThread()
+{
+ if ( parser )
+ {
+ parser->xmlreader_unregisterCallback( &xmlDOM );
+ parser->xmlreader_close();
+ }
+
+ if ( parserFactory && parser )
+ parserFactory->releaseInterface( parser );
+
+ parserFactory = 0;
+ parser = 0;
+}
+
+#define FILE_BUFFER_SIZE 32768
+void DownloadThread::DownloadFile( const wchar_t *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, FILE_FLAG_SEQUENTIAL_SCAN, NULL );
+ if ( file == INVALID_HANDLE_VALUE )
+ return;
+
+ while ( true )
+ {
+ char data[ FILE_BUFFER_SIZE ] = { 0 };
+ DWORD bytesRead = 0;
+ if ( ReadFile( file, data, FILE_BUFFER_SIZE, &bytesRead, NULL ) && bytesRead )
+ {
+ parser->xmlreader_feed( (void *)data, bytesRead );
+ }
+ else
+ break;
+ }
+
+ CloseHandle( file );
+ parser->xmlreader_feed( 0, 0 );
+ ReadNodes( fileName );
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/DownloadThread.h b/Src/Plugins/Library/ml_downloads/DownloadThread.h
new file mode 100644
index 00000000..31fad863
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadThread.h
@@ -0,0 +1,27 @@
+#ifndef NULLSOFT_DOWNLOADTHREADH
+#define NULLSOFT_DOWNLOADTHREADH
+
+#include "../xml/obj_xml.h"
+#include "../xml/XMLDOM.h"
+#include "../nu/Alias.h"
+#include "api__ml_downloads.h"
+#include <api/service/waServiceFactory.h>
+
+
+class DownloadThread
+{
+public:
+ DownloadThread();
+ virtual ~DownloadThread();
+
+ virtual void ReadNodes(const wchar_t *url) = 0;
+
+ void DownloadFile(const wchar_t *fileName);
+protected:
+ XMLDOM xmlDOM;
+private:
+ obj_xml *parser;
+ waServiceFactory *parserFactory;
+
+};
+#endif
diff --git a/Src/Plugins/Library/ml_downloads/DownloadViewCallback.cpp b/Src/Plugins/Library/ml_downloads/DownloadViewCallback.cpp
new file mode 100644
index 00000000..ffd2ca52
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadViewCallback.cpp
@@ -0,0 +1,125 @@
+#include "Main.h"
+
+//#include "../Components/wac_downloads/wac_downloads_download_manager.h"
+
+using namespace Nullsoft::Utility;
+
+DownloadViewCallback::DownloadViewCallback()
+{}
+
+void DownloadViewCallback::OnInit( DownloadToken token )
+{
+ // ---- Inform the download status service of our presence----
+ downloadStatus.AddDownloadThread( token, WAC_API_DOWNLOADMANAGER->GetSource( token ), WAC_API_DOWNLOADMANAGER->GetTitle( token ), WAC_API_DOWNLOADMANAGER->GetLocation( token ) );
+}
+
+void DownloadViewCallback::OnConnect( DownloadToken token )
+{}
+
+void DownloadViewCallback::OnData( DownloadToken token, void *data, size_t datalen )
+{
+ if ( token == NULL )
+ return;
+
+ api_httpreceiver *http = WAC_API_DOWNLOADMANAGER->GetReceiver( token );
+ size_t totalSize = http->content_length();
+ size_t downloaded = (size_t)WAC_API_DOWNLOADMANAGER->GetBytesDownloaded( token );
+
+ // if killswitch is turned on, then cancel download
+ downloadStatus.UpdateStatus( token, downloaded, totalSize );
+ dirty++;
+}
+
+void DownloadViewCallback::OnCancel( DownloadToken token )
+{
+ api_httpreceiver *http = WAC_API_DOWNLOADMANAGER->GetReceiver( token );
+
+ DownloadedFile *data = new DownloadedFile( AutoWide( http->get_url() ), WAC_API_DOWNLOADMANAGER->GetLocation( token ), WAC_API_DOWNLOADMANAGER->GetSource( token ), WAC_API_DOWNLOADMANAGER->GetTitle( token ), DownloadedFile::DOWNLOAD_CANCELED, _time64( 0 ) );
+
+ data->totalSize = http->content_length();
+ data->bytesDownloaded = (size_t)WAC_API_DOWNLOADMANAGER->GetBytesDownloaded( token );
+
+ {
+ AutoLock lock( downloadedFiles );
+ downloadedFiles.downloadList.push_back( *data );
+ DownloadsUpdated( token, &downloadedFiles.downloadList[ downloadedFiles.downloadList.size() - 1 ] );
+ }
+
+ downloadStatus.DownloadThreadDone( token );
+ delete data;
+ dirty++;
+}
+
+void DownloadViewCallback::OnError( DownloadToken token, int error )
+{
+ api_httpreceiver *http = WAC_API_DOWNLOADMANAGER->GetReceiver( token );
+
+ DownloadedFile *data = new DownloadedFile( AutoWide( http->get_url() ), WAC_API_DOWNLOADMANAGER->GetLocation( token ), WAC_API_DOWNLOADMANAGER->GetSource( token ), WAC_API_DOWNLOADMANAGER->GetTitle( token ), DownloadedFile::DOWNLOAD_FAILURE, _time64( 0 ) );
+
+ data->totalSize = http->content_length();
+ data->bytesDownloaded = (size_t)WAC_API_DOWNLOADMANAGER->GetBytesDownloaded( token );
+
+ {
+ AutoLock lock( downloadedFiles );
+ downloadedFiles.downloadList.push_back( *data );
+ DownloadsUpdated( token, &downloadedFiles.downloadList[ downloadedFiles.downloadList.size() - 1 ] );
+ }
+
+ downloadStatus.DownloadThreadDone( token );
+ delete data;
+ dirty++;
+}
+
+void DownloadViewCallback::OnFinish( DownloadToken token )
+{
+ api_httpreceiver *http = WAC_API_DOWNLOADMANAGER->GetReceiver( token );
+
+ DownloadedFile *data = new DownloadedFile( AutoWide( http->get_url() ), WAC_API_DOWNLOADMANAGER->GetLocation( token ), WAC_API_DOWNLOADMANAGER->GetSource( token ), WAC_API_DOWNLOADMANAGER->GetTitle( token ), DownloadedFile::DOWNLOAD_SUCCESS, _time64( 0 ) );
+
+ data->totalSize = http->content_length();
+ data->bytesDownloaded = (size_t)WAC_API_DOWNLOADMANAGER->GetBytesDownloaded( token );
+
+ {
+ AutoLock lock( downloadedFiles );
+ downloadedFiles.downloadList.push_back( *data );
+
+ //AddDownloadData(*data);
+
+ DownloadsUpdated( token, &downloadedFiles.downloadList[ downloadedFiles.downloadList.size() - 1 ] );
+ }
+
+ downloadStatus.DownloadThreadDone( token );
+ delete data;
+ dirty++;
+}
+
+
+size_t DownloadViewCallback::AddRef()
+{
+ return this->_ref_count.fetch_add( 1 );
+}
+
+size_t DownloadViewCallback::Release()
+{
+ std::size_t l_ref_countr = this->_ref_count.fetch_sub( 1 );
+ if ( l_ref_countr == 0 )
+ delete( this );
+
+ return l_ref_countr;
+}
+
+DownloadViewCallback::~DownloadViewCallback()
+{}
+
+#define CBCLASS DownloadViewCallback
+START_DISPATCH;
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONFINISH, OnFinish )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONCANCEL, OnCancel )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONERROR, OnError )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONDATA, OnData )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONCONNECT, OnConnect )
+VCB( IFC_DOWNLOADMANAGERCALLBACK_ONINIT, OnInit )
+CB( ADDREF, AddRef )
+CB( RELEASE, Release )
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/DownloadViewCallback.h b/Src/Plugins/Library/ml_downloads/DownloadViewCallback.h
new file mode 100644
index 00000000..6ecce16b
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadViewCallback.h
@@ -0,0 +1,37 @@
+#include <atomic>
+
+#include "Main.h"
+#include "Downloaded.h"
+
+#include "DownloadStatus.h"
+#include "DownloadsDialog.h"
+#include "api__ml_downloads.h"
+#include "api/service/waServiceFactory.h"
+#include "../../..\Components\wac_network\wac_network_http_receiver_api.h"
+
+
+class DownloadViewCallback : public ifc_downloadManagerCallback
+{
+public:
+ DownloadViewCallback();
+
+ void OnInit( DownloadToken token );
+ void OnConnect( DownloadToken token );
+ void OnData( DownloadToken token, void *data, size_t datalen );
+ void OnCancel( DownloadToken token );
+ void OnError( DownloadToken token, int error );
+ void OnFinish( DownloadToken token );
+
+ size_t AddRef();
+ size_t Release();
+
+private: // private destructor so no one accidentally calls delete directly on this reference counted object
+ ~DownloadViewCallback();
+
+protected:
+ RECVS_DISPATCH;
+
+private:
+ std::atomic<std::size_t> _ref_count = 1;
+};
+
diff --git a/Src/Plugins/Library/ml_downloads/Downloaded.cpp b/Src/Plugins/Library/ml_downloads/Downloaded.cpp
new file mode 100644
index 00000000..dc5a0e19
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/Downloaded.cpp
@@ -0,0 +1,141 @@
+#include "main.h"
+#include "Downloaded.h"
+
+DownloadList downloadedFiles;
+using namespace Nullsoft::Utility;
+Nullsoft::Utility::LockGuard downloadedLock;
+
+DownloadedFile::DownloadedFile()
+{
+ Init();
+}
+
+DownloadedFile::DownloadedFile( const wchar_t *_url, const wchar_t *_path, const wchar_t *_source, const wchar_t *_title, int downloadStatus, __time64_t downloadDate )
+{
+ Init();
+
+ this->downloadStatus = downloadStatus;
+ this->downloadDate = downloadDate;
+
+ SetSource( _source );
+ SetTitle( _title );
+ SetPath( _path );
+ SetURL( _url );
+}
+
+DownloadedFile::DownloadedFile( const DownloadedFile &copy )
+{
+ Init();
+
+ operator =( copy );
+}
+
+DownloadedFile::~DownloadedFile()
+{
+ Reset();
+}
+
+
+void DownloadedFile::Init()
+{
+ url = 0;
+ path = 0;
+ source = 0;
+ title = 0;
+ bytesDownloaded = 0;
+ totalSize = 0;
+ downloadDate = 0;
+}
+
+void DownloadedFile::Reset()
+{
+ if ( url )
+ {
+ free( url );
+ url = 0;
+ }
+
+ if ( path )
+ {
+ free( path );
+ path = 0;
+ }
+
+ if ( source )
+ {
+ free( source );
+ source = 0;
+ }
+
+ if ( title )
+ {
+ free( title );
+ title = 0;
+ }
+}
+
+void DownloadedFile::SetPath( const wchar_t *_path )
+{
+ if ( path )
+ free( path );
+
+ path = _wcsdup( _path );
+}
+
+void DownloadedFile::SetURL( const wchar_t *_url )
+{
+ if ( url )
+ free( url );
+
+ url = _wcsdup( _url );
+}
+
+void DownloadedFile::SetTitle( const wchar_t *_title )
+{
+ if ( title )
+ free( title );
+
+ title = _wcsdup( _title );
+}
+
+void DownloadedFile::SetSource( const wchar_t *_source )
+{
+ if ( source )
+ free( source );
+
+ source = _wcsdup( _source );
+}
+
+const DownloadedFile &DownloadedFile::operator =( const DownloadedFile &copy )
+{
+ Reset();
+ Init();
+
+ SetSource( copy.source );
+ SetTitle( copy.title );
+
+ bytesDownloaded = copy.bytesDownloaded;
+ totalSize = copy.totalSize;
+ downloadStatus = copy.downloadStatus;
+ downloadDate = copy.downloadDate;
+
+ SetPath( copy.path );
+ SetURL( copy.url );
+
+ return *this;
+}
+
+wchar_t *GetDownloadStatus( int downloadStatus )
+{
+ switch ( downloadStatus )
+ {
+ case DownloadedFile::DOWNLOAD_SUCCESS:
+ return WASABI_API_LNGSTRINGW( IDS_DOWNLOAD_SUCCESS );
+ case DownloadedFile::DOWNLOAD_FAILURE:
+ return WASABI_API_LNGSTRINGW( IDS_DOWNLOAD_FAILURE );
+ case DownloadedFile::DOWNLOAD_CANCELED:
+ return WASABI_API_LNGSTRINGW( IDS_DOWNLOAD_CANCELED );
+ default:
+ return WASABI_API_LNGSTRINGW( IDS_DOWNLOAD_FAILURE );
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/Downloaded.h b/Src/Plugins/Library/ml_downloads/Downloaded.h
new file mode 100644
index 00000000..17897f19
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/Downloaded.h
@@ -0,0 +1,86 @@
+#ifndef NULLSOFT_DOWNLOADEDH
+#define NULLSOFT_DOWNLOADEDH
+
+#include "../nu/AutoLock.h"
+#include "../nu/AutoCharFn.h"
+#include "..\..\General\gen_ml/ml.h"
+#include <vector>
+#include "../nu/MediaLibraryInterface.h"
+
+class DownloadedFile
+{
+public:
+ DownloadedFile();
+ DownloadedFile( const wchar_t *_url, const wchar_t *_path, const wchar_t *_source, const wchar_t *_title, int downloadStatus, __time64_t downloadDate );
+ DownloadedFile( const DownloadedFile &copy );
+ ~DownloadedFile();
+
+ const DownloadedFile &operator =( const DownloadedFile &copy );
+
+ void SetPath( const wchar_t *_path );
+ void SetURL( const wchar_t *_url );
+ void SetTitle( const wchar_t *_title );
+ void SetSource( const wchar_t *_source );
+
+ enum
+ {
+ DOWNLOAD_FAILURE = 0,
+ DOWNLOAD_SUCCESS = 1,
+ DOWNLOAD_CANCELED = 2,
+ };
+
+ size_t bytesDownloaded = 0;
+ size_t totalSize = 0;
+
+ int downloadStatus = 0;
+ __time64_t downloadDate = 0;
+
+ wchar_t *path = 0;
+ wchar_t *url = 0;
+ wchar_t *source = 0;
+ wchar_t *title = 0;
+
+
+private:
+ void Init();
+ void Reset();
+};
+
+class DownloadList
+{
+public:
+ typedef std::vector<DownloadedFile> DownloadedFileList;
+ typedef DownloadedFileList::iterator iterator;
+ typedef DownloadedFileList::const_iterator const_iterator;
+
+ operator Nullsoft::Utility::LockGuard &() { return downloadedLock; }
+
+ void Remove( size_t index ) { downloadList.erase( downloadList.begin() + index ); }
+
+ bool RemoveAndDelete( int index )
+ {
+ SendMessage( mediaLibrary.library, WM_ML_IPC, (WPARAM)downloadList[ index ].path, ML_IPC_DB_REMOVEITEMW );
+
+ if ( !DeleteFile( downloadList[ index ].path ) && GetLastError() != ERROR_FILE_NOT_FOUND )
+ return false;
+
+ downloadList.erase( downloadList.begin() + index );
+
+ return true;
+ }
+
+ DownloadedFileList downloadList;
+ Nullsoft::Utility::LockGuard downloadedLock;
+
+ iterator begin() { return downloadList.begin(); }
+ iterator end() { return downloadList.end(); }
+};
+
+extern DownloadList downloadedFiles;
+extern int downloadsItemSort;
+extern bool downloadsSortAscending;
+
+void CleanupDownloads();
+wchar_t *GetDownloadStatus( int downloadStatus );
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/DownloadsDialog.cpp b/Src/Plugins/Library/ml_downloads/DownloadsDialog.cpp
new file mode 100644
index 00000000..adb2b008
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadsDialog.cpp
@@ -0,0 +1,1657 @@
+#include "main.h"
+#include "api__ml_downloads.h"
+#include "RFCDate.h"
+#include "Downloaded.h"
+#include "DownloadStatus.h"
+#include "Defaults.h"
+#include "../nu/listview.h"
+#include "..\..\General\gen_ml/ml_ipc.h"
+#include "..\..\General\gen_ml/menu.h"
+#include <vector>
+#include "../nu/menushortcuts.h"
+#include <commctrl.h>
+#include <shlwapi.h>
+#include <shellapi.h>
+#include <strsafe.h>
+#include <algorithm>
+
+HWND downloads_window = 0;
+extern int downloads_treeItem;
+extern int no_auto_hide;
+int groupBtn = 1, enqueuedef = 0, customAllowed = 0;
+HMENU g_context_menus2 = NULL;
+static viewButtons view;
+
+#ifndef HDF_SORTUP
+#define HDF_SORTUP 0x0400
+#define HDF_SORTDOWN 0x0200
+#endif // !HDF_SORTUP
+
+using namespace Nullsoft::Utility;
+
+enum
+{
+ COL_TITLE = 0,
+ COL_PROGRESS,
+ COL_DATE,
+ COL_SOURCE,
+ COL_SIZE,
+ COL_PATH,
+ NUM_COLUMNS,
+};
+
+int downloadsSourceWidth = DOWNLOADSSOURCEWIDTHDEFAULT;
+int downloadsTitleWidth = DOWNLOADSTITLEWIDTHDEFAULT;
+int downloadsProgressWidth = DOWNLOADSPROGRESSWIDTHDEFAULT;
+int downloadsDateWidth = DOWNLOADSDATEWIDTHDEFAULTS;
+int downloadsSizeWidth = DOWNLOADSSIZEWIDTHDEFAULTS;
+int downloadsPathWidth = DOWNLOADSPATHWIDTHDEFAULTS;
+
+
+W_ListView downloadList;
+int downloadsItemSort = 2; // -1 means no sort active
+bool downloadsSortAscending = false;
+
+enum
+{
+ DOWNLOADSDIALOG_TIMER_UPDATESTATUSBAR = 0,
+};
+
+class DownloadListItem
+{
+public:
+ DownloadedFile *f = NULL;
+ DownloadToken token = NULL;
+ wchar_t *source = 0;
+ wchar_t *title = 0;
+ wchar_t *path = 0;
+ wchar_t status[ 20 ] = { 0 };
+
+ DownloadListItem( DownloadedFile *fi )
+ {
+ f = new DownloadedFile( *fi );
+ ZeroMemory( status, sizeof( status ) );
+ }
+
+ DownloadListItem( DownloadToken p_token, const wchar_t *p_source, const wchar_t *p_title, const wchar_t *p_path, size_t p_downloaded, size_t p_maxSize ) : token( p_token )
+ {
+ if ( p_maxSize )
+ StringCchPrintf( status, 20, WASABI_API_LNGSTRINGW( IDS_DOWNLOADING_PERCENT ), (int)( p_downloaded / ( p_maxSize / 100 ) ) );
+ else
+ {
+ if ( WAC_API_DOWNLOADMANAGER->IsPending( p_token ) )
+ WASABI_API_LNGSTRINGW_BUF( IDS_DOWNLOAD_PENDING, status, 20 );
+ else
+ WASABI_API_LNGSTRINGW_BUF( IDS_DOWNLOADING, status, 20 );
+ }
+
+ source = p_source ? _wcsdup( p_source ) : NULL;
+ title = p_title ? _wcsdup( p_title ) : NULL;
+ path = p_path ? _wcsdup( p_path ) : NULL;
+ }
+
+ ~DownloadListItem()
+ {
+ clean();
+
+ if ( f )
+ delete f;
+ }
+
+ void clean()
+ {
+ if ( source )
+ {
+ free( source );
+ source = 0;
+ }
+
+ if ( title )
+ {
+ free( title );
+ title = 0;
+ }
+
+ if ( path )
+ {
+ free( path );
+ path = 0;
+ }
+ }
+};
+
+static std::vector<DownloadListItem*> listContents;
+
+bool GetDownload( int &download )
+{
+ download = ListView_GetNextItem( downloadList.getwnd(), download, LVNI_ALL | LVNI_SELECTED );
+ if ( download == -1 )
+ return false;
+ else
+ return true;
+}
+
+void Downloads_Play( bool enqueue = false )
+{
+ int download = -1;
+ AutoLock lock( downloadedFiles );
+ while ( GetDownload( download ) )
+ {
+ if ( !enqueue )
+ {
+ if ( listContents[ download ]->f )
+ mediaLibrary.PlayFile( listContents[ download ]->f->path );
+ else if ( listContents[ download ]->path )
+ mediaLibrary.PlayFile( listContents[ download ]->path );
+
+ enqueue = true;
+ }
+ else
+ {
+ if ( listContents[ download ]->f )
+ mediaLibrary.EnqueueFile( listContents[ download ]->f->path );
+ else if ( listContents[ download ]->path )
+ mediaLibrary.EnqueueFile( listContents[ download ]->path );
+ }
+ }
+}
+
+void DownloadsUpdated( const DownloadStatus::Status &s, DownloadToken token )
+{
+ listContents.push_back( new DownloadListItem( token, s.source, s.title, s.path, s.downloaded, s.maxSize ) );
+
+ downloadList.SetVirtualCountAsync( (int)listContents.size() );
+}
+
+void DownloadsUpdated( DownloadToken token, const DownloadedFile *f )
+{
+ for ( DownloadListItem *l_content : listContents )
+ {
+ if ( l_content->token == token )
+ {
+ l_content->token = 0;
+ if ( f )
+ {
+ l_content->f = new DownloadedFile( *f );
+
+ l_content->clean();
+ }
+ else
+ lstrcpyn( l_content->status, L"Error", 20 );
+
+ break;
+ }
+ }
+
+ PostMessage( downloadList.getwnd(), LVM_REDRAWITEMS, 0, listContents.size() );
+}
+
+void DownloadsUpdated()
+{
+ for ( DownloadListItem *l_content : listContents )
+ delete l_content;
+
+ listContents.clear();
+
+ for ( DownloadedFile &l_download : downloadedFiles.downloadList )
+ listContents.push_back( new DownloadListItem( &l_download ) );
+
+ {
+ AutoLock lock( downloadStatus.statusLock );
+ for ( DownloadStatus::Downloads::iterator itr = downloadStatus.downloads.begin(); itr != downloadStatus.downloads.end(); itr++ )
+ {
+ listContents.push_back( new DownloadListItem( itr->first, itr->second.source, itr->second.title, itr->second.path, itr->second.downloaded, itr->second.maxSize ) );
+ }
+ }
+
+ downloadList.SetVirtualCountAsync( (int)listContents.size() );
+// Navigation_ShowService( SERVICE_DOWNLOADS, SHOWMODE_AUTO );
+}
+
+static void CleanupDownloads()
+{
+ {
+ AutoLock lock( downloadedFiles );
+ DownloadList::DownloadedFileList &downloads = downloadedFiles.downloadList;
+ DownloadList::iterator itr, next;
+ for ( itr = downloads.begin(); itr != downloads.end();)
+ {
+ next = itr;
+ ++next;
+ if ( !PathFileExists( itr->path ) )
+ downloads.erase( itr );
+ else
+ itr = next;
+ }
+ }
+
+// Navigation_ShowService(SERVICE_DOWNLOADS, SHOWMODE_AUTO);
+}
+
+void Downloads_UpdateStatusBar(HWND hwndDlg)
+{
+ wchar_t status[256]=L"";
+ downloadStatus.GetStatusString(status, 256);
+ SetWindowText(GetDlgItem(hwndDlg, IDC_STATUS), status);
+}
+
+void Downloads_Paint(HWND hwndDlg)
+{
+ int tab[] = { IDC_DOWNLOADLIST | DCW_SUNKENBORDER, };
+ dialogSkinner.Draw(hwndDlg, tab, sizeof(tab) / sizeof(tab[0]));
+}
+
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0, offsetY = 0;
+
+typedef struct _LAYOUT
+{
+ INT id;
+ HWND hwnd;
+ INT x;
+ INT y;
+ INT cx;
+ INT cy;
+ DWORD flags;
+ HRGN rgn;
+}
+LAYOUT, PLAYOUT;
+
+#define SETLAYOUTPOS(_layout, _x, _y, _cx, _cy) { _layout->x=_x; _layout->y=_y;_layout->cx=_cx;_layout->cy=_cy;_layout->rgn=NULL; }
+#define SETLAYOUTFLAGS(_layout, _r) \
+ { \
+ BOOL fVis; \
+ fVis = (WS_VISIBLE & (LONG)GetWindowLongPtr(_layout->hwnd, GWL_STYLE)); \
+ if (_layout->x == _r.left && _layout->y == _r.top) _layout->flags |= SWP_NOMOVE; \
+ if (_layout->cx == (_r.right - _r.left) && _layout->cy == (_r.bottom - _r.top)) _layout->flags |= SWP_NOSIZE; \
+ if ((SWP_HIDEWINDOW & _layout->flags) && !fVis) _layout->flags &= ~SWP_HIDEWINDOW; \
+ if ((SWP_SHOWWINDOW & _layout->flags) && fVis) _layout->flags &= ~SWP_SHOWWINDOW; \
+ }
+
+#define LAYOUTNEEEDUPDATE(_layout) ((SWP_NOMOVE | SWP_NOSIZE) != ((SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_SHOWWINDOW) & _layout->flags))
+
+#define GROUP_MIN 0x1
+#define GROUP_MAX 0x2
+#define GROUP_STATUSBAR 0x1
+#define GROUP_MAIN 0x2
+
+static void LayoutWindows(HWND hwnd, BOOL fRedraw, BOOL fUpdateAll = FALSE)
+{
+ static INT controls[] =
+ {
+ GROUP_STATUSBAR, IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, IDC_REMOVE, IDC_CLEANUP, IDC_STATUS,
+ GROUP_MAIN, IDC_DOWNLOADLIST
+ };
+
+ INT index;
+ RECT rc, rg, ri;
+ LAYOUT layout[sizeof(controls)/sizeof(controls[0])], *pl;
+ BOOL skipgroup;
+ HRGN rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+
+ if (rc.right == rc.left || rc.bottom == rc.top)
+ return;
+
+ if ( rc.right > WASABI_API_APP->getScaleX( 4 ) )
+ rc.right -= WASABI_API_APP->getScaleX( 4 );
+
+ SetRect( &rg, rc.left, rc.top, rc.right, rc.top );
+
+ pl = layout;
+ skipgroup = FALSE;
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ for (index = 0; index < sizeof(controls) / sizeof(*controls); index++)
+ {
+ if (controls[index] >= GROUP_MIN && controls[index] <= GROUP_MAX) // group id
+ {
+ skipgroup = FALSE;
+ switch (controls[index])
+ {
+ case GROUP_STATUSBAR:
+ {
+ wchar_t buffer[128] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDC_PLAY, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(GetDlgItem(hwnd, IDC_PLAY), buffer);
+
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1),
+ rc.bottom - WASABI_API_APP->getScaleY(HIWORD(idealSize)),
+ rc.right, rc.bottom);
+ rc.bottom = rg.top - WASABI_API_APP->getScaleY(3);
+ break;
+ }
+ case GROUP_MAIN:
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1), rc.top, rc.right, rc.bottom);
+ break;
+ }
+ continue;
+ }
+ if (skipgroup) continue;
+
+ pl->id = controls[index];
+ pl->hwnd = GetDlgItem(hwnd, pl->id);
+
+ if ( !pl->hwnd )
+ continue;
+
+ GetWindowRect(pl->hwnd, &ri);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&ri, 2);
+ pl->flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS;
+
+ switch (pl->id)
+ {
+ case IDC_PLAY:
+ case IDC_ENQUEUE:
+ case IDC_CUSTOM:
+ case IDC_REMOVE:
+ case IDC_CLEANUP:
+ if ( IDC_CUSTOM != pl->id || customAllowed )
+ {
+ if ( groupBtn && pl->id == IDC_PLAY && enqueuedef == 1 )
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if ( groupBtn && pl->id == IDC_ENQUEUE && enqueuedef != 1 )
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if ( groupBtn && ( pl->id == IDC_PLAY || pl->id == IDC_ENQUEUE ) && customAllowed )
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ wchar_t buffer[ 128 ] = { 0 };
+ GetWindowTextW( pl->hwnd, buffer, ARRAYSIZE( buffer ) );
+
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize( pl->hwnd, buffer );
+ LONG width = LOWORD( idealSize ) + WASABI_API_APP->getScaleX( 6 );
+
+ SETLAYOUTPOS( pl, rg.left, rg.bottom - WASABI_API_APP->getScaleY( HIWORD( idealSize ) ), width, WASABI_API_APP->getScaleY( HIWORD( idealSize ) ) );
+
+ pl->flags |= ( ( rg.right - rg.left ) > width ) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+
+ if ( SWP_SHOWWINDOW & pl->flags )
+ rg.left += ( pl->cx + WASABI_API_APP->getScaleX( 4 ) );
+ }
+ else
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ case IDC_STATUS:
+ SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left, (rg.bottom - rg.top));
+ pl->flags |= (pl->cx > 16) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ break;
+ case IDC_DOWNLOADLIST:
+ pl->flags |= (rg.top < rg.bottom) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS( pl, rg.left, rg.top + WASABI_API_APP->getScaleY( 1 ), rg.right - rg.left + WASABI_API_APP->getScaleY( 1 ), ( rg.bottom - rg.top ) - WASABI_API_APP->getScaleY( 2 ) );
+ break;
+ }
+
+ SETLAYOUTFLAGS(pl, ri);
+ if ( LAYOUTNEEEDUPDATE( pl ) )
+ {
+ if ( SWP_NOSIZE == ( ( SWP_HIDEWINDOW | SWP_SHOWWINDOW | SWP_NOSIZE ) & pl->flags ) && ri.left == ( pl->x + offsetX ) && ri.top == ( pl->y + offsetY ) && IsWindowVisible( pl->hwnd ) )
+ {
+ SetRect( &ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy );
+ ValidateRect( hwnd, &ri );
+ }
+
+ pl++;
+ }
+ else if ( ( fRedraw || ( !offsetX && !offsetY ) ) && IsWindowVisible( pl->hwnd ) )
+ {
+ ValidateRect( hwnd, &ri );
+ if ( GetUpdateRect( pl->hwnd, NULL, FALSE ) )
+ {
+ if ( !rgn )
+ rgn = CreateRectRgn( 0, 0, 0, 0 );
+
+ GetUpdateRgn( pl->hwnd, rgn, FALSE );
+ OffsetRgn( rgn, pl->x, pl->y );
+ InvalidateRgn( hwnd, rgn, FALSE );
+ }
+ }
+ }
+
+ if (pl != layout)
+ {
+ LAYOUT *pc;
+ HDWP hdwp = BeginDeferWindowPos((INT)(pl - layout));
+ for(pc = layout; pc < pl && hdwp; pc++)
+ {
+ hdwp = DeferWindowPos(hdwp, pc->hwnd, NULL, pc->x, pc->y, pc->cx, pc->cy, pc->flags);
+ }
+
+ if (hdwp)
+ EndDeferWindowPos(hdwp);
+
+ if ( !rgn )
+ rgn = CreateRectRgn( 0, 0, 0, 0 );
+
+ if (fRedraw)
+ {
+ GetUpdateRgn(hwnd, rgn, FALSE);
+ for ( pc = layout; pc < pl && hdwp; pc++ )
+ {
+ if ( pc->rgn )
+ {
+ OffsetRgn( pc->rgn, pc->x, pc->y );
+ CombineRgn( rgn, rgn, pc->rgn, RGN_OR );
+ }
+ }
+
+ RedrawWindow(hwnd, NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN);
+ }
+
+ if (g_rgnUpdate)
+ {
+ GetUpdateRgn(hwnd, g_rgnUpdate, FALSE);
+ for(pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(g_rgnUpdate, g_rgnUpdate, pc->rgn, RGN_OR);
+ }
+ }
+ }
+
+ for(pc = layout; pc < pl && hdwp; pc++) if (pc->rgn) DeleteObject(pc->rgn);
+ }
+
+ if ( rgn )
+ DeleteObject( rgn );
+
+ ValidateRgn(hwnd, NULL);
+}
+
+void Downloads_DisplayChange(HWND hwndDlg)
+{
+ ListView_SetTextColor(downloadList.getwnd(), dialogSkinner.Color(WADLG_ITEMFG));
+ ListView_SetBkColor(downloadList.getwnd(), dialogSkinner.Color(WADLG_ITEMBG));
+ ListView_SetTextBkColor(downloadList.getwnd(), dialogSkinner.Color(WADLG_ITEMBG));
+ downloadList.SetFont(dialogSkinner.GetFont());
+ LayoutWindows(hwndDlg, TRUE);
+}
+
+static void DownloadsDialog_SkinControls(HWND hwnd, const INT *itemList, INT itemCount, UINT skinType, UINT skinStyle)
+{
+ MLSKINWINDOW skinWindow = {0};
+ skinWindow.style = skinStyle;
+ skinWindow.skinType = skinType;
+
+ for(INT i = 0; i < itemCount; i++)
+ {
+ skinWindow.hwndToSkin = GetDlgItem(hwnd, itemList[i]);
+ if (NULL != skinWindow.hwndToSkin)
+ {
+ MLSkinWindow(plugin.hwndLibraryParent, &skinWindow);
+ }
+ }
+}
+
+static void DownloadDialog_InitializeList(HWND hwnd)
+{
+ HWND hControl = GetDlgItem(hwnd, IDC_DOWNLOADLIST);
+ if (NULL == hControl) return;
+
+ UINT styleEx = (UINT)GetWindowLongPtr(hControl, GWL_EXSTYLE);
+ SetWindowLongPtr(hControl, GWL_EXSTYLE, styleEx & ~WS_EX_NOPARENTNOTIFY);
+
+ styleEx = LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP;
+ SendMessage(hControl, LVM_SETEXTENDEDLISTVIEWSTYLE, styleEx, styleEx);
+ SendMessage(hControl, LVM_SETUNICODEFORMAT, (WPARAM)TRUE, 0L);
+
+ MLSKINWINDOW skinWindow;
+ skinWindow.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_ALTERNATEITEMS;
+ skinWindow.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ skinWindow.hwndToSkin = hControl;
+ MLSkinWindow(plugin.hwndLibraryParent, &skinWindow);
+}
+
+bool COL_SOURCE_Sort(const DownloadListItem* item1, const DownloadListItem* item2)
+{
+ if (item1->f && item2->f)
+ return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, (item1->f->source), -1, (item2->f->source), -1));
+ else if (!item1->f && !item2->f)
+ return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, (item1->source), -1, (item2->source), -1));
+ else if (!item1->f)
+ return (FALSE == downloadsSortAscending)?0:1;
+ else //if (!item2->f)
+ return (FALSE == downloadsSortAscending)?1:0;
+
+ //return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
+ // (item1->f?item1->f->source:item1->source), -1,
+ // (item2->f?item2->f->source:item2->source), -1));
+}
+
+bool COL_TITLE_Sort(const DownloadListItem* item1, const DownloadListItem* item2)
+{
+ if (item1->f && item2->f)
+ return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, (item1->f->title), -1, (item2->f->title), -1));
+ else if (!item1->f && !item2->f)
+ return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, (item1->title), -1, (item2->title), -1));
+ else if (!item1->f)
+ return (FALSE == downloadsSortAscending)?0:1;
+ else //if (!item2->f)
+ return (FALSE == downloadsSortAscending)?1:0;
+
+ //return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
+ // (item1->f?item1->f->title:item1->title), -1,
+ // (item2->f?item2->f->title:item2->title), -1));
+}
+
+bool COL_PROGRESS_Sort( const DownloadListItem *item1, const DownloadListItem *item2 )
+{
+ if ( item1->f && item2->f )
+ return ( item1->f->downloadStatus > item2->f->downloadStatus );
+ else if ( !item1->f && !item2->f )
+ return ( item1->token < item2->token );
+ else if ( !item1->f )
+ return ( FALSE == downloadsSortAscending ) ? 0 : 1;
+ else //if (!item2->f)
+ return ( FALSE == downloadsSortAscending ) ? 1 : 0;
+
+ //return ((item1->f?item1->f->downloadStatus:-1) < (item2->f?item2->f->downloadStatus:-1));
+
+ //return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
+ // (item1->f?GetDownloadStatus(item1->f->downloadStatus):item1->status), -1,
+ // (item2->f?GetDownloadStatus(item2->f->downloadStatus):item2->status), -1));
+}
+
+bool COL_PATH_Sort(const DownloadListItem* item1, const DownloadListItem* item2)
+{
+ if (item1->f && item2->f)
+ return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, (item1->f->path), -1, (item2->f->path), -1));
+ else if (!item1->f && !item2->f)
+ return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, (item1->path), -1, (item2->path), -1));
+ else if (!item1->f)
+ return (FALSE == downloadsSortAscending)?0:1;
+ else //if (!item2->f)
+ return (FALSE == downloadsSortAscending)?1:0;
+
+ //return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
+ // (item1->f?item1->f->path:item1->path), -1,
+ // (item2->f?item2->f->path:item2->path), -1));
+}
+
+bool COL_DATE_Sort(const DownloadListItem* item1, const DownloadListItem* item2)
+{
+ if (item1->f && item2->f)
+ return item1->f->downloadDate < item2->f->downloadDate;
+ else if (!item1->f && !item2->f)
+ return item1->token < item2->token;
+ else if (!item1->f)
+ return (FALSE == downloadsSortAscending)?0:1;
+ else //if (!item2->f)
+ return (FALSE == downloadsSortAscending)?1:0;
+}
+
+bool COL_SIZE_Sort(const DownloadListItem* item1, const DownloadListItem* item2)
+{
+ if (item1->f && item2->f)
+ return item1->f->totalSize < item2->f->totalSize;
+ else if (!item1->f && !item2->f)
+ return item1->token < item2->token;
+ else if (!item1->f)
+ return (FALSE == downloadsSortAscending)?0:1;
+ else //if (!item2->f)
+ return (FALSE == downloadsSortAscending)?1:0;
+}
+
+static BOOL Downloads_SortItems(int sortColumn)
+{
+ AutoLock lock (downloadedFiles);
+ switch (sortColumn)
+ {
+ case COL_TITLE:
+ std::sort(listContents.begin(), listContents.end(), COL_TITLE_Sort);
+ if (FALSE == downloadsSortAscending) std::reverse(listContents.begin(), listContents.end());
+ return TRUE;
+ case COL_PROGRESS:
+ std::sort(listContents.begin(), listContents.end(), COL_PROGRESS_Sort);
+ if (FALSE == downloadsSortAscending) std::reverse(listContents.begin(), listContents.end());
+ return TRUE;
+ case COL_DATE:
+ std::sort(listContents.begin(), listContents.end(), COL_DATE_Sort);
+ if (FALSE == downloadsSortAscending) std::reverse(listContents.begin(), listContents.end());
+ return TRUE;
+ case COL_SOURCE:
+ std::sort(listContents.begin(), listContents.end(), COL_SOURCE_Sort);
+ if (FALSE == downloadsSortAscending) std::reverse(listContents.begin(), listContents.end());
+ return TRUE;
+ case COL_SIZE:
+ std::sort(listContents.begin(), listContents.end(), COL_SIZE_Sort);
+ if (FALSE == downloadsSortAscending) std::reverse(listContents.begin(), listContents.end());
+ return TRUE;
+ case COL_PATH:
+ std::sort(listContents.begin(), listContents.end(), COL_PATH_Sort);
+ if (FALSE == downloadsSortAscending) std::reverse(listContents.begin(), listContents.end());
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void Downloads_SetListSortColumn(HWND hwnd, INT listId, INT index, BOOL fAscending)
+{
+ HWND hItems = GetDlgItem(hwnd, listId);
+ if (NULL == hItems) return;
+
+ HWND hHeader = (HWND)SNDMSG(hItems, LVM_GETHEADER, 0, 0L);
+ if (NULL == hHeader) return;
+
+ HDITEM item;
+ item.mask = HDI_FORMAT;
+ // reset first (ml req)
+ INT count = (INT)SNDMSG(hHeader, HDM_GETITEMCOUNT, 0, 0L);
+ for (INT i = 0; i < count; i++)
+ {
+ if (index != i && FALSE != (BOOL)SNDMSG(hHeader, HDM_GETITEM, i, (LPARAM)&item))
+ {
+ if (0 != ((HDF_SORTUP | HDF_SORTDOWN) & item.fmt))
+ {
+ item.fmt &= ~(HDF_SORTUP | HDF_SORTDOWN);
+ SNDMSG(hHeader, HDM_SETITEM, i, (LPARAM)&item);
+ }
+ }
+ }
+
+ if (FALSE != (BOOL)SNDMSG(hHeader, HDM_GETITEM, index, (LPARAM)&item))
+ {
+ INT fmt = item.fmt & ~(HDF_SORTUP | HDF_SORTDOWN);
+ fmt |= (FALSE == fAscending) ? HDF_SORTDOWN : HDF_SORTUP;
+ if (fmt != item.fmt)
+ {
+ item.fmt = fmt;
+ SNDMSG(hHeader, HDM_SETITEM, index, (LPARAM)&item);
+ }
+ }
+}
+
+static BOOL Downloads_Sort(HWND hwnd, INT iColumn, bool fAscending)
+{
+ BOOL result = TRUE;
+ downloadsSortAscending = fAscending;
+ Downloads_SortItems(iColumn);
+ Downloads_SetListSortColumn(hwnd, IDC_DOWNLOADLIST, iColumn, fAscending);
+
+ if (FALSE != result)
+ {
+ HWND hItems = GetDlgItem(hwnd, IDC_DOWNLOADLIST);
+ if (NULL != hItems)
+ InvalidateRect(hItems, NULL, TRUE);
+ }
+
+ return TRUE;
+}
+
+void Downloads_UpdateButtonText(HWND hwndDlg, int _enqueuedef)
+{
+ if (groupBtn)
+ {
+ switch(_enqueuedef)
+ {
+ case 1:
+ SetDlgItemTextW(hwndDlg, IDC_PLAY, view.enqueue);
+ customAllowed = FALSE;
+ break;
+
+ default:
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK_IN_USE, (INT_PTR)_enqueuedef, 0, 0};
+
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ SetDlgItemTextW(hwndDlg, IDC_PLAY, pszTextW);
+ customAllowed = TRUE;
+ }
+ else
+ {
+ SetDlgItemTextW(hwndDlg, IDC_PLAY, view.play);
+ customAllowed = FALSE;
+ }
+ break;
+ }
+ }
+}
+
+static void Downloads_ManageButtons( HWND hwndDlg )
+{
+ int has_selection = downloadList.GetSelectedCount();
+
+ const int buttonids[] = { IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, IDC_REMOVE };
+ for ( size_t i = 0; i != sizeof( buttonids ) / sizeof( buttonids[ 0 ] ); i++ )
+ {
+ HWND controlHWND = GetDlgItem( hwndDlg, buttonids[ i ] );
+ EnableWindow( controlHWND, has_selection );
+ }
+}
+
+void Downloads_Init(HWND hwndDlg)
+{
+ HWND hLibrary = plugin.hwndLibraryParent;
+ downloads_window = hwndDlg;
+
+ if (!view.play)
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view);
+ }
+
+ HACCEL accel = WASABI_API_LOADACCELERATORSW(IDR_VIEW_DOWNLOAD_ACCELERATORS);
+ if (accel)
+ WASABI_API_APP->app_addAccelerators(hwndDlg, &accel, 1, TRANSLATE_MODE_CHILD);
+
+ g_context_menus2 = WASABI_API_LOADMENU(IDR_MENU1);
+ groupBtn = ML_GROUPBTN_VAL();
+ enqueuedef = (ML_ENQDEF_VAL() == 1);
+
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK, (INT_PTR)hwndDlg, (INT_PTR)MAKELONG(IDC_CUSTOM, IDC_ENQUEUE), (INT_PTR)L"ml_downloads"};
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ customAllowed = TRUE;
+ SetDlgItemTextW(hwndDlg, IDC_CUSTOM, pszTextW);
+ }
+ else
+ customAllowed = FALSE;
+
+ MLSkinWindow2(hLibrary, hwndDlg, SKINNEDWND_TYPE_AUTO, SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+
+ const INT szControls[] = {IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM};
+ DownloadsDialog_SkinControls(hwndDlg, szControls, ARRAYSIZE(szControls), SKINNEDWND_TYPE_AUTO,
+ SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | (groupBtn ? SWBS_SPLITBUTTON : 0));
+
+ const INT szControlz[] = {IDC_REMOVE, IDC_CLEANUP, IDC_STATUS};
+ DownloadsDialog_SkinControls(hwndDlg, szControlz, ARRAYSIZE(szControlz), SKINNEDWND_TYPE_AUTO,
+ SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+
+ DownloadDialog_InitializeList(hwndDlg);
+ Downloads_UpdateStatusBar(hwndDlg);
+
+ downloadList.setwnd(GetDlgItem(hwndDlg, IDC_DOWNLOADLIST));
+ downloadList.AddCol(WASABI_API_LNGSTRINGW(IDS_TITLE), downloadsTitleWidth);
+ downloadList.AddCol(WASABI_API_LNGSTRINGW(IDS_PROGRESS), downloadsProgressWidth);
+ downloadList.AddCol(WASABI_API_LNGSTRINGW(IDS_DATE), downloadsDateWidth);
+ downloadList.AddCol(WASABI_API_LNGSTRINGW(IDS_SOURCE), downloadsSourceWidth);
+ downloadList.AddCol(WASABI_API_LNGSTRINGW(IDS_SIZE), downloadsSizeWidth);
+ downloadList.AddCol(WASABI_API_LNGSTRINGW(IDS_PATH), downloadsPathWidth);
+
+ DownloadsUpdated();
+
+ downloadList.SetVirtualCount((int)listContents.size());
+ Downloads_UpdateButtonText(hwndDlg, enqueuedef == 1);
+ Downloads_ManageButtons(hwndDlg);
+ Downloads_DisplayChange(hwndDlg);
+ Downloads_Sort(hwndDlg, downloadsItemSort, downloadsSortAscending);
+ SetTimer(hwndDlg, DOWNLOADSDIALOG_TIMER_UPDATESTATUSBAR , 1000, 0);
+}
+
+void Downloads_Timer( HWND hwndDlg, UINT timerId )
+{
+ switch ( timerId )
+ {
+ case DOWNLOADSDIALOG_TIMER_UPDATESTATUSBAR:
+ Downloads_UpdateStatusBar( hwndDlg );
+ {
+ AutoLock lock( downloadStatus.statusLock );
+ for ( DownloadListItem *l_content : listContents )
+ {
+ if ( l_content->token )
+ {
+ size_t d = downloadStatus.downloads[ l_content->token ].downloaded;
+ size_t s = downloadStatus.downloads[ l_content->token ].maxSize;
+
+ if ( s )
+ StringCchPrintf( l_content->status, 20, WASABI_API_LNGSTRINGW( IDS_DOWNLOADING_PERCENT ), (int)( d / ( s / 100 ) ) );
+ else
+ {
+ if ( WAC_API_DOWNLOADMANAGER->IsPending( l_content->token ) )
+ WASABI_API_LNGSTRINGW_BUF( IDS_DOWNLOAD_PENDING, l_content->status, 20 );
+ else
+ WASABI_API_LNGSTRINGW_BUF( IDS_DOWNLOADING, l_content->status, 20 );
+ }
+
+ PostMessage( downloadList.getwnd(), LVM_REDRAWITEMS, 0, listContents.size() );
+ }
+ }
+ }
+ break;
+ }
+}
+
+static INT Downloads_GetListSortColumn(HWND hwnd, INT listId, bool *fAscending)
+{
+ HWND hItems = GetDlgItem(hwnd, listId);
+ if (NULL != hItems)
+ {
+ HWND hHeader = (HWND)SNDMSG(hItems, LVM_GETHEADER, 0, 0L);
+ if (NULL != hHeader)
+ {
+ HDITEM item;
+ item.mask = HDI_FORMAT;
+
+ INT count = (INT)SNDMSG(hHeader, HDM_GETITEMCOUNT, 0, 0L);
+ for (INT i = 0; i < count; i++)
+ {
+ if (FALSE != (BOOL)SNDMSG(hHeader, HDM_GETITEM, i, (LPARAM)&item) &&
+ 0 != ((HDF_SORTUP | HDF_SORTDOWN) & item.fmt))
+ {
+ if (NULL != fAscending)
+ {
+ *fAscending = (0 != (HDF_SORTUP & item.fmt));
+ }
+ return i;
+ }
+ }
+ }
+ }
+ return -1;
+}
+
+void Downloads_Destroy( HWND hwndDlg )
+{
+ downloads_window = 0;
+ downloadsSourceWidth = downloadList.GetColumnWidth( COL_SOURCE );
+ downloadsTitleWidth = downloadList.GetColumnWidth( COL_TITLE );
+ downloadsProgressWidth = downloadList.GetColumnWidth( COL_PROGRESS );
+ downloadsPathWidth = downloadList.GetColumnWidth( COL_PATH );
+ downloadsDateWidth = downloadList.GetColumnWidth( COL_DATE );
+ downloadsSizeWidth = downloadList.GetColumnWidth( COL_SIZE );
+
+ for ( DownloadListItem *l_content : listContents )
+ delete l_content;
+
+ listContents.clear();
+
+ downloadList.setwnd( NULL );
+
+ bool fAscending;
+ downloadsItemSort = Downloads_GetListSortColumn( hwndDlg, IDC_DOWNLOADLIST, &fAscending );
+ downloadsSortAscending = ( -1 != downloadsItemSort ) ? ( FALSE != fAscending ) : true;
+
+ int activeDownloads = 0;
+ int historyDownloads = 0;
+ {
+ Nullsoft::Utility::AutoLock historylock( downloadedFiles.downloadedLock );
+ Nullsoft::Utility::AutoLock statuslock( downloadStatus.statusLock );
+ historyDownloads = (int)downloadedFiles.downloadList.size();
+ activeDownloads = (int)downloadStatus.downloads.size();
+ }
+
+ if ( !activeDownloads && !historyDownloads && !no_auto_hide )
+ {
+ HNAVITEM hItem = MLNavCtrl_FindItemById( plugin.hwndLibraryParent, downloads_treeItem );
+ if ( hItem )
+ {
+ MLNavCtrl_DeleteItem( plugin.hwndLibraryParent, hItem );
+ downloads_treeItem = 0;
+ }
+ }
+}
+
+void Downloads_Remove( bool del = false, HWND parent = NULL )
+{
+ int d = -1;
+ int r = 0;
+ while ( GetDownload( d ) )
+ {
+ int download = d - r;
+ DownloadListItem *item = listContents[ download ];
+ if ( item->f )
+ {
+ AutoLock lock( downloadedFiles );
+ int j = 0;
+ for ( DownloadList::iterator i = downloadedFiles.begin(); i != downloadedFiles.end(); ++i )
+ {
+ if ( !_wcsicmp( i->path, item->f->path ) )
+ {
+ if ( del )
+ {
+ if ( !downloadedFiles.RemoveAndDelete( j ) )
+ MessageBox( parent, WASABI_API_LNGSTRINGW( IDS_DELETEFAILED ), downloadedFiles.downloadList[ j ].path, 0 );
+ }
+ else
+ downloadedFiles.Remove( j );
+
+ delete item;
+ listContents.erase( listContents.begin() + download );
+ r++;
+ dirty++;
+
+ break;
+ }
+
+ ++j;
+ }
+ }
+ else if ( item->token )
+ {
+ AutoLock lock( downloadStatus.statusLock );
+ downloadStatus.downloads[ item->token ].killswitch = 1;
+ delete item;
+ listContents.erase( listContents.begin() + download );
+ r++;
+ }
+ else
+ {
+ delete item;
+ listContents.erase( listContents.begin() + download );
+ r++;
+ }
+ }
+
+ downloadList.SetVirtualCountAsync( (int)listContents.size() );
+ downloadList.UnselectAll();
+ // Navigation_ShowService(SERVICE_DOWNLOADS, SHOWMODE_AUTO);
+}
+
+void Downloads_Delete( HWND parent )
+{
+ wchar_t message[ 256 ] = { 0 };
+ int c = downloadList.GetSelectedCount();
+
+ if ( !c )
+ return;
+ else if ( c == 1 )
+ WASABI_API_LNGSTRINGW_BUF( IDS_PERM_DELETE_ARE_YOU_SURE, message, 256 );
+ else
+ StringCchPrintf( message, 256, WASABI_API_LNGSTRINGW( IDS_PERM_DELETE_THESE_ARE_YOU_SURE ), c );
+
+ if ( MessageBox( NULL, message, WASABI_API_LNGSTRINGW( IDS_DELETION ), MB_ICONWARNING | MB_YESNO ) == IDNO )
+ return;
+
+ Downloads_Remove( true, parent );
+}
+
+void Downloads_CleanUp(HWND hwndDlg)
+{
+ wchar_t titleStr[64] = {0};
+ if ( MessageBox( hwndDlg, WASABI_API_LNGSTRINGW( IDS_CLEAR_ALL_FINISHED_DOWNLOADS ), WASABI_API_LNGSTRINGW_BUF( IDS_CLEAN_UP_LIST, titleStr, 64 ), MB_ICONWARNING | MB_YESNO ) == IDNO )
+ return;
+
+ {
+ AutoLock lock( downloadedFiles );
+ downloadedFiles.downloadList.clear();
+ }
+
+ dirty++;
+
+ DownloadsUpdated();
+}
+
+void Downloads_InfoBox( HWND parent )
+{
+ int download = -1;
+ if ( GetDownload( download ) )
+ {
+ const wchar_t *fn;
+ if ( listContents[ download ]->f )
+ fn = listContents[ download ]->f->path;
+ else
+ fn = listContents[ download ]->path;
+
+ if ( fn )
+ {
+ infoBoxParamW p = { parent, fn };
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&p, IPC_INFOBOXW );
+ }
+ }
+}
+
+void Downloads_SelectAll()
+{
+ int l = downloadList.GetCount();
+ for ( int i = 0; i < l; i++ )
+ downloadList.SetSelected( i );
+}
+
+static void exploreItemFolder( HWND hwndDlg )
+{
+ if ( downloadList.GetSelectionMark() >= 0 )
+ {
+ int download = -1;
+ while ( GetDownload( download ) )
+ {
+ wchar_t *file;
+ if ( listContents[ download ]->f )
+ file = listContents[ download ]->f->path;
+ else
+ file = listContents[ download ]->path;
+
+ WASABI_API_EXPLORERFINDFILE->AddFile( file );
+ }
+ WASABI_API_EXPLORERFINDFILE->ShowFiles();
+ }
+}
+
+void Downloads_Cancel()
+{
+ int l_selected_count = downloadList.GetSelectedCount();
+ for ( int i = -1; i < l_selected_count; ++i )
+ {
+ if ( GetDownload( i ) )
+ {
+ dirty++;
+ if ( !listContents[ i ]->f )
+ WAC_API_DOWNLOADMANAGER->CancelDownload( listContents[ i ]->token );
+
+ break; // Workaround for 5.9.1 to avoid crash if cancel of many downloads in same time
+ }
+ }
+}
+
+int we_are_drag_and_dropping = 0;
+
+static void Downloads_OnColumnClick(HWND hwnd, NMLISTVIEW *plv)
+{
+ bool fAscending;
+ INT iSort = Downloads_GetListSortColumn(hwnd, IDC_DOWNLOADLIST, &fAscending);
+ fAscending = (-1 != iSort && iSort == plv->iSubItem) ? (!fAscending) : true;
+ Downloads_Sort(hwnd, plv->iSubItem, fAscending);
+}
+
+LRESULT DownloadList_Notify( LPNMHDR l, HWND hwndDlg )
+{
+ switch ( l->code )
+ {
+ case LVN_COLUMNCLICK:
+ Downloads_OnColumnClick( hwndDlg, (NMLISTVIEW *)l );
+ break;
+ case NM_DBLCLK:
+ Downloads_Play( ( ( !!( GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) ) ^ ML_ENQDEF_VAL() ) );
+ break;
+ case LVN_BEGINDRAG:
+ we_are_drag_and_dropping = 1;
+ SetCapture( hwndDlg );
+ break;
+ case LVN_ITEMCHANGED:
+ Downloads_ManageButtons( hwndDlg );
+ break;
+ case LVN_GETDISPINFO:
+ NMLVDISPINFO *lpdi = (NMLVDISPINFO *)l;
+ size_t item = lpdi->item.iItem;
+
+ if ( item < 0 || item >= listContents.size() )
+ return 0;
+
+ //if (FALSE == downloadsSortAscending) item = listContents.size() - item - 1;
+
+ DownloadListItem *l = listContents[ item ];
+
+ if ( lpdi->item.mask & LVIF_TEXT )
+ {
+ lpdi->item.pszText[ 0 ] = 0;
+ switch ( lpdi->item.iSubItem )
+ {
+ case COL_TITLE:
+ if ( !l->token && l->f )
+ {
+ wchar_t *l_title = L"";
+
+ if ( l->f->title != NULL )
+ l_title = l->f->title;
+
+ lstrcpyn( lpdi->item.pszText, l_title, lpdi->item.cchTextMax );
+ }
+ else
+ {
+ if ( l->title ) lstrcpyn( lpdi->item.pszText, l->title, lpdi->item.cchTextMax );
+ }
+ break;
+ case COL_PROGRESS:
+ if ( !l->token && l->f )
+ {
+ switch ( l->f->downloadStatus )
+ {
+ case DownloadedFile::DOWNLOAD_SUCCESS:
+ WASABI_API_LNGSTRINGW_BUF( IDS_DOWNLOAD_SUCCESS, lpdi->item.pszText, lpdi->item.cchTextMax );
+ break;
+ case DownloadedFile::DOWNLOAD_FAILURE:
+ WASABI_API_LNGSTRINGW_BUF( IDS_DOWNLOAD_FAILURE, lpdi->item.pszText, lpdi->item.cchTextMax );
+ break;
+ case DownloadedFile::DOWNLOAD_CANCELED:
+ WASABI_API_LNGSTRINGW_BUF( IDS_DOWNLOAD_CANCELED, lpdi->item.pszText, lpdi->item.cchTextMax );
+ break;
+ }
+ }
+ else lstrcpyn( lpdi->item.pszText, l->status, lpdi->item.cchTextMax );
+ break;
+ case COL_DATE:
+ {
+ if ( !l->token && l->f && l->f->downloadDate )
+ {
+ wchar_t tmp[ 128 ] = { 0 };
+ MakeDateString( l->f->downloadDate, tmp, 128 );
+ lstrcpyn( lpdi->item.pszText, tmp, lpdi->item.cchTextMax );
+ }
+ else
+ {
+ WASABI_API_LNGSTRINGW_BUF( IDS_N_A, lpdi->item.pszText, lpdi->item.cchTextMax );
+ }
+ break;
+ }
+ case COL_SOURCE:
+ if ( !l->token && l->f )
+ {
+ wchar_t *l_source = L"";
+
+ if ( l->f->source != NULL )
+ l_source = l->f->source;
+
+ lstrcpyn( lpdi->item.pszText, l_source, lpdi->item.cchTextMax );
+ }
+ else
+ lstrcpyn( lpdi->item.pszText, l->source, lpdi->item.cchTextMax );
+ break;
+ case COL_SIZE:
+ {
+ if ( !l->token && l->f && l->f->totalSize > 0 )
+ WASABI_API_LNG->FormattedSizeString( lpdi->item.pszText, lpdi->item.cchTextMax, l->f->totalSize );
+ else
+ WASABI_API_LNGSTRINGW_BUF( IDS_N_A, lpdi->item.pszText, lpdi->item.cchTextMax );
+ break;
+ }
+ case COL_PATH:
+ if ( !l->token && l->f )
+ {
+ wchar_t *l_path = L"";
+
+ if ( l->f->path != NULL )
+ l_path = l->f->path;
+
+ lstrcpyn( lpdi->item.pszText, l_path, lpdi->item.cchTextMax );
+ }
+ else
+ {
+ if ( l->path )
+ lstrcpyn( lpdi->item.pszText, l->path, lpdi->item.cchTextMax );
+ }
+
+ break;
+ }
+ }
+
+ break;
+ }
+
+ return 0;
+}
+
+void listbuild( wchar_t **buf, int &buf_size, int &buf_pos, const wchar_t *tbuf )
+{
+ if ( !*buf )
+ {
+ *buf = (wchar_t *)calloc( 4096, sizeof( wchar_t ) );
+ if ( *buf )
+ {
+ buf_size = 4096;
+ buf_pos = 0;
+ }
+ else
+ {
+ buf_size = buf_pos = 0;
+ }
+ }
+ int newsize = buf_pos + lstrlenW( tbuf ) + 1;
+ if ( newsize < buf_size )
+ {
+ size_t old_buf_size = buf_size;
+ buf_size = newsize + 4096;
+ wchar_t *new_buf = (wchar_t *)realloc( *buf, ( buf_size + 1 ) * sizeof( wchar_t ) );
+ if ( new_buf )
+ {
+ *buf = new_buf;
+ }
+ else
+ {
+ new_buf = (wchar_t*)calloc( ( buf_size + 1 ), sizeof( wchar_t ) );
+ if ( new_buf )
+ {
+ memcpy( new_buf, *buf, ( old_buf_size * sizeof( wchar_t ) ) );
+ free( *buf );
+ *buf = new_buf;
+ }
+ else buf_size = (int)old_buf_size;
+ }
+ }
+
+ StringCchCopyW( *buf + buf_pos, buf_size, tbuf );
+ buf_pos = newsize;
+}
+
+wchar_t *getSelectedList()
+{
+ wchar_t *path = NULL;
+ int buf_pos = 0;
+ int buf_size = 0;
+ int download = -1;
+
+ while ( GetDownload( download ) )
+ {
+ if ( listContents[ download ]->f )
+ listbuild( &path, buf_size, buf_pos, listContents[ download ]->f->path );
+ }
+
+ if ( path )
+ path[ buf_pos ] = 0;
+
+ return path;
+}
+
+static void SwapPlayEnqueueInMenu( HMENU listMenu )
+{
+ int playPos = -1, enqueuePos = -1;
+ MENUITEMINFOW playItem = { sizeof( MENUITEMINFOW ), 0, }, enqueueItem = { sizeof( MENUITEMINFOW ), 0, };
+
+ int numItems = GetMenuItemCount( listMenu );
+
+ for ( int i = 0; i < numItems; i++ )
+ {
+ UINT id = GetMenuItemID( listMenu, i );
+ if ( id == IDC_PLAY )
+ {
+ playItem.fMask = MIIM_ID;
+ playPos = i;
+ GetMenuItemInfoW( listMenu, i, TRUE, &playItem );
+ }
+ else if ( id == IDC_ENQUEUE )
+ {
+ enqueueItem.fMask = MIIM_ID;
+ enqueuePos = i;
+ GetMenuItemInfoW( listMenu, i, TRUE, &enqueueItem );
+ }
+ }
+
+ playItem.wID = IDC_ENQUEUE;
+ enqueueItem.wID = IDC_PLAY;
+ SetMenuItemInfoW( listMenu, playPos, TRUE, &playItem );
+ SetMenuItemInfoW( listMenu, enqueuePos, TRUE, &enqueueItem );
+}
+
+static void SyncMenuWithAccelerators( HWND hwndDlg, HMENU menu )
+{
+ HACCEL szAccel[ 24 ] = { 0 };
+ INT c = WASABI_API_APP->app_getAccelerators( hwndDlg, szAccel, sizeof( szAccel ) / sizeof( szAccel[ 0 ] ), FALSE );
+ AppendMenuShortcuts( menu, szAccel, c, MSF_REPLACE );
+}
+
+void UpdateMenuItems( HWND hwndDlg, HMENU menu )
+{
+ bool swapPlayEnqueue = false;
+ if ( ML_ENQDEF_VAL() )
+ {
+ SwapPlayEnqueueInMenu( menu );
+ swapPlayEnqueue = true;
+ }
+
+ SyncMenuWithAccelerators( hwndDlg, menu );
+ if ( swapPlayEnqueue )
+ SwapPlayEnqueueInMenu( menu );
+}
+
+static int IPC_LIBRARY_SENDTOMENU = 0;
+static librarySendToMenuStruct s = { 0 };
+
+static void DownloadList_RightClick(HWND hwndDlg, HWND listHwnd, POINTS pts)
+{
+ POINT pt;
+ POINTSTOPOINT(pt, pts);
+
+ RECT controlRect, headerRect;
+ if (FALSE == GetClientRect(listHwnd, &controlRect))
+ SetRectEmpty(&controlRect);
+ else
+ MapWindowPoints(listHwnd, HWND_DESKTOP, (POINT*)&controlRect, 2);
+
+ if ( -1 == pt.x && -1 == pt.y )
+ {
+ RECT itemRect;
+ int selected = downloadList.GetNextSelected();
+ if ( selected != -1 ) // if something is selected we'll drop the menu from there
+ {
+ downloadList.GetItemRect( selected, &itemRect );
+ ClientToScreen( listHwnd, (POINT *)&itemRect );
+ }
+ else // otherwise we'll drop it from the top-left corner of the listview, adjusting for the header location
+ {
+ GetWindowRect( listHwnd, &itemRect );
+
+ HWND hHeader = (HWND)SNDMSG( listHwnd, LVM_GETHEADER, 0, 0L );
+ RECT headerRect;
+ if ( ( WS_VISIBLE & GetWindowLongPtr( hHeader, GWL_STYLE ) ) && GetWindowRect( hHeader, &headerRect ) )
+ {
+ itemRect.top += ( headerRect.bottom - headerRect.top );
+ }
+ }
+
+ pt.x = itemRect.left;
+ pt.y = itemRect.top;
+ }
+
+ HWND hHeader = (HWND)SNDMSG(listHwnd, LVM_GETHEADER, 0, 0L);
+ if ( 0 == ( WS_VISIBLE & GetWindowLongPtr( hHeader, GWL_STYLE ) ) || FALSE == GetWindowRect( hHeader, &headerRect ) )
+ {
+ SetRectEmpty( &headerRect );
+ }
+
+ if ( FALSE != PtInRect( &headerRect, pt ) )
+ {
+ return;
+ }
+
+ LVHITTESTINFO hitTest;
+ hitTest.pt = pt;
+ MapWindowPoints( HWND_DESKTOP, listHwnd, &hitTest.pt, 1 );
+
+ int index = ( downloadList.GetNextSelected() != -1 ? ListView_HitTest( listHwnd, &hitTest ) : -1 );
+
+ HMENU baseMenu = WASABI_API_LOADMENU( IDR_MENU1 );
+
+ if ( baseMenu == NULL )
+ return;
+
+ HMENU menu = GetSubMenu( baseMenu, 0 );
+ if ( menu != NULL )
+ {
+ if ( ( index == -1 ) || ( index != -1 ) && listContents[ index ]->f )
+ DeleteMenu( menu, ID_DOWNLOADS_CANCELDOWNLOAD, MF_BYCOMMAND );
+
+ UINT enableExtras = MF_ENABLED;
+ if ( ( index == -1 ) || ( index != -1 ) && !listContents[ index ]->f
+ || ( index != -1 ) && ( listContents[ index ]->f->downloadStatus != 1 ) )
+ enableExtras = ( MF_GRAYED | MF_DISABLED );
+
+ UINT enableViewExtras = MF_ENABLED;
+ if ( index == -1 )
+ enableViewExtras = ( MF_GRAYED | MF_DISABLED );
+
+ EnableMenuItem( menu, IDC_PLAY, MF_BYCOMMAND | enableExtras );
+ EnableMenuItem( menu, IDC_ENQUEUE, MF_BYCOMMAND | enableExtras );
+ EnableMenuItem( menu, IDC_REMOVE, MF_BYCOMMAND | enableViewExtras );
+ EnableMenuItem( menu, IDC_DELETE, MF_BYCOMMAND | enableExtras );
+ EnableMenuItem( menu, IDC_INFOBOX, MF_BYCOMMAND | enableExtras );
+ EnableMenuItem( menu, ID_DOWNLOADS_EXPLORERITEMFOLDER, MF_BYCOMMAND | enableExtras );
+ EnableMenuItem( menu, 2, MF_BYPOSITION | enableExtras );
+
+ { // send-to menu shit...
+ ZeroMemory( &s, sizeof( s ) );
+ IPC_LIBRARY_SENDTOMENU = (int)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, ( WPARAM ) & "LibrarySendToMenu", IPC_REGISTER_WINAMP_IPCMESSAGE );
+ if ( IPC_LIBRARY_SENDTOMENU > 65536 && SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)0, IPC_LIBRARY_SENDTOMENU ) == 0xffffffff )
+ {
+ s.mode = 1;
+ s.hwnd = hwndDlg;
+ s.data_type = ML_TYPE_FILENAMESW;
+ s.ctx[ 1 ] = 1;
+ s.build_hMenu = GetSubMenu( menu, 2 );
+ }
+ }
+
+ UpdateMenuItems( hwndDlg, menu );
+
+ int r = Menu_TrackPopup( plugin.hwndLibraryParent, menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, pt.x, pt.y, hwndDlg, NULL );
+ if ( !SendMessage( hwndDlg, WM_COMMAND, r, 0 ) )
+ {
+ s.menu_id = r; // more send to menu shit...
+ if ( s.mode == 2 && SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU ) == 0xffffffff )
+ {
+ s.mode = 3;
+ wchar_t *path = getSelectedList();
+ if ( path )
+ {
+ s.data = path;
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU );
+ free( path );
+ }
+ }
+ }
+
+ if ( s.mode )
+ { // yet more send to menu shit...
+ s.mode = 4;
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU ); // cleanup
+ }
+ }
+
+ DestroyMenu( baseMenu );
+}
+
+static void Downloads_ContextMenu( HWND hwndDlg, WPARAM wParam, LPARAM lParam )
+{
+ HWND sourceWindow = (HWND)wParam;
+ if ( sourceWindow == downloadList.getwnd() )
+ DownloadList_RightClick( hwndDlg, sourceWindow, MAKEPOINTS( lParam ) );
+}
+
+enum
+{
+ BPM_ECHO_WM_COMMAND=0x1, // send WM_COMMAND and return value
+ BPM_WM_COMMAND = 0x2, // just send WM_COMMAND
+};
+
+BOOL Downloads_ButtonPopupMenu( HWND hwndDlg, int buttonId, HMENU menu, int flags = 0 )
+{
+ RECT r;
+ HWND buttonHWND = GetDlgItem( hwndDlg, buttonId );
+ GetWindowRect( buttonHWND, &r );
+ UpdateMenuItems( hwndDlg, menu );
+ MLSkinnedButton_SetDropDownState( buttonHWND, TRUE );
+ UINT tpmFlags = TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN | TPM_LEFTALIGN;
+ if ( !( flags & BPM_WM_COMMAND ) )
+ tpmFlags |= TPM_RETURNCMD;
+ int x = Menu_TrackPopup( plugin.hwndLibraryParent, menu, tpmFlags, r.left, r.top, hwndDlg, NULL );
+ if ( ( flags & BPM_ECHO_WM_COMMAND ) && x )
+ SendMessage( hwndDlg, WM_COMMAND, MAKEWPARAM( x, 0 ), 0 );
+ MLSkinnedButton_SetDropDownState( buttonHWND, FALSE );
+ return x;
+}
+
+static void Downloads_Play( HWND hwndDlg, HWND from, UINT idFrom )
+{
+ HMENU listMenu = GetSubMenu( g_context_menus2, 0 );
+ int count = GetMenuItemCount( listMenu );
+ if ( count > 2 )
+ {
+ for ( int i = 2; i < count; i++ )
+ {
+ DeleteMenu( listMenu, 2, MF_BYPOSITION );
+ }
+ }
+
+ Downloads_ButtonPopupMenu( hwndDlg, idFrom, listMenu, BPM_WM_COMMAND );
+}
+
+static BOOL WINAPI DownloadDialog_DlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITMENUPOPUP: // yet yet more send to menu shit...
+ if (wParam && (HMENU)wParam == s.build_hMenu && s.mode==1)
+ {
+ if (SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&s,IPC_LIBRARY_SENDTOMENU)==0xffffffff)
+ s.mode=2;
+ }
+ break;
+
+ case WM_USER+543:
+ Navigation_Update();
+ break;
+
+ case WM_CONTEXTMENU:
+ Downloads_ContextMenu(hwndDlg, wParam, lParam);
+ return TRUE;
+
+ case WM_NOTIFYFORMAT:
+ return NFR_UNICODE;
+
+ case WM_INITDIALOG:
+ Downloads_Init(hwndDlg);
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l = (LPNMHDR)lParam;
+ if (l->idFrom == IDC_DOWNLOADLIST)
+ return (BOOL)DownloadList_Notify(l,hwndDlg);
+ }
+ break;
+
+ case WM_DESTROY:
+ Downloads_Destroy(hwndDlg);
+ return 0;
+
+ case WM_DISPLAYCHANGE:
+ Downloads_DisplayChange(hwndDlg);
+ return 0;
+
+ case WM_TIMER:
+ Downloads_Timer(hwndDlg, (UINT)wParam);
+ break;
+
+ case WM_MOUSEMOVE:
+ if (we_are_drag_and_dropping && GetCapture() == hwndDlg)
+ {
+ POINT p = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
+ ClientToScreen(hwndDlg, &p);
+ mlDropItemStruct m;
+ ZeroMemory(&m, sizeof(mlDropItemStruct));
+ m.type = ML_TYPE_FILENAMESW;
+ m.p = p;
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&m,ML_IPC_HANDLEDRAG);
+ }
+ break;
+
+ case WM_LBUTTONUP:
+ if (we_are_drag_and_dropping && GetCapture() == hwndDlg)
+ {
+ we_are_drag_and_dropping = 0;
+ ReleaseCapture();
+ POINT p = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
+ ClientToScreen(hwndDlg, &p);
+ mlDropItemStruct m = {0};
+ m.type = ML_TYPE_FILENAMESW;
+ m.p = p;
+ m.flags = ML_HANDLEDRAG_FLAG_NOCURSOR;
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&m,ML_IPC_HANDLEDRAG);
+ if (m.result > 0)
+ {
+ m.flags = 0;
+ m.result = 0;
+ wchar_t* path = getSelectedList();
+ if(path)
+ {
+ m.data = path;
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&m,ML_IPC_HANDLEDROP);
+ free(path);
+ }
+ }
+ }
+ break;
+
+ case WM_PAINT:
+ {
+ int tab[] = { IDC_DOWNLOADLIST|DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwndDlg, tab, sizeof(tab) / sizeof(tab[0]));
+ }
+ return 0;
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags));
+ }
+ return 0;
+
+ case WM_USER + 0x200:
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, 1); // yes, we support no - redraw resize
+ return TRUE;
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+
+ case WM_APP + 104:
+ {
+ Downloads_UpdateButtonText(hwndDlg, (int)wParam);
+ LayoutWindows(hwndDlg, TRUE);
+ return 0;
+ }
+
+ case WM_COMMAND:
+ switch ( LOWORD( wParam ) )
+ {
+ case IDC_PLAY:
+ case IDC_ENQUEUE:
+ case IDC_CUSTOM:
+ if ( HIWORD( wParam ) == MLBN_DROPDOWN )
+ {
+ Downloads_Play( hwndDlg, (HWND)lParam, LOWORD( wParam ) );
+ }
+ else
+ {
+ bool action;
+ if ( LOWORD( wParam ) == IDC_PLAY )
+ {
+ action = ( HIWORD( wParam ) == 1 ) ? ML_ENQDEF_VAL() == 1 : 0;
+ }
+ else if ( LOWORD( wParam ) == IDC_ENQUEUE )
+ {
+ action = ( HIWORD( wParam ) == 1 ) ? ML_ENQDEF_VAL() != 1 : 1;
+ }
+ else
+ break;
+
+ Downloads_Play( action );
+ }
+ break;
+ case IDC_REMOVE:
+ Downloads_Remove();
+ break;
+ case IDC_DELETE:
+ Downloads_Delete( hwndDlg );
+ break;
+ case IDC_CLEANUP:
+ Downloads_CleanUp( hwndDlg );
+ break;
+ case IDC_INFOBOX:
+ Downloads_InfoBox( hwndDlg );
+ break;
+ case IDC_SELECTALL:
+ Downloads_SelectAll();
+ break;
+ case ID_DOWNLOADS_EXPLORERITEMFOLDER:
+ exploreItemFolder( hwndDlg );
+ break;
+ case ID_DOWNLOADS_CANCELDOWNLOAD:
+ Downloads_Cancel();
+ break;
+ default:
+ return 0;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+HWND CALLBACK DownloadDialog_Create( HWND hParent )
+{
+ return WASABI_API_CREATEDIALOGPARAMW( IDD_DOWNLOADS, hParent, DownloadDialog_DlgProc, 0 );
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/DownloadsDialog.h b/Src/Plugins/Library/ml_downloads/DownloadsDialog.h
new file mode 100644
index 00000000..080f6bb6
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadsDialog.h
@@ -0,0 +1,11 @@
+#ifndef NULLSOFT_DOWNLOADSDIALOGH
+#define NULLSOFT_DOWNLOADSDIALOGH
+
+#include "DownloadStatus.h"
+#include "Downloaded.h"
+
+void DownloadsUpdated();
+void DownloadsUpdated( DownloadToken token, const DownloadedFile *f );
+void DownloadsUpdated( const DownloadStatus::Status &s, DownloadToken token );
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/DownloadsParse.cpp b/Src/Plugins/Library/ml_downloads/DownloadsParse.cpp
new file mode 100644
index 00000000..627fb52b
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadsParse.cpp
@@ -0,0 +1,179 @@
+#include "Main.h"
+#include "DownloadsParse.h"
+#include "Downloaded.h"
+#include "Defaults.h"
+#include "ParseUtil.h"
+#include <wchar.h>
+#include <locale.h>
+
+static __time64_t filetime( const wchar_t *file )
+{
+ if ( !file || !*file )
+ return 0;
+
+ WIN32_FIND_DATAW f = { 0 };
+
+ HANDLE h = FindFirstFileW( file, &f );
+ if ( h == INVALID_HANDLE_VALUE )
+ {
+ /*wchar_t tmp[128] = {0};
+ wsprintf(tmp,L"%d",GetLastError());
+ MessageBox(0,file,tmp,0);*/
+ return 0;
+ }
+
+ FindClose( h );
+ SYSTEMTIME s = { 0 };
+ FileTimeToSystemTime( &f.ftCreationTime, &s );
+
+ tm t = { 0 };
+ t.tm_year = s.wYear - 1900;
+ t.tm_mon = s.wMonth - 1;
+ t.tm_mday = s.wDay;
+ t.tm_hour = s.wHour;
+ t.tm_min = s.wMinute;
+ t.tm_sec = s.wMinute;
+ __time64_t ret = _mktime64(&t);
+ /*wchar_t tmp[128] = {0};
+ wsprintf(tmp,L"%d",ret);
+ MessageBox(0,tmp,0,0);*/
+
+ return ret;
+}
+
+size_t GetFileSize(LPCWSTR path)
+{
+ WIN32_FIND_DATAW data = {0};
+ if (path && *path)
+ {
+ HANDLE h = FindFirstFileW(path, &data);
+ if (h == INVALID_HANDLE_VALUE)
+ return -1;
+ FindClose(h);
+ }
+ return data.nFileSizeLow/* | (__int64)data.nFileSizeHigh << 32*/;
+}
+
+static void ReadDownload( const XMLNode *item )
+{
+ DownloadedFile newDownloaded;
+
+ const wchar_t *source = GetContent( item, L"source" );
+ if ( !source ) source = GetContent( item, L"channel" );
+ newDownloaded.SetSource( source );
+
+ const wchar_t *title = GetContent( item, L"title" );
+ if ( !title ) title = GetContent( item, L"item" );;
+ newDownloaded.SetTitle( title );
+
+ const wchar_t *url = GetContent( item, L"url" );
+ newDownloaded.SetURL( url );
+
+ const wchar_t *path = GetContent( item, L"path" );
+ newDownloaded.SetPath( path );
+
+ // TODO ideally should be able to cope with __int64
+ // but the db is setup for int currently...
+ const wchar_t *size = GetContent( item, L"size" );
+ if ( size && size[ 0 ] )
+ {
+ size_t val = _wtoi( size );
+ if ( val > 0 ) newDownloaded.totalSize = val;
+ else if ( !val )
+ {
+ val = GetFileSize( path );
+ if ( val > 0 ) newDownloaded.totalSize = val;
+ else newDownloaded.totalSize = -1;
+ dirty++;
+ }
+ }
+ else
+ {
+ size_t val = GetFileSize( path );
+ if ( val > 0 ) newDownloaded.totalSize = val;
+ else newDownloaded.totalSize = -1;
+ dirty++;
+ }
+
+ const wchar_t *downloadDate = GetContent( item, L"downloadDate" );
+ if ( downloadDate && downloadDate[ 0 ] )
+ {
+ __time64_t val = (__time64_t)_wtoi( downloadDate );
+ if ( time > 0 ) newDownloaded.downloadDate = val;
+ }
+ else
+ {
+ __time64_t val = filetime( newDownloaded.path );
+ if ( time > 0 ) newDownloaded.downloadDate = val;
+ }
+
+ const wchar_t *status = GetContent( item, L"downloadStatus" );
+ if ( status && status[ 0 ] )
+ {
+ newDownloaded.downloadStatus = _wtoi( status );
+ }
+ else
+ {
+ newDownloaded.downloadStatus = DownloadedFile::DOWNLOAD_SUCCESS;
+ }
+
+ downloadedFiles.downloadList.push_back( newDownloaded );
+}
+
+void DownloadsParse::ReadNodes( const wchar_t *url )
+{
+ XMLNode::NodeList::const_iterator itr;
+ const XMLNode *curNode = xmlDOM.GetRoot();
+
+ if ( curNode->Present( L"winamp:preferences" ) )
+ curNode = curNode->Get( L"winamp:preferences" );
+
+ curNode = curNode->Get( L"downloads" );
+ if ( curNode )
+ {
+ const wchar_t *prop = curNode->GetProperty( L"downloadsTitleWidth" );
+ if ( prop && prop[ 0 ] )
+ downloadsTitleWidth = _wtoi( prop );
+ if ( downloadsTitleWidth <= 0 )
+ downloadsTitleWidth = DOWNLOADSTITLEWIDTHDEFAULT;
+
+ prop = curNode->GetProperty( L"downloadsProgressWidth" );
+ if ( prop && prop[ 0 ] )
+ downloadsProgressWidth = _wtoi( prop );
+ if ( downloadsProgressWidth <= 0 )
+ downloadsProgressWidth = DOWNLOADSPROGRESSWIDTHDEFAULT;
+
+ prop = curNode->GetProperty( L"downloadsDateWidth" );
+ if ( prop && prop[ 0 ] )
+ downloadsDateWidth = _wtoi( prop );
+ if ( downloadsDateWidth <= 0 )
+ downloadsDateWidth = DOWNLOADSDATEWIDTHDEFAULTS;
+
+ prop = curNode->GetProperty( L"downloadsSourceWidth" );
+ if ( prop && prop[ 0 ] )
+ downloadsSourceWidth = _wtoi( prop );
+ if ( downloadsSourceWidth <= 0 )
+ downloadsSourceWidth = DOWNLOADSSOURCEWIDTHDEFAULT;
+
+ prop = curNode->GetProperty( L"downloadsPathWidth" );
+ if ( prop && prop[ 0 ] )
+ downloadsPathWidth = _wtoi( prop );
+ if ( downloadsPathWidth <= 0 )
+ downloadsPathWidth = DOWNLOADSPATHWIDTHDEFAULTS;
+
+ prop = curNode->GetProperty( L"downloadsItemSort" );
+ if ( prop && prop[ 0 ] )
+ downloadsItemSort = _wtoi( prop );
+
+ prop = curNode->GetProperty( L"downloadsSortAscending" );
+ if ( prop && prop[ 0 ] )
+ downloadsSortAscending = !PropertyIsFalse( curNode, L"downloadsSortAscending" );
+
+ const XMLNode::NodeList *downloadsList = curNode->GetList( L"download" );
+ if ( downloadsList )
+ {
+ for ( itr = downloadsList->begin(); itr != downloadsList->end(); itr++ )
+ ReadDownload( *itr );
+ }
+ }
+}
diff --git a/Src/Plugins/Library/ml_downloads/DownloadsParse.h b/Src/Plugins/Library/ml_downloads/DownloadsParse.h
new file mode 100644
index 00000000..8f70ceb9
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/DownloadsParse.h
@@ -0,0 +1,13 @@
+#ifndef NULLSOFT_DOWNLOADSPARSEH
+#define NULLSOFT_DOWNLOADSPARSEH
+
+#include "DownloadThread.h"
+
+class DownloadsParse : public DownloadThread
+{
+public:
+ virtual void ReadNodes(const wchar_t *url);
+
+};
+
+#endif
diff --git a/Src/Plugins/Library/ml_downloads/Main.cpp b/Src/Plugins/Library/ml_downloads/Main.cpp
new file mode 100644
index 00000000..09a260eb
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/Main.cpp
@@ -0,0 +1,387 @@
+#include "main.h"
+#include "DownloadsDialog.h"
+#include "DownloadsParse.h"
+#include "Preferences.h"
+#include "XMLWriter.h"
+#include "..\..\General\gen_ml/ml.h"
+#include "Defaults.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+
+#include "api__ml_downloads.h"
+#include "Downloaded.h"
+#include "DownloadStatus.h"
+#include <api/service/waServiceFactory.h>
+
+#include <strsafe.h>
+
+wchar_t *ml_cfg = 0,
+ xmlFileName[1024] = {0};
+
+ATOM VIEWPROP = 0;
+
+api_downloadManager *WAC_API_DOWNLOADMANAGER = 0;
+DownloadViewCallback *downloadViewCallback = 0;
+
+static int Init();
+static void Quit();
+static INT_PTR MessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+DWORD threadStorage=TLS_OUT_OF_INDEXES;
+extern "C" winampMediaLibraryPlugin plugin =
+ {
+ MLHDR_VER,
+ "nullsoft(ml_downloads.dll)",
+ Init,
+ Quit,
+ MessageProc,
+ 0,
+ 0,
+ 0,
+ };
+
+//static prefsDlgRecW preferences;
+//static wchar_t preferencesName[64] = {0};
+int downloads_treeItem = 0,
+ podcast_parent = 0,
+ no_auto_hide = 0;
+HANDLE hMainThread;
+
+HCURSOR hDragNDropCursor;
+int winampVersion = 0, dirty = 0;
+
+api_application *applicationApi = NULL;
+api_explorerfindfile *WASABI_API_EXPLORERFINDFILE = 0;
+
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+
+int icons[4] = {-1, -1, -1, -1};
+int activeIcon = 0;
+int totalIcons = 4;
+static Nullsoft::Utility::LockGuard navigationLock;
+
+// used to get the downloads view parented to the podcast view
+#define CSTR_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
+#define NAVITEM_PREFIX L"podcast_svc_"
+#define SERVICE_PODCAST 720
+
+enum
+{
+ DOWNLOADS_TIMER_NAVNODE = 35784,
+};
+
+HNAVITEM Navigation_FindService(UINT serviceId)
+{
+ HWND hLibrary = plugin.hwndLibraryParent;
+ INT cchPrefix = ARRAYSIZE(NAVITEM_PREFIX) - 1;
+
+ WCHAR szBuffer[256] = {0};
+ NAVITEM itemInfo = {0};
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.mask = NIMF_TEXTINVARIANT;
+ itemInfo.cchInvariantMax = ARRAYSIZE(szBuffer);
+ itemInfo.pszInvariant = szBuffer;
+ itemInfo.hItem = MLNavCtrl_GetFirst(hLibrary);
+
+ while(NULL != itemInfo.hItem)
+ {
+ if (FALSE != MLNavItem_GetInfo(hLibrary, &itemInfo) &&
+ CSTR_EQUAL == CompareString(CSTR_INVARIANT, NORM_IGNORECASE, itemInfo.pszInvariant, cchPrefix,
+ NAVITEM_PREFIX, cchPrefix))
+ {
+ return itemInfo.hItem;
+ }
+ itemInfo.hItem = MLNavItem_GetNext(hLibrary, itemInfo.hItem);
+ }
+ return NULL;
+}
+
+void Navigation_Update_Icon(void)
+{
+ Nullsoft::Utility::AutoLock navlock(navigationLock);
+
+ HNAVITEM hItem = NULL;
+ if(downloads_treeItem)
+ {
+ hItem = MLNavCtrl_FindItemById(plugin.hwndLibraryParent,downloads_treeItem);
+ }
+
+ if (hItem)
+ {
+ NAVITEM item;
+ item.cbSize = sizeof(NAVITEM);
+ item.hItem = hItem;
+ item.iSelectedImage = item.iImage = icons[activeIcon];
+ activeIcon = (activeIcon + 1) % totalIcons;
+ item.mask = NIMF_IMAGE | NIMF_IMAGESEL;
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &item);
+ }
+}
+
+BOOL Navigation_Update(void)
+{
+ int activeDownloads = 0;
+ int historyDownloads = 0;
+ {
+ Nullsoft::Utility::AutoLock historylock(downloadedFiles.downloadedLock);
+ historyDownloads = (int)downloadedFiles.downloadList.size();
+ }
+
+ {
+ Nullsoft::Utility::AutoLock statuslock(downloadStatus.statusLock);
+ activeDownloads = (int)downloadStatus.downloads.size();
+ }
+
+ Nullsoft::Utility::AutoLock navlock(navigationLock);
+ HNAVITEM hItem = NULL;
+ if (downloads_treeItem)
+ {
+ hItem = MLNavCtrl_FindItemById(plugin.hwndLibraryParent,downloads_treeItem);
+ }
+
+ NAVINSERTSTRUCT nis = {0};
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.pszText = WASABI_API_LNGSTRINGW(IDS_DOWNLOADS);
+ nis.item.iSelectedImage = nis.item.iImage = icons[0];
+
+ WCHAR szName[256] = {0};
+ if (activeDownloads && SUCCEEDED(StringCchPrintf(szName, ARRAYSIZE(szName), L"%s (%u)", WASABI_API_LNGSTRINGW(IDS_DOWNLOADS), activeDownloads)))
+ nis.item.pszText = szName;
+
+ if (activeDownloads > 0 || historyDownloads > 0 || no_auto_hide == 2)
+ {
+ if (!hItem)
+ {
+ nis.item.pszInvariant = NAVITEM_UNIQUESTR;
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL;
+ if(podcast_parent) nis.hParent = Navigation_FindService(SERVICE_PODCAST);
+ NAVITEM nvItem = {sizeof(NAVITEM),0,NIMF_ITEMID,};
+
+ nvItem.hItem = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+
+ MLNavItem_GetInfo(plugin.hwndLibraryParent, &nvItem);
+ downloads_treeItem = nvItem.id;
+ }
+ else
+ {
+ nis.item.hItem = hItem;
+ nis.item.mask = NIMF_TEXT;
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &nis.item);
+ }
+ }
+ else if (hItem)
+ {
+ nis.item.hItem = hItem;
+ nis.item.mask = NIMF_TEXT | NIMF_IMAGE | NIMF_IMAGESEL;
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &nis.item);
+ }
+
+ return TRUE;
+}
+
+void CALLBACK Downloads_Nav_Timer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed)
+{
+ switch (eventId)
+ {
+ case DOWNLOADS_TIMER_NAVNODE:
+ if (downloadStatus.CurrentlyDownloading())
+ Navigation_Update_Icon();
+ break;
+ }
+}
+
+
+int Init()
+{
+ hMainThread = GetCurrentThread();
+ hDragNDropCursor = LoadCursor( GetModuleHandle( L"gen_ml.dll" ), MAKEINTRESOURCE( ML_IDC_DRAGDROP ) );
+ threadStorage = TlsAlloc();
+
+ if ( 0 == VIEWPROP )
+ {
+ VIEWPROP = GlobalAddAtom( L"Nullsoft_DownloadsView" );
+ if ( VIEWPROP == 0 )
+ return ML_INIT_FAILURE;
+ }
+
+ winampVersion = (int)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETVERSION );
+
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid( DownloadManagerGUID );
+ if ( !sf ) // no sense in continuing
+ return ML_INIT_FAILURE;
+ else
+ WAC_API_DOWNLOADMANAGER = reinterpret_cast<api_downloadManager *>( sf->getInterface() );
+
+ // loader so that we can get the localisation service api for use
+ sf = plugin.service->service_getServiceByGuid( languageApiGUID );
+ if ( sf )
+ WASABI_API_LNG = reinterpret_cast<api_language*>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( applicationApiServiceGuid );
+ if ( sf )
+ WASABI_API_APP = reinterpret_cast<api_application*>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( ExplorerFindFileApiGUID );
+ if ( sf )
+ WASABI_API_EXPLORERFINDFILE = reinterpret_cast<api_explorerfindfile*>( sf->getInterface() );
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG( plugin.hDllInstance, MlDownloadsLangGUID );
+
+ static wchar_t szDescription[ 256 ];
+ StringCchPrintf( szDescription, ARRAYSIZE( szDescription ), WASABI_API_LNGSTRINGW( IDS_PLUGIN_NAME ), PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR );
+ plugin.description = (char*)szDescription;
+
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ BuildDefaults( plugin.hwndWinampParent );
+
+ mediaLibrary.BuildPath( L"Plugins\\ml\\downloads.xml", xmlFileName, 1024 );
+ if ( PathFileExists( xmlFileName ) )
+ {
+ DownloadsParse downloader;
+ downloader.DownloadFile( xmlFileName );
+ }
+ else
+ {
+ wchar_t xmlRssFileName[ 1024 ] = { 0 };
+ mediaLibrary.BuildPath( L"Plugins\\ml\\feeds\\rss.xml", xmlRssFileName, 1024 );
+ {
+ DownloadsParse rssDownloader;
+ rssDownloader.DownloadFile( xmlRssFileName );
+ }
+ }
+
+ icons[ 0 ] = mediaLibrary.AddTreeImageBmp( IDB_TREEITEM_DOWNLOADS );
+ icons[ 1 ] = mediaLibrary.AddTreeImageBmp( IDB_TREEITEM_DOWNLOADS1 );
+ icons[ 2 ] = mediaLibrary.AddTreeImageBmp( IDB_TREEITEM_DOWNLOADS2 );
+ icons[ 3 ] = mediaLibrary.AddTreeImageBmp( IDB_TREEITEM_DOWNLOADS3 );
+
+ ml_cfg = (wchar_t *)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETMLINIFILEW );
+
+ podcast_parent = ( !!GetPrivateProfileInt( L"gen_ml_config", L"podcast_parent", 0, ml_cfg ) );
+ no_auto_hide = GetPrivateProfileInt( L"gen_ml_config", L"no_auto_hide", 0, ml_cfg );
+
+ Navigation_Update();
+
+ downloadViewCallback = new DownloadViewCallback();
+ WAC_API_DOWNLOADMANAGER->RegisterStatusCallback( downloadViewCallback );
+
+ SetTimer( plugin.hwndLibraryParent, DOWNLOADS_TIMER_NAVNODE, 1000, Downloads_Nav_Timer );
+
+ return ML_INIT_SUCCESS;
+}
+
+void Quit()
+{
+ KillTimer( plugin.hwndLibraryParent, DOWNLOADS_TIMER_NAVNODE );
+
+ // If there are still files downloading, cancel download to remove incomplete downloaded files
+ while ( downloadStatus.CurrentlyDownloading() )
+ {
+ Nullsoft::Utility::AutoLock lock(downloadStatus.statusLock);
+ DownloadToken dltoken = downloadStatus.downloads.begin()->first;
+ WAC_API_DOWNLOADMANAGER->CancelDownload(dltoken);
+ }
+
+ if (dirty)
+ {
+ SaveDownloads( xmlFileName, downloadedFiles );
+ dirty = 0;
+ }
+
+ WAC_API_DOWNLOADMANAGER->UnregisterStatusCallback( downloadViewCallback );
+ downloadViewCallback->Release();
+
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid( applicationApiServiceGuid );
+ if ( sf != NULL )
+ sf->releaseInterface(WASABI_API_APP);
+
+ sf = plugin.service->service_getServiceByGuid(ExplorerFindFileApiGUID);
+ if ( sf != NULL )
+ sf->releaseInterface(WASABI_API_EXPLORERFINDFILE);
+
+ sf = plugin.service->service_getServiceByGuid(DownloadManagerGUID);
+ if ( sf != NULL )
+ sf->releaseInterface(WAC_API_DOWNLOADMANAGER);
+
+ if ( VIEWPROP != 0 )
+ {
+ GlobalDeleteAtom(VIEWPROP);
+ VIEWPROP = 0;
+ }
+}
+
+HWND CALLBACK DownloadDialog_Create( HWND hParent );
+
+INT_PTR MessageProc( int msg, INT_PTR param1, INT_PTR param2, INT_PTR param3 )
+{
+ switch ( msg )
+ {
+ case ML_MSG_DOWNLOADS_VIEW_LOADED:
+ return TRUE;
+
+ case ML_MSG_DOWNLOADS_VIEW_POSITION:
+ {
+ // toggle the position of the node based on the preferences settings
+ if ( param2 )
+ {
+ podcast_parent = (int)param1;
+ if ( downloads_treeItem )
+ {
+ HNAVITEM hItem = MLNavCtrl_FindItemById( plugin.hwndLibraryParent, downloads_treeItem );
+ if (hItem)
+ {
+ if ( MLNavCtrl_DeleteItem( plugin.hwndLibraryParent, hItem ) )
+ {
+ downloads_treeItem = 0;
+ }
+ }
+ }
+ }
+
+ Navigation_Update();
+ return 0;
+ }
+
+ case ML_MSG_TREE_ONCREATEVIEW:
+ return ( param1 == downloads_treeItem ) ? (INT_PTR)DownloadDialog_Create( (HWND)param2 ) : 0;
+
+ case ML_MSG_NO_CONFIG:
+ return TRUE;
+
+ case ML_MSG_NOTOKTOQUIT:
+ if (downloadStatus.CurrentlyDownloading())
+ {
+ wchar_t titleStr[32] = {0};
+ if ( MessageBox( plugin.hwndLibraryParent, WASABI_API_LNGSTRINGW( IDS_CANCEL_DOWNLOADS_AND_QUIT ), WASABI_API_LNGSTRINGW_BUF( IDS_CONFIRM_QUIT, titleStr, 32 ), MB_YESNO | MB_ICONQUESTION ) == IDNO )
+ return TRUE;
+ }
+ return FALSE;
+
+ case ML_MSG_WRITE_CONFIG:
+ if ( dirty )
+ {
+ SaveDownloads( xmlFileName, downloadedFiles );
+ dirty = 0;
+ }
+ break;
+
+ case ML_MSG_VIEW_PLAY_ENQUEUE_CHANGE:
+ enqueuedef = (int)param1;
+ groupBtn = (int)param2;
+ PostMessage( downloads_window, WM_APP + 104, param1, param2 );
+ return 0;
+ }
+
+ return 0;
+}
+
+
+extern "C" __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+{
+ return &plugin;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/Main.h b/Src/Plugins/Library/ml_downloads/Main.h
new file mode 100644
index 00000000..39c4de66
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/Main.h
@@ -0,0 +1,43 @@
+#ifndef NULLSOFT_MAINH
+#define NULLSOFT_MAINH
+
+#include "Downloaded.h"
+
+#define PLUGIN_VERSION_MAJOR 1
+#define PLUGIN_VERSION_MINOR 33
+
+extern int winampVersion, podcast_parent, dirty;
+
+#define NAVITEM_UNIQUESTR L"download_svc"
+BOOL Navigation_Update(void);
+
+bool AddDownloadData(const DownloadedFile &data);
+void CloseDatabase();
+
+#include "resource.h"
+#include "../nu/DialogSkinner.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "../nu/AutoChar.h"
+#include "../nu/AutoWide.h"
+#include "../nu/AutoLock.h"
+#include <windows.h>
+#include <shlwapi.h>
+
+extern ATOM VIEWPROP;
+extern winampMediaLibraryPlugin plugin;
+extern int downloads_treeItem;
+
+#include "../Components/wac_downloadManager/wac_downloadManager_api.h"
+
+#define ML_ENQDEF_VAL() (!!GetPrivateProfileInt(L"gen_ml_config", L"enqueuedef", 0, ml_cfg))
+#define ML_GROUPBTN_VAL() (!!GetPrivateProfileInt(L"gen_ml_config", L"groupbtn", 1, ml_cfg))
+extern wchar_t* ml_cfg;
+
+#include "DownloadViewCallback.h"
+
+extern DownloadViewCallback *downloadViewCallback;
+
+#endif
+
+extern HWND downloads_window;
+extern int groupBtn, enqueuedef; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/MessageProcessor.cpp b/Src/Plugins/Library/ml_downloads/MessageProcessor.cpp
new file mode 100644
index 00000000..6067965c
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/MessageProcessor.cpp
@@ -0,0 +1,7 @@
+#include "main.h"
+#include "MessageProcessor.h"
+
+#define CBCLASS MessageProcessor
+START_DISPATCH;
+CB(API_MESSAGEPROCESSOR_PROCESS_MESSAGE, ProcessMessage)
+END_DISPATCH; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/MessageProcessor.h b/Src/Plugins/Library/ml_downloads/MessageProcessor.h
new file mode 100644
index 00000000..c48e4b8a
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/MessageProcessor.h
@@ -0,0 +1,35 @@
+#ifndef NULLSOFT_ML_WIRE_MESSAGEPROCESSOR_H
+#define NULLSOFT_ML_WIRE_MESSAGEPROCESSOR_H
+
+#include <api/application/api_messageprocessor.h>
+#include "main.h"
+#ifndef WM_FORWARDMSG
+#define WM_FORWARDMSG 0x037F
+#endif
+
+class MessageProcessor : public api_messageprocessor
+{
+public:
+ bool ProcessMessage(MSG *msg)
+ {
+
+ if (msg->message < WM_KEYFIRST || msg->message > WM_KEYLAST)
+ return false;
+
+ HWND hWndCtl = ::GetFocus();
+
+ if (IsChild(browserHWND, hWndCtl))
+ {
+ // find a direct child of the dialog from the window that has focus
+ while(::GetParent(hWndCtl) != browserHWND)
+ hWndCtl = ::GetParent(hWndCtl);
+
+ if (activeBrowser->translateKey(*msg))
+ return true;
+ }
+ return false;
+ }
+protected:
+ RECVS_DISPATCH;
+};
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/ParseUtil.cpp b/Src/Plugins/Library/ml_downloads/ParseUtil.cpp
new file mode 100644
index 00000000..0a5c8d57
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/ParseUtil.cpp
@@ -0,0 +1,43 @@
+#include "ParseUtil.h"
+
+bool PropertyIsTrue( const XMLNode *item, const wchar_t *property )
+{
+ if ( !item )
+ return false;
+
+ const wchar_t *value = item->GetProperty( property );
+ if ( !value )
+ return false;
+
+ return !_wcsicmp( value, L"true" );
+}
+
+bool PropertyIsFalse( const XMLNode *item, const wchar_t *property )
+{
+ if ( !item )
+ return false;
+
+ const wchar_t *value = item->GetProperty( property );
+ if ( !value )
+ return false;
+
+ return !_wcsicmp( value, L"false" );
+}
+
+const wchar_t *GetContent( const XMLNode *item, const wchar_t *tag )
+{
+ const XMLNode *curNode = item->Get( tag );
+ if ( curNode )
+ return curNode->GetContent();
+ else
+ return 0;
+}
+
+const wchar_t *GetProperty( const XMLNode *item, const wchar_t *tag, const wchar_t *property )
+{
+ const XMLNode *curNode = item->Get( tag );
+ if ( curNode )
+ return curNode->GetProperty( property );
+ else
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/ParseUtil.h b/Src/Plugins/Library/ml_downloads/ParseUtil.h
new file mode 100644
index 00000000..a277d729
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/ParseUtil.h
@@ -0,0 +1,8 @@
+#pragma once
+#include "../xml/XMLNode.h"
+#include <bfc/platform/types.h>
+bool PropertyIsTrue(const XMLNode *item, const wchar_t *property);
+// not necessarily the opposite of PropertyIsTrue (returns false when field is empty
+bool PropertyIsFalse(const XMLNode *item, const wchar_t *property);
+const wchar_t *GetContent(const XMLNode *item, const wchar_t *tag);
+const wchar_t *GetProperty(const XMLNode *item, const wchar_t *tag, const wchar_t *property); \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/Preferences.cpp b/Src/Plugins/Library/ml_downloads/Preferences.cpp
new file mode 100644
index 00000000..fcd3a9cf
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/Preferences.cpp
@@ -0,0 +1,77 @@
+#include "main.h"
+#include "api__ml_downloads.h"
+#include "../winamp/wa_ipc.h"
+#include "Defaults.h"
+#include <shlobj.h>
+
+void Preferences_Init(HWND hwndDlg)
+{
+ SetDlgItemText(hwndDlg, IDC_DOWNLOADLOCATION, defaultDownloadPath);
+}
+
+BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam)
+{
+ wchar_t cl[32] = {0};
+ GetClassNameW(hwnd, cl, ARRAYSIZE(cl));
+ if (!lstrcmpiW(cl, WC_TREEVIEW))
+ {
+ PostMessage(hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection(hwnd));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int CALLBACK WINAPI BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
+{
+ if(uMsg == BFFM_INITIALIZED)
+ {
+ SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, (LPARAM)defaultDownloadPath);
+
+ // this is not nice but it fixes the selection not working correctly on all OSes
+ EnumChildWindows(hwnd, browseEnumProc, 0);
+ }
+ return 0;
+}
+
+void Preferences_Browse(HWND hwndDlg)
+{
+ wchar_t folder[MAX_PATH] = {0};
+ BROWSEINFO browse = {0};
+ lstrcpyn(folder, defaultDownloadPath, MAX_PATH);
+ browse.hwndOwner = hwndDlg;
+ browse.lpszTitle = WASABI_API_LNGSTRINGW(IDS_CHOOSE_FOLDER);
+ browse.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
+ browse.lpfn = BrowseCallbackProc;
+ LPITEMIDLIST itemList = SHBrowseForFolder(&browse);
+ if (itemList)
+ {
+ SHGetPathFromIDList(itemList, folder);
+ lstrcpyn(defaultDownloadPath, folder, MAX_PATH);
+ SetWindowText(GetDlgItem(hwndDlg, IDC_DOWNLOADLOCATION), folder);
+ LPMALLOC malloc;
+ SHGetMalloc(&malloc);
+ malloc->Free(itemList);
+ }
+}
+
+
+BOOL CALLBACK PreferencesDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ Preferences_Init(hwndDlg);
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_BROWSE:
+ Preferences_Browse(hwndDlg);
+ break;
+ }
+ break;
+ }
+
+ return 0;
+}
diff --git a/Src/Plugins/Library/ml_downloads/Preferences.h b/Src/Plugins/Library/ml_downloads/Preferences.h
new file mode 100644
index 00000000..c0615aab
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/Preferences.h
@@ -0,0 +1,6 @@
+#ifndef NULLSOFT_PREFERENCESH
+#define NULLSOFT_PREFERENCESH
+
+BOOL CALLBACK PreferencesDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+#endif
diff --git a/Src/Plugins/Library/ml_downloads/RFCDate.cpp b/Src/Plugins/Library/ml_downloads/RFCDate.cpp
new file mode 100644
index 00000000..ab57f9af
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/RFCDate.cpp
@@ -0,0 +1,216 @@
+#include "main.h"
+#include "RFCDate.h"
+#include <strsafe.h>
+
+void MakeDateString(__time64_t convertTime, wchar_t *date_str, size_t len)
+{
+ SYSTEMTIME sysTime = {0};
+ tm *newtime = _localtime64(&convertTime);
+
+ sysTime.wYear = newtime->tm_year + 1900;
+ sysTime.wMonth = newtime->tm_mon + 1;
+ sysTime.wDayOfWeek = newtime->tm_wday;
+ sysTime.wDay = newtime->tm_mday;
+ sysTime.wHour = newtime->tm_hour;
+ sysTime.wMinute = newtime->tm_min;
+ sysTime.wSecond = newtime->tm_sec;
+
+ GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &sysTime, NULL, date_str, (int)len);
+
+}
+
+void MakeRFCDateString(__time64_t convertTime, wchar_t *data_str, size_t len)
+{
+ SYSTEMTIME sysTime = {0};
+ tm *newtime = _gmtime64(&convertTime);
+
+ sysTime.wYear = newtime->tm_year + 1900;
+ sysTime.wMonth = newtime->tm_mon + 1;
+ sysTime.wDayOfWeek = newtime->tm_wday;
+ sysTime.wDay = newtime->tm_mday;
+ sysTime.wHour = newtime->tm_hour;
+ sysTime.wMinute = newtime->tm_min;
+ sysTime.wSecond = newtime->tm_sec;
+
+ wchar_t rfcDate[64] = {0}, rfcTime[64] = {0};
+ GetDateFormat(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), 0, &sysTime, L"ddd',' d MMM yyyy ", rfcDate, 64);
+ GetTimeFormat(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), 0, &sysTime, L"HH':'mm':'ss 'GMT'", rfcTime, 64);
+ StringCchPrintf(data_str,len,L"%s%s",rfcDate,rfcTime);
+}
+
+static int getMonthOfYear(const wchar_t *str);
+static int validateTime(struct tm *tmloc, int gmoffset);
+static const wchar_t *getNextField(const wchar_t *pos);
+static int getGMOffset(const wchar_t *str);
+
+enum
+{
+ DAY_OF_MONTH = 0,
+ MONTH_OF_YEAR,
+ YEAR,
+ TIME,
+ TIMEZONE,
+ DATE_END
+};
+
+__time64_t MakeRFCDate(const wchar_t *date)
+{
+ __time64_t result = 0;
+ const wchar_t *pos = date;
+ tm tmloc = {0};
+ const wchar_t *strmin;
+ const wchar_t *strsec;
+ int gmoffset = 1;
+ int state = DAY_OF_MONTH;
+ tzset();
+ /* skip weekday if present */
+ while (pos && *pos && !iswdigit(*pos)) pos++;
+
+ do
+ {
+ switch (state)
+ {
+ case DAY_OF_MONTH:
+ tmloc.tm_mday = _wtoi(pos);
+ break;
+ case MONTH_OF_YEAR:
+ tmloc.tm_mon = getMonthOfYear(pos);
+ break;
+ case YEAR:
+ {
+ /* TODO: we're only accepting 4-digit dates...*/
+ const wchar_t *test = pos; int numDigits = 0;
+ while (iswdigit(*test) && *test) { test++; numDigits++; }
+ if (numDigits == 2) // let's hope we never have 2 digit years!
+ tmloc.tm_year = _wtoi(pos) + 100; // assume 2 digit years are 20xx
+ else
+ tmloc.tm_year = _wtoi(pos) - 1900;
+ }
+ break;
+ case TIME:
+ strmin = wcschr(pos, L':');
+ strsec = strmin ? wcschr(strmin + 1, L':') : 0;
+
+ tmloc.tm_hour = _wtoi(pos);
+ if (!strmin) return _time64(0);
+ tmloc.tm_min = _wtoi(strmin + 1);
+ if (!strsec) return _time64(0);
+ tmloc.tm_sec = _wtoi(strsec + 1);
+ break;
+ case TIMEZONE:
+ gmoffset = getGMOffset(pos);
+ break;
+ case DATE_END:
+ pos = 0;
+ break;
+ }
+
+ state++;
+ }
+ while ((pos = getNextField(pos)));
+
+ tmloc.tm_isdst = 0; //_daylight;
+
+ if (validateTime(&tmloc, gmoffset))
+ {
+ result = _mktime64(&tmloc) - _timezone;
+ //if (_daylight)
+ }
+
+ return result;
+}
+
+const wchar_t *getNextField(const wchar_t *pos)
+{
+ if (!pos)
+ return 0;
+ while (pos && *pos && !iswspace(*pos)) pos++;
+ while (pos && *pos && iswspace(*pos)) pos++;
+
+ return ((pos && *pos) ? pos : 0);
+}
+
+int validateTime(struct tm *tmloc, int gmoffset)
+{
+ int result = 1;
+
+ if (tmloc->tm_mday < 1 || tmloc->tm_mday > 31 ||
+ tmloc->tm_mon < 0 || tmloc->tm_mon > 11 ||
+ tmloc->tm_year < 0 || tmloc->tm_year > 2000 ||
+ tmloc->tm_hour < 0 || tmloc->tm_hour > 23 ||
+ tmloc->tm_min < 0 || tmloc->tm_min > 59 ||
+ tmloc->tm_sec < 0 || tmloc->tm_sec > 59 ||
+ gmoffset == 1)
+ result = 0;
+
+ return result;
+}
+
+int getMonthOfYear(const wchar_t *str)
+{
+ int mon = -1;
+ /* This is not the most efficient way to determine
+ the month (we could use integer comparisons, for instance)
+ but I don't think this will be a performance bottleneck :)
+ */
+
+ if (!wcsnicmp(str, L"Jan", 3))
+ mon = 0;
+ else if (!wcsnicmp(str, L"Feb", 3))
+ mon = 1;
+ else if (!wcsnicmp(str, L"Mar", 3))
+ mon = 2;
+ else if (!wcsnicmp(str, L"Apr", 3))
+ mon = 3;
+ else if (!wcsnicmp(str, L"May", 3))
+ mon = 4;
+ else if (!wcsnicmp(str, L"Jun", 3))
+ mon = 5;
+ else if (!wcsnicmp(str, L"Jul", 3))
+ mon = 6;
+ else if (!wcsnicmp(str, L"Aug", 3))
+ mon = 7;
+ else if (!wcsnicmp(str, L"Sep", 3))
+ mon = 8;
+ else if (!wcsnicmp(str, L"Oct", 3))
+ mon = 9;
+ else if (!wcsnicmp(str, L"Nov", 3))
+ mon = 10;
+ else if (!wcsnicmp(str, L"Dec", 3))
+ mon = 11;
+
+ return mon;
+}
+
+int getGMOffset(const wchar_t *str)
+{
+ int secs = 0;
+ /* See note in getMonthOfYear() */
+
+ if (!wcsnicmp(str, L"UT", 2) || !wcsnicmp(str, L"GMT", 3))
+ secs = 0;
+ else if (!wcsnicmp(str, L"EDT", 3))
+ secs = -4 * 3600;
+ else if (!wcsnicmp(str, L"PST", 3))
+ secs = -8 * 3600;
+ else if (!wcsnicmp(str, L"EST", 3) || !wcsnicmp(str, L"CDT", 3))
+ secs = -5 * 3600;
+ else if (!wcsnicmp(str, L"MST", 3) || !wcsnicmp(str, L"PDT", 3))
+ secs = -7 * 3600;
+ else if (!wcsnicmp(str, L"CST", 3) || !wcsnicmp(str, L"MDT", 3))
+ secs = -6 * 3600;
+ else if ( (str[0] == L'+' || str[0] == L'-') &&
+ iswdigit(str[1]) && iswdigit(str[2]) &&
+ iswdigit(str[3]) && iswdigit(str[4]))
+ {
+ secs = 3600 * (10 * (str[1] - 48) + str[2] - 48);
+ secs += 60 * (10 * (str[3] - 48) + str[4] - 48);
+
+ if (str[0] == L'-')
+ secs = -secs;
+ }
+ else
+ secs = 1;
+
+ return secs;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/RFCDate.h b/Src/Plugins/Library/ml_downloads/RFCDate.h
new file mode 100644
index 00000000..7606272f
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/RFCDate.h
@@ -0,0 +1,7 @@
+#ifndef NULLSOFT_RFCDATEH
+#define NULLSOFT_RFCDATEH
+
+void MakeRFCDateString(__time64_t convertTime, wchar_t *data_str, size_t len);
+__time64_t MakeRFCDate(const wchar_t *date);
+void MakeDateString(__time64_t convertTime, wchar_t *date_str, size_t len);
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/TODO.txt b/Src/Plugins/Library/ml_downloads/TODO.txt
new file mode 100644
index 00000000..db9d5965
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/TODO.txt
@@ -0,0 +1,51 @@
+to fix
+----
+crash on shutdown
+
+
+/////------------ 1.1 below --------------
+automatically switch to 'custom' if you click on dropdown in "add" or "edit" url dialog
+
+need icon for listened media
+maybe one for read text?
+
+strip whitespace from beginning of titles
+
+multiple-select
+
+Allow for customizing the download location in add/edit url
+
+deletable items (needs to move to a separate 'deleted items' list so we don't re-add them next rss refresh)
+
+drag-n-drop from webpages
+
+once we get an HTTP 200, we should put the downloaded on the 'downloads' list, and be able to update the download percentage status as necessary
+
+BACKGROUND DOWNLOADER
+<<<
+avoid multiple downloads of the same thing
+avoid downlaoding things that have already been downloaded.
+range / if-range to handle download resuming
+save the last modified dates from "Last-Modified" header
+save unfinished downloads to an XML file and read on load
+>>>
+
+UNIFIED DOWNLOAD MANAGER CONCEPT !!!!!
+
+who needs updates
+downloaded file list
+downloads page (to refresh view)
+item object
+podcast page (to refresh view)
+
+
+new way of listing items
+---
+create a common "items" data structure that select channels add their items to.
+When a channel is select, it adds its items.
+When a channel is deselected, it removes its items.
+When a channel is refreshed, it re-adds its items (assuming the item-adder function protects against dupes)
+only potential issue is if a channel somehow "loses" items (or an item's GUID is changes)
+could be fixed by either 1) keeps track of "parent channel" in the items list
+2) rebuilding the entire items list on every channel refresh
+or 3) preventing GUID changes and item deletions (or forcing an item list rebuild if it does occur)
diff --git a/Src/Plugins/Library/ml_downloads/Util.h b/Src/Plugins/Library/ml_downloads/Util.h
new file mode 100644
index 00000000..12acd266
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/Util.h
@@ -0,0 +1,64 @@
+#ifndef NULLSOFT_PODCAST_PLUGIN_UTIL_HEADER
+#define NULLSOFT_PODCAST_PLUGIN_UTIL_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+#include "../nu/trace.h"
+
+#define CSTR_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(blah) (sizeof(blah)/sizeof(*blah))
+#endif
+
+#ifndef LONGX86
+#ifdef _WIN64
+ #define LONGX86 LONG_PTR
+#else /*_WIN64*/
+ #define LONGX86 LONG
+#endif /*_WIN64*/
+#endif // LONGX86
+
+#ifdef __cplusplus
+ #define SENDMSG(__hwnd, __msgId, __wParam, __lParam) ::SendMessageW((__hwnd), (__msgId), (__wParam), (__lParam))
+#else
+ #define SENDMSG(__hwnd, __msgId, __wParam, __lParam) SendMessageW((__hwnd), (__msgId), (__wParam), (__lParam))
+#endif // __cplusplus
+
+#define SENDMLIPC(__hwndML, __ipcMsgId, __param) SENDMSG((__hwndML), WM_ML_IPC, (WPARAM)(__param), (LPARAM)(__ipcMsgId))
+#define SENDWAIPC(__hwndWA, __ipcMsgId, __param) SENDMSG((__hwndWA), WM_WA_IPC, (WPARAM)(__param), (LPARAM)(__ipcMsgId))
+
+#define MSGRESULT(__hwnd, __result) { SetWindowLongPtrW((__hwnd), DWL_MSGRESULT, ((LONGX86)(LONG_PTR)(__result))); return TRUE; }
+
+#define SENDCMD(__hwnd, __ctrlId, __eventId, __hctrl) (SENDMSG((__hwnd), WM_COMMAND, MAKEWPARAM(__ctrlId, __eventId), (LPARAM)(__hctrl)))
+
+
+LPWSTR Plugin_MallocString(size_t cchLen);
+LPWSTR Plugin_ReAllocString(LPWSTR pszString, size_t cchLen);
+void Plugin_FreeString(LPWSTR pszString);
+LPWSTR Plugin_CopyString(LPCWSTR pszSource);
+
+LPSTR Plugin_MallocAnsiString(size_t cchLen);
+LPSTR Plugin_CopyAnsiString(LPCSTR pszSource);
+void Plugin_FreeAnsiString(LPSTR pszString);
+
+LPWSTR Plugin_DuplicateResString(LPCWSTR pszResource);
+void Plugin_FreeResString(LPWSTR pszResource);
+HRESULT Plugin_CopyResString(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszString);
+
+LPSTR Plugin_WideCharToMultiByte(UINT codePage, DWORD dwFlags, LPCWSTR lpWideCharStr, INT cchWideChar, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar);
+LPWSTR Plugin_MultiByteToWideChar(UINT codePage, DWORD dwFlags, LPCSTR lpMultiByteStr, INT cbMultiByte);
+
+void Plugin_SafeRelease(IUnknown *pUnk);
+
+
+HRESULT Plugin_FileExtensionFromUrl(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszUrl, LPCWSTR defaultExtension);
+void Plugin_ReplaceBadPathChars(LPWSTR pszPath);
+INT Plugin_CleanDirectory(LPWSTR pszPath);
+
+HRESULT Plugin_EnsurePathExist(LPCWSTR pszDirectory);
+
+#endif // NULLSOFT_PODCAST_PLUGIN_UTIL_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/XMLWriter.cpp b/Src/Plugins/Library/ml_downloads/XMLWriter.cpp
new file mode 100644
index 00000000..fdc8f2ca
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/XMLWriter.cpp
@@ -0,0 +1,125 @@
+#include "Main.h"
+#include "RFCDate.h"
+#include "Downloaded.h"
+#include "../nu/AutoLock.h"
+#include "Defaults.h"
+#include "../Agave/Language/api_language.h"
+
+using namespace Nullsoft::Utility;
+
+static void XMLWriteString(FILE *fp, const wchar_t *str)
+{
+ for (const wchar_t *itr = str; *itr; itr=CharNextW(itr))
+ {
+ switch (*itr)
+ {
+ case '<': fputws(L"&lt;", fp); break;
+ case '>': fputws(L"&gt;", fp); break;
+ case '&': fputws(L"&amp;", fp); break;
+ case '\"': fputws(L"&quot;", fp); break;
+ case '\'': fputws(L"&apos;", fp); break;
+ default: fputwc(*itr, fp); break;
+ }
+ }
+}
+
+static void InstaProp(FILE *fp, const wchar_t *property, const wchar_t *value)
+{
+ fwprintf(fp, L" %s=\"", property);
+ XMLWriteString(fp, value);
+ fputws(L"\"", fp);
+}
+
+static void InstaProp(FILE *fp, const wchar_t *property, int value)
+{
+ fwprintf(fp, L" %s=\"%i\"", property, value);
+}
+
+static void InstaPropI64(FILE *fp, const wchar_t *property, int64_t value)
+{
+ fwprintf(fp, L" %s=\"%I64d\"", property, value);
+}
+
+static void InstaPropTime(FILE *fp, const wchar_t *property, __time64_t value)
+{
+ fwprintf(fp, L" %s=\"%I64u\"", property, value);
+}
+
+static void InstaProp(FILE *fp, const wchar_t *property, float value)
+{
+ _fwprintf_l(fp, L" %s=\"%3.3f\"", WASABI_API_LNG->Get_C_NumericLocale(), property, value);
+}
+
+static void InstaProp(FILE *fp, const wchar_t *property, bool value)
+{
+ fwprintf(fp, L" %s=\"", property);
+ if (value)
+ fputws(L"true\"", fp);
+ else
+ fputws(L"false\"", fp);
+}
+
+static void InstaTag(FILE *fp, const wchar_t *tag, const wchar_t *content)
+{
+ if (content && !((INT_PTR)content < 65536) && *content)
+ {
+ fwprintf(fp, L"<%s>", tag);
+ XMLWriteString(fp, content);
+ fwprintf(fp, L"</%s>\r\n", tag);
+ }
+}
+
+static void InstaTag(FILE *fp, const wchar_t *tag, unsigned int uint)
+{
+ fwprintf(fp, L"<%s>%u</%s>", tag, uint, tag);
+}
+
+static void InstaTag(FILE *fp, const wchar_t *tag, __time64_t uint)
+{
+ fwprintf(fp, L"<%s>%I64u</%s>", tag, uint, tag);
+}
+
+static void BuildXMLDownloads(FILE *fp, DownloadList &downloads)
+{
+ fputws(L"<downloads", fp);
+ InstaProp(fp, L"downloadsTitleWidth", downloadsTitleWidth);
+ InstaProp(fp, L"downloadsProgressWidth", downloadsProgressWidth);
+ InstaProp(fp, L"downloadsDateWidth", downloadsDateWidth);
+ InstaProp(fp, L"downloadsSourceWidth", downloadsSourceWidth);
+ InstaProp(fp, L"downloadsPathWidth", downloadsPathWidth);
+ InstaProp(fp, L"downloadsItemSort", downloadsItemSort);
+ InstaProp(fp, L"downloadsSortAscending", downloadsSortAscending);
+ fputws(L">\r\n", fp);
+
+ AutoLock lock (downloads);
+ DownloadList::const_iterator itr;
+ for ( itr = downloads.begin(); itr != downloads.end(); itr++ )
+ {
+ fputws(L"<download>", fp);
+ InstaTag(fp, L"source", itr->source);
+ InstaTag(fp, L"title", itr->title);
+ InstaTag(fp, L"url", itr->url);
+ InstaTag(fp, L"path", itr->path);
+ InstaTag(fp, L"size", (unsigned int)itr->totalSize);
+ InstaTag(fp, L"downloadDate", itr->downloadDate);
+ wchar_t status[64] = {0};
+ _itow(itr->downloadStatus, status, 10);
+ InstaTag(fp, L"downloadStatus", status);
+ fputws(L"</download>\r\n", fp);
+ }
+
+ fputws(L"</downloads>", fp);
+}
+
+void SaveDownloads(const wchar_t *fileName, DownloadList &downloads)
+{
+ FILE *fp = _wfopen(fileName, L"wb");
+ if (fp)
+ {
+ wchar_t BOM = 0xFEFF;
+ fwrite(&BOM, sizeof(BOM), 1, fp);
+ fputws(L"<?xml version=\"1.0\" encoding=\"UTF-16\"?>\r\n", fp);
+ BuildXMLDownloads(fp, downloads);
+ fclose(fp);
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/XMLWriter.h b/Src/Plugins/Library/ml_downloads/XMLWriter.h
new file mode 100644
index 00000000..26389756
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/XMLWriter.h
@@ -0,0 +1,6 @@
+#ifndef NULLSOFT_XMLWRITERH
+#define NULLSOFT_XMLWRITERH
+#include "Downloaded.h"
+
+void SaveDownloads(const wchar_t *fileName, DownloadList &downloads);
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/api__ml_downloads.h b/Src/Plugins/Library/ml_downloads/api__ml_downloads.h
new file mode 100644
index 00000000..574b0bf2
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/api__ml_downloads.h
@@ -0,0 +1,11 @@
+#ifndef NULLSOFT_ML_DOWNLOADS_API_H
+#define NULLSOFT_ML_DOWNLOADS_API_H
+
+#include "../Agave/Language/api_language.h"
+
+#include "api/application/api_application.h"
+#define WASABI_API_APP applicationApi
+
+#include "../Agave/ExplorerFindFile/api_explorerfindfile.h"
+
+#endif // !NULLSOFT_ML_DOWNLOADS_API_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/date.c b/Src/Plugins/Library/ml_downloads/date.c
new file mode 100644
index 00000000..bbe876c6
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/date.c
@@ -0,0 +1 @@
+#include "time.h"
diff --git a/Src/Plugins/Library/ml_downloads/date.h b/Src/Plugins/Library/ml_downloads/date.h
new file mode 100644
index 00000000..58204571
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/date.h
@@ -0,0 +1,20 @@
+#ifndef _OD_DATE_
+#define _OD_DATE_
+
+#include <time.h>
+
+
+
+/* These functions all work with "Internet"(RFC 822) format Date/Time strings only */
+/* An example of an RFC 822 format Date/Time string is "Thu, 28 Aug 2003 21:30:47 EDT" */
+
+/* converts the RFC 822 format date string into UTC Calendar time */
+time_t getDateSecs(char *date);
+
+/* returns a string that represents the given UTC Calendar time value as an
+ RFC 822 format string. The buffer is user-supplied and must be at least
+ 30 bytes in size. */
+char *getDateStr(time_t tmval, char *buffer, int gmt);
+
+#endif /*_OD_DATE_*/
+
diff --git a/Src/Plugins/Library/ml_downloads/db.cpp b/Src/Plugins/Library/ml_downloads/db.cpp
new file mode 100644
index 00000000..db0e1b5b
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/db.cpp
@@ -0,0 +1,150 @@
+#include <shlwapi.h>
+
+#include "api__ml_downloads.h"
+#include "Downloaded.h"
+#include "../nde/nde_c.h"
+#include "../nu/AutoLock.h"
+
+/* DB Schema
+Source
+Url
+Title
+DownloadDate
+Length
+Filename
+*/
+
+static Nullsoft::Utility::LockGuard dbcs;
+static nde_table_t table = 0;
+static nde_database_t db = 0;
+using namespace Nullsoft::Utility;
+
+enum
+{
+ DB_ID_SOURCE = 0,
+ DB_ID_URL = 1,
+ DB_ID_TITLE = 2,
+ DB_ID_DOWNLOADDATE = 3,
+ DB_ID_LENGTH = 4,
+ DB_ID_FILENAME = 5
+};
+
+static bool OpenDatabase()
+{
+ AutoLock lock(dbcs);
+ if (!db)
+ db = NDE_CreateDatabase();
+
+ return true;
+}
+
+void CloseDatabase()
+{
+ AutoLock lock( dbcs );
+ if ( db )
+ {
+ if ( table )
+ NDE_Database_CloseTable( db, table );
+
+ NDE_DestroyDatabase( db );
+ }
+
+ db = 0;
+}
+
+static void CreateFields( nde_table_t table )
+{
+ // create defaults
+ NDE_Table_NewColumnW( table, DB_ID_SOURCE, L"source", FIELD_STRING );
+ NDE_Table_NewColumnW( table, DB_ID_URL, L"url", FIELD_STRING );
+ NDE_Table_NewColumnW( table, DB_ID_TITLE, L"title", FIELD_STRING );
+ NDE_Table_NewColumnW( table, DB_ID_DOWNLOADDATE, L"downloaddate", FIELD_DATETIME );
+ NDE_Table_NewColumnW( table, DB_ID_LENGTH, L"length", FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, DB_ID_FILENAME, L"filename", FIELD_FILENAME );
+ NDE_Table_PostColumns( table );
+ NDE_Table_AddIndexByIDW( table, DB_ID_URL, L"url" );
+}
+
+static bool OpenTable()
+{
+ AutoLock lock( dbcs );
+ if ( !OpenDatabase() )
+ return false;
+
+ if ( !table )
+ {
+ const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath();
+ wchar_t tablePath[ MAX_PATH ] = { 0 }, indexPath[ MAX_PATH ] = { 0 };
+
+ PathCombineW( tablePath, inidir, L"plugins" );
+ PathAppendW( tablePath, L"downloads.dat" );
+
+ PathCombineW( indexPath, inidir, L"plugins" );
+ PathAppendW( indexPath, L"downloads.idx" );
+
+ table = NDE_Database_OpenTable( db, tablePath, indexPath, NDE_OPEN_ALWAYS, NDE_CACHE );
+ if ( table )
+ CreateFields( table );
+ }
+
+ return !!table;
+}
+
+static void db_add( nde_scanner_t s, unsigned char id, wchar_t *data )
+{
+ if ( data )
+ {
+ nde_field_t f = NDE_Scanner_NewFieldByID( s, id );
+ NDE_StringField_SetString( f, data );
+ }
+}
+
+static void db_add_int( nde_scanner_t s, unsigned char id, int data )
+{
+ if ( data )
+ {
+ nde_field_t f = NDE_Scanner_NewFieldByID( s, id );
+ NDE_IntegerField_SetValue( f, data );
+ }
+}
+
+static void db_add_time( nde_scanner_t s, unsigned char id, time_t data )
+{
+ if ( data )
+ {
+ nde_field_t f = NDE_Scanner_NewFieldByID( s, id );
+ NDE_IntegerField_SetValue( f, static_cast<int>( data ) );
+ }
+}
+
+bool AddDownloadData( const DownloadedFile &data )
+{
+ AutoLock lock( dbcs );
+ if ( !OpenTable() )
+ return false;
+
+ nde_scanner_t s = NDE_Table_CreateScanner( table );
+ if ( s )
+ {
+ NDE_Scanner_New( s );
+ db_add( s, DB_ID_SOURCE, data.source );
+ db_add( s, DB_ID_URL, data.url );
+ db_add( s, DB_ID_TITLE, data.title );
+ db_add_time( s, DB_ID_DOWNLOADDATE, data.downloadDate );
+ db_add_int( s, DB_ID_LENGTH, (int)data.totalSize );
+ db_add( s, DB_ID_FILENAME, data.path );
+ NDE_Scanner_Post( s );
+ NDE_Table_DestroyScanner( table, s );
+ NDE_Table_Sync( table );
+
+ return true;
+ }
+ return false;
+}
+
+void CompactDatabase()
+{
+ AutoLock lock( dbcs );
+ if ( OpenTable() )
+ NDE_Table_Compact( table );
+}
diff --git a/Src/Plugins/Library/ml_downloads/errors.h b/Src/Plugins/Library/ml_downloads/errors.h
new file mode 100644
index 00000000..29766060
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/errors.h
@@ -0,0 +1,15 @@
+#ifndef NULLSOFT_ML_DOWNLOADS_ERRORS_H
+#define NULLSOFT_ML_DOWNLOADS_ERRORS_H
+
+enum
+{
+ DOWNLOAD_SUCCESS = 0,
+ DOWNLOAD_404,
+ DOWNLOAD_TIMEOUT,
+ DOWNLOAD_NOHTTP,
+ DOWNLOAD_NOPARSER,
+ DOWNLOAD_CONNECTIONRESET,
+ DOWNLOAD_ERROR_PARSING_XML,
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/layout.cpp b/Src/Plugins/Library/ml_downloads/layout.cpp
new file mode 100644
index 00000000..bfdd9591
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/layout.cpp
@@ -0,0 +1,215 @@
+#include "./layout.h"
+
+#include <windows.h>
+
+BOOL Layout_Initialize( HWND hwnd, const INT *itemList, INT itemCount, LAYOUTITEM *layout )
+{
+ if ( NULL == itemList || NULL == layout )
+ return FALSE;
+
+ LAYOUTITEM *item;
+
+ for ( INT i = 0; i < itemCount; i++ )
+ {
+ item = &layout[ i ];
+ item->hwnd = GetDlgItem( hwnd, itemList[ i ] );
+ if ( item->hwnd == NULL )
+ continue;
+
+ if ( FALSE == GetWindowRect( item->hwnd, &item->rect ) )
+ SetRectEmpty( &item->rect );
+ else
+ MapWindowPoints( HWND_DESKTOP, hwnd, (POINT *)&item->rect, 2 );
+
+ item->cx = item->rect.right - item->rect.left;
+ item->cy = item->rect.bottom - item->rect.top;
+ item->x = item->rect.left;
+ item->y = item->rect.top;
+ item->flags = 0;
+ }
+
+ return TRUE;
+}
+
+BOOL Layout_Perform( HWND hwnd, LAYOUTITEM *layout, INT layoutCount, BOOL fRedraw )
+{
+ HDWP hdwp, hdwpTemp;
+ hdwp = BeginDeferWindowPos( layoutCount );
+ if ( hdwp == NULL )
+ return FALSE;
+
+ UINT baseFlags = SWP_NOACTIVATE | SWP_NOZORDER;
+ if ( fRedraw == FALSE )
+ baseFlags |= ( SWP_NOREDRAW | SWP_NOCOPYBITS );
+
+ LAYOUTITEM *item;
+ for ( INT i = 0; i < layoutCount; i++ )
+ {
+ item = &layout[ i ];
+ if ( item->hwnd == NULL )
+ continue;
+
+ UINT flags = baseFlags | ( item->flags & ~( SWP_HIDEWINDOW | SWP_SHOWWINDOW ) );
+ if ( item->x == item->rect.left && item->y == item->rect.top )
+ flags |= SWP_NOMOVE;
+
+ if ( item->cx == ( item->rect.right - item->rect.left ) && item->cy == ( item->rect.bottom - item->rect.top ) )
+ flags |= SWP_NOSIZE;
+
+ if ( ( SWP_HIDEWINDOW & item->flags ) != 0 )
+ {
+ UINT windowStyle = (UINT)GetWindowLongPtr( item->hwnd, GWL_STYLE );
+ if ( ( WS_VISIBLE & windowStyle ) != 0 )
+ {
+ SetWindowLongPtr( item->hwnd, GWL_STYLE, windowStyle & ~WS_VISIBLE );
+ if ( FALSE != fRedraw )
+ {
+ RedrawWindow( hwnd, &item->rect, NULL, RDW_INVALIDATE | RDW_ERASE );
+ }
+ }
+ }
+
+ if ( ( SWP_NOSIZE | SWP_NOMOVE ) != ( ( SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED ) & flags ) )
+ {
+ hdwpTemp = DeferWindowPos( hdwp, item->hwnd, NULL, item->x, item->y, item->cx, item->cy, flags );
+ if ( hdwpTemp == NULL )
+ break;
+
+ hdwp = hdwpTemp;
+ }
+ }
+
+ BOOL result = ( hdwp != NULL ) ? EndDeferWindowPos( hdwp ) : FALSE;
+
+ for ( INT i = 0; i < layoutCount; i++ )
+ {
+ item = &layout[ i ];
+ if ( NULL != item->hwnd && 0 != ( SWP_SHOWWINDOW & item->flags ) )
+ {
+ UINT windowStyle = (UINT)GetWindowLongPtr( item->hwnd, GWL_STYLE );
+ if ( 0 == ( WS_VISIBLE & windowStyle ) )
+ {
+ SetWindowLongPtr( item->hwnd, GWL_STYLE, windowStyle | WS_VISIBLE );
+ if ( FALSE != fRedraw )
+ RedrawWindow( item->hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN );
+ }
+ }
+ }
+
+ return result;
+}
+
+static void Layout_SetItemVisibility( const RECT *rect, LAYOUTITEM *item )
+{
+ if ( NULL == item || NULL == item->hwnd )
+ return;
+
+ BOOL outsider = ( item->cx <= 0 || item->cy <= 0 ||
+ item->x >= rect->right || item->y >= rect->bottom ||
+ ( item->x + item->cx ) < rect->left || ( item->y + item->cy ) < rect->top );
+
+ UINT windowStyle = (UINT)GetWindowLongPtr( item->hwnd, GWL_STYLE );
+ if ( 0 == ( WS_VISIBLE & windowStyle ) )
+ {
+ if ( !outsider )
+ {
+ item->flags |= SWP_SHOWWINDOW;
+ }
+ }
+ else
+ {
+ if ( outsider )
+ {
+ item->flags |= SWP_HIDEWINDOW;
+ }
+ }
+}
+
+BOOL Layout_SetVisibility( const RECT *rect, LAYOUTITEM *layout, INT layoutCount )
+{
+ if ( NULL == rect || NULL == layout )
+ return FALSE;
+
+ for ( INT i = 0; i < layoutCount; i++ )
+ {
+ Layout_SetItemVisibility( rect, &layout[ i ] );
+ }
+
+ return TRUE;
+}
+
+BOOL Layout_SetVisibilityEx( const RECT *rect, const INT *indexList, INT indexCount, LAYOUTITEM *layout )
+{
+ if ( NULL == rect || NULL == indexList || NULL == layout )
+ return FALSE;
+
+ for ( INT i = 0; i < indexCount; i++ )
+ {
+ Layout_SetItemVisibility( rect, &layout[ indexList[ i ] ] );
+ }
+
+ return TRUE;
+}
+
+BOOL Layout_GetValidRgn( HRGN validRgn, POINTS parrentOffset, const RECT *validRect, LAYOUTITEM *layout, INT layoutCount )
+{
+ if ( NULL == validRgn )
+ return FALSE;
+
+ SetRectRgn( validRgn, 0, 0, 0, 0 );
+
+ if ( NULL == layout )
+ return FALSE;
+
+ HRGN rgn = CreateRectRgn( 0, 0, 0, 0 );
+ if ( NULL == rgn )
+ return FALSE;
+
+ LAYOUTITEM *item;
+ LONG l, t, r, b;
+
+ for ( INT i = 0; i < layoutCount; i++ )
+ {
+ item = &layout[ i ];
+ if ( NULL != item->hwnd && 0 == ( ( SWP_HIDEWINDOW | SWP_SHOWWINDOW ) & item->flags ) )
+ {
+ l = item->x + parrentOffset.x;
+ t = item->y + parrentOffset.y;
+ r = l + item->cx;
+ b = t + item->cy;
+ if ( 0 != ( SWP_NOREDRAW & item->flags ) || ( l == item->rect.left && t == item->rect.top && r == item->rect.right && b == item->rect.bottom ) )
+ {
+ if ( NULL != validRect )
+ {
+ if ( l < validRect->left )
+ l = validRect->left;
+
+ if ( t < validRect->top )
+ t = validRect->top;
+
+ if ( r > validRect->right )
+ r = validRect->right;
+
+ if ( b > validRect->bottom )
+ b = validRect->bottom;
+ }
+
+ if ( l < r && t < b )
+ {
+ SetRectRgn( rgn, l, t, r, b );
+ CombineRgn( validRgn, validRgn, rgn, RGN_OR );
+
+ if ( NULLREGION != GetUpdateRgn( item->hwnd, rgn, FALSE ) )
+ {
+ OffsetRgn( rgn, parrentOffset.x, parrentOffset.y );
+ CombineRgn( validRgn, validRgn, rgn, RGN_DIFF );
+ }
+ }
+ }
+ }
+ }
+
+ DeleteObject( rgn );
+
+ return TRUE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/layout.h b/Src/Plugins/Library/ml_downloads/layout.h
new file mode 100644
index 00000000..9af73396
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/layout.h
@@ -0,0 +1,43 @@
+#ifndef NULLSOFT_PODCAST_PLUGIN_LAYOUT_HEADER
+#define NULLSOFT_PODCAST_PLUGIN_LAYOUT_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+typedef struct __LAYOUTITEM
+{
+ HWND hwnd;
+ LONG x;
+ LONG y;
+ LONG cx;
+ LONG cy;
+ UINT flags;
+ RECT rect;
+} LAYOUTITEM;
+
+
+#define LI_GET_R(__li) ((__li).x + (__li).cx)
+#define LI_GET_B(__li) ((__li).y + (__li).cy)
+#define LI_EXPAND_W(__li, __delta) { (__li).cx += (__delta); }
+#define LI_EXPAND_H(__li, __delta) { (__li).cy += (__delta); }
+#define LI_SHIFT_L(__li, __delta) { (__li).x += (__delta); }
+#define LI_SHIFT_T(__li, __delta) { { (__li).y += (__delta); }
+#define LI_SET_L(__li, __val) { (__li).x = (__val); }
+#define LI_SET_T(__li, __val) { (__li).y = (__val); }
+#define LI_SET_W(__li, __val) { (__li).cx = (__val); }
+#define LI_SET_H(__li, __val) { (__li).cy = (__val); }
+#define LI_SET_R(__li, __val) { (__li).cx = ((__val) - (__li).x); }
+#define LI_SET_B(__li, __val) { (__li).cy = ((__val) - (__li).y); }
+
+BOOL Layout_Initialize(HWND hwnd, const INT *itemList, INT itemCount, LAYOUTITEM *layout);
+BOOL Layout_SetVisibilityEx(const RECT *rect, const INT *indexList, INT indexCount, LAYOUTITEM *layout);
+BOOL Layout_SetVisibility(const RECT *rect, LAYOUTITEM *layout, INT layoutCount);
+BOOL Layout_Perform(HWND hwnd, LAYOUTITEM *layout, INT layoutCount, BOOL fRedraw);
+
+BOOL Layout_GetValidRgn(HRGN validRgn, POINTS parrentOffset, const RECT *validRect, LAYOUTITEM *layout, INT layoutCount);
+
+
+#endif //NULLSOFT_PODCAST_PLUGIN_LAYOUT_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/ml_downloads.rc b/Src/Plugins/Library/ml_downloads/ml_downloads.rc
new file mode 100644
index 00000000..35737775
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/ml_downloads.rc
@@ -0,0 +1,249 @@
+// 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
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_DOWNLOADS DIALOGEX 0, 0, 266, 92
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_DOWNLOADLIST,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | LVS_OWNERDATA | WS_TABSTOP,0,0,264,79
+ CONTROL "Play",IDC_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,81,37,11
+ CONTROL "Enqueue",IDC_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,39,81,37,11
+ CONTROL "Remove",IDC_REMOVE,"Button",BS_OWNERDRAW | WS_TABSTOP,80,81,37,11
+ CONTROL "Clean up",IDC_CLEANUP,"Button",BS_OWNERDRAW | WS_TABSTOP,120,81,37,11
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_ENDELLIPSIS | WS_GROUP,163,81,102,11
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+IDR_MENU1 MENU
+BEGIN
+ POPUP "Downloads"
+ BEGIN
+ MENUITEM "Play selection\tEnter", IDC_PLAY
+ MENUITEM "Enqueue selection\tShift+Enter", IDC_ENQUEUE
+ POPUP "Send to:"
+ BEGIN
+ MENUITEM "", ID_Menu
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Select all\tCtrl+A", IDC_SELECTALL
+ MENUITEM "View file info...\tAlt+3", IDC_INFOBOX
+ MENUITEM "Explore item folder\tCtrl+F", ID_DOWNLOADS_EXPLORERITEMFOLDER
+ MENUITEM SEPARATOR
+ MENUITEM "Remove from list\tDel", IDC_REMOVE
+ MENUITEM "Delete file\tShift+Del", IDC_DELETE
+ MENUITEM "Cancel download", ID_DOWNLOADS_CANCELDOWNLOAD
+ END
+ POPUP "Navigation"
+ BEGIN
+ MENUITEM "&Preferences", ID_NAVIGATION_PREFERENCES
+ MENUITEM SEPARATOR
+ MENUITEM "Help", ID_NAVIGATION_HELP
+ END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Bitmap
+//
+
+IDB_TREEITEM_DOWNLOADS BITMAP "resources\\downloadIcon.bmp"
+IDB_TREEITEM_DOWNLOADS1 BITMAP "resources\\downloadIcon1.bmp"
+IDB_TREEITEM_DOWNLOADS2 BITMAP "resources\\downloadIcon2.bmp"
+IDB_TREEITEM_DOWNLOADS3 BITMAP "resources\\downloadIcon3.bmp"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Accelerator
+//
+
+IDR_VIEW_DOWNLOAD_ACCELERATORS ACCELERATORS
+BEGIN
+ "F", ID_DOWNLOADS_EXPLORERITEMFOLDER, VIRTKEY, CONTROL, NOINVERT
+ VK_DELETE, IDC_DELETE, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, IDC_ENQUEUE, VIRTKEY, SHIFT, NOINVERT
+ "3", IDC_INFOBOX, VIRTKEY, ALT, NOINVERT
+ VK_RETURN, IDC_PLAY, VIRTKEY, NOINVERT
+ VK_DELETE, IDC_REMOVE, VIRTKEY, NOINVERT
+ "A", IDC_SELECTALL, VIRTKEY, CONTROL, NOINVERT
+ VK_RETURN, IDC_CUSTOM, VIRTKEY, SHIFT, CONTROL, NOINVERT
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_PLUGIN_NAME "Nullsoft Downloads v%d.%02d"
+ 65535 "{706549D3-D813-45dd-9A0B-E3793A1B63A8}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DOWNLOADS "Downloads"
+ IDS_DOWNLOADING "Downloading..."
+ IDS_CHOOSE_FOLDER "Choose folder to store downloaded media."
+ IDS_FILE_NOT_FOUND "Cannot connect to %s\nFile not Found."
+ IDS_CONNECTION_TIMED_OUT "Cannot connect to %s\nConnection timed out."
+ IDS_NO_JNETLIB "HTTP Downloader library is missing.\nPlease reinstall Winamp."
+ IDS_JNETLIB_MISSING "JNetLib missing"
+ IDS_NO_EXPAT "XML Parsing library is missing.\nPlease reinstall Winamp."
+END
+
+STRINGTABLE
+BEGIN
+ IDS_EXPAT_MISSING "Expat missing"
+ IDS_WDAY_SUN "Sun"
+ IDS_WDAY_MON "Mon"
+ IDS_WDAY_TUE "Tue"
+ IDS_WDAY_WED "Wed"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_WDAY_THU "Thu"
+ IDS_WDAY_FRI "Fri"
+ IDS_WDAY_SAT "Sat"
+ IDS_MONTH_JAN "Jan"
+ IDS_MONTH_FEB "Feb"
+ IDS_MONTH_MAR "Mar"
+ IDS_MONTH_APR "Apr"
+ IDS_MONTH_MAY "May"
+ IDS_MONTH_JUN "Jun"
+ IDS_MONTH_JUL "Jul"
+ IDS_MONTH_AUG "Aug"
+ IDS_MONTH_SEP "Sep"
+ IDS_MONTH_OCT "Oct"
+ IDS_MONTH_NOV "Nov"
+ IDS_MONTH_DEC "Dec"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_INVALID_LINK "Invalid link (404 or timeout)"
+ IDS_CONNECTION_RESET_BY_PEER "Connection reset by peer."
+ IDS_DOWNLOADING_PERCENT "Downloading %d%%"
+ IDS_SOURCE "Source"
+ IDS_TITLE "Title"
+ IDS_PROGRESS "Status"
+ IDS_PATH "Download Location"
+ IDS_PERM_DELETE_ARE_YOU_SURE
+ "This will permanently delete this file, are you sure?"
+ IDS_PERM_DELETE_THESE_ARE_YOU_SURE
+ "This will permanently delete these %d files, are you sure?"
+ IDS_DELETION "Deletion"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CLEAR_ALL_FINISHED_DOWNLOADS
+ "This will clear all finished downloads, are you sure?\n\nTip: You can view all your downloaded podcasts in Local Media/Podcasts"
+ IDS_CLEAN_UP_LIST "Clean Up List"
+ IDS_DONE "Done"
+ IDS_SAVE "Save"
+ IDS_REQUIRES_INTERNET_CONNECTION_ENSURE_CONNECTION
+ "The media library feature you are attempting to use requires an internet connection. Please make sure you are connected to the internet and try again."
+ IDS_ADD_TO_DOWNLOADS "Added to downloads."
+ IDS_SURE_YOU_WANT_TO_REMOVE_THIS
+ "Are you sure you want to remove %s\n(%s)"
+ IDS_CONFIRM "Confirm"
+ IDS_CANCEL_DOWNLOADS_AND_QUIT
+ "There are downloads currently in progress.\nAre you sure you want to cancel these downloads and quit?"
+ IDS_CONFIRM_QUIT "Confirm Quit"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DELETEFAILED "Delete Failed"
+ IDS_SORRY "Sorry"
+ IDS_DATE "Download Date"
+ IDS_N_A "n/a"
+ IDS_SIZE "Size"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DOWNLOAD_SUCCESS "Completed"
+ IDS_DOWNLOAD_FAILURE "Failed"
+ IDS_DOWNLOAD_CANCELED "Cancelled"
+ IDS_DOWNLOAD_PENDING "Pending..."
+ IDS_DOWNLOADING_PROGRESS
+ "Downloading %u file(s), %s of %s complete (%d%%)"
+ IDS_DOWNLOADING_COMPLETE "Downloading %u files, %s complete"
+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/Plugins/Library/ml_downloads/ml_downloads.sln b/Src/Plugins/Library/ml_downloads/ml_downloads.sln
new file mode 100644
index 00000000..dd12dca8
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/ml_downloads.sln
@@ -0,0 +1,93 @@
+
+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}") = "ml_downloads", "ml_downloads.vcxproj", "{23E1FE5D-8833-4095-97D5-A5972CFA477C}"
+ ProjectSection(ProjectDependencies) = postProject
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27} = {57C90706-B25D-4ACA-9B33-95CDB2427C27}
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}
+ {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D}
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nde", "..\nde\nde.vcxproj", "{4D25C321-7F8B-424E-9899-D80A364BAF1A}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\replicant\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}"
+ ProjectSection(ProjectDependencies) = postProject
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\replicant\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\replicant\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}"
+ ProjectSection(ProjectDependencies) = postProject
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\replicant\zlib\zlib.vcxproj", "{0F9730E4-45DA-4BD2-A50A-403A4BC9751A}"
+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
+ {23E1FE5D-8833-4095-97D5-A5972CFA477C}.Debug|Win32.ActiveCfg = Debug|Win32
+ {23E1FE5D-8833-4095-97D5-A5972CFA477C}.Debug|Win32.Build.0 = Debug|Win32
+ {23E1FE5D-8833-4095-97D5-A5972CFA477C}.Debug|x64.ActiveCfg = Debug|x64
+ {23E1FE5D-8833-4095-97D5-A5972CFA477C}.Debug|x64.Build.0 = Debug|x64
+ {23E1FE5D-8833-4095-97D5-A5972CFA477C}.Release|Win32.ActiveCfg = Release|Win32
+ {23E1FE5D-8833-4095-97D5-A5972CFA477C}.Release|Win32.Build.0 = Release|Win32
+ {23E1FE5D-8833-4095-97D5-A5972CFA477C}.Release|x64.ActiveCfg = Release|x64
+ {23E1FE5D-8833-4095-97D5-A5972CFA477C}.Release|x64.Build.0 = Release|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.ActiveCfg = Debug|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.Build.0 = Debug|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.ActiveCfg = Debug|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.Build.0 = Debug|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.ActiveCfg = Release|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.Build.0 = Release|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.ActiveCfg = Release|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.Build.0 = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.Build.0 = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.ActiveCfg = Debug|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.Build.0 = Debug|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.ActiveCfg = Debug|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.Build.0 = Debug|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.ActiveCfg = Release|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.Build.0 = Release|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.ActiveCfg = Release|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C4E799C1-027B-487B-8E1A-31F2D26A1AFE}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_downloads/ml_downloads.vcxproj b/Src/Plugins/Library/ml_downloads/ml_downloads.vcxproj
new file mode 100644
index 00000000..3d27d8bf
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/ml_downloads.vcxproj
@@ -0,0 +1,370 @@
+<?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>{23E1FE5D-8833-4095-97D5-A5972CFA477C}</ProjectGuid>
+ <RootNamespace>ml_downloads</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>
+ <IncludePath>$(IncludePath)</IncludePath>
+ <LibraryPath>$(LibraryPath)</LibraryPath>
+ <EmbedManifest>true</EmbedManifest>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <EmbedManifest>true</EmbedManifest>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <IncludePath>$(IncludePath)</IncludePath>
+ <LibraryPath>$(LibraryPath)</LibraryPath>
+ <EmbedManifest>true</EmbedManifest>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <EmbedManifest>true</EmbedManifest>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg">
+ <VcpkgEnableManifest>false</VcpkgEnableManifest>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>false</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\
+xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>false</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\
+xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;IGNORE_API_GRACENOTE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>true</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;IGNORE_API_GRACENOTE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\nde\nde.vcxproj">
+ <Project>{4d25c321-7f8b-424e-9899-d80a364baf1a}</Project>
+ <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
+ <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\General\gen_ml\menu.h" />
+ <ClInclude Include="..\..\..\nu\Alias.h" />
+ <ClInclude Include="..\..\..\nu\ChildSizer.h" />
+ <ClInclude Include="..\..\..\nu\DialogSkinner.h" />
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h" />
+ <ClInclude Include="..\..\..\nu\menushortcuts.h" />
+ <ClInclude Include="..\..\..\xml\XMLDOM.h" />
+ <ClInclude Include="..\..\..\xml\XMLNode.h" />
+ <ClInclude Include="api__ml_downloads.h" />
+ <ClInclude Include="Defaults.h" />
+ <ClInclude Include="Downloaded.h" />
+ <ClInclude Include="DownloadsDialog.h" />
+ <ClInclude Include="DownloadsParse.h" />
+ <ClInclude Include="DownloadStatus.h" />
+ <ClInclude Include="DownloadThread.h" />
+ <ClInclude Include="DownloadViewCallback.h" />
+ <ClInclude Include="errors.h" />
+ <ClInclude Include="layout.h" />
+ <ClInclude Include="Main.h" />
+ <ClInclude Include="ParseUtil.h" />
+ <ClInclude Include="Preferences.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="RFCDate.h" />
+ <ClInclude Include="Util.h" />
+ <ClInclude Include="XMLWriter.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp" />
+ <ClCompile Include="..\..\..\nu\ChildSizer.cpp" />
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp" />
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)%(Filename)1.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)%(Filename)1.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)%(Filename)1.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)%(Filename)1.obj</ObjectFileName>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp" />
+ <ClCompile Include="..\..\..\xml\XMLDOM.cpp" />
+ <ClCompile Include="..\..\..\xml\XMLNode.cpp" />
+ <ClCompile Include="db.cpp" />
+ <ClCompile Include="Defaults.cpp" />
+ <ClCompile Include="Downloaded.cpp" />
+ <ClCompile Include="DownloadsDialog.cpp" />
+ <ClCompile Include="DownloadsParse.cpp" />
+ <ClCompile Include="DownloadStatus.cpp" />
+ <ClCompile Include="DownloadThread.cpp" />
+ <ClCompile Include="DownloadViewCallback.cpp" />
+ <ClCompile Include="layout.cpp" />
+ <ClCompile Include="Main.cpp" />
+ <ClCompile Include="ParseUtil.cpp" />
+ <ClCompile Include="Preferences.cpp" />
+ <ClCompile Include="RFCDate.cpp" />
+ <ClCompile Include="util.cpp">
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)%(Filename)2.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)%(Filename)2.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)%(Filename)2.obj</ObjectFileName>
+ <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)%(Filename)2.obj</ObjectFileName>
+ </ClCompile>
+ <ClCompile Include="XMLWriter.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\bitmap1.bmp" />
+ <Image Include="resources\downloadIcon.bmp" />
+ <Image Include="resources\downloadIcon1.bmp" />
+ <Image Include="resources\downloadIcon2.bmp" />
+ <Image Include="resources\downloadIcon3.bmp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_downloads.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="DESIGN.txt" />
+ <Text Include="TODO.txt" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/ml_downloads.vcxproj.filters b/Src/Plugins/Library/ml_downloads/ml_downloads.vcxproj.filters
new file mode 100644
index 00000000..9883c853
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/ml_downloads.vcxproj.filters
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="db.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Defaults.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Downloaded.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DownloadsDialog.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DownloadsParse.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DownloadStatus.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DownloadThread.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DownloadViewCallback.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="layout.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ParseUtil.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Preferences.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="RFCDate.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="util.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="XMLWriter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\ChildSizer.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\xml\XMLDOM.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\xml\XMLNode.cpp">
+ <Filter>Source Files\xml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\xml\XMLDOM.cpp">
+ <Filter>Source Files\xml</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="api__ml_downloads.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Defaults.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Downloaded.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DownloadsDialog.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DownloadsParse.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DownloadStatus.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DownloadThread.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DownloadViewCallback.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="errors.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="layout.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="XMLWriter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Util.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="RFCDate.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Preferences.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ParseUtil.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\Alias.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\ChildSizer.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\DialogSkinner.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\menu.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\menushortcuts.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\xml\XMLDOM.h">
+ <Filter>Header Files\xml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\xml\XMLNode.h">
+ <Filter>Header Files\xml</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\downloadIcon.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\downloadIcon1.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\downloadIcon2.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\downloadIcon3.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\bitmap1.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="DESIGN.txt" />
+ <Text Include="TODO.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{fbb3be6a-b7ea-4313-b14c-3e90ed67b4ec}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{52a7174b-e9ac-428f-8f7c-921bcb4e1637}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{dcbaa963-b32c-488a-a8d0-cd04a987d873}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Image Files">
+ <UniqueIdentifier>{3d2c389d-dacb-4c28-985e-6404ec435898}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\nu">
+ <UniqueIdentifier>{87d018de-72e8-4fe5-94e6-ca03aeb0ede9}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\gen_ml">
+ <UniqueIdentifier>{4ec61852-ecfb-400c-ae96-803f47338909}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\xml">
+ <UniqueIdentifier>{87422940-a801-4fae-9a04-bac04b096146}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\nu">
+ <UniqueIdentifier>{7d1c9f6a-447c-4b7e-bb0a-bd7aef6f1919}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\gen_ml">
+ <UniqueIdentifier>{b5409075-f53b-4665-991a-3996953e0b03}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\xml">
+ <UniqueIdentifier>{c8ff3a21-0fca-40ea-bce7-8b7492a07ad0}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_downloads.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/png.rc b/Src/Plugins/Library/ml_downloads/png.rc
new file mode 100644
index 00000000..5f5d3de1
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/png.rc
@@ -0,0 +1,63 @@
+// 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
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// RCDATA
+//
+
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.\0"
+END
+
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/Src/Plugins/Library/ml_downloads/resource.h b/Src/Plugins/Library/ml_downloads/resource.h
new file mode 100644
index 00000000..c3c33609
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/resource.h
@@ -0,0 +1,120 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_downloads.rc
+//
+#define IDS_NULLSOFT_PODCAST 0
+#define IDS_PODCASTS 1
+#define IDS_DOWNLOADING_KB_COMPLETE 2
+#define IDS_DOWNLOADING_KB_PROGRESS 3
+#define IDS_DOWNLOADS 5
+#define IDS_DOWNLOADING 6
+#define IDS_CHOOSE_FOLDER 7
+#define IDS_FILE_NOT_FOUND 9
+#define IDS_CONNECTION_TIMED_OUT 10
+#define IDS_ERROR_PARSING_XML 11
+#define IDS_NO_JNETLIB 13
+#define IDS_JNETLIB_MISSING 14
+#define IDS_NO_EXPAT 15
+#define IDS_EXPAT_MISSING 16
+#define IDS_CONNECTION_RESET 17
+#define IDS_WDAY_SUN 28
+#define IDS_WDAY_MON 29
+#define IDS_WDAY_TUE 30
+#define IDS_WDAY_WED 31
+#define IDS_WDAY_THU 32
+#define IDS_WDAY_FRI 33
+#define IDS_WDAY_SAT 34
+#define IDS_MONTH_JAN 35
+#define IDS_MONTH_FEB 36
+#define IDS_MONTH_MAR 37
+#define IDS_MONTH_APR 38
+#define IDS_MONTH_MAY 39
+#define IDS_MONTH_JUN 40
+#define IDS_MONTH_JUL 41
+#define IDS_MONTH_AUG 42
+#define IDS_MONTH_SEP 43
+#define IDS_MONTH_OCT 44
+#define IDS_MONTH_NOV 45
+#define IDS_MONTH_DEC 46
+#define IDS_INVALID_LINK 54
+#define IDS_CONNECTION_RESET_BY_PEER 55
+#define IDS_DOWNLOADING_PERCENT 56
+#define IDS_SOURCE 57
+#define IDS_ITEM 58
+#define IDS_TITLE 58
+#define IDS_PROGRESS 59
+#define IDS_PATH 60
+#define IDS_PERM_DELETE_ARE_YOU_SURE 61
+#define IDS_PERM_DELETE_THESE_ARE_YOU_SURE 62
+#define IDS_DELETION 63
+#define IDS_CLEAR_ALL_FINISHED_DOWNLOADS 64
+#define IDS_CLEAN_UP_LIST 65
+#define IDS_DONE 66
+#define IDS_SAVE 67
+#define IDS_REQUIRES_INTERNET_CONNECTION_ENSURE_CONNECTION 68
+#define IDS_ADD_TO_DOWNLOADS 69
+#define IDS_SURE_YOU_WANT_TO_REMOVE_THIS 74
+#define IDS_CONFIRM 75
+#define IDS_CANCEL_DOWNLOADS_AND_QUIT 76
+#define IDS_CONFIRM_QUIT 77
+#define IDS_DELETEFAILED 80
+#define IDS_SORRY 81
+#define IDS_DATE 82
+#define IDS_N_A 83
+#define IDS_SIZE 84
+#define IDD_DOWNLOADS 107
+#define IDR_MENU1 112
+#define IDR_VIEW_DOWNLOAD_ACCELERATORS 122
+#define IDB_TREEITEM_DOWNLOADS 126
+#define IDB_TREEITEM_DOWNLOADS1 131
+#define IDS_DOWNLOAD_SUCCESS 134
+#define IDS_DOWNLOAD_FAILURE 135
+#define IDS_DOWNLOAD_CANCELED 136
+#define IDB_TREEITEM_DOWNLOADS2 136
+#define IDB_BITMAP2 137
+#define IDB_TREEITEM_DOWNLOADS3 137
+#define IDS_DOWNLOAD_PENDING 138
+#define IDS_DOWNLOADING_PROGRESS 139
+#define IDS_DOWNLOADING_COMPLETE 140
+#define IDC_CUSTOM 1000
+#define IDC_DELETE 1011
+#define IDC_DOWNLOADLIST 1029
+#define IDC_STATICDOWNLOADLOCATION 1034
+#define IDC_STATICAUTODOWNLOAD 1035
+#define IDC_ADD 1044
+#define IDC_EDIT 1045
+#define IDC_PLAY 1047
+#define IDC_ENQUEUE 1048
+#define IDC_DOWNLOAD 1050
+#define IDC_CANCEL 1051
+#define IDC_BROWSE 1051
+#define IDC_ENQUEUE2 1051
+#define IDC_BROWSER 1057
+#define IDC_STATUS 1058
+#define IDC_DOWNLOADLOCATION 1059
+#define IDC_SETTINGSBOX 1060
+#define IDC_CLEANUP 1060
+#define IDC_PLAYACTION 1065
+#define IDC_ENQUEUEACTION 1066
+#define ID_PLAYMEDIA_IDC 40018
+#define IDC_REMOVE 40024
+#define ID_Menu 40026
+#define IDC_INFOBOX 40028
+#define IDC_SELECTALL 40030
+#define ID_NAVIGATION_PREFERENCES 40032
+#define ID_NAVIGATION_HELP 40033
+#define ID_DOWNLOADS_EXPLORERITEMFOLDER 40034
+#define ID_ENQUEUE 40036
+#define ID_DOWNLOADS_CANCELDOWNLOAD 40043
+#define IDS_PLUGIN_NAME 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 141
+#define _APS_NEXT_COMMAND_VALUE 40044
+#define _APS_NEXT_CONTROL_VALUE 1068
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_downloads/resources/downloadIcon.bmp b/Src/Plugins/Library/ml_downloads/resources/downloadIcon.bmp
new file mode 100644
index 00000000..ab762e4f
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/resources/downloadIcon.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_downloads/resources/downloadIcon.png b/Src/Plugins/Library/ml_downloads/resources/downloadIcon.png
new file mode 100644
index 00000000..ff92ea88
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/resources/downloadIcon.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_downloads/resources/downloadIcon1.bmp b/Src/Plugins/Library/ml_downloads/resources/downloadIcon1.bmp
new file mode 100644
index 00000000..4fef75f4
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/resources/downloadIcon1.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_downloads/resources/downloadIcon2.bmp b/Src/Plugins/Library/ml_downloads/resources/downloadIcon2.bmp
new file mode 100644
index 00000000..4b59943a
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/resources/downloadIcon2.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_downloads/resources/downloadIcon3.bmp b/Src/Plugins/Library/ml_downloads/resources/downloadIcon3.bmp
new file mode 100644
index 00000000..4e18a399
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/resources/downloadIcon3.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_downloads/util.cpp b/Src/Plugins/Library/ml_downloads/util.cpp
new file mode 100644
index 00000000..9a8a7927
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/util.cpp
@@ -0,0 +1,255 @@
+#include "main.h"
+#include "./util.h"
+#include "api__ml_downloads.h"
+
+#include <strsafe.h>
+
+LPWSTR Plugin_MallocString(size_t cchLen)
+{
+ return (LPWSTR)calloc(cchLen, sizeof(WCHAR));
+}
+
+void Plugin_FreeString(LPWSTR pszString)
+{
+ if (NULL != pszString)
+ {
+ free(pszString);
+ }
+}
+
+LPWSTR Plugin_ReAllocString(LPWSTR pszString, size_t cchLen)
+{
+ return (LPWSTR)realloc(pszString, sizeof(WCHAR) * cchLen);
+}
+
+LPWSTR Plugin_CopyString(LPCWSTR pszSource)
+{
+ if (NULL == pszSource)
+ return NULL;
+
+ INT cchSource = lstrlenW(pszSource) + 1;
+
+ LPWSTR copy = Plugin_MallocString(cchSource);
+ if (NULL != copy)
+ {
+ CopyMemory(copy, pszSource, sizeof(WCHAR) * cchSource);
+ }
+ return copy;
+}
+
+LPSTR Plugin_MallocAnsiString(size_t cchLen)
+{
+ return (LPSTR)calloc(cchLen, sizeof(CHAR));
+}
+
+LPSTR Plugin_CopyAnsiString(LPCSTR pszSource)
+{
+ if (NULL == pszSource)
+ return NULL;
+
+ INT cchSource = lstrlenA(pszSource) + 1;
+
+ LPSTR copy = Plugin_MallocAnsiString(cchSource);
+ if (NULL != copy)
+ {
+ CopyMemory(copy, pszSource, sizeof(CHAR) * cchSource);
+ }
+ return copy;
+
+}
+void Plugin_FreeAnsiString(LPSTR pszString)
+{
+ Plugin_FreeString((LPWSTR)pszString);
+}
+
+LPSTR Plugin_WideCharToMultiByte(UINT codePage, DWORD dwFlags, LPCWSTR lpWideCharStr, INT cchWideChar, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar)
+{
+ INT cchBuffer = WideCharToMultiByte(codePage, dwFlags, lpWideCharStr, cchWideChar, NULL, 0, lpDefaultChar, lpUsedDefaultChar);
+ if (0 == cchBuffer) return NULL;
+
+ LPSTR buffer = Plugin_MallocAnsiString(cchBuffer);
+ if (NULL == buffer) return NULL;
+
+ if (0 == WideCharToMultiByte(codePage, dwFlags, lpWideCharStr, cchWideChar, buffer, cchBuffer, lpDefaultChar, lpUsedDefaultChar))
+ {
+ Plugin_FreeAnsiString(buffer);
+ return NULL;
+ }
+ return buffer;
+}
+
+LPWSTR Plugin_MultiByteToWideChar(UINT codePage, DWORD dwFlags, LPCSTR lpMultiByteStr, INT cbMultiByte)
+{
+ if (NULL == lpMultiByteStr) return NULL;
+ INT cchBuffer = MultiByteToWideChar(codePage, dwFlags, lpMultiByteStr, cbMultiByte, NULL, 0);
+ if (NULL == cchBuffer) return NULL;
+
+ if (cbMultiByte > 0) cchBuffer++;
+
+ LPWSTR buffer = Plugin_MallocString(cchBuffer);
+ if (NULL == buffer) return NULL;
+
+ if (0 == MultiByteToWideChar(codePage, dwFlags, lpMultiByteStr, cbMultiByte, buffer, cchBuffer))
+ {
+ Plugin_FreeString(buffer);
+ return NULL;
+ }
+
+ if (cbMultiByte > 0)
+ {
+ buffer[cchBuffer - 1] = L'\0';
+ }
+ return buffer;
+}
+
+
+LPWSTR Plugin_DuplicateResString(LPCWSTR pszResource)
+{
+ return (IS_INTRESOURCE(pszResource)) ?
+ (LPWSTR)pszResource :
+ Plugin_CopyString(pszResource);
+}
+
+void Plugin_FreeResString(LPWSTR pszResource)
+{
+ if (!IS_INTRESOURCE(pszResource))
+ Plugin_FreeString(pszResource);
+}
+
+HRESULT Plugin_CopyResString(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszString)
+{
+ if (NULL == pszBuffer)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+
+ if (NULL == pszString)
+ {
+ pszBuffer[0] = L'\0';
+ }
+ else if (IS_INTRESOURCE(pszString))
+ {
+ if (NULL == WASABI_API_LNG)
+ hr = E_FAIL;
+ else
+ WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)pszString, pszBuffer, cchBufferMax);
+ }
+ else
+ {
+ hr = StringCchCopy(pszBuffer, cchBufferMax, pszString);
+ }
+ return hr;
+}
+
+void Plugin_SafeRelease(IUnknown *pUnk)
+{
+ if (NULL != pUnk)
+ pUnk->Release();
+}
+
+HRESULT Plugin_FileExtensionFromUrl(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszUrl, LPCWSTR defaultExtension)
+{
+ LPCWSTR cursor = pszUrl;
+ while (L'\0' != *cursor && L'?' != *cursor)
+ cursor = CharNext(cursor);
+
+ LPCWSTR end = cursor;
+
+ while (cursor != pszUrl && L'/' != *cursor && L'\\' != *cursor && L'.' != *cursor)
+ cursor = CharPrev(pszUrl, cursor);
+
+ if (L'.' == *cursor && cursor != pszUrl)
+ {
+ if (CharNext(cursor) < end)
+ {
+ INT cchExtension = (INT)(INT_PTR)(end - cursor);
+ return StringCchCopyN(pszBuffer, cchBufferMax, cursor, cchExtension);
+ }
+ }
+
+ return StringCchCopy(pszBuffer, cchBufferMax, defaultExtension);
+}
+
+void Plugin_ReplaceBadPathChars(LPWSTR pszPath)
+{
+ if (NULL == pszPath)
+ return;
+
+ while (L'\0' != *pszPath)
+ {
+ switch(*pszPath)
+ {
+ case L'?':
+ case L'/':
+ case L'\\':
+ case L':':
+ case L'*':
+ case L'\"':
+ case L'<':
+ case L'>':
+ case L'|':
+ *pszPath = L'_';
+ break;
+ default:
+ if (*pszPath < 32) *pszPath = L'_';
+ break;
+ }
+ pszPath = CharNextW(pszPath);
+ }
+}
+
+INT Plugin_CleanDirectory(LPWSTR pszPath)
+{
+ if (NULL == pszPath)
+ return 0;
+
+ INT cchPath = lstrlen(pszPath);
+ LPWSTR cursor = pszPath + cchPath;
+ while (cursor-- != pszPath && (L' ' == *cursor || L'.' == *cursor))
+ *cursor = L'\0';
+
+ return (cchPath - (INT)(INT_PTR)(cursor - pszPath) - 1);
+}
+
+HRESULT Plugin_EnsurePathExist(LPCWSTR pszDirectory)
+{
+ DWORD ec = ERROR_SUCCESS;
+
+ UINT errorMode = SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
+
+ if (0 == CreateDirectory(pszDirectory, NULL))
+ {
+ ec = GetLastError();
+ if (ERROR_PATH_NOT_FOUND == ec)
+ {
+ LPCWSTR pszBlock = pszDirectory;
+ WCHAR szBuffer[MAX_PATH] = {0};
+
+ LPCTSTR pszCursor = PathFindNextComponent(pszBlock);
+ ec = (pszCursor == pszBlock || S_OK != StringCchCopyN(szBuffer, ARRAYSIZE(szBuffer), pszBlock, (pszCursor - pszBlock))) ?
+ ERROR_INVALID_NAME : ERROR_SUCCESS;
+
+ pszBlock = pszCursor;
+
+ while (ERROR_SUCCESS == ec && NULL != (pszCursor = PathFindNextComponent(pszBlock)))
+ {
+ if (pszCursor == pszBlock || S_OK != StringCchCatN(szBuffer, ARRAYSIZE(szBuffer), pszBlock, (pszCursor - pszBlock)))
+ ec = ERROR_INVALID_NAME;
+
+ if (ERROR_SUCCESS == ec && !CreateDirectory(szBuffer, NULL))
+ {
+ ec = GetLastError();
+ if (ERROR_ALREADY_EXISTS == ec) ec = ERROR_SUCCESS;
+ }
+ pszBlock = pszCursor;
+ }
+ }
+
+ if (ERROR_ALREADY_EXISTS == ec)
+ ec = ERROR_SUCCESS;
+ }
+
+ SetErrorMode(errorMode);
+ SetLastError(ec);
+ return HRESULT_FROM_WIN32(ec);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_downloads/version.rc2 b/Src/Plugins/Library/ml_downloads/version.rc2
new file mode 100644
index 00000000..f6e65323
--- /dev/null
+++ b/Src/Plugins/Library/ml_downloads/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,33,0,0
+ 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 Media Library Plug-in"
+ VALUE "FileVersion", "1,33,0,0"
+ VALUE "InternalName", "Nullsoft Downloads"
+ VALUE "LegalCopyright", "Copyright © 2010-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_downloads.dll"
+ VALUE "ProductName", "Winamp"
+ VALUE "ProductVersion", STR_WINAMP_PRODUCTVER
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END