aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Library/ml_plg
diff options
context:
space:
mode:
Diffstat (limited to 'Src/Plugins/Library/ml_plg')
-rw-r--r--Src/Plugins/Library/ml_plg/AddPlaylist.cpp139
-rw-r--r--Src/Plugins/Library/ml_plg/AlbumID.cpp45
-rw-r--r--Src/Plugins/Library/ml_plg/IDScanner.cpp698
-rw-r--r--Src/Plugins/Library/ml_plg/IDScanner.h97
-rw-r--r--Src/Plugins/Library/ml_plg/PlaylistGeneratorAPI.cpp44
-rw-r--r--Src/Plugins/Library/ml_plg/PlaylistGeneratorAPI.h14
-rw-r--r--Src/Plugins/Library/ml_plg/api__ml_plg.h50
-rw-r--r--Src/Plugins/Library/ml_plg/api_playlist_generator.h36
-rw-r--r--Src/Plugins/Library/ml_plg/atltransactionmanager.h697
-rw-r--r--Src/Plugins/Library/ml_plg/generate.cpp766
-rw-r--r--Src/Plugins/Library/ml_plg/gn_logo_88x83.bmpbin0 -> 21966 bytes
-rw-r--r--Src/Plugins/Library/ml_plg/impl_playlist.cpp328
-rw-r--r--Src/Plugins/Library/ml_plg/impl_playlist.h65
-rw-r--r--Src/Plugins/Library/ml_plg/main.h87
-rw-r--r--Src/Plugins/Library/ml_plg/ml_plg.cpp719
-rw-r--r--Src/Plugins/Library/ml_plg/ml_plg.rc312
-rw-r--r--Src/Plugins/Library/ml_plg/ml_plg.sln30
-rw-r--r--Src/Plugins/Library/ml_plg/ml_plg.vcxproj331
-rw-r--r--Src/Plugins/Library/ml_plg/ml_plg.vcxproj.filters130
-rw-r--r--Src/Plugins/Library/ml_plg/pass1.cpp101
-rw-r--r--Src/Plugins/Library/ml_plg/pass2.cpp72
-rw-r--r--Src/Plugins/Library/ml_plg/playlist.cpp635
-rw-r--r--Src/Plugins/Library/ml_plg/playlist.h40
-rw-r--r--Src/Plugins/Library/ml_plg/prefs.cpp596
-rw-r--r--Src/Plugins/Library/ml_plg/resource.h120
-rw-r--r--Src/Plugins/Library/ml_plg/util.cpp36
-rw-r--r--Src/Plugins/Library/ml_plg/version.rc239
-rw-r--r--Src/Plugins/Library/ml_plg/view.cpp368
28 files changed, 6595 insertions, 0 deletions
diff --git a/Src/Plugins/Library/ml_plg/AddPlaylist.cpp b/Src/Plugins/Library/ml_plg/AddPlaylist.cpp
new file mode 100644
index 00000000..23334514
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/AddPlaylist.cpp
@@ -0,0 +1,139 @@
+#include <shlwapi.h>
+#include <strsafe.h> // Include this last
+#include <algorithm>
+
+#include "main.h"
+#include "resource.h"
+
+#include "../../General/gen_ml/config.h"
+#include "../nu/autolock.h"
+#include "../nu/autowide.h"
+//#include "../playlist/api_playlists.h"
+#include "../nu/MediaLibraryInterface.h"
+
+
+
+wchar_t *createPlayListDBFileName( wchar_t *filename ) // filename is ignored but used for temp space, make sure it's 1024+256 chars =)
+{
+ wchar_t g_path[ MAX_PATH ] = { 0 };
+ mediaLibrary.BuildPath( L"Plugins\\ml", g_path, MAX_PATH );
+
+ wchar_t *filenameptr;
+ int x = 32;
+ for ( ;;)
+ {
+ GetTempFileNameW( g_path, L"plf", GetTickCount() + x * 5000, filename );
+
+ if ( lstrlenW( filename ) > 4 )
+ {
+ int length = lstrlenW( filename ); // Get length
+ StringCchCopyW( filename + length - 4, length, L".m3u" ); // Add the extension
+ }
+
+ HANDLE h = CreateFileW( filename, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, CREATE_NEW, 0, 0 );
+ if ( h != INVALID_HANDLE_VALUE )
+ {
+ filenameptr = filename + lstrlenW( g_path ) + 1;
+ CloseHandle( h );
+ break;
+ }
+ if ( ++x > 4096 )
+ {
+ filenameptr = L"error.m3u";
+ break;
+ }
+ }
+
+ // Cleanup
+
+ return filenameptr; // return pointer to just the base filename
+}
+
+void CreateAndAddPlaylist( const wchar_t *name )
+{
+ wchar_t filename[ MAX_PATH ] = { 0 }, dir[ MAX_PATH ] = { 0 };
+ GetTempPathW( MAX_PATH, dir );
+ GetTempFileNameW( dir, L"ml_playlist", 0, filename );
+ StringCchPrintfW( filename, MAX_PATH, L"%s.m3u8", filename );
+
+ int result = -1;
+
+ // See if we want to include the seed tracks in our playlist
+ if ( useSeed == TRUE )
+ {
+ seedPlaylist.AppendPlaylist( currentPlaylist );
+ result = AGAVE_API_PLAYLISTMGR->Save( filename, &seedPlaylist ); // Save the seed playlist contents since we appended the regular playlist to it
+ }
+ else
+ result = AGAVE_API_PLAYLISTMGR->Save( filename, &currentPlaylist ); // Save the current playlist contents to the file so they will be in ML view
+
+ if ( result == PLAYLISTMANAGER_SUCCESS )
+ {
+ mlAddPlaylist p = { sizeof( p ),name,filename, PL_FLAGS_IMPORT,-1,-1 };
+ SendMessage( plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&p, ML_IPC_PLAYLIST_ADD );
+ DeleteFileW( filename );
+ }
+}
+
+void TimeStamp( wchar_t *buf, int cch )
+{
+ wchar_t time_str[ 32 ] = { 0 }, date_str[ 32 ] = { 0 };
+
+ GetDateFormat( 0, 0, 0, 0, date_str, 32 );
+ GetTimeFormat( 0, 0, 0, 0, time_str, 32 );
+
+ StringCchPrintfW( buf, cch, L"%s - %s", date_str, time_str );
+}
+
+INT_PTR CALLBACK AddPlaylistDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ Playlist *seedPlaylist = (Playlist *)lParam;
+
+ switch ( uMsg )
+ {
+ case WM_INITDIALOG:
+ {
+ //Add the default name here:
+ wchar_t default_pl_name[ 1024 ] = { 0 }, timestamp_t[ 64 ] = { 0 }, title[ 256 ] = { 0 };
+ TimeStamp( timestamp_t, 64 ); // Get the timestamp
+
+ if ( seedPlaylist->entries[ 0 ] ) // Only grab the seed track if it is actually there
+ seedPlaylist->entries[ 0 ]->GetTitle( title, 256 );
+
+ StringCchPrintf( default_pl_name, 1024, L"%s'%s' @ %s", WASABI_API_LNGSTRINGW( IDS_PL_NAME_PREFIX ), title, timestamp_t );
+ SetDlgItemText( hwndDlg, IDC_EDIT_NAME, default_pl_name );
+
+ PostMessage( hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem( hwndDlg, IDC_EDIT_NAME ), TRUE );
+ break;
+ }
+ case WM_COMMAND:
+ switch ( LOWORD( wParam ) )
+ {
+ case IDOK: // On OK create the playlist
+ {
+ wchar_t name[ 256 ] = { 0 };
+ GetDlgItemText( hwndDlg, IDC_EDIT_NAME, name, 255 );
+ if ( !name[ 0 ] ) // Error if a valid name is not provided
+ {
+ wchar_t titleStr[ 32 ] = { 0 };
+ MessageBox( hwndDlg, WASABI_API_LNGSTRINGW( IDS_ENTER_A_NAME ),
+ WASABI_API_LNGSTRINGW_BUF( IDS_ERROR, titleStr, 32 ), MB_OK );
+ break;
+ }
+
+ CreateAndAddPlaylist( name );
+ AGAVE_API_PLAYLISTS->Flush();
+
+ //lParam = IDOK; // Set the code to ok as a return to the calling function
+ EndDialog( hwndDlg, IDOK );
+ }
+ break;
+ case IDCANCEL:
+ //lParam = IDCANCEL; // Set the code to cancel as a return to the calling function
+ EndDialog( hwndDlg, IDCANCEL );
+ break;
+ }
+ break;
+ }
+ return FALSE;
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/AlbumID.cpp b/Src/Plugins/Library/ml_plg/AlbumID.cpp
new file mode 100644
index 00000000..2fd6d14b
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/AlbumID.cpp
@@ -0,0 +1,45 @@
+#include "playlist.h"
+#include "main.h"
+#include "api__ml_plg.h"
+#include "../../General/gen_ml/ml.h"
+#include <atlbase.h>
+#include "IDScanner.h"
+
+IConnectionPoint *GetConnectionPoint(IUnknown *punk, REFIID riid)
+{
+ if (!punk)
+ return 0;
+
+ IConnectionPointContainer *pcpc;
+ IConnectionPoint *pcp = 0;
+
+ HRESULT hr = punk->QueryInterface(IID_IConnectionPointContainer, (void **) & pcpc);
+ if (SUCCEEDED(hr))
+ {
+ pcpc->FindConnectionPoint(riid, &pcp);
+ pcpc->Release();
+ }
+ return pcp;
+}
+
+bool IDScanner::SetupMusicID()
+{
+ if (!SetupPlaylistSDK())
+ return false;
+
+ if (musicID)
+ return true;
+
+ musicID = AGAVE_API_GRACENOTE->GetMusicID();
+ if (musicID)
+ {
+ IConnectionPoint *icp = GetConnectionPoint(musicID, DIID__ICDDBMusicIDManagerEvents);
+ if (icp)
+ {
+ icp->Advise(static_cast<IDispatch *>(this), &m_dwCookie);
+ icp->Release();
+ }
+ return true;
+ }
+ return false;
+}
diff --git a/Src/Plugins/Library/ml_plg/IDScanner.cpp b/Src/Plugins/Library/ml_plg/IDScanner.cpp
new file mode 100644
index 00000000..2345ab06
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/IDScanner.cpp
@@ -0,0 +1,698 @@
+#include "IDScanner.h"
+#include "main.h"
+#include "../winamp/wa_ipc.h"
+#include "api__ml_plg.h"
+#include "playlist.h"
+#include <assert.h>
+#include <atlbase.h>
+#include <strsafe.h> // include this last
+
+//#define DEBUG_CALLBACKS
+
+IDScanner::IDScanner() : systemCallbacks(0)
+{
+ musicID=0;
+ killswitch=0;
+ filesComplete=0;
+ filesTotal=0;
+ state=STATE_IDLE;
+ m_dwCookie=0;
+ syscb_registered=false;
+
+ // Create the stack that will hold our batched up files for step 4 processing
+ //process_items;
+}
+
+IDScanner::~IDScanner()
+{
+ // ToDo: Make sure we clean up the processing stack here if we need to do that
+ //Shutdown();
+}
+
+void IDScanner::Shutdown()
+{
+ if (musicID)
+ {
+ IConnectionPoint *icp = GetConnectionPoint(musicID, DIID__ICDDBMusicIDManagerEvents);
+ if (icp)
+ {
+ icp->Unadvise(m_dwCookie);
+ icp->Release();
+ }
+ musicID->Shutdown();
+ musicID->Release();
+ }
+ musicID=0;
+
+ // Deregister the system callbacks
+ WASABI_API_SYSCB->syscb_deregisterCallback(this);
+}
+
+static HRESULT FillTag(ICddbFileInfo *info, BSTR filename)
+{
+ ICddbID3TagPtr infotag = NULL;
+ infotag.CreateInstance(CLSID_CddbID3Tag);
+ ICddbFileTag2_5Ptr tag2_5 = NULL;
+ infotag->QueryInterface(&tag2_5);
+ itemRecordW *record = AGAVE_API_MLDB->GetFile(filename);
+ if (record && infotag && tag2_5)
+ {
+ wchar_t itemp[64] = {0};
+ if (record->artist)
+ infotag->put_LeadArtist(record->artist);
+
+ if (record->album)
+ infotag->put_Album(record->album);
+
+ if (record->title)
+ infotag->put_Title(record->title);
+
+ if (record->genre)
+ infotag->put_Genre(record->genre);
+
+ if (record->track > 0)
+ infotag->put_TrackPosition(_itow(record->track, itemp, 10));
+
+// TODO: if (record->tracks > 0)
+
+ if (record->year > 0)
+ infotag->put_Year(_itow(record->year, itemp, 10));
+
+ if (record->publisher)
+ infotag->put_Label(record->publisher);
+
+ /*
+ if (GetFileInfo(filename, L"ISRC", meta, 512) && meta[0])
+ infotag->put_ISRC(meta);
+ */
+
+ if (record->disc > 0)
+ infotag->put_PartOfSet(_itow(record->disc, itemp, 10));
+
+ if (record->albumartist)
+ tag2_5->put_DiscArtist(record->albumartist);
+
+ if (record->composer)
+ tag2_5->put_Composer(record->composer);
+
+ if (record->length > 0)
+ tag2_5->put_LengthMS(_itow(record->length*1000, itemp, 10));
+
+ if (record->bpm > 0)
+ infotag->put_BeatsPerMinute(_itow(record->bpm, itemp, 10));
+
+ /*
+ if (GetFileInfo(filename, L"conductor", meta, 512) && meta[0])
+ tag2_5->put_Conductor(meta);
+ */
+ AGAVE_API_MLDB->FreeRecord(record);
+ }
+
+ if (info) info->put_Tag(infotag);
+
+ return S_OK;
+}
+
+void IDScanner::CommitFileInfo(ICddbFileInfo *match)
+{
+ ICddbFileTagPtr tag;
+ match->get_Tag(&tag);
+
+ ICddbDisc2Ptr disc1, disc;
+ match->get_Disc(&disc1);
+
+ ICddbDisc2_5Ptr disc2_5;
+ ICddbTrackPtr track;
+ ICddbTrack2_5Ptr track2;
+ if (disc1)
+ {
+ musicID->GetFullDisc(disc1, &disc);
+ if (disc == 0)
+ disc=disc1;
+ disc->QueryInterface(&disc2_5);
+ disc->GetTrack(1, &track);
+ if (track)
+ track->QueryInterface(&track2);
+ }
+
+ CComBSTR file, tagID, extData;
+ match->get_Filename(&file);
+ tag->get_FileId(&tagID);
+ playlistMgr->FileSetTagID(file, tagID, CDDB_UPDATE_NONE);
+
+ ICddbFileTag2_5Ptr tag2;
+ tag->QueryInterface(&tag2);
+ playlistMgr->FileSetFieldVal(file, gnpl_crit_field_xdev1, L"0"); // mark as done!
+
+ if (tag2) // try tag first
+ tag2->get_ExtDataSerialized(&extData);
+
+ if (!extData && track2 != 0) // WMA files don't get their tag object's extended data set correctly, so fallback to track extended data
+ track2->get_ExtDataSerialized(&extData);
+
+ if (!extData && disc2_5 != 0) // finally, fall back to disc extended data
+ disc2_5->get_ExtDataSerialized(&extData);
+
+ playlistMgr->FileSetExtDataSerialized(file, extData, CDDB_UPDATE_NONE);
+
+ if (tagID)
+ AGAVE_API_MLDB->SetField(file, "GracenoteFileID", tagID);
+ if (extData)
+ AGAVE_API_MLDB->SetField(file, "GracenoteExtData", extData);
+
+ // TODO: if we don't have an artist & album, we might as well grab this out of the tag now
+
+ // TODO: make thread-safe and optional
+ /*
+ updateFileInfo(file, L"GracenoteFileID", tagID);
+ updateFileInfo(file, L"GracenoteExtData", extData);
+ WriteFileInfo(file);
+ */
+}
+
+STDMETHODIMP STDMETHODCALLTYPE IDScanner::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ else if (IsEqualIID(riid, __uuidof(_ICDDBMusicIDManagerEvents)))
+ *ppvObject = (_ICDDBMusicIDManagerEvents *)this;
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+ULONG STDMETHODCALLTYPE IDScanner::AddRef(void)
+{
+ return 1;
+}
+
+ULONG STDMETHODCALLTYPE IDScanner::Release(void)
+{
+ return 0;
+}
+
+HRESULT STDMETHODCALLTYPE IDScanner::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ switch (dispid)
+ {
+ case 1: // OnTrackIDStatusUpdate, params: CddbMusicIDStatus Status, BSTR filename, long* Abort
+ {
+ //long *abort = pdispparams->rgvarg[0].plVal;
+ // TODO: is this safe to put here? Or does this make us get partial results
+ }
+ break;
+ case 2: // OnAlbumIDStatusUpdate, params: CddbMusicIDStatus Status, BSTR filename, long current_file, long total_files, long* Abort
+ {
+ long *abort = pdispparams->rgvarg[0].plVal;
+ /*long total_files = pdispparams->rgvarg[1].lVal;
+ long current_file= pdispparams->rgvarg[2].lVal;*/
+ CddbMusicIDStatus status = (CddbMusicIDStatus)pdispparams->rgvarg[4].lVal;
+ BSTR filename = pdispparams->rgvarg[3].bstrVal;
+
+ // TODO: is this safe to put here? Or does this make us get partial results
+ if (killswitch)
+ *abort = 1;
+ }
+ break;
+ case 3: // OnTrackIDComplete, params: LONG match_code, ICddbFileInfo* pInfoIn, ICddbFileInfoList* pListOut
+ break;
+ case 4:
+ break;// OnAlbumIDComplete, params: LONG match_code, ICddbFileInfoList* pListIn, ICddbFileInfoLists* pListsOut
+ case 5:
+ break; // OnGetFingerprint
+ case 6:
+ break;
+ case 7://OnLibraryIDListStarted
+ break;
+ case 8: // OnLibraryIDListComplete
+ {
+
+ long *abort = pdispparams->rgvarg[0].plVal;
+ if (killswitch)
+ *abort = 1;
+ /*long FilesError =pdispparams->rgvarg[1].lVal;
+ long FilesNoMatch=pdispparams->rgvarg[2].lVal;
+ long FilesFuzzy=pdispparams->rgvarg[3].lVal;
+ long FilesExact=pdispparams->rgvarg[4].lVal;*/
+ filesTotal=pdispparams->rgvarg[5].lVal;
+ filesComplete=pdispparams->rgvarg[6].lVal;
+ IDispatch *disp = pdispparams->rgvarg[7].pdispVal;
+ if (disp)
+ {
+ ICddbFileInfoList* matchList=0;
+ disp->QueryInterface(&matchList);
+ if (matchList)
+ {
+ long matchcount;
+ matchList->get_Count(&matchcount);
+ for (int j = 1;j <= matchcount;j++)
+ {
+ ICddbFileInfoPtr match;
+ matchList->GetFileInfo(j, &match);
+ CommitFileInfo(match);
+ }
+
+ matchList->Release();
+ }
+ return S_OK;
+ }
+ else
+ return E_FAIL;
+
+ }
+ break;
+ case 9: //OnLibraryIDComplete
+ break;
+ case 10: // OnGetFingerprintInfo
+ {
+ long *abort = pdispparams->rgvarg[0].plVal;
+ IDispatch *disp = pdispparams->rgvarg[1].pdispVal;
+ BSTR filename = pdispparams->rgvarg[2].bstrVal;
+
+ ICddbFileInfoPtr info;
+ disp->QueryInterface(&info);
+ return AGAVE_API_GRACENOTE->CreateFingerprint(musicID, AGAVE_API_DECODE, info, filename, abort);
+ }
+ break;
+ case 11: // OnGetTagInfo
+ {
+ pdispparams->rgvarg[0].plVal;
+ IDispatch *disp = pdispparams->rgvarg[1].pdispVal;
+ BSTR filename = pdispparams->rgvarg[2].bstrVal;
+
+ ICddbFileInfoPtr info;
+ disp->QueryInterface(&info);
+ return FillTag(info, filename);
+ }
+ break;
+ }
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+HRESULT STDMETHODCALLTYPE IDScanner::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ *rgdispid = DISPID_UNKNOWN;
+ return DISP_E_UNKNOWNNAME;
+}
+
+HRESULT STDMETHODCALLTYPE IDScanner::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT STDMETHODCALLTYPE IDScanner::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+void IDScanner::SetGracenoteData(BSTR filename, BSTR tagID, BSTR extData)
+{
+ bool foundExt=false;
+ if (extData && extData[0])
+ {
+ playlistMgr->FileSetExtDataSerialized(filename, extData, CDDB_UPDATE_NONE);
+ CComBSTR test;
+ playlistMgr->FileGetExtDataSerialized(filename, &test, 0); // benski> 24 Jul 2007 - there is a currently a bug that makes this always E_FAIL
+ if (test)
+ foundExt=true;
+ }
+
+ if (!foundExt) // no Extended Data (or invalid), but we have a Tag ID, we'll ask the playlist SDK to do a quick lookup
+ {
+ playlistMgr->FileSetTagID(filename, tagID, CDDB_UPDATE_EXTENDED);
+
+ // write back to Media Library database
+ CComBSTR extData;
+ playlistMgr->FileGetExtDataSerialized(filename, &extData, 0); // benski> 24 Jul 2007 - there is a currently a bug that makes this always E_FAIL
+ if (extData)
+ AGAVE_API_MLDB->SetField(filename, "GracenoteExtData", extData);
+ }
+ else
+ playlistMgr->FileSetTagID(filename, tagID, CDDB_UPDATE_NONE);
+}
+
+/*
+//void IDScanner::ProcessDatabaseDifferences(Device * dev, C_ItemList * ml0,C_ItemList * itemRecordsOnDevice, C_ItemList * itemRecordsNotOnDevice, C_ItemList * songsInML, C_ItemList * songsNotInML)
+void IDScanner::ProcessDatabaseDifferences(Device * dev, C_ItemList * ml0,C_ItemList * itemRecordsOnDevice, C_ItemList * itemRecordsNotOnDevice, C_ItemList * songsInML, C_ItemList * songsNotInML)
+{
+ C_ItemList device2;
+ C_ItemList *device0=&device2;
+
+ int l = dev->getPlaylistLength(0);
+ for(int i=0; i<l; i++) device0->Add((void*)dev->getPlaylistTrack(0,i));
+
+
+ qsort(ml0->GetAll(),ml0->GetSize(),sizeof(void*),sortfunc_ItemRecords);
+ nu::qsort(device0->GetAll(), device0->GetSize(), sizeof(void*), dev, compareSongs);
+
+ C_ItemList *ml = new C_ItemList;
+ C_ItemList *device = new C_ItemList;
+
+ int i,j;
+
+ {
+ itemRecordW * lastice = NULL;
+ songid_t lastsong = NULL;
+ for(i=0; i<ml0->GetSize(); i++) {
+ itemRecordW * it = (itemRecordW*)ml0->Get(i);
+ if(lastice) if(compareItemRecords(lastice,it)==0) continue;
+ ml->Add(it);
+ lastice = it;
+ }
+ for(i=0; i<device0->GetSize(); i++) {
+ songid_t song = (songid_t)device0->Get(i);
+ if(lastsong) if(compareSongs((void*)&song,(void*)&lastsong, dev)==0) continue;
+ device->Add((void*)song);
+ lastsong = song;
+ }
+ }
+
+ i=0,j=0;
+ int li = device->GetSize();
+ int lj = ml->GetSize();
+ while(i<li && j<lj) {
+ itemRecordW * it = (itemRecordW*)ml->Get(j);
+ songid_t song = (songid_t)device->Get(i);
+
+ int cmp = compareItemRecordAndSongId(it,song, dev);
+ if(cmp == 0) { // song on both
+ if(itemRecordsOnDevice) itemRecordsOnDevice->Add(it);
+ if(songsInML) songsInML->Add((void*)song);
+ i++;
+ j++;
+ }
+ else if(cmp > 0) { //song in ml and not on device
+ if(itemRecordsNotOnDevice) itemRecordsNotOnDevice->Add(it);
+ j++;
+ }
+ else { // song on device but not in ML
+ if(songsNotInML) songsNotInML->Add((void*)song);
+ i++;
+ }
+ }
+ // any leftovers?
+ if(songsNotInML) while(i<li) {
+ songid_t song = (songid_t)device->Get(i++);
+
+ songsNotInML->Add((void*)song);
+ }
+ if(itemRecordsNotOnDevice) while(j<lj) {
+ itemRecordW * it = (itemRecordW *)ml->Get(j++);
+
+ itemRecordsNotOnDevice->Add(it);
+ }
+ delete ml; delete device;
+}
+*/
+
+/*
+2-pass strategy
+Pass 1: Find all tracks with Gracenote Extended Data
+Pass 2: Find File ID & extended data by fingerprint
+*/
+void IDScanner::ScanDatabase()
+{
+ filesComplete=0;
+ filesTotal=0;
+ state=STATE_INITIALIZING;
+ killswitch=0; // reset just in case
+
+ if (SetupPlaylistSDK())
+ {
+ // If this is our first time running then lets register the wasabi system callbacks for adding and removing tracks
+ if (!syscb_registered)
+ {
+ WASABI_API_SYSCB->syscb_registerCallback(this);
+ syscb_registered = true;
+ }
+
+ // Set up the MLDB manager
+ InitializeMLDBManager();
+
+ state=STATE_SYNC;
+ /* Get a list of files in the media library database */
+ itemRecordListW *results = AGAVE_API_MLDB->Query(L"type=0");
+ if (results)
+ {
+ filesTotal=results->Size;
+ for (int i=0;i<results->Size;i++)
+ {
+ if (killswitch)
+ break;
+ wchar_t * filename = results->Items[i].filename;
+ HRESULT hr=playlistMgr->AddEntry(filename); // Add entry to gracenote DB
+ assert(SUCCEEDED(S_OK));
+ if (hr == S_OK)
+ {
+ // Fill in Artist & Album info since we have it in the itemRecordList anyway
+ // TODO: probably want to use SKIP_THE_AND_WHITESPACE here
+ if (results->Items[i].album && results->Items[i].album[0])
+ hr=playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_album_name, results->Items[i].album);
+
+ if (results->Items[i].artist && results->Items[i].artist[0])
+ hr=playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_track_artist_name, results->Items[i].artist);
+
+ // Populate title information so that we have more complete data.
+ if (results->Items[i].title && results->Items[i].title[0])
+ hr=playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_track_name, results->Items[i].title);
+
+ wchar_t storage[64] = {0};
+ // Populate the file length in milliseconds
+ if (results->Items[i].length > 0)
+ {
+ _itow(results->Items[i].length,storage, 10);
+ hr=playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_file_length, storage);
+ }
+
+ // Populate the file size in kilobytes
+ if (results->Items[i].filesize > 0)
+ {
+ _itow(results->Items[i].filesize,storage, 10);
+ hr=playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_file_size, storage);
+ }
+
+ wchar_t *tagID = getRecordExtendedItem(&results->Items[i], L"GracenoteFileID");
+ if (tagID && tagID[0])
+ {
+ SetGracenoteData(filename, tagID, getRecordExtendedItem(&results->Items[i], L"GracenoteExtData"));
+ hr=playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_xdev1, L"0"); // done with this file!
+ }
+ else
+ hr=playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_xdev1, L"1"); // move to phase 1
+ }
+ filesComplete=i+1;
+ }
+
+ AGAVE_API_MLDB->FreeRecordList(results);
+ state=STATE_METADATA;
+ filesComplete=0;
+ if (!killswitch)
+ Pass1();
+ filesComplete=0;
+ state=STATE_MUSICID;
+ if (!killswitch)
+ Pass2();
+ state=STATE_DONE;
+ if (!killswitch)
+ AGAVE_API_MLDB->Sync();
+ }
+ // Set the pass 2 flag back so that on next generation we dont try to run it
+ run_pass2_flag = false;
+ }
+ else
+ state=STATE_ERROR;
+}
+
+bool IDScanner::GetStatus(long *pass, long *track, long *tracks)
+{
+ *pass = state;
+ *track = filesComplete;
+ *tracks = filesTotal;
+ return true;
+}
+
+// System callback handlers from WASABI
+
+FOURCC IDScanner::GetEventType()
+{
+ return api_mldb::SYSCALLBACK;
+}
+int IDScanner::notify(int msg, intptr_t param1, intptr_t param2)
+{
+ wchar_t *filename = (wchar_t *)param1;
+
+ switch (msg)
+ {
+ case api_mldb::MLDB_FILE_ADDED:
+ {
+
+ DebugCallbackMessage(param1, L"File Added: '%s'");
+
+ // Call the add/update function that needs to run on our lonesome playlist generator thread
+ WASABI_API_THREADPOOL->RunFunction(plg_thread, IDScanner::MLDBFileAddedOnThread, _wcsdup(filename), (intptr_t)this, api_threadpool::FLAG_REQUIRE_COM_STA);
+ }
+ break;
+ case api_mldb::MLDB_FILE_REMOVED_PRE:
+ {
+ // We are not concerned with the PRE scenario
+ //DebugCallbackMessage(param1, L"File Removed PRE: '%s'");
+ }
+ break;
+ case api_mldb::MLDB_FILE_REMOVED_POST:
+ {
+ WASABI_API_THREADPOOL->RunFunction(plg_thread, IDScanner::MLDBFileRemovedOnThread, _wcsdup(filename), (intptr_t)this, api_threadpool::FLAG_REQUIRE_COM_STA);
+ // We will only care about the post scenario since we just need to remove the file entry from gracenote.
+ //DebugCallbackMessage(param1, L"File Removed POST: '%s'");
+ }
+ break;
+ case api_mldb::MLDB_FILE_UPDATED:
+ {
+ // For now we call the add method even on an update
+ WASABI_API_THREADPOOL->RunFunction(plg_thread, IDScanner::MLDBFileAddedOnThread, _wcsdup(filename), (intptr_t)this, api_threadpool::FLAG_REQUIRE_COM_STA);
+ //DebugCallbackMessage(param1, L"File Updated: '%s'");
+ }
+ break;
+ case api_mldb::MLDB_CLEARED:
+ {
+ WASABI_API_THREADPOOL->RunFunction(plg_thread, IDScanner::MLDBClearedOnThread, 0, (intptr_t)this, api_threadpool::FLAG_REQUIRE_COM_STA);
+ //DebugCallbackMessage(param1, L"MLDB Cleared");
+ }
+ break;
+ default: return 0;
+ }
+ return 1;
+}
+
+// Outputs a messagebox with a filename to know when callbacks are being triggered
+inline void IDScanner::DebugCallbackMessage(const intptr_t text, const wchar_t *message)
+{
+//#ifdef DEBUG_CALLBACKS
+#if defined(DEBUG) && defined(DEBUG_CALLBACKS)
+ const int size = MAX_PATH + 256;
+ wchar_t *filename = (wchar_t *)text;
+ wchar_t buff[size] = {0};
+
+ //wsprintf(buff, size, message, filename);
+ StringCchPrintf(buff, size, message, filename);
+ MessageBox(0, buff, L"Wasabi Callback Debug", 0);
+#endif
+}
+
+int IDScanner::MLDBFileAddedOnThread(HANDLE handle, void *user_data, intptr_t id)
+{
+ if (!playlistMgr) return 0;
+
+ // Variables to hold information about the file query
+ wchar_t *filename = (wchar_t *)user_data;
+ IDScanner *scanner = (IDScanner *)id;
+
+ wchar_t buff[1024] = {0};
+ _ltow(scanner->state, buff, 10);
+
+ itemRecordW *result = AGAVE_API_MLDB->GetFile(filename);
+
+ HRESULT hr=playlistMgr->AddEntry(filename); // Add the file entry to the gracenote DB
+
+ assert(SUCCEEDED(S_OK));
+
+ if (hr == S_OK /*&& results->Size == 1*/)
+ {
+ // Fill in Artist & Album info since we have it in the itemRecordList anyway
+ // TODO: probably want to use SKIP_THE_AND_WHITESPACE here
+ if (result->album && result->album[0])
+ playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_album_name, result->album);
+
+ if (result->artist && result->artist[0])
+ playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_track_artist_name, result->artist);
+
+ // Populate title, file size, and length information so that we have more complete data.
+ if (result->title && result->title[0])
+ playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_track_name, result->title);
+
+
+ wchar_t storage[64] = {0};
+ // Populate the file length in milliseconds
+ if (result->length > 0)
+ {
+ _itow(result->length,storage, 10);
+ playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_file_length, storage);
+ }
+
+ // Populate the file size in kilobytes
+ if (result->filesize > 0)
+ {
+ _itow(result->filesize,storage, 10);
+ playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_file_size, storage);
+ }
+
+ wchar_t *tagID = getRecordExtendedItem(result, L"GracenoteFileID");
+ if (tagID && tagID[0])
+ {
+ scanner->SetGracenoteData(filename, tagID, getRecordExtendedItem(result, L"GracenoteExtData"));
+ // We have everything we need at this point in the gracenote DB
+ playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_xdev1, L"0"); // done with this file!
+ }
+ else // Set it to the final scan
+ {
+ playlistMgr->FileSetFieldVal(filename, gnpl_crit_field_xdev1, L"2"); // move to phase 2, we can skip phase 1
+ // Add the current file to the step 4 processing stack
+ // TODOD is there a mem leak here??
+ ProcessItem *itemz = new ProcessItem();
+ itemz->filename = filename;
+ //scanner->process_items.push(*itemz); // Add the current item coming in to the queue
+
+ // Set the flag so that we know we will need to rerun step 4 (pass 2) on a playlist regeneration, this only needs to happen if there is an actual change.
+ run_pass2_flag = true;
+ }
+ }
+
+ if (result)
+ AGAVE_API_MLDB->FreeRecord(result);
+
+ // ToDo: We need to do this free when we pop it off of the processing stack later
+ free(filename); // Clean up the user data
+ return 0;
+}
+
+int IDScanner::MLDBFileRemovedOnThread(HANDLE handle, void *user_data, intptr_t id)
+{
+ wchar_t *filename = (wchar_t *)user_data;
+
+ HRESULT hr = playlistMgr->DeleteFile(filename);
+
+ if (hr == S_OK)
+ return 0;
+ else
+ return 1;
+
+ free(filename); // Clean up the user data
+}
+
+int IDScanner::MLDBClearedOnThread(HANDLE handle, void *user_data, intptr_t id)
+{
+ return ResetDB(false);
+}
+
+int IDScanner::ProcessStackItems(void)
+{
+ // ToDo: Run through the stack items and process stage 4 on them
+ //this->
+ return 1;
+}
+
+#define CBCLASS IDScanner
+START_DISPATCH;
+CB(SYSCALLBACK_GETEVENTTYPE, GetEventType);
+CB(SYSCALLBACK_NOTIFY, notify);
+END_DISPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/IDScanner.h b/Src/Plugins/Library/ml_plg/IDScanner.h
new file mode 100644
index 00000000..951bcab1
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/IDScanner.h
@@ -0,0 +1,97 @@
+#ifndef NULLSOFT_ML_PLG_IDSCANNER_H
+#define NULLSOFT_ML_PLG_IDSCANNER_H
+
+#include "../gracenote/gracenote.h"
+
+#include <api/syscb/callbacks/svccb.h>
+#include <api/syscb/api_syscb.h>
+#include "../ml_local/api_mldb.h"
+
+//#include "../nu/lockfreestack.h"
+
+// Regular declarations
+
+struct ProcessItem
+{
+ wchar_t *filename;
+ ProcessItem *next;
+};
+
+class IDScanner : public _ICDDBMusicIDManagerEvents, public SysCallback
+{
+public:
+ IDScanner();
+ ~IDScanner();
+ void ScanDatabase();
+ bool GetStatus(long *state, long *track, long *tracks);
+ void Kill() { killswitch=1; }
+ void Shutdown();
+
+ // Processing data for step 4
+ //LockFreeStack<ProcessItem> process_items;
+
+ // Thread functions
+ static int MLDBFileAddedOnThread(HANDLE handle, void *user_data, intptr_t id);
+ static int MLDBFileRemovedOnThread(HANDLE handle, void *user_data, intptr_t id);
+ static int MLDBClearedOnThread(HANDLE handle, void *user_data, intptr_t id);
+ static int Pass2OnThread(HANDLE handle, void *user_data, intptr_t id);
+ int ProcessStackItems(void);
+
+ enum
+ {
+ STATE_ERROR = -1,
+ STATE_IDLE = 0,
+ STATE_INITIALIZING=1,
+ STATE_SYNC = 2,
+ STATE_METADATA = 3,
+ STATE_MUSICID = 4,
+ STATE_DONE = 5,
+ };
+private:
+ // *** IUnknown Methods ***
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+
+ // *** Sys callback ***
+ api_syscb *systemCallbacks;
+ FOURCC GetEventType();
+ int notify(int msg, intptr_t param1, intptr_t param2);
+ static void DebugCallbackMessage(const intptr_t text, const wchar_t *message);
+
+
+
+ // *** ***
+ void Pass1();
+ void Pass2();
+
+
+ // *** Helper functions ***
+ bool SetupMusicID();
+ void CommitFileInfo(ICddbFileInfo *match);
+ void SetGracenoteData(BSTR filename, BSTR tagId, BSTR extData); // extData can be NULL
+
+
+ // *** Data ***
+ ICDDBMusicIDManager3 *musicID;
+ volatile int killswitch;
+ volatile long filesComplete, filesTotal;
+ volatile long state;
+ DWORD m_dwCookie;
+ bool syscb_registered;
+
+protected:
+ RECVS_DISPATCH;
+
+
+};
+
+IConnectionPoint *GetConnectionPoint(IUnknown *punk, REFIID riid);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/PlaylistGeneratorAPI.cpp b/Src/Plugins/Library/ml_plg/PlaylistGeneratorAPI.cpp
new file mode 100644
index 00000000..e5ba2531
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/PlaylistGeneratorAPI.cpp
@@ -0,0 +1,44 @@
+
+#include "PlaylistGeneratorAPI.h"
+#include <api/service/waservicefactory.h>
+
+#include "main.h"
+
+int PlaylistGeneratorAPI::GeneratePlaylist(HWND parent, const itemRecordListW *selectedSeedRecordList)
+{
+ if (hwndDlgCurrent) // Warn if trying to open two seperate playlist generators
+ {
+ MultipleInstancesWarning();
+ return DISPATCH_SUCCESS;
+ }
+
+ AddSeedTracks(selectedSeedRecordList);
+
+ if (SongsSelected())
+ return DISPATCH_SUCCESS;
+
+ return DISPATCH_FAILURE;
+}
+
+int PlaylistGeneratorAPI::AddSeedTracks(const itemRecordListW *recordList)
+{
+ wchar_t winamp_title[MAX_TITLE_SIZE] = {0};
+
+ for (int i = 0; i < recordList->Size; i++)
+ {
+ itemRecordW *item = &recordList->Items[i];
+ GetTitleFormattingML(item->filename, item, winamp_title, MAX_TITLE_SIZE);
+
+ seedPlaylist.AppendWithInfo(item->filename, winamp_title, item->length * 1000, item->filesize * 1024);
+ }
+
+ return true;
+}
+
+
+
+#define CBCLASS PlaylistGeneratorAPI
+START_DISPATCH;
+CB(API_PLAYLIST_GENERATOR_GENERATEPLAYLIST, GeneratePlaylist)
+END_DISPATCH;
+#undef CBCLASS
diff --git a/Src/Plugins/Library/ml_plg/PlaylistGeneratorAPI.h b/Src/Plugins/Library/ml_plg/PlaylistGeneratorAPI.h
new file mode 100644
index 00000000..c494d9f9
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/PlaylistGeneratorAPI.h
@@ -0,0 +1,14 @@
+#include "api_playlist_generator.h"
+
+class PlaylistGeneratorAPI : public api_playlist_generator
+{
+public:
+ // Exposed API functions
+ int GeneratePlaylist(HWND parent, const itemRecordListW *selectedSeedRecordList);
+
+ // Helper functions
+ int AddSeedTracks(const itemRecordListW *recordList);
+
+protected:
+ RECVS_DISPATCH;
+};
diff --git a/Src/Plugins/Library/ml_plg/api__ml_plg.h b/Src/Plugins/Library/ml_plg/api__ml_plg.h
new file mode 100644
index 00000000..3a8bd5a8
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/api__ml_plg.h
@@ -0,0 +1,50 @@
+#ifndef NULLSOFT_ML_PLG_API_H
+#define NULLSOFT_ML_PLG_API_H
+
+#include <api/application/api_application.h>
+extern api_application *applicationApi;
+#define WASABI_API_APP applicationApi
+
+#include "../playlist/api_playlistmanager.h"
+extern api_playlistmanager *playlistManagerApi;
+#define AGAVE_API_PLAYLISTMGR playlistManagerApi
+
+#include "../Agave/Config/api_config.h"
+//extern api_config *agaveConfigApi;
+//#define AGAVE_API_CONFIG agaveConfigApi
+
+#include "../Winamp/api_decodefile.h"
+extern api_decodefile *decodeApi;
+#define AGAVE_API_DECODE decodeApi
+
+#include "../gracenote/api_gracenote.h"
+extern api_gracenote *gracenoteApi;
+#define AGAVE_API_GRACENOTE gracenoteApi
+
+#include "../Agave/Metadata/api_metadata.h"
+extern api_metadata *metadataApi;
+#define AGAVE_API_METADATA metadataApi
+
+#include "../ml_local/api_mldb.h"
+extern api_mldb *mldbApi;
+#define AGAVE_API_MLDB mldbApi
+
+#include <api/syscb/api_syscb.h>
+extern api_syscb *sysCallbackApi;
+#define WASABI_API_SYSCB sysCallbackApi
+
+#include "../nu/threadpool/api_threadpool.h"
+extern api_threadpool *threadPoolApi;
+#define WASABI_API_THREADPOOL threadPoolApi
+
+// (BigG) Added for playlist export support
+#include "../playlist/api_playlists.h"
+extern api_playlists *playlistsApi;
+#define AGAVE_API_PLAYLISTS playlistsApi
+
+// Added for Stat collection
+#include "../Winamp/api_stats.h"
+extern api_stats *statsApi;
+#define AGAVE_API_STATS statsApi
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/api_playlist_generator.h b/Src/Plugins/Library/ml_plg/api_playlist_generator.h
new file mode 100644
index 00000000..58de6724
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/api_playlist_generator.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <bfc/dispatch.h>
+#include <api/service/services.h>
+
+#include "..\..\General\gen_ml/ml.h"
+
+// {70B27610-D1C9-4442-ABF2-763AD041E458}
+static const GUID playlistGeneratorGUID =
+{ 0x70b27610, 0xd1c9, 0x4442, { 0xab, 0xf2, 0x76, 0x3a, 0xd0, 0x41, 0xe4, 0x58 } };
+
+class api_playlist_generator : public Dispatchable
+{
+protected:
+ api_playlist_generator() {}
+ ~api_playlist_generator() {}
+public:
+ static FOURCC getServiceType() { return WaSvc::UNIQUE; }
+ static const char *getServiceName() { return "Playlist Generator Service"; }
+ static GUID getServiceGuid() { return playlistGeneratorGUID; }
+
+ int GeneratePlaylist(HWND parent, const itemRecordListW *selectedSeedRecordList);
+
+ enum
+ {
+ API_PLAYLIST_GENERATOR_GENERATEPLAYLIST = 0,
+ };
+
+};
+
+inline int api_playlist_generator::GeneratePlaylist(HWND parent, const itemRecordListW *selectedSeedRecordList)
+{
+ return _call(API_PLAYLIST_GENERATOR_GENERATEPLAYLIST, (int)DISPATCH_FAILURE, parent, selectedSeedRecordList);
+}
+
+
diff --git a/Src/Plugins/Library/ml_plg/atltransactionmanager.h b/Src/Plugins/Library/ml_plg/atltransactionmanager.h
new file mode 100644
index 00000000..4294ec4f
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/atltransactionmanager.h
@@ -0,0 +1,697 @@
+// This is a part of the Active Template Library.
+// Copyright (C) Microsoft Corporation
+// All rights reserved.
+//
+// This source code is only intended as a supplement to the
+// Active Template Library Reference and related
+// electronic documentation provided with the library.
+// See these sources for detailed information regarding the
+// Active Template Library product.
+
+#ifndef __ATLTRANSACTIONMANAGER_H__
+#define __ATLTRANSACTIONMANAGER_H__
+
+#pragma once
+
+#include <atldef.h>
+
+#if !defined(_ATL_USE_WINAPI_FAMILY_DESKTOP_APP)
+#error This file is not compatible with the current WINAPI_FAMILY
+#endif
+
+#include <ktmw32.h>
+#include <tchar.h>
+
+extern "C" _VCRTIMP bool __cdecl __uncaught_exception();
+
+#pragma pack(push,_ATL_PACKING)
+namespace ATL
+{
+
+/// <summary>
+/// CAtlTransactionManager class provides a wrapper to Kernel Transaction Manager (KTM) functions.</summary>
+class CAtlTransactionManager
+{
+public:
+ /// <summary>
+ /// CAtlTransactionManager constructor</summary>
+ /// <param name="bFallback">TRUE - support fallback. If transacted function fails, the class automatically calls the "non-transacted" function. FALSE - no "fallback" calls.</param>
+ /// <param name="bAutoCreateTransaction">TRUE - auto-create transaction handler in constructor. FALSE - don't create</param>
+ explicit CAtlTransactionManager(_In_ BOOL bFallback = TRUE, _In_ BOOL bAutoCreateTransaction = TRUE) :
+ m_hTransaction(NULL), m_bFallback(bFallback)
+ {
+ if (bAutoCreateTransaction)
+ {
+ Create();
+ }
+ }
+
+ /// <summary>
+ /// CAtlTransactionManager destructor. In normal processing, the transaction is automatically committed and closed. If the destructor is called during an exception unwind, the transaction is rolled back and closed.</summary>
+ ~CAtlTransactionManager()
+ {
+ if (m_hTransaction != NULL)
+ {
+ if (__uncaught_exception())
+ {
+ Rollback();
+ }
+ else
+ {
+ Commit();
+ }
+
+ Close();
+ }
+ }
+
+private:
+ // Copy construction and copy are not supported, so make sure that the compiler does not generate
+ // implicit versions and that a compiler error is issued if someone attempts to use them.
+ CAtlTransactionManager(_In_ const CAtlTransactionManager &atm);
+ CAtlTransactionManager &operator=(_In_ const CAtlTransactionManager &atm);
+
+// Attributes:
+public:
+ /// <summary>
+ /// Returns transaction handle</summary>
+ /// <returns>
+ /// Returns the transaction handle for a class. Returns NULL if the CAtlTransactionManager is not attached to a handle.</returns>
+ HANDLE GetHandle() const
+ {
+ return m_hTransaction;
+ }
+
+ /// <summary>
+ /// Determines whether the fallback calls are enabled </summary>
+ /// <returns>
+ /// Returns TRUE is the class support fallback calls. FALSE - otherwise.</returns>
+ BOOL IsFallback() const
+ {
+ return m_bFallback;
+ }
+
+// Operattions:
+public:
+ /// <summary>
+ /// Creates transaction handle. This wrapper calls Windows CreateTransaction function</summary>
+ /// <returns>
+ /// TRUE if succeeds; otherwise FALSE.</returns>
+ BOOL Create();
+
+ /// <summary>
+ /// Closes transaction handle. This wrapper calls Windows CloseHandle function. The method is automatically called in destructor</summary>
+ /// <returns>
+ /// TRUE if succeeds; otherwise FALSE.</returns>
+ BOOL Close();
+
+ /// <summary>
+ /// Requests that the transaction be committed. This wrapper calls Windows CommitTransaction function. The method is automatically called in destructor.</summary>
+ /// <returns>
+ /// TRUE if succeeds; otherwise FALSE.</returns>
+ BOOL Commit();
+
+ /// <summary>
+ /// Requests that the transaction be rolled back. This wrapper calls Windows RollbackTransaction function</summary>
+ /// <returns>
+ /// TRUE if succeeds; otherwise FALSE.</returns>
+ BOOL Rollback();
+
+ /// <summary>
+ /// Creates or opens a file, file stream, or directory as a transacted operation. This wrapper calls Windows CreateFileTransacted function</summary>
+ /// <returns>
+ /// Returns a handle that can be used to access the object.</returns>
+ /// <param name="lpFileName">The name of an object to be created or opened.</param>
+ /// <param name="dwDesiredAccess">The access to the object, which can be summarized as read, write, both or neither (zero). The most commonly used values are GENERIC_READ, GENERIC_WRITE, or both (GENERIC_READ | GENERIC_WRITE).</param>
+ /// <param name="dwShareMode">The sharing mode of an object, which can be read, write, both, delete, all of these, or none: 0, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE</param>
+ /// <param name="lpSecurityAttributes">A pointer to a SECURITY_ATTRIBUTES structure that contains an optional security descriptor and also determines whether or not the returned handle can be inherited by child processes. The parameter can be NULL</param>
+ /// <param name="dwCreationDisposition">An action to take on files that exist and do not exist. This parameter must be one of the following values, which cannot be combined: CREATE_ALWAYS, CREATE_NEW, OPEN_ALWAYS, OPEN_EXISTING or TRUNCATE_EXISTING</param>
+ /// <param name="dwFlagsAndAttributes">The file attributes and flags. This parameter can include any combination of the available file attributes (FILE_ATTRIBUTE_*). All other file attributes override FILE_ATTRIBUTE_NORMAL. This parameter can also contain combinations of flags (FILE_FLAG_*) for control of buffering behavior, access modes, and other special-purpose flags. These combine with any FILE_ATTRIBUTE_* values.</param>
+ /// <param name="hTemplateFile">A valid handle to a template file with the GENERIC_READ access right. The template file supplies file attributes and extended attributes for the file that is being created. This parameter can be NULL.</param>
+ HANDLE CreateFile(
+ _In_z_ LPCTSTR lpFileName,
+ _In_ DWORD dwDesiredAccess,
+ _In_ DWORD dwShareMode,
+ _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ _In_ DWORD dwCreationDisposition,
+ _In_ DWORD dwFlagsAndAttributes,
+ _In_opt_ HANDLE hTemplateFile);
+
+ /// <summary>
+ /// Deletes an existing file as a transacted operation. This wrapper calls Windows DeleteFileTransacted function</summary>
+ /// <returns>
+ /// TRUE if succeeds; otherwise FALSE.</returns>
+ /// <param name="lpFileName">The name of the file to be deleted.</param>
+ BOOL DeleteFile(_In_z_ LPCTSTR lpFileName);
+
+ /// <summary>
+ /// Moves an existing file or a directory, including its children, as a transacted operation. This wrapper calls Windows MoveFileTransacted function</summary>
+ /// <returns>
+ /// TRUE if succeeds; otherwise FALSE.</returns>
+ /// <param name="lpOldFileName">The current name of the existing file or directory on the local computer.</param>
+ /// <param name="lpNewFileName">The new name for the file or directory. The new name must not already exist. A new file may be on a different file system or drive. A new directory must be on the same drive.</param>
+ BOOL MoveFile(
+ _In_z_ LPCTSTR lpOldFileName,
+ _In_z_ LPCTSTR lpNewFileName);
+
+ /// <summary>
+ /// Retrieves file system attributes for a specified file or directory as a transacted operation. This wrapper calls Windows GetFileAttributesTransacted function</summary>
+ /// <returns>
+ /// File attributes (see WIN32_FILE_ATTRIBUTE_DATA::dwFileAttributes desciption).</returns>
+ /// <param name="lpFileName">The name of the file or directory.</param>
+ DWORD GetFileAttributes(_In_z_ LPCTSTR lpFileName);
+
+ /// <summary>
+ /// Retrieves file system attributes for a specified file or directory as a transacted operation. This wrapper calls Windows GetFileAttributesTransacted function</summary>
+ /// <returns>
+ /// TRUE if succeeds; otherwise FALSE.</returns>
+ /// <param name="lpFileName">The name of the file or directory.</param>
+ /// <param name="fInfoLevelId">The level of attribute information to retrieve.</param>
+ /// <param name="lpFileInformation">A pointer to a buffer that receives the attribute information. The type of attribute information that is stored into this buffer is determined by the value of fInfoLevelId. If the fInfoLevelId parameter is GetFileExInfoStandard then this parameter points to a WIN32_FILE_ATTRIBUTE_DATA structure.</param>
+ _Success_(return != FALSE) BOOL GetFileAttributesEx(
+ _In_z_ LPCTSTR lpFileName,
+ _In_ GET_FILEEX_INFO_LEVELS fInfoLevelId,
+ _Out_opt_ LPVOID lpFileInformation);
+
+ /// <summary>
+ /// Sets the attributes for a file or directory as a transacted operation. This wrapper calls Windows SetFileAttributesTransacted function</summary>
+ /// <returns>
+ /// TRUE if succeeds; otherwise FALSE.</returns>
+ /// <param name="lpFileName">The name of the file or directory.</param>
+ /// <param name="dwAttributes">The file attributes to set for the file. See SetFileAttributesTransacted function description</param>
+ BOOL SetFileAttributes(
+ _In_z_ LPCTSTR lpFileName,
+ _In_ DWORD dwAttributes);
+
+ /// <summary>
+ /// Searches a directory for a file or subdirectory with a name that matches a specific name as a transacted operation. This wrapper calls Windows FindFirstFileTransacted function</summary>
+ /// <returns>
+ /// If the function succeeds, the return value is a search handle used in a subsequent call to FindNextFile or FindClose. If the function fails or fails to locate files from the search string in the lpFileName parameter, the return value is INVALID_HANDLE_VALUE.</returns>
+ /// <param name="lpFileName">The directory or path, and the file name, which can include wildcard characters, for example, an asterisk (*) or a question mark (?).</param>
+ /// <param name="pNextInfo">A pointer to the WIN32_FIND_DATA structure that receives information about a found file or subdirectory.</param>
+ _Success_(return != INVALID_HANDLE_VALUE) HANDLE FindFirstFile(
+ _In_z_ LPCTSTR lpFileName,
+ _Out_opt_ WIN32_FIND_DATA* pNextInfo);
+
+ /// <summary>
+ /// Creates the specified registry key and associates it with a transaction. If the key already exists, the function opens it. This wrapper calls Windows RegCreateKeyTransacted function</summary>
+ /// <returns>
+ /// If the function succeeds, the return value is ERROR_SUCCESS. If the function fails, the return value is a nonzero error code defined in Winerror.h</returns>
+ /// <param name="hKey">A handle to an open registry key.</param>
+ /// <param name="lpSubKey">The name of a subkey that this function opens or creates.</param>
+ /// <param name="dwReserved">This parameter is reserved and must be zero</param>
+ /// <param name="ulOptions">This parameter can be one of the following values: REG_OPTION_BACKUP_RESTORE, REG_OPTION_NON_VOLATILE or REG_OPTION_VOLATILE.</param>
+ /// <param name="samDesired">A mask that specifies the access rights for the key</param>
+ /// <param name="lpSecurityAttributes"> pointer to a SECURITY_ATTRIBUTES structure that determines whether the returned handle can be inherited by child processes. If lpSecurityAttributes is NULL, the handle cannot be inherited</param>
+ /// <param name="phkResult">A pointer to a variable that receives a handle to the opened or created key. If the key is not one of the predefined registry keys, call the RegCloseKey function after you have finished using the handle</param>
+ /// <param name="lpdwDisposition">A pointer to a variable that receives one of the following disposition values: REG_CREATED_NEW_KEY or REG_OPENED_EXISTING_KEY</param>
+ LSTATUS RegCreateKeyEx(
+ _In_ HKEY hKey,
+ _In_z_ LPCTSTR lpSubKey,
+ _Reserved_ DWORD dwReserved,
+ _In_opt_z_ LPTSTR lpClass,
+ _In_ DWORD dwOptions,
+ _In_ REGSAM samDesired,
+ _In_opt_ CONST LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ _Out_ PHKEY phkResult,
+ _Out_opt_ LPDWORD lpdwDisposition);
+ /// <summary>
+ /// Opens the specified registry key and associates it with a transaction. This wrapper calls Windows RegOpenKeyTransacted function</summary>
+ /// <returns>
+ /// If the function succeeds, the return value is ERROR_SUCCESS. If the function fails, the return value is a nonzero error code defined in Winerror.h</returns>
+ /// <param name="hKey">A handle to an open registry key.</param>
+ /// <param name="lpSubKey">The name of the registry subkey to be opened.</param>
+ /// <param name="ulOptions">This parameter is reserved and must be zero.</param>
+ /// <param name="samDesired">A mask that specifies the access rights for the key</param>
+ /// <param name="phkResult">A pointer to a variable that receives a handle to the opened or created key. If the key is not one of the predefined registry keys, call the RegCloseKey function after you have finished using the handle</param>
+ LSTATUS RegOpenKeyEx(
+ _In_ HKEY hKey,
+ _In_opt_z_ LPCTSTR lpSubKey,
+ _In_ DWORD ulOptions,
+ _In_ REGSAM samDesired,
+ _Out_ PHKEY phkResult);
+ /// <summary>
+ /// Deletes a subkey and its values from the specified platform-specific view of the registry as a transacted operation. This wrapper calls Windows RegDeleteKeyTransacted function</summary>
+ /// <returns>
+ /// If the function succeeds, the return value is ERROR_SUCCESS. If the function fails, the return value is a nonzero error code defined in Winerror.h</returns>
+ /// <param name="hKey">A handle to an open registry key.</param>
+ /// <param name="lpSubKey">The name of the key to be deleted.</param>
+ LSTATUS RegDeleteKey(
+ _In_ HKEY hKey,
+ _In_z_ LPCTSTR lpSubKey);
+
+protected:
+ /// <summary>
+ /// Transaction handle</summary>
+ HANDLE m_hTransaction;
+
+ /// <summary>
+ /// TRUE: if the fallback is supported; FALSE - otherwise.</summary>
+ BOOL m_bFallback;
+};
+
+inline BOOL CAtlTransactionManager::Create()
+{
+ if (m_hTransaction != NULL)
+ {
+ // Already created
+ ATLASSERT(FALSE);
+ return FALSE;
+ }
+
+ typedef HANDLE (WINAPI* PFNCREATETRANSACTION)(LPSECURITY_ATTRIBUTES, LPGUID, DWORD, DWORD, DWORD, DWORD, LPWSTR);
+ static bool bInitialized = false;
+ static PFNCREATETRANSACTION pfCreateTransaction = NULL;
+
+ if (!bInitialized)
+ {
+ HMODULE hKTM32 = AtlLoadSystemLibraryUsingFullPath(L"ktmw32.dll");
+ if (hKTM32 != NULL)
+ {
+ pfCreateTransaction = (PFNCREATETRANSACTION)GetProcAddress(hKTM32, "CreateTransaction");
+ }
+ bInitialized = true;
+ }
+
+ if (pfCreateTransaction == NULL)
+ {
+ return FALSE;
+ }
+
+ SECURITY_ATTRIBUTES sa;
+ ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
+
+ m_hTransaction = (*pfCreateTransaction)(&sa, 0, 0, 0, 0, 0, NULL);
+ return m_hTransaction != NULL;
+}
+
+inline BOOL CAtlTransactionManager::Close()
+{
+ if (m_hTransaction == NULL)
+ {
+ return FALSE;
+ }
+
+ if (!::CloseHandle(m_hTransaction))
+ {
+ return FALSE;
+ }
+
+ m_hTransaction = NULL;
+ return TRUE;
+}
+
+inline BOOL CAtlTransactionManager::Commit()
+{
+ if (m_hTransaction == NULL)
+ {
+ ATLASSERT(FALSE);
+ return FALSE;
+ }
+
+ typedef BOOL (WINAPI* PFNCOMMITTRANSACTION)(HANDLE);
+ static bool bInitialized = false;
+ static PFNCOMMITTRANSACTION pfCommitTransaction = NULL;
+
+ if (!bInitialized)
+ {
+ HMODULE hKTM32 = AtlLoadSystemLibraryUsingFullPath(L"ktmw32.dll");
+ if (hKTM32 != NULL)
+ {
+ pfCommitTransaction = (PFNCOMMITTRANSACTION)GetProcAddress(hKTM32, "CommitTransaction");
+ }
+ bInitialized = true;
+ }
+
+ if (pfCommitTransaction != NULL)
+ {
+ return (*pfCommitTransaction)(m_hTransaction);
+ }
+
+ return FALSE;
+}
+
+inline BOOL CAtlTransactionManager::Rollback()
+{
+ if (m_hTransaction == NULL)
+ {
+ ATLASSERT(FALSE);
+ return FALSE;
+ }
+
+ typedef BOOL (WINAPI* PFNROLLBACKTRANSACTION)(HANDLE);
+ static bool bInitialized = false;
+ static PFNROLLBACKTRANSACTION pfRollbackTransaction = NULL;
+
+ if (!bInitialized)
+ {
+ HMODULE hKTM32 = AtlLoadSystemLibraryUsingFullPath(L"ktmw32.dll");
+ if (hKTM32 != NULL)
+ {
+ pfRollbackTransaction = (PFNROLLBACKTRANSACTION)GetProcAddress(hKTM32, "RollbackTransaction");
+ }
+ bInitialized = true;
+ }
+
+ if (pfRollbackTransaction != NULL)
+ {
+ return (*pfRollbackTransaction)(m_hTransaction);
+ }
+
+ return FALSE;
+}
+
+inline HANDLE CAtlTransactionManager::CreateFile(
+ _In_z_ LPCTSTR lpFileName,
+ _In_ DWORD dwDesiredAccess,
+ _In_ DWORD dwShareMode,
+ _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ _In_ DWORD dwCreationDisposition,
+ _In_ DWORD dwFlagsAndAttributes,
+ _In_opt_ HANDLE hTemplateFile)
+{
+ if (m_hTransaction != NULL)
+ {
+ HMODULE hKernel32 = ::GetModuleHandle(_T("kernel32.dll"));
+ ATLASSERT(hKernel32 != NULL);
+ if (hKernel32 == NULL)
+ {
+ return INVALID_HANDLE_VALUE;
+ }
+
+#ifdef _UNICODE
+ typedef HANDLE (WINAPI* PFNCREATEFILETRANSACTED)(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE, HANDLE, PUSHORT, PVOID);
+ PFNCREATEFILETRANSACTED pfCreateTransacted = (PFNCREATEFILETRANSACTED)GetProcAddress(hKernel32, "CreateFileTransactedW");
+#else
+ typedef HANDLE (WINAPI* PFNCREATEFILETRANSACTED)(LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE, HANDLE, PUSHORT, PVOID);
+ PFNCREATEFILETRANSACTED pfCreateTransacted = (PFNCREATEFILETRANSACTED)GetProcAddress(hKernel32, "CreateFileTransactedA");
+#endif
+ if (pfCreateTransacted != NULL)
+ {
+ return (*pfCreateTransacted)(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile, m_hTransaction, NULL, NULL);
+ }
+ }
+ else if (m_bFallback)
+ {
+ return ::CreateFile((LPCTSTR)lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, NULL);
+ }
+
+ return INVALID_HANDLE_VALUE;
+}
+
+inline BOOL CAtlTransactionManager::DeleteFile(_In_z_ LPCTSTR lpFileName)
+{
+ if (m_hTransaction != NULL)
+ {
+ HMODULE hKernel32 = ::GetModuleHandle(_T("kernel32.dll"));
+ ATLASSERT(hKernel32 != NULL);
+ if (hKernel32 == NULL)
+ {
+ return FALSE;
+ }
+
+#ifdef _UNICODE
+ typedef BOOL (WINAPI* PFNDELETEFILETRANSACTED)(LPCWSTR, HANDLE);
+ PFNDELETEFILETRANSACTED pfDeleteTransacted = (PFNDELETEFILETRANSACTED)GetProcAddress(hKernel32, "DeleteFileTransactedW");
+#else
+ typedef BOOL (WINAPI* PFNDELETEFILETRANSACTED)(LPCSTR, HANDLE);
+ PFNDELETEFILETRANSACTED pfDeleteTransacted = (PFNDELETEFILETRANSACTED)GetProcAddress(hKernel32, "DeleteFileTransactedA");
+#endif
+ if (pfDeleteTransacted != NULL)
+ {
+ return (*pfDeleteTransacted)(lpFileName, m_hTransaction);
+ }
+ }
+ else if (m_bFallback)
+ {
+ return DeleteFile((LPTSTR)lpFileName);
+ }
+
+ return FALSE;
+}
+
+inline BOOL CAtlTransactionManager::MoveFile(
+ _In_z_ LPCTSTR lpOldFileName,
+ _In_z_ LPCTSTR lpNewFileName)
+{
+ if (m_hTransaction != NULL)
+ {
+ HMODULE hKernel32 = ::GetModuleHandle(_T("kernel32.dll"));
+ ATLASSERT(hKernel32 != NULL);
+ if (hKernel32 == NULL)
+ {
+ return FALSE;
+ }
+
+#ifdef _UNICODE
+ typedef BOOL (WINAPI* PFNMOVEFILETRANSACTED)(LPCWSTR, LPCWSTR, LPPROGRESS_ROUTINE, LPVOID, DWORD, HANDLE);
+ PFNMOVEFILETRANSACTED pfMoveFileTransacted = (PFNMOVEFILETRANSACTED)GetProcAddress(hKernel32, "MoveFileTransactedW");
+#else
+ typedef BOOL (WINAPI* PFNMOVEFILETRANSACTED)(LPCSTR, LPCSTR, LPPROGRESS_ROUTINE, LPVOID, DWORD, HANDLE);
+ PFNMOVEFILETRANSACTED pfMoveFileTransacted = (PFNMOVEFILETRANSACTED)GetProcAddress(hKernel32, "MoveFileTransactedA");
+#endif
+ if (pfMoveFileTransacted != NULL)
+ {
+ return (*pfMoveFileTransacted)(lpOldFileName, lpNewFileName, NULL, NULL, MOVEFILE_COPY_ALLOWED, m_hTransaction);
+ }
+ }
+ else if (m_bFallback)
+ {
+ return ::MoveFile(lpOldFileName, lpNewFileName);
+ }
+
+ return FALSE;
+}
+
+inline _Success_(return != FALSE) BOOL CAtlTransactionManager::GetFileAttributesEx(
+ _In_z_ LPCTSTR lpFileName,
+ _In_ GET_FILEEX_INFO_LEVELS fInfoLevelId,
+ _Out_opt_ LPVOID lpFileInformation)
+{
+ if (lpFileInformation == NULL)
+ {
+ return FALSE;
+ }
+
+ if (m_hTransaction != NULL)
+ {
+ HMODULE hKernel32 = ::GetModuleHandle(_T("kernel32.dll"));
+ ATLASSERT(hKernel32 != NULL);
+ if (hKernel32 == NULL)
+ {
+ return FALSE;
+ }
+
+#ifdef _UNICODE
+ typedef BOOL (WINAPI* PFNGETFILEATTRIBUTESTRANSACTED)(LPCWSTR, GET_FILEEX_INFO_LEVELS, LPVOID, HANDLE);
+ PFNGETFILEATTRIBUTESTRANSACTED pfGetFileAttributesTransacted = (PFNGETFILEATTRIBUTESTRANSACTED)GetProcAddress(hKernel32, "GetFileAttributesTransactedW");
+#else
+ typedef BOOL (WINAPI* PFNGETFILEATTRIBUTESTRANSACTED)(LPCSTR, GET_FILEEX_INFO_LEVELS, LPVOID, HANDLE);
+ PFNGETFILEATTRIBUTESTRANSACTED pfGetFileAttributesTransacted = (PFNGETFILEATTRIBUTESTRANSACTED)GetProcAddress(hKernel32, "GetFileAttributesTransactedA");
+#endif
+ if (pfGetFileAttributesTransacted != NULL)
+ {
+ return (*pfGetFileAttributesTransacted)(lpFileName, fInfoLevelId, lpFileInformation, m_hTransaction);
+ }
+ }
+ else if (m_bFallback)
+ {
+ return ::GetFileAttributesEx((LPCTSTR)lpFileName, fInfoLevelId, lpFileInformation);
+ }
+
+ return FALSE;
+}
+
+inline DWORD CAtlTransactionManager::GetFileAttributes(_In_z_ LPCTSTR lpFileName)
+{
+ WIN32_FILE_ATTRIBUTE_DATA fileAttributeData;
+ if (GetFileAttributesEx(lpFileName, GetFileExInfoStandard, &fileAttributeData))
+ {
+ return fileAttributeData.dwFileAttributes;
+ }
+
+ return 0;
+}
+
+inline BOOL CAtlTransactionManager::SetFileAttributes(
+ _In_z_ LPCTSTR lpFileName,
+ _In_ DWORD dwAttributes)
+{
+ if (m_hTransaction != NULL)
+ {
+ HMODULE hKernel32 = ::GetModuleHandle(_T("kernel32.dll"));
+ ATLASSERT(hKernel32 != NULL);
+ if (hKernel32 == NULL)
+ {
+ return FALSE;
+ }
+
+#ifdef _UNICODE
+ typedef BOOL (WINAPI* PFNSETFILEATTRIBUTESTRANSACTED)(LPCWSTR, DWORD, HANDLE);
+ PFNSETFILEATTRIBUTESTRANSACTED pfSetFileAttributesTransacted = (PFNSETFILEATTRIBUTESTRANSACTED)GetProcAddress(hKernel32, "SetFileAttributesTransactedW");
+#else
+ typedef BOOL (WINAPI* PFNSETFILEATTRIBUTESTRANSACTED)(LPCSTR, DWORD, HANDLE);
+ PFNSETFILEATTRIBUTESTRANSACTED pfSetFileAttributesTransacted = (PFNSETFILEATTRIBUTESTRANSACTED)GetProcAddress(hKernel32, "SetFileAttributesTransactedA");
+#endif
+ if (pfSetFileAttributesTransacted != NULL)
+ {
+ return (*pfSetFileAttributesTransacted)(lpFileName, dwAttributes, m_hTransaction);
+ }
+ }
+ else if (m_bFallback)
+ {
+ return ::SetFileAttributes((LPCTSTR)lpFileName, dwAttributes);
+ }
+
+ return FALSE;
+}
+
+inline _Success_(return != INVALID_HANDLE_VALUE) HANDLE CAtlTransactionManager::FindFirstFile(
+ _In_z_ LPCTSTR lpFileName,
+ _Out_opt_ WIN32_FIND_DATA* pNextInfo)
+{
+ if (pNextInfo == NULL)
+ {
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (m_hTransaction != NULL)
+ {
+ HMODULE hKernel32 = ::GetModuleHandle(_T("kernel32.dll"));
+ ATLASSERT(hKernel32 != NULL);
+ if (hKernel32 == NULL)
+ {
+ return INVALID_HANDLE_VALUE;
+ }
+
+#ifdef _UNICODE
+ typedef HANDLE (WINAPI* PFNFINDFIRSTFILETRANSACTED)(LPCWSTR, FINDEX_INFO_LEVELS, LPVOID, FINDEX_SEARCH_OPS, LPVOID, DWORD, HANDLE);
+ PFNFINDFIRSTFILETRANSACTED pfFindFirstFileTransacted = (PFNFINDFIRSTFILETRANSACTED)GetProcAddress(hKernel32, "FindFirstFileTransactedW");
+#else
+ typedef HANDLE (WINAPI* PFNFINDFIRSTFILETRANSACTED)(LPCSTR, FINDEX_INFO_LEVELS, LPVOID, FINDEX_SEARCH_OPS, LPVOID, DWORD, HANDLE);
+ PFNFINDFIRSTFILETRANSACTED pfFindFirstFileTransacted = (PFNFINDFIRSTFILETRANSACTED)GetProcAddress(hKernel32, "FindFirstFileTransactedA");
+#endif
+ if (pfFindFirstFileTransacted != NULL)
+ {
+ return (*pfFindFirstFileTransacted)(lpFileName, FindExInfoStandard, pNextInfo, FindExSearchNameMatch, NULL, 0, m_hTransaction);
+ }
+ }
+ else if (m_bFallback)
+ {
+ return ::FindFirstFile(lpFileName, pNextInfo);
+ }
+
+ return INVALID_HANDLE_VALUE;
+}
+
+inline LSTATUS CAtlTransactionManager::RegOpenKeyEx(
+ _In_ HKEY hKey,
+ _In_opt_z_ LPCTSTR lpSubKey,
+ _In_ DWORD ulOptions,
+ _In_ REGSAM samDesired,
+ _Out_ PHKEY phkResult)
+{
+ if (m_hTransaction != NULL)
+ {
+ HMODULE hAdvAPI32 = ::GetModuleHandle(_T("Advapi32.dll"));
+ ATLASSERT(hAdvAPI32 != NULL);
+ if (hAdvAPI32 == NULL)
+ {
+ return ERROR_INVALID_FUNCTION;
+ }
+
+#ifdef _UNICODE
+ typedef LSTATUS (WINAPI* PFNREGOPENKEYTRANSACTED)(HKEY, LPCWSTR, DWORD, REGSAM, PHKEY, HANDLE, PVOID);
+ PFNREGOPENKEYTRANSACTED pfRegOpenKeyTransacted = (PFNREGOPENKEYTRANSACTED)GetProcAddress(hAdvAPI32, "RegOpenKeyTransactedW");
+#else
+ typedef LSTATUS (WINAPI* PFNREGOPENKEYTRANSACTED)(HKEY, LPCSTR, DWORD, REGSAM, PHKEY, HANDLE, PVOID);
+ PFNREGOPENKEYTRANSACTED pfRegOpenKeyTransacted = (PFNREGOPENKEYTRANSACTED)GetProcAddress(hAdvAPI32, "RegOpenKeyTransactedA");
+#endif
+ if (pfRegOpenKeyTransacted != NULL)
+ {
+ return (*pfRegOpenKeyTransacted)(hKey, lpSubKey, ulOptions, samDesired, phkResult, m_hTransaction, NULL);
+ }
+ }
+ else if (m_bFallback)
+ {
+ return ::RegOpenKeyEx(hKey, lpSubKey, ulOptions, samDesired, phkResult);
+ }
+
+ return ERROR_INVALID_FUNCTION;
+}
+
+inline LSTATUS CAtlTransactionManager::RegCreateKeyEx(
+ _In_ HKEY hKey,
+ _In_z_ LPCTSTR lpSubKey,
+ _Reserved_ DWORD dwReserved,
+ _In_opt_z_ LPTSTR lpClass,
+ _In_ DWORD dwOptions,
+ _In_ REGSAM samDesired,
+ _In_opt_ CONST LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ _Out_ PHKEY phkResult,
+ _Out_opt_ LPDWORD lpdwDisposition)
+{
+ if (m_hTransaction != NULL)
+ {
+ HMODULE hAdvAPI32 = ::GetModuleHandle(_T("Advapi32.dll"));
+ ATLASSERT(hAdvAPI32 != NULL);
+ if (hAdvAPI32 == NULL)
+ {
+ return ERROR_INVALID_FUNCTION;
+ }
+
+#ifdef _UNICODE
+ typedef LSTATUS (WINAPI* PFNREGCREATEKEYTRANSACTED)(HKEY, LPCWSTR, DWORD, LPWSTR, DWORD, REGSAM, CONST LPSECURITY_ATTRIBUTES, PHKEY, LPDWORD, HANDLE, PVOID);
+ PFNREGCREATEKEYTRANSACTED pfRegCreateKeyTransacted = (PFNREGCREATEKEYTRANSACTED)GetProcAddress(hAdvAPI32, "RegCreateKeyTransactedW");
+#else
+ typedef LSTATUS (WINAPI* PFNREGCREATEKEYTRANSACTED)(HKEY, LPCSTR, DWORD, LPSTR, DWORD, REGSAM, CONST LPSECURITY_ATTRIBUTES, PHKEY, LPDWORD, HANDLE, PVOID);
+ PFNREGCREATEKEYTRANSACTED pfRegCreateKeyTransacted = (PFNREGCREATEKEYTRANSACTED)GetProcAddress(hAdvAPI32, "RegCreateKeyTransactedA");
+#endif
+ if (pfRegCreateKeyTransacted != NULL)
+ {
+ return (*pfRegCreateKeyTransacted)(hKey, lpSubKey, dwReserved, lpClass, dwOptions, samDesired, lpSecurityAttributes, phkResult, lpdwDisposition, m_hTransaction, NULL);
+ }
+ }
+ else if (m_bFallback)
+ {
+ return ::RegCreateKeyEx(hKey, lpSubKey, dwReserved, lpClass, dwOptions, samDesired, lpSecurityAttributes, phkResult, lpdwDisposition);
+ }
+
+ return ERROR_INVALID_FUNCTION;
+}
+
+inline LSTATUS CAtlTransactionManager::RegDeleteKey(_In_ HKEY hKey, _In_z_ LPCTSTR lpSubKey)
+{
+ if (m_hTransaction != NULL)
+ {
+ HMODULE hAdvAPI32 = ::GetModuleHandle(_T("Advapi32.dll"));
+ ATLASSERT(hAdvAPI32 != NULL);
+ if (hAdvAPI32 == NULL)
+ {
+ return ERROR_INVALID_FUNCTION;
+ }
+
+#ifdef _UNICODE
+ typedef LSTATUS (WINAPI* PFNREGDELETEKEYTRANSACTED)(HKEY, LPCWSTR, REGSAM, DWORD, HANDLE, PVOID);
+ PFNREGDELETEKEYTRANSACTED pfRegDeleteKeyTransacted = (PFNREGDELETEKEYTRANSACTED)GetProcAddress(hAdvAPI32, "RegDeleteKeyTransactedW");
+#else
+ typedef LSTATUS (WINAPI* PFNREGDELETEKEYTRANSACTED)(HKEY, LPCSTR, REGSAM, DWORD, HANDLE, PVOID);
+ PFNREGDELETEKEYTRANSACTED pfRegDeleteKeyTransacted = (PFNREGDELETEKEYTRANSACTED)GetProcAddress(hAdvAPI32, "RegDeleteKeyTransactedA");
+#endif
+ if (pfRegDeleteKeyTransacted != NULL)
+ {
+ return (*pfRegDeleteKeyTransacted)(hKey, lpSubKey, 0, 0, m_hTransaction, NULL);
+ }
+ }
+ else if (m_bFallback)
+ {
+ return ::RegDeleteKey(hKey, lpSubKey);
+ }
+
+ return ERROR_INVALID_FUNCTION;
+}
+
+} //namespace ATL
+#pragma pack(pop)
+
+#endif // __ATLTRANSACTIONMANAGER_H__
diff --git a/Src/Plugins/Library/ml_plg/generate.cpp b/Src/Plugins/Library/ml_plg/generate.cpp
new file mode 100644
index 00000000..ed392dda
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/generate.cpp
@@ -0,0 +1,766 @@
+#include "../gracenote/gracenote.h"
+#include "api__ml_plg.h"
+#include <windows.h>
+#include "resource.h"
+#include "../../General/gen_ml/ml.h"
+#include "../winamp/wa_ipc.h"
+#include "../Agave/Language/api_language.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "../nu/ComboBox.h"
+
+#include "main.h"
+#include <shlwapi.h>
+#include <assert.h>
+#include "playlist.h"
+#include <atlbase.h>
+#include "IDScanner.h"
+//#include "../Wasabi/bfc/util/timefmt.h"
+//#include <bfc/util/timefmt.h>
+
+#include <strsafe.h> // should be last
+
+HWND hwndDlgCurrent = 0;
+bool optionsVisible = true;
+bool isGenerating = false;
+int originalWidth = 877;
+//#define DIALOG_WIDTH_OPTIONS 877 // use originalWidth instead
+#define DIALOG_WIDTH_NO_OPTIONS 610
+#define DIALOG_HIDDEN_COLUMN_ID 4
+
+// Pass in 0 for width or height in order to preserve its current dimension
+void SizeWindow(HWND hwnd, int width, int height)
+{
+ if (width == 0 || height == 0) // Preserve only if one of the items is 0
+ {
+ RECT windowRect;
+ GetWindowRect(hwnd, &windowRect);
+
+ if (width == 0) // Preserve the width
+ width = windowRect.right - windowRect.left;
+ if (height == 0) // Preserve the height
+ height = windowRect.bottom - windowRect.top;
+ }
+ SetWindowPos(hwnd, NULL, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
+}
+
+void ClientResize(HWND hWnd, int nWidth, int nHeight)
+{
+ RECT rcClient, rcWind;
+ POINT ptDiff;
+ GetClientRect(hWnd, &rcClient);
+ GetWindowRect(hWnd, &rcWind);
+ ptDiff.x = (rcWind.right - rcWind.left) - rcClient.right;
+ ptDiff.y = (rcWind.bottom - rcWind.top) - rcClient.bottom;
+ MoveWindow(hWnd,rcWind.left, rcWind.top, nWidth + ptDiff.x, nHeight + ptDiff.y, TRUE);
+}
+
+void SetMarqueeProgress(bool isMarquee)
+{
+ HWND hwndProgress = GetDlgItem(hwndDlgCurrent,IDC_PROGRESS_GENERATE);
+ static long state = GetWindowLongW(hwndProgress, GWL_STYLE); // Capture the initial state of the progress bar
+
+
+ if (isMarquee) // Set it to marquee style
+ {
+ SetWindowLong (hwndProgress, GWL_STYLE, GetWindowLong(hwndProgress, GWL_STYLE) | PBS_MARQUEE);
+ //SendMessage(hwndProgress, PBM_SETMARQUEE, 1, 10);
+ SendMessage(hwndProgress, PBM_SETMARQUEE, 1, 30);
+ }
+ else // Restore the normal progress bar
+ {
+ SetWindowLong (hwndProgress, GWL_STYLE, state);
+ //SendMessage(hwndProgress, WM_PAINT, 0, 0);
+ InvalidateRect(hwndProgress, 0, 1); // Force a repaint of the marquee after turning it off because there are stuck pixels in XP
+ }
+}
+
+// Sets the query check state as well as enabling all the controls involved
+void SetMLQueryCheckState(HWND hwndDlg, unsigned int checked)
+{
+ // Get the handles to all the child controls we want to enable / disable
+ HWND hwndButtonMlQuery = GetDlgItem(hwndDlg, IDC_BUTTON_ML_QUERY);
+ HWND hwndEditMlQuery = GetDlgItem(hwndDlg, IDC_EDIT_ML_QUERY);
+ HWND hwndButtonRestoreQueryDefault = GetDlgItem(hwndDlg, IDC_BUTTON_RESTORE_QUERY_DEFAULT);
+
+ if (checked) // enable all the controls related to ML query
+ {
+ CheckDlgButton(hwndDlg, IDC_CHECK_ML_QUERY, TRUE);
+ EnableWindow (hwndButtonMlQuery, TRUE );
+ EnableWindow (hwndEditMlQuery, TRUE );
+ EnableWindow (hwndButtonRestoreQueryDefault, TRUE );
+ useMLQuery = true;
+ }
+ else // disable all the controls related to ML query
+ {
+ CheckDlgButton(hwndDlg, IDC_CHECK_ML_QUERY, FALSE);
+ EnableWindow (hwndButtonMlQuery, FALSE );
+ EnableWindow (hwndEditMlQuery, FALSE );
+ EnableWindow (hwndButtonRestoreQueryDefault, FALSE );
+ useMLQuery = false;
+ }
+}
+
+void SetButtonsEnabledState(bool enabled_flag)
+{
+ int itemIds[] =
+ {
+ IDC_BUTTON_PLAY_NOW,
+ IDC_BUTTON_ENQUEUE_NOW,
+ IDC_BUTTON_SAVEAS,
+ IDC_BUTTON_REGENERATE
+ };
+
+ for(int i = 0; i < sizeof(itemIds) / sizeof(itemIds[0]); i++)
+ EnableWindow(GetDlgItem(hwndDlgCurrent, itemIds[i]), enabled_flag);
+}
+
+void ToggleOptions(bool reset)
+{
+ if (reset)
+ optionsVisible = false;
+ else
+ optionsVisible = !optionsVisible; // Toggle the options visible state
+
+ // to resolve tabbing issues when in the collapsed
+ // state we need to disable some of the controls (dro)
+ int itemIds[] = {
+ IDC_RADIO_PLAYLIST_ITEMS,
+ IDC_RADIO_PLAYLIST_LENGTH,
+ IDC_RADIO_PLAYLIST_SIZE,
+ IDC_COMBO_LENGTH,
+ IDC_CHECK_USE_SEED,
+ IDC_CHECK_MULTIPLE_ARTISTS,
+ IDC_CHECK_MULTIPLE_ALBUMS,
+ IDC_CHECK_ML_QUERY,
+ IDC_EDIT_ML_QUERY,
+ IDC_BUTTON_ML_QUERY,
+ IDC_BUTTON_RESTORE_QUERY_DEFAULT
+ };
+ for(int i = 0; i < sizeof(itemIds) / sizeof(itemIds[0]); i++)
+ EnableWindow(GetDlgItem(hwndDlgCurrent, itemIds[i]), optionsVisible);
+
+ SetMLQueryCheckState(hwndDlgCurrent, useMLQuery);
+
+ if (optionsVisible)
+ {
+ SizeWindow(hwndDlgCurrent, originalWidth, 0); // Resize the window to the correct width
+ SetDlgItemText(hwndDlgCurrent, IDC_BUTTON_OPTIONS, WASABI_API_LNGSTRINGW(IDS_OPTIONS)); // Set the dialog button to show the correct options mode
+ }
+ else
+ {
+ SizeWindow(hwndDlgCurrent, DIALOG_WIDTH_NO_OPTIONS, 0);
+ SetDlgItemText(hwndDlgCurrent, IDC_BUTTON_OPTIONS, WASABI_API_LNGSTRINGW(IDS_NO_OPTIONS)); // Set the dialog button to show the correct options mode
+ }
+}
+
+// ToDo: Make this more human readable
+void FormatToMinutesAndSeconds(const int lengthInSeconds, wchar_t *buff, const size_t cchBuf)
+{
+ //StringCchPrintfW(buff, cchBuf, L"%d:%02d", lengthInSeconds / 60, lengthInSeconds % 60);
+
+ int total_length_s = lengthInSeconds;
+ int uncert = 0;
+
+ // Minutes and seconds
+ if (total_length_s < 60*60) StringCchPrintfW(buff, 64, L"%s%u:%02u", uncert ? L"~" : L"", total_length_s / 60, total_length_s % 60);
+ // Hours minutes and seconds
+ else if (total_length_s < 60*60*24) StringCchPrintfW(buff, 64, L"%s%u:%02u:%02u", uncert ? L"~" : L"", total_length_s / 60 / 60, (total_length_s / 60) % 60, total_length_s % 60);
+ else
+ {
+ wchar_t days[16] = {0};
+ int total_days = total_length_s / (60 * 60 * 24); // Calculate days
+ total_length_s -= total_days * 60 * 60 * 24; // Remove days from length
+ StringCchPrintfW(buff, 64,
+ //WASABI_API_LNGSTRINGW(IDS_LENGTH_DURATION_STRING),
+ L"%s%u %s+%u:%02u:%02u",
+ ((uncert) ? L"~" : L""), total_days, // Approximate
+ WASABI_API_LNGSTRINGW_BUF(total_days == 1 ? IDS_DAY : IDS_DAYS, days, 16), // Days
+ total_length_s / 60 / 60, // Hours
+ (total_length_s / 60) % 60, // Minutes
+ total_length_s % 60); // Seconds
+ }
+}
+
+// Refreashed the statistics about the generated playlist
+void UpdateStats(void)
+{
+ const int MAX_STATS = 512;
+ wchar_t stats[MAX_STATS] = {0};
+ wchar_t lengthText[MAX_STATS] = {0};
+ wchar_t sizeText[MAX_STATS] = {0};
+ int count = (int)currentPlaylist.GetNumItems();
+ uint64_t length = currentPlaylist.GetPlaylistLengthMilliseconds();
+ uint64_t size = currentPlaylist.GetPlaylistSizeBytes();
+
+ // Add the seed stats?
+ if (useSeed == TRUE)
+ {
+ count += (int)seedPlaylist.GetNumItems();
+ length += seedPlaylist.GetPlaylistLengthMilliseconds();
+ size += seedPlaylist.GetPlaylistSizeBytes();
+ }
+
+ FormatToMinutesAndSeconds((int)(length / 1000), lengthText, MAX_STATS); // / 1000 because we have it in milliseconds and not seconds
+ StrFormatByteSizeW(size, sizeText, MAX_STATS); // Get the human readable formatting for filesize
+
+ StringCchPrintf(stats, MAX_STATS, WASABI_API_LNGSTRINGW(IDS_STATS), count, lengthText, sizeText);
+
+ SetDlgItemText(hwndDlgCurrent, IDC_STATIC_STATS, stats); // Set the dialog button to show the correct options mode
+}
+
+// Update the progress to the current
+static void doProgressBar(HWND h, int x, int t=-1) {
+ h = GetDlgItem(h,IDC_PROGRESS_GENERATE);
+ if(t!=-1 && SendMessage(h,PBM_GETRANGE,0,0) != t)
+ SendMessage(h,PBM_SETRANGE32,0,t);
+ SendMessage(h,PBM_SETPOS,x,0);
+}
+
+// Update the status while id scanner is active
+static void FillStatus(HWND hwndDlg)
+{
+ long state, track, tracks;
+ if (scanner.GetStatus(&state, &track, &tracks))
+ {
+ static int x=0;
+ wchar_t *ticker;
+ switch (x++)
+ {
+ case 0: ticker=L""; break;
+ case 1: ticker=L"."; break;
+ case 2: ticker=L".."; break;
+ default: ticker=L"...";
+ }
+ x%=4;
+ wchar_t status[1024]=L"";
+ switch (state)
+ {
+ case IDScanner::STATE_ERROR:
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_INITIALIZING,status,1024);
+ KillTimer(hwndDlg, 1);
+ doProgressBar(hwndDlg,0,1);
+ ShowErrorDlg(hwndDlg);
+ break;
+ case IDScanner::STATE_IDLE:
+ WASABI_API_LNGSTRINGW_BUF(IDS_IDLE,status,1024);
+ doProgressBar(hwndDlg,0);
+ break;
+ case IDScanner::STATE_INITIALIZING:
+ StringCchPrintfW(status, 1024, WASABI_API_LNGSTRINGW(IDS_INITIALIZING), ticker);
+ doProgressBar(hwndDlg,0);
+ break;
+ case IDScanner::STATE_SYNC:
+ StringCchPrintfW(status, 1024, WASABI_API_LNGSTRINGW(IDS_SYNC), track, tracks, ticker);
+ doProgressBar(hwndDlg,track,tracks);
+ break;
+ case IDScanner::STATE_METADATA:
+ StringCchPrintfW(status, 1024, WASABI_API_LNGSTRINGW(IDS_METADATA), track, tracks, ticker);
+ doProgressBar(hwndDlg,track,tracks);
+ break;
+ case IDScanner::STATE_MUSICID:
+ StringCchPrintfW(status, 1024, WASABI_API_LNGSTRINGW(IDS_MUSICID), track, tracks, ticker);
+ doProgressBar(hwndDlg,track,tracks);
+ break;
+ case IDScanner::STATE_DONE:
+ if (!isGenerating) // Only set the done state if the gneeration has not started yet
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_DONE,status,1024);
+ doProgressBar(hwndDlg,0,0); // Turn off the progress bar to 0
+ }
+
+ KillTimer(hwndDlg, 1);
+ break;
+ }
+ if (!isGenerating) // Only set the done state if the gneeration has not started yet
+ {
+ SetDlgItemTextW(hwndDlg, IDC_STATIC_PROGRESS_STATE, status);
+ }
+ }
+}
+
+// Function calls appropriate items when a generation is requested
+void Regenerate(HWND hwndDlg)
+{
+ SendMessage(GetDlgItem(hwndDlgCurrent, IDC_LIST_RESULTS2),LVM_DELETEALLITEMS,0,0); // Clear the listview of all playlist items
+
+ SetTimer(hwndDlg, 1, 500, 0); // Set the progress timer for the scanner
+ StartScan();
+
+ MoreLikeTheseSongs(&seedPlaylist);
+}
+
+// Function draws in colors for the seed listview items
+LRESULT CustomDrawListViewColors(LPARAM lParam)
+{
+ LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)lParam;
+
+ switch(lplvcd->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT : //Before the paint cycle begins
+ return CDRF_NOTIFYITEMDRAW; //request notifications for individual listview items
+
+ case CDDS_ITEMPREPAINT: //Before an item is drawn
+ if (lplvcd->nmcd.dwItemSpec < seedPlaylist.entries.size()) // Check how many seeds we have, thats how we know which rows in the view to color paint
+ {
+ if (useSeed == TRUE)
+ {
+ lplvcd->clrText = RGB(0,0,255); // Color seed tracks blue
+ }
+ else
+ {
+ lplvcd->clrText = RGB(100,100,100); // Color seed tracks a faded grey
+ }
+ }
+ return CDRF_NEWFONT;
+ break;
+ }
+ return CDRF_DODEFAULT;
+}
+
+int SetRadioControlsState(HWND hwndDlg)
+{
+ // Set the radio buttons for playlist length type, items or minutes
+ if(plLengthType == PL_ITEMS)
+ {
+ CheckDlgButton(hwndDlg,IDC_RADIO_PLAYLIST_ITEMS,TRUE);
+ SendMessage(hwndDlg, WM_COMMAND, IDC_RADIO_PLAYLIST_ITEMS, 0);
+ SetPlLengthTypeComboToItems(hwndDlg, plItems);
+ }
+ else if(plLengthType == PL_MINUTES)
+ {
+ CheckDlgButton(hwndDlg,IDC_RADIO_PLAYLIST_LENGTH,TRUE);
+ SendMessage(hwndDlg, WM_COMMAND, IDC_RADIO_PLAYLIST_LENGTH, 0);
+ SetPlLengthTypeComboToMinutes(hwndDlg, plMinutes);
+ }
+ else if(plLengthType == PL_MEGABYTES)
+ {
+ CheckDlgButton(hwndDlg,IDC_RADIO_PLAYLIST_SIZE,TRUE);
+ SendMessage(hwndDlg, WM_COMMAND, IDC_RADIO_PLAYLIST_SIZE, 0);
+ SetPlLengthTypeComboToMegabytes(hwndDlg, plMegabytes);
+ }
+
+ return 0;
+}
+
+// Update the combo box contents depending on which lengthType we are using
+int UpdateComboLength(HWND hwndDlg)
+{
+ const int BUF_SIZE = 32;
+ ComboBox combo(hwndDlg, IDC_COMBO_LENGTH);
+ wchar_t buf[BUF_SIZE] = {0};
+ combo.GetEditText(buf, BUF_SIZE);
+
+ switch(plLengthType)
+ {
+ case PL_ITEMS:
+ plItems = _wtoi(buf);
+ return 0;
+ break;
+ case PL_MINUTES:
+ plMinutes = _wtoi(buf);
+ return 0;
+ break;
+ case PL_MEGABYTES:
+ plMegabytes = _wtoi(buf);
+ return 0;
+ break;
+ }
+ return 1;
+}
+
+LRESULT tab_fix_proc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if(uMsg == WM_CHAR)
+ {
+ if(wParam == VK_TAB)
+ {
+ SendMessage(hwndDlgCurrent, WM_NEXTDLGCTL, (GetAsyncKeyState(VK_SHIFT)&0x8000), FALSE);
+ return TRUE;
+ }
+ }
+
+ return CallWindowProcW((WNDPROC)GetPropW(hwndDlg, L"tab_fix_proc"), hwndDlg, uMsg, wParam, lParam);
+}
+
+// this will prevent the hidden column (for making the headers work better)
+// from appearing as sizeable / disabled (as it effectively is)
+LRESULT header_block_proc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if(uMsg == WM_SETCURSOR)
+ {
+ HDHITTESTINFO hitTest;
+ GetCursorPos(&hitTest.pt);
+ ScreenToClient(hwndDlg, &hitTest.pt);
+ hitTest.flags = hitTest.iItem = 0;
+ SendMessage(hwndDlg, HDM_HITTEST, FALSE, (LPARAM)&hitTest);
+ if(hitTest.iItem == DIALOG_HIDDEN_COLUMN_ID || hitTest.iItem == -1)
+ {
+ SetCursor(LoadCursor(NULL, IDC_ARROW));
+ return TRUE;
+ }
+ }
+
+ return CallWindowProcW((WNDPROC)GetPropW(hwndDlg, L"header_block_proc"), hwndDlg, uMsg, wParam, lParam);
+}
+
+INT_PTR CALLBACK GenerateProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ {
+ RECT r;
+ GetWindowRect(hwndDlg, &r);
+ originalWidth = r.right - r.left;
+
+ // bit hacky but it will resolve issues with tabbing and the combobox in a
+ // dropdown style still not 100% sure why it's failing to work though (dro)
+ HWND combobox = GetWindow(GetDlgItem(hwndDlg, IDC_COMBO_LENGTH), GW_CHILD);
+ SetPropW(combobox, L"tab_fix_proc",(HANDLE)SetWindowLongPtrW(combobox, GWLP_WNDPROC, (LONG_PTR)tab_fix_proc));
+
+ hwndDlgCurrent = hwndDlg; // Set the global so that we have a window open
+
+ // this will make sure that we've got thr aacplus logo shown even when using a localised version
+ SendDlgItemMessage(hwndDlg,IDC_LOGO,STM_SETIMAGE,IMAGE_BITMAP,
+ (LPARAM)LoadImage(plugin.hDllInstance,MAKEINTRESOURCE(IDB_GN_LOGO),IMAGE_BITMAP,0,0,LR_SHARED));
+
+ BoldStatusText(GetDlgItem(hwndDlg, IDC_STATIC_PROGRESS_STATE) );
+
+ SetRadioControlsState(hwndDlg); // Set the playlist length state
+
+ if(multipleArtists)
+ CheckDlgButton(hwndDlg,IDC_CHECK_MULTIPLE_ARTISTS,TRUE);
+ if(multipleAlbums)
+ CheckDlgButton(hwndDlg,IDC_CHECK_MULTIPLE_ALBUMS,TRUE);
+ if(useSeed)
+ CheckDlgButton(hwndDlg,IDC_CHECK_USE_SEED,TRUE);
+
+ // Set up the colums for the playlist listing
+ #define ListView_InsertColumnW(hwnd, iCol, pcol) \
+ (int)SNDMSG((hwnd), LVM_INSERTCOLUMNW, (WPARAM)(int)(iCol), (LPARAM)(const LV_COLUMNW *)(pcol))
+ //SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam);
+
+ // Add the columns to the listbox
+ HWND hwndlist = GetDlgItem(hwndDlg,IDC_LIST_RESULTS2);
+ ListView_SetExtendedListViewStyle(hwndlist, LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);
+ LVCOLUMNW lvc = {0, };
+ lvc.mask = LVCF_TEXT|LVCF_WIDTH;
+ lvc.pszText = WASABI_API_LNGSTRINGW(IDS_TITLE); // Initialize the columns of the listview
+ lvc.cx = 160;
+ ListView_InsertColumnW(hwndlist, 0, &lvc);
+ lvc.pszText = WASABI_API_LNGSTRINGW(IDS_LENGTH);
+ lvc.cx = 80;
+ ListView_InsertColumnW(hwndlist, 1, &lvc);
+ lvc.pszText = WASABI_API_LNGSTRINGW(IDS_SIZE);
+ lvc.cx = 80;
+ ListView_InsertColumnW(hwndlist, 2, &lvc);
+ lvc.pszText = WASABI_API_LNGSTRINGW(IDS_SEED);
+ lvc.cx = 80;
+ ListView_InsertColumnW(hwndlist, 3, &lvc);
+ lvc.pszText = 0;
+ lvc.cx = 0;
+ ListView_InsertColumnW(hwndlist, DIALOG_HIDDEN_COLUMN_ID, &lvc);
+
+ // Autosize the columns taking the header into consideration
+ ListView_SetColumnWidth(hwndlist,0,LVSCW_AUTOSIZE_USEHEADER);
+ ListView_SetColumnWidth(hwndlist,1,LVSCW_AUTOSIZE_USEHEADER);
+ ListView_SetColumnWidth(hwndlist,2,LVSCW_AUTOSIZE_USEHEADER);
+ ListView_SetColumnWidth(hwndlist,3,LVSCW_AUTOSIZE_USEHEADER);
+
+ HWND hwndListHeader = ListView_GetHeader(hwndlist);
+ SetPropW(hwndListHeader, L"header_block_proc",(HANDLE)SetWindowLongPtrW(hwndListHeader, GWLP_WNDPROC, (LONG_PTR)header_block_proc));
+
+ // Background color for highlighting seed tracks.
+ //hbrBkcolor = CreateSolidBrush ( RGB(255,0,0) );
+
+ BoldStatusText(GetDlgItem(hwndDlg, IDC_STATIC_STATS) );
+
+ // Populate the query textbox
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_ML_QUERY, mlQuery); // Set the text for the query
+
+ // Disable the regenerate button because we will be scanning the library and generating on initialization
+ //EnableWindow (GetDlgItem(hwndDlgCurrent, IDC_BUTTON_REGENERATE), FALSE ); // This is for initialization
+ SetButtonsEnabledState(false);
+
+ // Set up the window with the options hidden
+ ToggleOptions(true);
+
+ // Show the window since we are modeless
+ POINT pt = {(LONG)GetPrivateProfileInt(L"ml_plg", L"generate_x",-1, mediaLibrary.GetWinampIniW()),
+ (LONG)GetPrivateProfileInt(L"ml_plg", L"generate_y",-1, mediaLibrary.GetWinampIniW())};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ else
+ ShowWindow(hwndDlg, SW_SHOW);
+
+ Regenerate(hwndDlg);
+
+ if (WASABI_API_APP) // Add direct mousewheel support for the main tracklist view of seed and generated tracks
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(GetDlgItem(hwndDlg, IDC_LIST_RESULTS2), TRUE);
+
+ return TRUE;
+ }
+ break;
+
+ // Trying to change the background color of seed tracks here
+ /*case WM_CTLCOLORSTATIC:
+ {
+ HDC hdc = (HDC) wParam;
+ HWND hwndStatic = (HWND) lParam;
+
+ if ( hwndStatic == GetDlgItem ( hwndDlg, IDC_LIST_RESULTS2 ))
+ {
+ SetBkMode ( hdc, TRANSPARENT );
+ return (LRESULT) hbrBkcolor;
+ }
+ }
+ break;*/
+
+ case WM_TIMER:
+ FillStatus(hwndDlg);
+ break;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDCANCEL:
+ {
+ RECT rect = {0};
+ GetWindowRect(hwndDlg, &rect);
+ char buf[16] = {0};
+ StringCchPrintfA(buf, 16, "%d", rect.left);
+ WritePrivateProfileStringA("ml_plg", "generate_x", buf, mediaLibrary.GetWinampIni());
+ StringCchPrintfA(buf, 16, "%d", rect.top);
+ WritePrivateProfileStringA("ml_plg", "generate_y", buf, mediaLibrary.GetWinampIni());
+
+ EndDialog(hwndDlg, 0);
+ hwndDlgCurrent = 0; // Set to null so new instance can be opened
+
+ WriteSettingsToIni(hwndDlg);
+
+ // We need to free up our seed tracks because we no longer require them
+ seedPlaylist.Clear(); // Clear the global seed list
+ }
+ break;
+ case IDC_BUTTON_CANCEL:
+ SendMessage(hwndDlg, WM_COMMAND, IDCANCEL, 0);
+ break;
+ case IDC_BUTTON_REGENERATE:
+ Regenerate(hwndDlg);
+ break;
+ case IDC_RADIO_PLAYLIST_ITEMS:
+ SetDlgItemText(hwndDlg, IDC_LENGTH_TYPE, WASABI_API_LNGSTRINGW(IDS_ITEMS));
+ plLengthType = PL_ITEMS; // Set to # of items
+ SetPlLengthTypeComboToItems(hwndDlg, plItems);
+ break;
+ case IDC_RADIO_PLAYLIST_LENGTH:
+ SetDlgItemText(hwndDlg, IDC_LENGTH_TYPE, WASABI_API_LNGSTRINGW(IDS_MINUTES));
+ plLengthType = PL_MINUTES; // Set to minutes
+ SetPlLengthTypeComboToMinutes(hwndDlg, plMinutes);
+ break;
+ case IDC_RADIO_PLAYLIST_SIZE:
+ SetDlgItemText(hwndDlg, IDC_LENGTH_TYPE, WASABI_API_LNGSTRINGW(IDS_MEGABYTES));
+ plLengthType = PL_MEGABYTES; // Set to megabytes
+ SetPlLengthTypeComboToMegabytes(hwndDlg, plMegabytes);
+ break;
+ case IDC_COMBO_LENGTH:
+ {
+ UpdateComboLength(hwndDlg);
+ }
+ break;
+ case IDC_BUTTON_OPTIONS:
+ ToggleOptions(false);
+ break;
+ case IDC_BUTTON_PLAY_NOW:
+ playPlaylist(currentPlaylist, false, 0, /*seed,*/ useSeed); // Play the current playlist taking the seed track into consideration
+ SendMessage(hwndDlg, WM_COMMAND, IDCANCEL, 0); // Close up the dialog because we are done
+ break;
+ case IDC_BUTTON_ENQUEUE_NOW:
+ playPlaylist(currentPlaylist, true, 0, /*seed,*/ useSeed); // Enqueue the current playlist taking the seed track into consideration
+ SendMessage(hwndDlg, WM_COMMAND, IDCANCEL, 0); // Close up the dialog because we are done
+ break;
+ case IDC_BUTTON_SAVEAS:
+ {
+ // ToDo spawn a dialog to save the current playlist as a ML playlist
+ int save_result = WASABI_API_DIALOGBOXPARAM(IDD_ADD_PLAYLIST, hwndDlg, AddPlaylistDialogProc, (LPARAM)&seedPlaylist/*seed*/);
+ if (save_result == IDOK) // If the user accepted that playlist dialog then go ahead and close up everything
+ {
+ SendMessage(hwndDlg, WM_COMMAND, IDCANCEL, 0); // Close up the dialog because we are done
+ }
+ }
+ break;
+ case IDC_BUTTON_ML_QUERY:
+ {
+ char temp[1024] = {0};
+ GetDlgItemTextA(hwndDlg, IDC_EDIT_ML_QUERY, temp, sizeof(temp) - 1); // Retreive the current custom ML query
+
+ ml_editview meq = {hwndDlg, (temp[0] == 0) ? DEFAULT_ML_QUERY : temp, "ML Query", -1}; // Create the editview
+ meq.name = WASABI_API_LNGSTRING(IDS_ML_QUERY); // Set a custom title
+ if(!(int)SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (LPARAM)&meq, ML_IPC_EDITVIEW))
+ return 0; // Spawn the edit view
+ SetDlgItemTextA(hwndDlg, IDC_EDIT_ML_QUERY, meq.query); // Set the text back to the edited query
+ }
+ break;
+ case IDC_BUTTON_RESTORE_QUERY_DEFAULT:
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_ML_QUERY, _T(DEFAULT_ML_QUERY)); // Set the text back to the edited query
+ break;
+ case IDC_CHECK_USE_SEED:
+ useSeed = IsDlgButtonChecked(hwndDlg,IDC_CHECK_USE_SEED);
+ UpdateStats(); // Update the track stats, because the seed status can change them
+ RedrawWindow(GetDlgItem(hwndDlg,IDC_LIST_RESULTS2), 0, 0, RDW_INVALIDATE); // Refresh the colors in the list view
+ break;
+ case IDC_CHECK_MULTIPLE_ARTISTS: // Set the multiple tracks per artist option when checked
+ multipleArtists = IsDlgButtonChecked(hwndDlg, IDC_CHECK_MULTIPLE_ARTISTS);
+ break;
+ case IDC_CHECK_MULTIPLE_ALBUMS: // Set the multiple tracks per album option when checked
+ multipleAlbums = IsDlgButtonChecked(hwndDlg, IDC_CHECK_MULTIPLE_ALBUMS);
+ break;
+ case IDC_CHECK_ML_QUERY:
+ SetMLQueryCheckState(hwndDlg, IsDlgButtonChecked(hwndDlg, IDC_CHECK_ML_QUERY));
+ break;
+ case IDC_EDIT_ML_QUERY:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ GetDlgItemTextW(hwndDlg, IDC_EDIT_ML_QUERY, mlQuery, MAX_ML_QUERY_SIZE); // Set the text back to the edited query
+ break;
+ }
+ }
+ break;
+
+ case WM_NOTIFY:
+ if(((LPNMHDR)lParam)->code == HDN_BEGINTRACKW || ((LPNMHDR)lParam)->code == HDN_BEGINTRACKA ||
+ ((LPNMHDR)lParam)->code == HDN_ITEMCHANGINGW || ((LPNMHDR)lParam)->code == HDN_ITEMCHANGINGA)
+ {
+ LPNMHEADER pNMHeader = (LPNMHEADER)lParam;
+ if(pNMHeader->iItem == DIALOG_HIDDEN_COLUMN_ID)
+ {
+ SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)TRUE);
+ return TRUE;
+ }
+ }
+ else if(((LPNMHDR)lParam)->code == NM_CUSTOMDRAW) // Notify for List View custom redraw (seed track colors)
+ {
+#if defined(_WIN64)
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, (LONG)CustomDrawListViewColors(lParam));
+#else
+ SetWindowLong(hwndDlg, DWL_MSGRESULT, (LONG)CustomDrawListViewColors(lParam));
+#endif
+ return TRUE;
+ }
+
+ {
+ const int controls[] =
+ {
+ IDC_LIST_RESULTS2,
+ };
+ if (WASABI_API_APP->DirectMouseWheel_ProcessDialogMessage(hwndDlg, msg, wParam, lParam, controls, ARRAYSIZE(controls)) != FALSE)
+ {
+ return TRUE;
+ }
+ }
+ break;
+ case WM_DESTROY:
+ {
+ if (WASABI_API_APP)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(GetDlgItem(hwndDlg, IDC_LIST_RESULTS2), FALSE);
+ }
+ break;
+ }
+
+ return 0;
+}
+
+int AddResultListItem(Playlist *playlist, int index, int position, bool seed)
+{
+ const unsigned int MAX_INFO = 256;
+ wchar_t filename[MAX_INFO] = {0};
+ wchar_t info[MAX_INFO] = {0};
+ wchar_t *seedText = 0;
+ LVITEMW lvi={LVIF_TEXT, position, 0};
+
+ playlist->GetItem(index,filename,MAX_INFO);
+
+ // Add the title column
+ playlist->GetItemTitle(index, info, MAX_INFO);
+ lvi.pszText=info;
+ lvi.cchTextMax=sizeof(info) / sizeof(*info);
+ SendMessage(GetDlgItem(hwndDlgCurrent,IDC_LIST_RESULTS2),LVM_INSERTITEMW,0,(LPARAM)&lvi);
+
+ // Add the length column
+ int length = playlist->GetItemLengthMilliseconds(index);
+ if (length <= 0)
+ StringCchCopyW(info, MAX_INFO, WASABI_API_LNGSTRINGW(IDS_UNKNOWN));
+ else
+ FormatToMinutesAndSeconds(length / 1000, info, MAX_INFO); // / 1000 because we have it in milliseconds and not seconds
+
+ lvi.pszText=info;
+ lvi.cchTextMax=sizeof(info) / sizeof(*info);
+ lvi.iSubItem = 1;
+ SendMessage(GetDlgItem(hwndDlgCurrent,IDC_LIST_RESULTS2),LVM_SETITEMW,0,(LPARAM)&lvi);
+
+ // Add the size column
+ int size = playlist->GetItemSizeBytes(index);
+ if (size <= 0)
+ StringCchCopyW(info, MAX_INFO, WASABI_API_LNGSTRINGW(IDS_UNKNOWN));
+ else
+ StrFormatByteSizeW(size, info, MAX_INFO);
+ lvi.pszText=info;
+ lvi.cchTextMax=sizeof(info) / sizeof(*info);
+ lvi.iSubItem = 2;
+ SendMessage(GetDlgItem(hwndDlgCurrent,IDC_LIST_RESULTS2),LVM_SETITEMW,0,(LPARAM)&lvi);
+
+ // Add the seed track column
+ if (seed == true)
+ seedText = WASABI_API_LNGSTRINGW(IDS_YES);
+ else
+ seedText = WASABI_API_LNGSTRINGW(IDS_NO);
+ lvi.pszText=seedText;
+ lvi.cchTextMax=sizeof(seedText) / sizeof(*seedText);
+ lvi.iSubItem = 3;
+ SendMessage(GetDlgItem(hwndDlgCurrent,IDC_LIST_RESULTS2),LVM_SETITEMW,0,(LPARAM)&lvi);
+
+ return 0;
+}
+
+void CantPopulateResults(void)
+{
+ wchar_t message[256] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_EXCUSE_ME, message, 256);
+ MessageBoxW(hwndDlgCurrent, message, WASABI_API_LNGSTRINGW(IDS_NULLSOFT_PLAYLIST_GENERATOR), MB_OK | MB_ICONINFORMATION);
+}
+
+void PopulateResults(Playlist *playlist)
+{
+ // Add all of the seed tracks to the listview
+ int listLength = (playlist) ? (int)playlist->GetNumItems() : 0;
+ int seedLength = (int)seedPlaylist.GetNumItems();
+ for (int i = 0; i < seedLength; i++)
+ {
+ AddResultListItem(&seedPlaylist, i, i, true);
+ }
+
+ // Add all of the generated tracks to the listview
+ for (int i = 0; i < listLength; i++)
+ {
+ AddResultListItem(playlist, i, seedLength + i, false);
+ }
+
+ // After we are done populating the data then we can size the columns accordingly
+ HWND hwndlist = GetDlgItem(hwndDlgCurrent,IDC_LIST_RESULTS2);
+ ListView_SetColumnWidth(hwndlist,0,(listLength ? LVSCW_AUTOSIZE : LVSCW_AUTOSIZE_USEHEADER));
+ ListView_SetColumnWidth(hwndlist,1,LVSCW_AUTOSIZE_USEHEADER);
+ ListView_SetColumnWidth(hwndlist,2,LVSCW_AUTOSIZE_USEHEADER);
+ ListView_SetColumnWidth(hwndlist,3,LVSCW_AUTOSIZE_USEHEADER);
+
+ // Refresh the playlist stats
+ UpdateStats();
+
+ // Change the progress status to read done 'generated'
+ SetDlgItemText(hwndDlgCurrent,IDC_STATIC_PROGRESS_STATE, WASABI_API_LNGSTRINGW(IDS_DONE));
+
+ SetMarqueeProgress(false); // Turn the marquee off because we are actually generating the tracks
+
+ //EnableWindow (GetDlgItem(hwndDlgCurrent, IDC_BUTTON_REGENERATE), TRUE );
+ SetButtonsEnabledState(true); // Renable the buttons
+
+}
diff --git a/Src/Plugins/Library/ml_plg/gn_logo_88x83.bmp b/Src/Plugins/Library/ml_plg/gn_logo_88x83.bmp
new file mode 100644
index 00000000..bae676e7
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/gn_logo_88x83.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_plg/impl_playlist.cpp b/Src/Plugins/Library/ml_plg/impl_playlist.cpp
new file mode 100644
index 00000000..4183a583
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/impl_playlist.cpp
@@ -0,0 +1,328 @@
+#include "main.h"
+#include "impl_playlist.h"
+#include <algorithm>
+#include "../nu/MediaLibraryInterface.h"
+#include "../nu/AutoChar.h"
+#include "../Winamp/strutil.h"
+#include <shlwapi.h>
+
+void Playlist::Clear()
+{
+ for ( pl_entry *entry : entries )
+ delete entry;
+
+ entries.clear();
+}
+
+void Playlist::OnFile( const wchar_t *filename, const wchar_t *title, int lengthInMS, int sizeInBytes, ifc_plentryinfo *info )
+{
+ entries.push_back( new pl_entry( filename, title, lengthInMS, sizeInBytes, info ) );
+}
+
+void Playlist::AppendWithInfo( const wchar_t *filename, const wchar_t *title, int lengthInMS, int sizeInBytes )
+{
+ entries.push_back( new pl_entry( filename, title, lengthInMS, sizeInBytes ) );
+}
+
+Playlist::~Playlist()
+{
+ Clear();
+}
+
+
+size_t Playlist::GetNumItems()
+{
+ return entries.size();
+}
+
+size_t Playlist::GetItem( size_t item, wchar_t *filename, size_t filenameCch )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->GetFilename( filename, filenameCch );
+}
+
+size_t Playlist::GetItemTitle( size_t item, wchar_t *title, size_t titleCch )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->GetTitle( title, titleCch );
+}
+
+const wchar_t *Playlist::ItemTitle( size_t item )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->filetitle;
+}
+
+const wchar_t *Playlist::ItemName( size_t item )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->filename;
+}
+
+int Playlist::GetItemLengthMilliseconds( size_t item )
+{
+ if ( item >= entries.size() )
+ return -1;
+
+ return entries[ item ]->GetLengthInMilliseconds();
+}
+
+int Playlist::GetItemSizeBytes( size_t item )
+{
+ if ( item >= entries.size() )
+ return -1;
+
+ return entries[ item ]->GetSizeInBytes();
+}
+
+size_t Playlist::GetItemExtendedInfo( size_t item, const wchar_t *metadata, wchar_t *info, size_t infoCch )
+{
+ if ( item >= entries.size() )
+ return 0;
+
+ return entries[ item ]->GetExtendedInfo( metadata, info, infoCch );
+}
+
+uint64_t Playlist::GetPlaylistSizeBytes( void )
+{
+ uint64_t size = 0;
+
+ for ( pl_entry *l_entry : entries )
+ size += l_entry->GetSizeInBytes();
+
+ return size;
+}
+
+uint64_t Playlist::GetPlaylistLengthMilliseconds( void )
+{
+ uint64_t length = 0;
+
+ for ( pl_entry *l_entry : entries )
+ length += l_entry->GetLengthInMilliseconds();
+
+
+ return length;
+}
+
+
+
+int Playlist::Reverse()
+{
+ // TODO: keep a bool flag and just do size-item-1 every time a GetItem* function is called
+ std::reverse( entries.begin(), entries.end() );
+
+ return PLAYLIST_SUCCESS;
+}
+
+int Playlist::Swap( size_t item1, size_t item2 )
+{
+ std::swap( entries[ item1 ], entries[ item2 ] );
+
+ return PLAYLIST_SUCCESS;
+}
+
+class RandMod
+{
+ public:
+ RandMod( int ( *_generator )( ) ) : generator( _generator ) {}
+ int operator()( int n ) { return generator() % n; }
+ int ( *generator )( );
+};
+
+int Playlist::Randomize( int ( *generator )( ) )
+{
+ RandMod randMod( generator );
+ std::random_shuffle( entries.begin(), entries.end(), randMod );
+
+ return PLAYLIST_SUCCESS;
+}
+
+void Playlist::Remove( size_t item )
+{
+ delete entries[ item ];
+ entries.erase( entries.begin() + item );
+}
+
+void Playlist::SetItemFilename( size_t item, const wchar_t *filename )
+{
+ if ( item < entries.size() )
+ entries[ item ]->SetFilename( filename );
+}
+
+void Playlist::SetItemTitle( size_t item, const wchar_t *title )
+{
+ if ( item < entries.size() )
+ entries[ item ]->SetTitle( title );
+}
+
+void Playlist::SetItemLengthMilliseconds( size_t item, int length )
+{
+ if ( item < entries.size() )
+ entries[ item ]->SetLengthMilliseconds( length );
+}
+
+void Playlist::SetItemSizeBytes( size_t item, int size )
+{
+ if ( item < entries.size() )
+ entries[ item ]->SetSizeBytes( size );
+}
+
+void GetTitle( pl_entry *&a )
+{
+ if ( !a->cached )
+ {
+ wchar_t title[ FILETITLE_SIZE ] = { 0 };
+ int length = -1;
+ mediaLibrary.GetFileInfo( a->filename, title, FILETITLE_SIZE, &length );
+ a->SetLengthMilliseconds( length * 1000 );
+ a->SetTitle( title );
+ }
+}
+static bool PlayList_sortByTitle( pl_entry *&a, pl_entry *&b )
+{
+ GetTitle( a );
+ GetTitle( b );
+
+ int comp = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE /*|NORM_IGNOREKANATYPE*/ | NORM_IGNOREWIDTH, a->filetitle, -1, b->filetitle, -1 );
+ return comp == CSTR_LESS_THAN;
+ // TODO: grab this function from winamp - return CompareStringLogical(a.strTitle, b.strTitle)<0;
+}
+
+static bool PlayList_sortByFile( pl_entry *&a, pl_entry *&b ) //const void *a, const void *b)
+{
+ const wchar_t *file1 = PathFindFileNameW( a->filename );
+ const wchar_t *file2 = PathFindFileNameW( b->filename );
+
+ int comp = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | /*NORM_IGNOREKANATYPE |*/ NORM_IGNOREWIDTH, file1, -1, file2, -1 );
+ return comp == CSTR_LESS_THAN;
+ // TODO: grab this function from winamp - return FileCompareLogical(file1, file2)<0;
+}
+
+static bool PlayList_sortByDirectory( pl_entry *&a, pl_entry *&b ) // by dir, then by title
+{
+ const wchar_t *directory1 = a->filename;
+ const wchar_t *directory2 = b->filename;
+
+ const wchar_t *directoryEnd1 = scanstr_backcW( directory1, L"\\", 0 );
+ const wchar_t *directoryEnd2 = scanstr_backcW( directory2, L"\\", 0 );
+
+ size_t dirLen1 = directoryEnd1 - directory1;
+ size_t dirLen2 = directoryEnd2 - directory2;
+
+ if ( !dirLen1 && !dirLen2 ) // both in the current directory?
+ return PlayList_sortByFile( a, b ); // not optimized, because the function does another scanstr_back, but easy for now :)
+
+ if ( !dirLen1 ) // only the first dir is empty?
+ return true; // sort it first
+
+ if ( !dirLen2 ) // only the second dir empty?
+ return false; // empty dirs go first
+
+#if 0 // TODO: grab this function from winamp
+ int comp = FileCompareLogicalN( directory1, dirLen1, directory2, dirLen2 );
+ if ( comp == 0 )
+ return PlayList_sortByFile( a, b );
+ else
+ return comp < 0;
+#endif
+
+ int comp = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | /*NORM_IGNOREKANATYPE | */NORM_IGNOREWIDTH, directory1, (int)dirLen1, directory2, (int)dirLen2 );
+ if ( comp == CSTR_EQUAL ) // same dir
+ return PlayList_sortByFile( a, b ); // do second sort
+ else // different dirs
+ return comp == CSTR_LESS_THAN;
+}
+
+int Playlist::SortByTitle()
+{
+ std::sort( entries.begin(), entries.end(), PlayList_sortByTitle );
+
+ return 1;
+}
+
+int Playlist::SortByFilename()
+{
+ std::sort( entries.begin(), entries.end(), PlayList_sortByFile );
+
+ return 1;
+}
+
+int Playlist::SortByDirectory()
+{
+ std::sort( entries.begin(), entries.end(), PlayList_sortByDirectory );
+
+ return 1;
+}
+/*
+int Playlist::Move(size_t itemSrc, size_t itemDest)
+{
+ if (itemSrc < itemDest)
+ std::rotate(&entries[itemSrc], &entries[itemSrc], &entries[itemDest]);
+ else
+ if (itemSrc > itemDest)
+ std::rotate(&entries[itemDest], &entries[itemSrc], &entries[itemSrc]);
+ return 1;
+}*/
+
+bool Playlist::IsCached( size_t item )
+{
+ return entries[ item ]->cached;
+}
+
+void Playlist::ClearCache( size_t item )
+{
+ entries[ item ]->cached = false;
+}
+
+void Playlist::InsertPlaylist( Playlist &copy, size_t index )
+{
+ for ( pl_entry *l_entry : copy.entries )
+ {
+ entries.insert( entries.begin() + index, l_entry );
+ ++index;
+ }
+
+ copy.entries.clear();
+}
+
+void Playlist::AppendPlaylist( Playlist &copy )
+{
+ entries.insert( entries.end(), std::make_move_iterator( copy.entries.begin() ), std::make_move_iterator( copy.entries.end() ) );
+ //for ( pl_entry *l_entry : copy.entries )
+ // entries.push_back( l_entry );
+
+ copy.entries.clear();
+}
+
+
+#define CBCLASS Playlist
+START_MULTIPATCH;
+START_PATCH( patch_playlist )
+M_VCB( patch_playlist, ifc_playlist, IFC_PLAYLIST_CLEAR, Clear )
+//M_VCB(patch_playlist, ifc_playlist, IFC_PLAYLIST_APPENDWITHINFO, AppendWithInfo)
+//M_VCB(patch_playlist, ifc_playlist, IFC_PLAYLIST_APPEND, Append)
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_GETNUMITEMS, GetNumItems )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_GETITEM, GetItem )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_GETITEMTITLE, GetItemTitle )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_GETITEMLENGTHMILLISECONDS, GetItemLengthMilliseconds )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_GETITEMEXTENDEDINFO, GetItemExtendedInfo )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_REVERSE, Reverse )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_SWAP, Swap )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_RANDOMIZE, Randomize )
+M_VCB( patch_playlist, ifc_playlist, IFC_PLAYLIST_REMOVE, Remove )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_SORTBYTITLE, SortByTitle )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_SORTBYFILENAME, SortByFilename )
+M_CB( patch_playlist, ifc_playlist, IFC_PLAYLIST_SORTBYDIRECTORY, SortByDirectory )
+NEXT_PATCH( patch_playlistloadercallback )
+M_VCB( patch_playlistloadercallback, ifc_playlistloadercallback, IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile );
+END_PATCH
+END_MULTIPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/impl_playlist.h b/Src/Plugins/Library/ml_plg/impl_playlist.h
new file mode 100644
index 00000000..589146af
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/impl_playlist.h
@@ -0,0 +1,65 @@
+#ifndef NULLSOFT_ML_PLG_IMPL_PLAYLIST_H
+#define NULLSOFT_ML_PLG_IMPL_PLAYLIST_H
+
+#include "../playlist/ifc_playlist.h"
+#include <vector>
+#include <windows.h> // for MAX_PATH
+#include "../playlist/pl_entry.h"
+#include <bfc/multipatch.h>
+#include <bfc/platform/types.h>
+#include "../playlist/ifc_playlistloadercallback.h"
+
+enum
+{
+ patch_playlist,
+ patch_playlistloadercallback
+};
+
+class Playlist : public MultiPatch<patch_playlist, ifc_playlist>, public MultiPatch<patch_playlistloadercallback, ifc_playlistloadercallback>
+{
+public:
+ ~Playlist();
+
+ void Clear();
+ void OnFile( const wchar_t *filename, const wchar_t *title, int lengthInMS, int sizeInKB, ifc_plentryinfo *info );
+ void AppendWithInfo( const wchar_t *filename, const wchar_t *title, int lengthInMS, int sizeInBytes );
+
+ size_t GetNumItems();
+
+ size_t GetItem( size_t item, wchar_t *filename, size_t filenameCch );
+ size_t GetItemTitle( size_t item, wchar_t *title, size_t titleCch );
+ const wchar_t *ItemTitle( size_t item );
+ const wchar_t *ItemName( size_t item );
+ int GetItemLengthMilliseconds( size_t item );
+ int GetItemSizeBytes( size_t item );
+ size_t GetItemExtendedInfo( size_t item, const wchar_t *metadata, wchar_t *info, size_t infoCch );
+ uint64_t GetPlaylistSizeBytes( void );
+ uint64_t GetPlaylistLengthMilliseconds( void );
+
+ bool IsCached( size_t item );
+ void ClearCache( size_t item );
+
+ void SetItemFilename( size_t item, const wchar_t *filename );
+ void SetItemTitle( size_t item, const wchar_t *title );
+ void SetItemLengthMilliseconds( size_t item, int length );
+ void SetItemSizeBytes( size_t item, int size );
+
+ int Reverse();
+ int Swap( size_t item1, size_t item2 );
+ int Randomize( int ( *generator )( ) );
+ void Remove( size_t item );
+
+ int SortByTitle();
+ int SortByFilename();
+ int SortByDirectory(); //sorts by directory and then by filename
+
+ void InsertPlaylist( Playlist &copy, size_t index );
+ void AppendPlaylist( Playlist &copy );
+
+ typedef std::vector<pl_entry*> PlaylistEntries;
+ PlaylistEntries entries;
+
+protected:
+ RECVS_MULTIPATCH;
+};
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/main.h b/Src/Plugins/Library/ml_plg/main.h
new file mode 100644
index 00000000..6a47e5d4
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/main.h
@@ -0,0 +1,87 @@
+#ifndef NULLSOFT_ML_PLG_MAIN_H
+#define NULLSOFT_ML_PLG_MAIN_H
+#include <windows.h>
+#include "playlist.h"
+#include "../../General/gen_ml/ml.h"
+#include "IDScanner.h"
+#include "api__ml_plg.h"
+#include "../winamp/wa_ipc.h"
+#include "../Agave/Language/api_language.h"
+#include "../nu/threadpool/api_threadpool.h"
+#include <api/service/waservicefactory.h>
+#include "impl_playlist.h"
+
+#define DEFAULT_ML_QUERY "playcount = \"0\" OR lastplay < [1 month ago] AND rating != \"1\" AND rating != \"2\""
+#define MAX_ML_QUERY_SIZE 8192
+#define MAX_TITLE_SIZE 512
+
+extern winampMediaLibraryPlugin plugin;
+
+//extern int plLength;
+extern int plItems;
+extern int plMinutes;
+extern int plMegabytes;
+extern int plLengthType;
+extern int multipleArtists;
+extern int multipleAlbums;
+extern int useSeed;
+
+extern int useMLQuery;
+//extern wchar_t *customMLQuery;
+extern wchar_t mlQuery[];
+extern Playlist seedPlaylist;
+extern bool isGenerating;
+
+extern IDScanner scanner;
+
+extern ThreadID *plg_thread;
+extern bool reset_db_flag;
+extern bool run_full_scan_flag;
+extern volatile bool run_pass2_flag;
+
+extern HWND hwndDlgCurrent;
+
+INT_PTR CALLBACK PrefsProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+INT_PTR CALLBACK GenerateProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+INT_PTR CALLBACK ViewProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+INT_PTR CALLBACK BGScanProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+INT_PTR CALLBACK AddPlaylistDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+// util.cpp
+int GetFileInfo(const wchar_t *filename, wchar_t *metadata, wchar_t *dest, int len);
+int updateFileInfo(const wchar_t *filename, wchar_t *metadata, wchar_t *data);
+void WriteFileInfo(const wchar_t *filename);
+
+void Pass1(int *killswitch);
+void Pass2(int *killswitch);
+
+bool StartScan();
+void StopScan();
+
+int ShutdownScanner(HANDLE handle, void *user_data, intptr_t id);
+int ResetDBOnThread(bool silent); // Goes onto the plg dedicated thread when called
+int ResetDB(bool silent); // For calling functions that are already on the plg dedicated thread
+int NukeDB(void); // For nuking the DB old skool (deleting all the files by force)
+
+
+// ml_plg.cpp
+//void SongSelected(const wchar_t * fn, HWND parent);
+void MultipleInstancesWarning(void);
+HWND SongsSelected(void);
+void WriteSettingsToIni(HWND hwndDlg);
+
+// Dialog manipulation methods
+// prefs.cpp & generate.cpp
+void ShowErrorDlg(HWND parent);
+void SetPlLengthTypeComboToItems(HWND hwndDlg, int value);
+void SetPlLengthTypeComboToMinutes(HWND hwndDlg, int value);
+void SetPlLengthTypeComboToMegabytes(HWND hwndDlg, int value);
+int SetRadioControlsState(HWND hwndDlg);
+void BoldStatusText(HWND hwndDlg);
+void PopulateResults(Playlist *playlist);
+void CantPopulateResults(void);
+void SetMarqueeProgress(bool isMarquee);
+void SetButtonsEnabledState(bool enabled_flag);
+BOOL windowOffScreen(HWND hwnd, POINT pt);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/ml_plg.cpp b/Src/Plugins/Library/ml_plg/ml_plg.cpp
new file mode 100644
index 00000000..da519db3
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/ml_plg.cpp
@@ -0,0 +1,719 @@
+#define PLUGIN_VER L"1.81"
+#define FORCED_REBUILD_VERSION 1 // When changing this be sure to update 'forcedRebuildVersion' logic if no breaking changes are introduced in the new version
+
+#include "api__ml_plg.h"
+#include "../../General/gen_ml/ml.h"
+#include "resource.h"
+#include "main.h"
+#include "../winamp/wa_ipc.h"
+#include "../winamp/ipc_pe.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "../nu/AutoChar.h"
+#include "../nu/ns_wc.h"
+#include <api/service/waservicefactory.h>
+#include "playlist.h"
+#include "../Agave/Language/api_language.h"
+#include "resource.h"
+//#include "mldbcallbacks.h"
+#include "../../General/gen_ml/menufucker.h"
+#include "../nu/ServiceWatcher.h"
+//#include "api_playlist_generator.h"
+#include "../nu/Singleton.h"
+#include "PlaylistGeneratorApi.h"
+
+
+//#include "wacmldbcallbacks.h"
+#include <strsafe.h> // make sure this always gets #include'd last
+
+// For the playlist generator API
+static PlaylistGeneratorAPI playlistGeneratorAPI;
+static SingletonServiceFactory<api_playlist_generator, PlaylistGeneratorAPI> playlistGeneratorFactory;
+
+
+api_threadpool *WASABI_API_THREADPOOL = 0;
+api_application *WASABI_API_APP = 0;
+api_playlistmanager *AGAVE_API_PLAYLISTMGR = 0;
+api_language *WASABI_API_LNG = 0;
+api_config *AGAVE_API_CONFIG=0;
+api_gracenote *AGAVE_API_GRACENOTE=0;
+api_decodefile *AGAVE_API_DECODE=0;
+api_metadata *AGAVE_API_METADATA=0;
+api_mldb *AGAVE_API_MLDB = 0;
+api_playlists *AGAVE_API_PLAYLISTS = 0;
+api_stats *AGAVE_API_STATS = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+api_syscb *WASABI_API_SYSCB=0;
+
+class MLDBWatcher : public ServiceWatcherSingle
+{
+public:
+ void OnDeregister()
+ {
+ StopScan();
+ }
+};
+
+static MLDBWatcher mldbWatcher;
+
+extern winampMediaLibraryPlugin plugin;
+
+template <class api_T>
+void ServiceBuild(api_T *&api_t, GUID factoryGUID_t)
+{
+ if (plugin.service)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ api_t = reinterpret_cast<api_T *>( factory->getInterface() );
+ }
+}
+
+template <class api_T>
+void ServiceRelease(api_T *&api_t, GUID factoryGUID_t)
+{
+ if (plugin.service && api_t)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ factory->releaseInterface(api_t);
+ }
+ api_t = NULL;
+}
+
+static HWND winampPlaylist;
+
+static int ML_IPC_MENUFUCKER_BUILD;
+static int ML_IPC_MENUFUCKER_RESULT;
+
+bool pluginEnabled = true;
+int scanMode = 0; // 0 = not inited, 1 = on start, 2 = on use
+int plLengthType = PL_ITEMS; // 0 = not inited, 1 = items (# of), 2 = length (minutes), 3 = size (kilobytes)
+//int plLength = 20; // Length of playlist, used for either # of items or for minutes target of the playlist
+int plItems = 20; // Number of desired items in the playlist
+int plMinutes = 60; // Number of desired minutes in the playlist
+int plMegabytes = 650; // Size of the desired playlist in kilobytes
+int multipleArtists = FALSE; // Generate tracks from the same artists in a single playlist
+int multipleAlbums = FALSE; // Generate tracks from the same albums in a single playlist
+int useSeed = TRUE; // Put the seed track into the generated playlist
+int useMLQuery = FALSE; // Use a custom query against the media library database to post process the results
+wchar_t mlQuery[MAX_ML_QUERY_SIZE] = {0}; // Storage for the custom query
+int forcedRebuildVersion = 0; // Stores the ID of a forced rebuild when upgrading, 0 is never reset, 1 reset with ml_plg rewrite, 2+ and on are reserved for future scenarios
+
+ThreadID *plg_thread=0; // Thread ID for the single gracenote thread that we always make API calls on.
+bool reset_db_flag = false; // Flag that gets set whenever the DB needs to be reset before a scan.
+bool run_full_scan_flag = true; // Flag that gets set whenever there are media library changes so step 4 (pass 2) can be rerun for any changed files
+volatile bool run_pass2_flag = false; // Flag that gets set whenever there are media library changes so step 4 (pass 2) can be rerun for any changed files
+
+
+void WriteIntToIni(const char *key, const int value)
+{
+ char buf[32] = {0};
+ _itoa(value, buf, 10);
+ WritePrivateProfileStringA("ml_plg", key, buf, mediaLibrary.GetWinampIni());
+}
+
+// BE CAREFULL! Using this could potentially internationalize floats on some versions of windows eg. '1,6' instead of '1.6'
+void WriteFloatToIni(const char *key, const float value)
+{
+ char buf[32] = {0};
+ StringCchPrintfA(buf, 32, "%.2f", value);
+ WritePrivateProfileStringA("ml_plg", key, buf, mediaLibrary.GetWinampIni());
+}
+
+int Init()
+{
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ ServiceBuild(WASABI_API_SYSCB, syscbApiServiceGuid);
+ ServiceBuild(WASABI_API_APP, applicationApiServiceGuid);
+ ServiceBuild(AGAVE_API_PLAYLISTMGR, api_playlistmanagerGUID);
+ ServiceBuild(AGAVE_API_CONFIG, AgaveConfigGUID);
+ ServiceBuild(AGAVE_API_DECODE, decodeFileGUID);
+ ServiceBuild(AGAVE_API_GRACENOTE, gracenoteApiGUID);
+ ServiceBuild(AGAVE_API_METADATA, api_metadataGUID);
+ ServiceBuild(AGAVE_API_PLAYLISTS, api_playlistsGUID);
+ ServiceBuild(AGAVE_API_STATS, AnonymousStatsGUID);
+ ServiceBuild(WASABI_API_LNG, languageApiGUID);
+ ServiceBuild(WASABI_API_THREADPOOL, ThreadPoolGUID);
+
+ playlistGeneratorFactory.Register(plugin.service, &playlistGeneratorAPI);
+
+ if (WASABI_API_THREADPOOL)
+ plg_thread = WASABI_API_THREADPOOL->ReserveThread(api_threadpool::FLAG_REQUIRE_COM_STA);
+
+ if (!plg_thread)
+ return ML_INIT_FAILURE; // if we weren't able to get a thread from the threadpool, bail out
+
+ // no guarantee that AGAVE_API_MLDB will be available yet, so we'll start a watcher for it
+ mldbWatcher.WatchWith(plugin.service);
+ mldbWatcher.WatchFor(&AGAVE_API_MLDB, mldbApiGuid);
+ WASABI_API_SYSCB->syscb_registerCallback(&mldbWatcher);
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG(plugin.hDllInstance,MlPlgLangGUID);
+
+ ML_IPC_MENUFUCKER_BUILD = (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"menufucker_build", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ ML_IPC_MENUFUCKER_RESULT = (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"menufucker_result", IPC_REGISTER_WINAMP_IPCMESSAGE);
+
+ winampPlaylist = (HWND)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,IPC_GETWND_PE,IPC_GETWND);
+
+ static wchar_t szDescription[256];
+ StringCchPrintf(szDescription, ARRAYSIZE(szDescription),
+ WASABI_API_LNGSTRINGW(IDS_NULLSOFT_PLAYLIST_GENERATOR), PLUGIN_VER);
+ plugin.description = (char*)szDescription;
+
+ // Load variables from winamp.ini
+ scanMode = GetPrivateProfileInt(L"ml_plg", L"scanmode", 0, mediaLibrary.GetWinampIniW());
+ pluginEnabled = GetPrivateProfileInt(L"ml_plg", L"enable", 1, mediaLibrary.GetWinampIniW())!=0;
+ multipleArtists = GetPrivateProfileInt(L"ml_plg", L"multipleArtists", multipleArtists, mediaLibrary.GetWinampIniW());
+ multipleAlbums = GetPrivateProfileInt(L"ml_plg", L"multipleAlbums", multipleAlbums, mediaLibrary.GetWinampIniW());
+
+ plLengthType = GetPrivateProfileInt(L"ml_plg", L"plLengthType", plLengthType, mediaLibrary.GetWinampIniW());
+ plItems = GetPrivateProfileInt(L"ml_plg", L"plItems", plItems, mediaLibrary.GetWinampIniW());
+ plMinutes = GetPrivateProfileInt(L"ml_plg", L"plMinutes", plMinutes, mediaLibrary.GetWinampIniW());
+ plMegabytes = GetPrivateProfileInt(L"ml_plg", L"plMegabytes", plMegabytes, mediaLibrary.GetWinampIniW());
+ useSeed = GetPrivateProfileInt(L"ml_plg", L"useSeed", useSeed, mediaLibrary.GetWinampIniW());
+ useMLQuery = GetPrivateProfileInt(L"ml_plg", L"useMLQuery", useMLQuery, mediaLibrary.GetWinampIniW());
+
+ char temp[MAX_ML_QUERY_SIZE] = {0};
+ GetPrivateProfileStringA("ml_plg", "mlQuery", DEFAULT_ML_QUERY, temp, sizeof(temp), mediaLibrary.GetWinampIni());
+ MultiByteToWideCharSZ(CP_UTF8, 0, temp, -1, mlQuery, sizeof(mlQuery)/sizeof(mlQuery[0]));
+ //GetPrivateProfileStringA("ml_plg", "forcedRebuildVersion", "", temp, sizeof(temp), mediaLibrary.GetWinampIni());
+ //forcedRebuildVersion = (float)atof(temp);
+ forcedRebuildVersion = GetPrivateProfileIntA("ml_plg","forcedRebuildVersion", forcedRebuildVersion, mediaLibrary.GetWinampIni());
+
+ // Here we check if the person is upgrading from the old ml_plg, if that value is less than our current version then we need to force a rebuild
+ if (forcedRebuildVersion < FORCED_REBUILD_VERSION/*atof(PLUGIN_VER)*/) // NOTE: Hard code this to a version if no breaking changes were made
+ { // Otherwise there will be a forced reset every time version is incremented
+ reset_db_flag = true;
+ //ResetDBOnThread(true);
+ }
+ forcedRebuildVersion = FORCED_REBUILD_VERSION; //(float)atof(PLUGIN_VER);
+
+ if(scanMode == 1) // If scanmode is set to rescan on winamp launch
+ WASABI_API_CREATEDIALOGPARAMW(IDD_NAG, plugin.hwndWinampParent, BGScanProcedure, 1); // 1 means silent!
+
+ return ML_INIT_SUCCESS;
+}
+
+void Quit()
+{
+ StopScan();
+ HANDLE wait_event = CreateEvent(NULL, FALSE, FALSE, 0);
+ WASABI_API_THREADPOOL->RunFunction(plg_thread, ShutdownScanner, (void *)wait_event, 0, api_threadpool::FLAG_REQUIRE_COM_STA);
+ WaitForSingleObject(wait_event, INFINITE);
+
+ mldbWatcher.StopWatching();
+ WASABI_API_THREADPOOL->ReleaseThread(plg_thread);
+ ServiceRelease(WASABI_API_APP, applicationApiServiceGuid);
+ ServiceRelease(AGAVE_API_PLAYLISTMGR, api_playlistmanagerGUID);
+ ServiceRelease(AGAVE_API_CONFIG, AgaveConfigGUID);
+ ServiceRelease(AGAVE_API_DECODE, decodeFileGUID);
+ ServiceRelease(AGAVE_API_GRACENOTE, gracenoteApiGUID);
+ ServiceRelease(AGAVE_API_METADATA, api_metadataGUID);
+ ServiceRelease(AGAVE_API_MLDB, mldbApiGuid);
+ ServiceRelease(AGAVE_API_PLAYLISTS, api_playlistsGUID);
+ ServiceRelease(AGAVE_API_STATS, AnonymousStatsGUID);
+ ServiceRelease(WASABI_API_THREADPOOL, ThreadPoolGUID);
+
+ playlistGeneratorFactory.Deregister(plugin.service);
+ //WASABI_API_SYSCB->syscb_deregisterCallback(&IDscanner);
+}
+
+static void FixAmps(wchar_t *str, size_t len)
+{
+ size_t realSize = 0;
+ size_t extra = 0;
+ wchar_t *itr = str;
+ while (itr && *itr)
+ {
+ if (*itr == L'&')
+ extra++;
+ itr++;
+ realSize++;
+ }
+
+ extra = min(len - (realSize + 1), extra);
+
+ while (extra)
+ {
+ str[extra+realSize] = str[realSize];
+ if (str[realSize] == L'&')
+ {
+ extra--;
+ str[extra+realSize] = L'&';
+ }
+ realSize--;
+ }
+}
+
+static void FixStrForMenu(wchar_t *str, size_t len)
+{
+ FixAmps(str,len);
+}
+
+// Triggered once some seed tracks are selected and added by the user
+HWND SongsSelected(void)
+{
+ // I know this function is a one-liner but it may not be the case forever
+ //WASABI_API_CREATEDIALOG(IDD_GENERATE, GetDesktopWindow(), GenerateProcedure);
+ return WASABI_API_CREATEDIALOGW(IDD_GENERATE, plugin.hwndLibraryParent, GenerateProcedure);
+}
+
+// Display the warning message that the current file is not in ML so it cannot be used as a seed track
+void NotInMLWarning(const wchar_t *filename)
+{
+ wchar_t message[MAX_PATH + 256] = {0};
+ StringCchPrintfW(message, MAX_PATH + 256, WASABI_API_LNGSTRINGW(IDS_CANT_USE_SEED), filename);
+ MessageBoxW(plugin.hwndLibraryParent, message, (LPWSTR)plugin.description, MB_OK| MB_ICONINFORMATION);
+}
+
+void MultipleInstancesWarning(void)
+{
+ MessageBoxW(plugin.hwndLibraryParent, WASABI_API_LNGSTRINGW(IDS_THERE_CAN_BE_ONLY_ONE), (LPWSTR)plugin.description, MB_OK | MB_ICONINFORMATION);
+}
+
+// Add seed tracks from main media library view
+int AddSeedTracks(menufucker_t *mf)
+{
+ const int count = mf->extinf.mediaview.items->Size;
+ int position = ListView_GetNextItem(mf->extinf.mediaview.list, -1, LVNI_SELECTED); // Start the search from -1 so that we dont ignore the 0th selection
+
+ while (position >= 0 && position < count)
+ {
+ wchar_t winamp_title[MAX_TITLE_SIZE] = {0};
+ itemRecordW *item = &mf->extinf.mediaview.items->Items[position];
+ if (item)
+ {
+ GetTitleFormattingML(item->filename, item, winamp_title, MAX_TITLE_SIZE);
+ seedPlaylist.AppendWithInfo(item->filename, winamp_title, item->length * 1000, item->filesize * 1024);
+ AGAVE_API_MLDB->FreeRecord(item);
+ }
+ position = ListView_GetNextItem(mf->extinf.mediaview.list, position, LVNI_SELECTED);
+ }
+
+ return true;
+}
+
+// Add seed tracks from a media library playlist
+int AddSeedTracksMlPlaylist(menufucker_t *mf)
+{
+ int position = ListView_GetNextItem(mf->extinf.mlplaylist.list, -1, LVNI_SELECTED); // Start the search from -1 so that we dont ignore the 0th selection
+
+ while (position >= 0)
+ {
+ wchar_t filename[MAX_PATH] = {0};
+ mf->extinf.mlplaylist.pl->GetItem(position, filename, MAX_PATH);
+ itemRecordW *item = AGAVE_API_MLDB->GetFile(filename);
+ if (item)
+ {
+ wchar_t winamp_title[MAX_TITLE_SIZE] = {0};
+ GetTitleFormattingML(item->filename, item, winamp_title, MAX_TITLE_SIZE);
+ seedPlaylist.AppendWithInfo(item->filename, winamp_title, item->length * 1000, item->filesize * 1024);
+ AGAVE_API_MLDB->FreeRecord(item);
+ }
+ position = ListView_GetNextItem(mf->extinf.mlplaylist.list, position, LVNI_SELECTED);
+ }
+
+ return true;
+}
+
+// Add tracks from the winamp playlist
+int AddSeedTracksPlaylist(menufucker_t *mf, int first_selection)
+{
+ bool isSuccess = true;
+
+ int position = first_selection;
+ while (position >= 0)
+ {
+ wchar_t winamp_title[MAX_TITLE_SIZE] = {0};
+ fileinfoW inf={0};
+ inf.index = position;
+
+ SendMessage(winampPlaylist,WM_WA_IPC,IPC_PE_GETINDEXINFOW_INPROC,(LPARAM)&inf);
+ itemRecordW *item = AGAVE_API_MLDB->GetFile(inf.file);
+
+ if (item)
+ {
+ GetTitleFormattingML(inf.file, item, winamp_title, MAX_TITLE_SIZE);
+ seedPlaylist.AppendWithInfo(item->filename, winamp_title, item->length * 1000, item->filesize * 1024);
+ AGAVE_API_MLDB->FreeRecord(item);
+ }
+ else
+ {
+ NotInMLWarning(inf.file); // Popup to warn that its not in the ML
+ }
+
+ position = (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, position, IPC_PLAYLIST_GET_NEXT_SELECTED);
+ }
+
+ if (seedPlaylist.GetNumItems() == 0)
+ isSuccess = false;
+
+ return isSuccess;
+}
+
+// Add a single seed track from the now playing song ticker
+int AddSeedTrack(const wchar_t *filename)
+{
+ bool isSuccess = true;
+
+ if (filename)
+ {
+ wchar_t winamp_title[MAX_TITLE_SIZE] = {0};
+ itemRecordW *item = AGAVE_API_MLDB->GetFile(filename);
+ if (item)
+ {
+ GetTitleFormattingML(filename, item, winamp_title, MAX_TITLE_SIZE);
+ seedPlaylist.AppendWithInfo(filename, winamp_title, item->length * 1000, item->filesize * 1024);
+ AGAVE_API_MLDB->FreeRecord(item);
+ }
+ else
+ {
+ NotInMLWarning(filename); // Popup to warn that its not in the ML
+ }
+ }
+ else
+ {
+ NotInMLWarning(filename); // Popup to warn that its not in the ML
+ }
+
+ if (seedPlaylist.GetNumItems() == 0)
+ isSuccess = false;
+
+ return isSuccess;
+}
+
+void WriteSettingsToIni(HWND hwndDlg)
+{
+ /*char buf[32] = {0};
+
+ StringCchPrintfA(buf, 32, "%d", plLengthType);
+ WritePrivateProfileStringA("ml_plg","plLengthType",buf,mediaLibrary.GetWinampIni());*/
+
+ WriteIntToIni("plLengthType", plLengthType);
+ WriteIntToIni("plItems", plItems);
+ WriteIntToIni("plMinutes", plMinutes);
+ WriteIntToIni("plMegabytes", plMegabytes);
+ WriteIntToIni("forcedRebuildVersion", forcedRebuildVersion);
+ //WriteFloatToIni("forcedRebuildVersion", forcedRebuildVersion);
+
+ WriteIntToIni("multipleArtists", multipleArtists);
+ WriteIntToIni("multipleAlbums", multipleAlbums);
+ WriteIntToIni("useSeed", useSeed);
+ WriteIntToIni("useMLQuery", useMLQuery);
+
+ /*multipleArtists = IsDlgButtonChecked(hwndDlg,IDC_CHECK_MULTIPLE_ARTISTS);
+ WritePrivateProfileStringA("ml_plg","multipleArtists",multipleArtists?"1":"0",mediaLibrary.GetWinampIni());
+
+ multipleAlbums = IsDlgButtonChecked(hwndDlg,IDC_CHECK_MULTIPLE_ALBUMS);
+ WritePrivateProfileStringA("ml_plg","multipleAlbums",multipleAlbums?"1":"0",mediaLibrary.GetWinampIni());
+
+ useSeed = IsDlgButtonChecked(hwndDlg,IDC_CHECK_USE_SEED);
+ WritePrivateProfileStringA("ml_plg","useSeed",useSeed?"1":"0",mediaLibrary.GetWinampIni());
+
+ useMLQuery = IsDlgButtonChecked(hwndDlg,IDC_CHECK_ML_QUERY);
+ WritePrivateProfileStringA("ml_plg","useMLQuery", useMLQuery ? "1" : "0",mediaLibrary.GetWinampIni());*/
+
+ //WritePrivateProfileStringW(L"ml_plg",L"mlQuery", mlQuery ,mediaLibrary.GetWinampIniW());
+ WritePrivateProfileStringA("ml_plg", "mlQuery", AutoChar(mlQuery, CP_UTF8), mediaLibrary.GetWinampIni());
+}
+
+static bool IsInternetAvailable()
+{
+ return !!SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_INETAVAILABLE);
+}
+
+INT_PTR MessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ static int mymenuid=0;
+
+ if(message_type == ML_IPC_MENUFUCKER_BUILD && pluginEnabled && IsInternetAvailable())
+ {
+ menufucker_t* mf = (menufucker_t*)param1;
+ wchar_t str[100] = {0}, str2[64] = {0};
+ MENUITEMINFOW mii =
+ {
+ sizeof(MENUITEMINFOW),
+ MIIM_TYPE | MIIM_ID,
+ MFT_STRING,
+ MFS_ENABLED,
+ (UINT)mf->nextidx,
+ 0
+ };
+
+ mymenuid = mf->nextidx;
+ mf->nextidx++;
+
+ if(mf->type == MENU_MEDIAVIEW)
+ {
+ int n = ListView_GetSelectionMark(mf->extinf.mediaview.list);
+ if(n == -1)
+ {
+ mymenuid=0;
+ return 0;
+ }
+
+ itemRecordW * ice = &mf->extinf.mediaview.items->Items[n];
+ if(!ice->title || !ice->title[0])
+ {
+ mymenuid=0;
+ return 0;
+ }
+
+ int len = lstrlenW(ice->title);
+ if (len > 39)
+ {
+ StringCchPrintfW(str2, 40, L"%.36s...", ice->title);
+ StringCchPrintfW(str, 100, WASABI_API_LNGSTRINGW(IDS_PLAY_TRACKS_SIMILAR_TO), str2);
+ }
+ else
+ {
+ StringCchPrintfW(str, 100, WASABI_API_LNGSTRINGW(IDS_PLAY_TRACKS_SIMILAR_TO), ice->title);
+ }
+
+ FixStrForMenu(str,100);
+ mii.dwTypeData = str;
+ mii.cch = (UINT)wcslen(str);
+
+ if(!InsertMenuItem(mf->menu,0xdeadbeef,FALSE,&mii))
+ {
+ InsertMenuItem(mf->menu,40012,FALSE,&mii);
+ mii.wID = 0xdeadbeef;
+ mii.fType = MFT_SEPARATOR;
+ InsertMenuItem(mf->menu,40012,FALSE,&mii);
+ }
+ }
+ else if(mf->type == MENU_MLPLAYLIST)
+ {
+ int n = ListView_GetSelectionMark(mf->extinf.mlplaylist.list);
+ if(n == -1)
+ {
+ mymenuid=0;
+ return 0;
+ }
+ wchar_t filename[MAX_PATH] = {0}, title[75] = {0};
+ mf->extinf.mlplaylist.pl->GetItem(n,filename,MAX_PATH);
+ AGAVE_API_METADATA->GetExtendedFileInfo(filename, L"title", title, 75);
+ if(!title[0])
+ {
+ mymenuid=0;
+ return 0;
+ }
+
+ int len = lstrlenW(title);
+ if (len > 39)
+ {
+ StringCchPrintfW(str2, 40, L"%.36s...", title);
+ StringCchPrintfW(str, 100, WASABI_API_LNGSTRINGW(IDS_PLAY_TRACKS_SIMILAR_TO), str2);
+ }
+ else
+ {
+ StringCchPrintfW(str, 100, WASABI_API_LNGSTRINGW(IDS_PLAY_TRACKS_SIMILAR_TO), title);
+ }
+
+ FixStrForMenu(str,100);
+ mii.dwTypeData = str;
+ mii.cch = (UINT)wcslen(str);
+ InsertMenuItem(mf->menu,3,TRUE,&mii);
+ mii.wID = 0xdeadc0de;
+ mii.fType = MFT_SEPARATOR;
+ InsertMenuItem(mf->menu,3,TRUE,&mii);
+ }
+ else if(mf->type == MENU_PLAYLIST)
+ {
+ int n = (int)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,-1,IPC_PLAYLIST_GET_NEXT_SELECTED);
+ if(n == -1) {
+ mymenuid=0;
+ return 0;
+ }
+
+ fileinfoW inf={0};
+ inf.index = n;
+ SendMessage(winampPlaylist,WM_WA_IPC,IPC_PE_GETINDEXINFOW_INPROC,(LPARAM)&inf);
+ wchar_t title[75] = {0};
+ AGAVE_API_METADATA->GetExtendedFileInfo(inf.file, L"title", title, 75);
+ if(!title[0])
+ {
+ mymenuid=0;
+ return 0;
+ }
+
+
+ int len = lstrlenW(title);
+ if (len > 39)
+ {
+ StringCchPrintfW(str2, 40, L"%.36s...", title);
+ StringCchPrintfW(str, 100, WASABI_API_LNGSTRINGW(IDS_PLAY_TRACKS_SIMILAR_TO), str2);
+ }
+ else
+ {
+ StringCchPrintfW(str, 100, WASABI_API_LNGSTRINGW(IDS_PLAY_TRACKS_SIMILAR_TO), title);
+ }
+
+ FixStrForMenu(str,100);
+ mii.dwTypeData = str;
+ mii.cch = (UINT)wcslen(str);
+
+ InsertMenuItem(mf->menu,40470/*40208*/,FALSE,&mii);
+ mii.wID = 0xdeadc0de;
+ mii.fType = MFT_SEPARATOR;
+ InsertMenuItem(mf->menu,40470/*40208*/,FALSE,&mii);
+ }
+ else if (mf->type == MENU_SONGTICKER)
+ {
+ wchar_t * file = (wchar_t*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GET_PLAYING_FILENAME);
+ wchar_t title[75] = {0};
+ AGAVE_API_METADATA->GetExtendedFileInfo(file, L"title", title, 75);
+ if(!title[0])
+ {
+ mymenuid=0;
+ return 0;
+ }
+
+ int len = lstrlenW(title);
+ if (len > 39)
+ {
+ StringCchPrintfW(str2, 40, L"%.36s...", title);
+ StringCchPrintfW(str, 100, WASABI_API_LNGSTRINGW(IDS_PLAY_TRACKS_SIMILAR_TO), str2);
+ }
+ else
+ {
+ StringCchPrintfW(str, 100, WASABI_API_LNGSTRINGW(IDS_PLAY_TRACKS_SIMILAR_TO), title);
+ }
+
+ FixStrForMenu(str,100);
+ mii.dwTypeData = str;
+ mii.cch = (UINT)wcslen(str);
+
+ InsertMenuItem(mf->menu,0,TRUE,&mii);
+ }
+ }
+ else if(message_type == ML_IPC_MENUFUCKER_RESULT && mymenuid != 0 && pluginEnabled)
+ {
+ menufucker_t* mf = (menufucker_t*)param1;
+ DeleteMenu(mf->menu,mymenuid,MF_BYCOMMAND);
+ if(mf->type == MENU_PLAYLIST || mf->type == MENU_MLPLAYLIST) DeleteMenu(mf->menu,0xdeadc0de,MF_BYCOMMAND);
+
+ if(param2 == mymenuid && mymenuid != 0)
+ {
+ if(mf->type == MENU_MEDIAVIEW) // Main Media Library View
+ {
+ int n = ListView_GetSelectionMark(mf->extinf.mediaview.list);
+ if(n == -1)
+ {
+ mymenuid=0;
+ return 0;
+ }
+
+ if (hwndDlgCurrent) // Warn if trying to open two seperate playlist generators
+ MultipleInstancesWarning();
+ else
+ {
+ if (AddSeedTracks(mf)) // Make sure that we added the seed tracks successfully
+ SongsSelected();
+ }
+ }
+ else if(mf->type == MENU_MLPLAYLIST) // Media library playlist view
+ {
+ // Check to see if anything is selected
+ int n = ListView_GetSelectionMark(mf->extinf.mlplaylist.list);
+ if(n == -1)
+ {
+ mymenuid=0;
+ return 0;
+ }
+
+ if (hwndDlgCurrent) // Warn if trying to open two seperate playlist generators
+ MultipleInstancesWarning();
+ else
+ {
+ if (AddSeedTracksMlPlaylist(mf)) // Make sure that we added the seed tracks successfully
+ SongsSelected();
+ }
+ }
+ else if(mf->type == MENU_PLAYLIST) // Main window playlist
+ {
+ // Check to see if anything is selected
+ int n = (int)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,-1,IPC_PLAYLIST_GET_NEXT_SELECTED);
+ if(n == -1)
+ {
+ mymenuid=0;
+ return 0;
+ }
+
+ if (hwndDlgCurrent) // Warn if trying to open two seperate playlist generators
+ MultipleInstancesWarning();
+ else
+ {
+ if (AddSeedTracksPlaylist(mf, n)) // Make sure that we added the seed tracks successfully
+ SongsSelected();
+ }
+ }
+ else if(mf->type == MENU_SONGTICKER) // Current playing track in the song ticker
+ {
+ wchar_t * file = (wchar_t*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GET_PLAYING_FILENAME);
+ if (file)
+ {
+ if (hwndDlgCurrent) // Warn if trying to open two seperate playlist generators
+ MultipleInstancesWarning();
+ else
+ {
+ if (AddSeedTrack(file)) // Make sure that we added the seed tracks successfully
+ SongsSelected();
+ }
+ }
+ }
+ }
+ mymenuid=0;
+ }
+ else switch (message_type)
+ {
+ case ML_MSG_CONFIG:
+ {
+ HWND parent = (HWND)param1;
+ WASABI_API_DIALOGBOXW(IDD_PREFS, parent, PrefsProcedure);
+ return TRUE;
+ }
+ break;
+ }
+ return 0;
+}
+
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_plg.dll)", // name filled in later
+ Init,
+ Quit,
+ MessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+extern "C"
+{
+ __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+ {
+ return &plugin;
+ }
+
+ __declspec( dllexport ) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param) {
+ // prompt to remove our settings with default as no (just incase)
+ static wchar_t title[256];
+ StringCchPrintf(title, ARRAYSIZE(title),
+ WASABI_API_LNGSTRINGW(IDS_NULLSOFT_PLAYLIST_GENERATOR), PLUGIN_VER);
+
+ if(MessageBoxW(hwndDlg,WASABI_API_LNGSTRINGW(IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS),
+ title,MB_YESNO|MB_DEFBUTTON2) == IDYES)
+ {
+ WritePrivateProfileStringW(L"ml_plg",0,0,mediaLibrary.GetWinampIniW());
+ }
+
+ // allow an on-the-fly removal (since we've got to be with a compatible client build)
+ return ML_PLUGIN_UNINSTALL_NOW;
+ }
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/ml_plg.rc b/Src/Plugins/Library/ml_plg/ml_plg.rc
new file mode 100644
index 00000000..0423ed7c
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/ml_plg.rc
@@ -0,0 +1,312 @@
+// 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_PREFS DIALOGEX 0, 0, 329, 228
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Winamp Playlist Generator"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ GROUPBOX "Scanner",IDC_STATIC,5,5,319,163
+ LTEXT "",IDC_BLURB,12,18,305,57
+ CONTROL "",IDC_PROGRESS1,"msctls_progress32",WS_BORDER,12,79,305,14
+ GROUPBOX "Status",IDC_GROUP_STATUS,12,99,305,36
+ LTEXT "Idle",IDC_STATUS,18,112,294,18
+ PUSHBUTTON "Scan",IDC_TEST,254,172,70,14
+ PUSHBUTTON "Reset Database...",IDC_RESETDB,254,191,70,14
+ DEFPUSHBUTTON "Close",IDOK,254,209,70,14
+ CONTROL "Automatically start background scanning when I use this feature",IDC_SCANONUSE,
+ "Button",BS_AUTORADIOBUTTON,12,139,222,10
+ CONTROL "Automatically start background scanning when Winamp launches",IDC_SCANLAUNCH,
+ "Button",BS_AUTORADIOBUTTON,12,152,222,10
+ CONTROL 108,IDC_LOGO,"Static",SS_BITMAP,5,172,59,51
+END
+
+#if defined(APSTUDIO_INVOKED) || defined(DISABLED)
+#if defined(APSTUDIO_INVOKED)
+IDD_FAIL$(DISABLED) DIALOGEX 0, 0, 220, 65
+#else
+IDD_FAIL DIALOGEX 0, 0, 220, 65
+#endif
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_CHILD | WS_CAPTION | WS_SYSMENU
+CAPTION "Playlist Generation"
+FONT 8, "MS Shell Dlg", 400, 0, 0x0
+BEGIN
+ CONTROL 108,IDC_STATIC,"Static",SS_BITMAP,7,7,59,51
+ LTEXT "Generating Playlist...",IDC_STATIC_GENERATING,72,18,68,8
+ PUSHBUTTON "Cancel",IDC_BUTTON_GENERATE_CANCEL,93,44,50,14
+END
+#endif
+
+IDD_GENERATE DIALOGEX 0, 0, 580, 275
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_APPWINDOW
+CAPTION "Generate Playlist"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "",IDC_STATIC_STATS,5,7,328,8
+ PUSHBUTTON "Options >>>",IDC_BUTTON_OPTIONS,337,4,60,14
+ CONTROL "",IDC_LIST_RESULTS2,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,5,22,392,194
+ CONTROL 108,IDC_LOGO,"Static",SS_BITMAP,5,220,59,51
+ GROUPBOX "Status",IDC_GROUP_STATUS,69,217,264,36
+ LTEXT "Generating Playlist...",IDC_STATIC_PROGRESS_STATE,75,230,252,18
+ PUSHBUTTON "Save Playlist...",IDC_BUTTON_SAVEAS,337,220,60,14,BS_MULTILINE
+ PUSHBUTTON "Regenerate",IDC_BUTTON_REGENERATE,337,238,60,14
+ CONTROL "",IDC_PROGRESS_GENERATE,"msctls_progress32",WS_BORDER,69,257,136,14
+ PUSHBUTTON "Play Now",IDC_BUTTON_PLAY_NOW,209,257,60,14
+ PUSHBUTTON "Enqueue Now",IDC_BUTTON_ENQUEUE_NOW,273,257,60,14
+ PUSHBUTTON "Close",IDC_BUTTON_CANCEL,337,257,60,14
+ GROUPBOX "Options",IDC_STATIC,403,4,172,267
+ GROUPBOX "Playlist Entries",IDC_GROUP_PL_ENTRIES,409,15,160,68
+ CONTROL "Number of playlist items",IDC_RADIO_PLAYLIST_ITEMS,
+ "Button",BS_AUTORADIOBUTTON,415,28,92,10
+ CONTROL "Playlist length",IDC_RADIO_PLAYLIST_LENGTH,"Button",BS_AUTORADIOBUTTON,415,40,60,10
+ CONTROL "Playlist size",IDC_RADIO_PLAYLIST_SIZE,"Button",BS_AUTORADIOBUTTON,415,52,52,10
+ LTEXT "(up to)",IDC_STATIC_UP_TO,415,67,23,8
+ COMBOBOX IDC_COMBO_LENGTH,441,65,50,92,CBS_DROPDOWN | WS_TABSTOP
+ LTEXT "items",IDC_LENGTH_TYPE,494,67,70,8
+ GROUPBOX "Seed Settings",IDC_GROUP_SEED,409,87,160,55
+ CONTROL "Add seed track to playlist",IDC_CHECK_USE_SEED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,416,100,97,10
+ CONTROL "Add multiple tracks from the same artist",IDC_CHECK_MULTIPLE_ARTISTS,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,416,114,143,10
+ CONTROL "Add multiple tracks from the same album",IDC_CHECK_MULTIPLE_ALBUMS,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,416,127,145,10
+ GROUPBOX "Library Query",IDC_GROUP_ML_QUERY,409,146,160,120
+ CONTROL "Apply custom Library query:",IDC_CHECK_ML_QUERY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,416,159,125,10
+ EDITTEXT IDC_EDIT_ML_QUERY,416,173,147,14,ES_AUTOHSCROLL
+ PUSHBUTTON "Edit ML Query...",IDC_BUTTON_ML_QUERY,416,191,72,14
+ PUSHBUTTON "Restore Default",IDC_BUTTON_RESTORE_QUERY_DEFAULT,492,191,71,14
+ LTEXT "Default Query:\n\t- Never played\n\t\tOR\n\t- Played over 1 month ago\n\t\tbut rated a 3 or above.",IDC_STATIC_QUERY_DESCRIPTION,416,209,147,50
+END
+
+IDD_ADD_PLAYLIST DIALOGEX 0, 0, 230, 44
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "New Playlist"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ DEFPUSHBUTTON "OK",IDOK,121,25,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,175,25,50,14
+ LTEXT "Name:",IDC_STATIC_NAME,5,7,29,8
+ EDITTEXT IDC_EDIT_NAME,33,5,192,14,ES_AUTOHSCROLL
+END
+
+IDD_NAG DIALOGEX 0, 0, 178, 61
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Scanning in Progress..."
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ DEFPUSHBUTTON "Close",IDCANCEL,123,42,50,14
+ CONTROL 108,IDC_STATIC,"Static",SS_BITMAP,5,5,59,51
+ LTEXT "The Winamp Playlist Generator is now scanning new files...",IDC_STATIC,69,5,99,33
+ PUSHBUTTON "Details...",IDC_BUTTON1,69,42,50,14
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_PREFS, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 324
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 223
+ END
+
+ "IDD_FAIL$(DISABLED)", DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 213
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 58
+ END
+
+ IDD_GENERATE, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 575
+ TOPMARGIN, 4
+ BOTTOMMARGIN, 271
+ END
+
+ IDD_ADD_PLAYLIST, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 225
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 39
+ END
+
+ IDD_NAG, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 173
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 57
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.K.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
+#pragma code_page(1252)
+#endif //_WIN32
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Bitmap
+//
+
+IDB_GN_LOGO BITMAP "gn_logo_88x83.bmp"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_PLAYLIST_GENERATOR "Nullsoft Playlist Generator v%s"
+ 65535 "{0CE0174D-8334-479e-B322-9D80D48FC74D}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_PLAY_TRACKS_SIMILAR_TO "View tracks similar to ""%s"""
+ IDS_SCANNING_NEEDED " (scanning needed)"
+ IDS_SCANNER_BLURB "Give the Winamp Playlist Generator a song that you love and it will pick out a playlist of similar songs. In the mood for some rocking tunes? Just pick the rockingest, then use the ""View tracks similar to"" menu option and we'll sort you out with a selection of great tunes.\n\nBut to do this, we first need to scan your music library. This may take a little while, but it's totally worth it. Click ""Scan"" to begin."
+ IDS_ERROR_INITIALIZING "Error Initializing"
+ IDS_IDLE "Idle"
+ IDS_INITIALIZING "Step 1/4: Initializing%s"
+ IDS_SYNC "Step 2/4: Scanning Local Media database.\n%d of %d files complete%s"
+ IDS_METADATA "Step 3/4: Reading metadata.\n%d of %d files complete%s"
+ IDS_MUSICID "Step 4/4: Analyzing files.\n%d of %d files complete%s"
+ IDS_DONE "Done"
+ IDS_PAUSE "Stop"
+ IDS_SCAN "Scan"
+ IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS
+ "Do you also want to remove the saved settings for this plug-in?"
+ IDS_INITFAILMSG "Playlist Generator failed to initialize. If you suspect that the database could be corrupt please reset it from the 'Nullsoft Playlist Generator' configuration options."
+ IDS_RESETDB "Reset Database"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_RESETDB_TEXT "This will reset the database, the playlist generator will need to scan your whole library again. Proceed?"
+ IDS_MINUTES "minutes"
+ IDS_ITEMS "items"
+ IDS_ARTIST "Artist"
+ IDS_ALBUM "Album"
+ IDS_TRACK "Track"
+ IDS_TITLE "Track Title"
+ IDS_SIZE "Size"
+ IDS_LENGTH "Length"
+ IDS_MEGABYTES "megabytes"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_EXCUSE_ME "Sorry.\nNo tracks could be generated with your current option settings.\nPlease try different settings or different seed track(s). Or try again once the size of your library has increased."
+ IDS_SEED "Seed?"
+ IDS_YES "Yes"
+ IDS_NO " "
+ IDS_OPTIONS "Options <<"
+ IDS_NO_OPTIONS "Options >>"
+ IDS_STATS "Items: [%i] Length: [%s] Size: [%s]"
+ IDS_ML_QUERY "Library Query for Gracenote"
+ IDS_ENTER_A_NAME "Please enter a name."
+ IDS_ERROR "Error"
+ IDS_PL_NAME_PREFIX "GP - "
+END
+
+STRINGTABLE
+BEGIN
+ IDS_GENERATING "Generating a playlist..."
+ IDS_CANT_USE_SEED "Sorry, but the file '%s' cannot be used as a seed track because it is not in the Library."
+ IDS_UNKNOWN "Unknown"
+ IDS_THERE_CAN_BE_ONLY_ONE
+ "Please close the current playlist generator window before opening a new one."
+ IDS_DAYS "days"
+ IDS_DAY "day"
+ IDS_ERROR_RESET "Failed to reset the Gracenote DB. Please restart Winamp and try to reset again."
+ IDS_CANNOT_SHUT_DOWN "Unexpected Error. Cannot shut down the playlist generator engine."
+END
+
+#endif // English (U.K.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+#include "version.rc2"
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/Src/Plugins/Library/ml_plg/ml_plg.sln b/Src/Plugins/Library/ml_plg/ml_plg.sln
new file mode 100644
index 00000000..57b4aa31
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/ml_plg.sln
@@ -0,0 +1,30 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29509.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_plg", "ml_plg.vcxproj", "{4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}"
+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
+ {4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}.Debug|Win32.ActiveCfg = Debug|Win32
+ {4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}.Debug|Win32.Build.0 = Debug|Win32
+ {4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}.Debug|x64.ActiveCfg = Debug|x64
+ {4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}.Debug|x64.Build.0 = Debug|x64
+ {4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}.Release|Win32.ActiveCfg = Release|Win32
+ {4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}.Release|Win32.Build.0 = Release|Win32
+ {4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}.Release|x64.ActiveCfg = Release|x64
+ {4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C70730C8-94A1-4059-8966-8FD679D682D9}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_plg/ml_plg.vcxproj b/Src/Plugins/Library/ml_plg/ml_plg.vcxproj
new file mode 100644
index 00000000..d97866f3
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/ml_plg.vcxproj
@@ -0,0 +1,331 @@
+<?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>{4E2B4D6C-E2D4-450E-ACE8-2E6546790BFF}</ProjectGuid>
+ <RootNamespace>ml_plg</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)'=='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>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|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)'=='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|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;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;ML_PLG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <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;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_PLG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <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>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;NDEBUG;_WINDOWS;_USRDLL;ML_PLG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <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>
+ <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>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_PLG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <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>
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp" />
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="..\..\..\nu\ServiceWatcher.cpp" />
+ <ClCompile Include="..\..\..\nu\sort.cpp" />
+ <ClCompile Include="..\..\..\playlist\plstring.cpp" />
+ <ClCompile Include="..\..\..\playlist\pl_entry.cpp" />
+ <ClCompile Include="..\..\..\Winamp\strutil.cpp" />
+ <ClCompile Include="AddPlaylist.cpp" />
+ <ClCompile Include="AlbumID.cpp" />
+ <ClCompile Include="generate.cpp" />
+ <ClCompile Include="IDScanner.cpp" />
+ <ClCompile Include="impl_playlist.cpp" />
+ <ClCompile Include="ml_plg.cpp" />
+ <ClCompile Include="pass1.cpp" />
+ <ClCompile Include="pass2.cpp" />
+ <ClCompile Include="playlist.cpp" />
+ <ClCompile Include="PlaylistGeneratorAPI.cpp" />
+ <ClCompile Include="prefs.cpp" />
+ <ClCompile Include="util.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\nu\ComboBox.h" />
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h" />
+ <ClInclude Include="..\..\..\nu\sort.h" />
+ <ClInclude Include="..\..\..\playlist\plstring.h" />
+ <ClInclude Include="..\..\..\playlist\pl_entry.h" />
+ <ClInclude Include="..\..\..\Winamp\strutil.h" />
+ <ClInclude Include="api__ml_plg.h" />
+ <ClInclude Include="api_playlist_generator.h" />
+ <ClInclude Include="IDScanner.h" />
+ <ClInclude Include="impl_playlist.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="playlist.h" />
+ <ClInclude Include="PlaylistGeneratorAPI.h" />
+ <ClInclude Include="resource.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="gn_logo_88x83.bmp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_plg.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ </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_plg/ml_plg.vcxproj.filters b/Src/Plugins/Library/ml_plg/ml_plg.vcxproj.filters
new file mode 100644
index 00000000..8ca6cc4f
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/ml_plg.vcxproj.filters
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="AddPlaylist.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="AlbumID.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="generate.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="IDScanner.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="impl_playlist.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ml_plg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="pass1.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="pass2.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="playlist.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PlaylistGeneratorAPI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="prefs.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="util.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\playlist\pl_entry.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\playlist\plstring.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\ServiceWatcher.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\sort.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\Winamp\strutil.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PlaylistGeneratorAPI.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="playlist.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="impl_playlist.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="IDScanner.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="api__ml_plg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="api_playlist_generator.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\ComboBox.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\playlist\plstring.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\sort.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\Winamp\strutil.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\playlist\pl_entry.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{5c57af84-bce1-4236-9a99-d6af4b649140}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{8c2d8c3b-a651-4079-87b0-6631b6b0cced}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{79665634-6d8e-44ea-9748-ce739b474314}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Image Files">
+ <UniqueIdentifier>{f007a32d-06d5-409a-9574-daaa6d1dacbd}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_plg.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="gn_logo_88x83.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/pass1.cpp b/Src/Plugins/Library/ml_plg/pass1.cpp
new file mode 100644
index 00000000..c1240519
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/pass1.cpp
@@ -0,0 +1,101 @@
+#include "main.h"
+#include "playlist.h"
+#include <atlbase.h>
+#include "IDScanner.h"
+#include "api__ml_plg.h"
+
+static long PlaylistManagerCount()
+{
+ ICddbPL2FindDataPtr pFindData;
+ ICddbDisc2Ptr pDisc;
+ long count =0;
+ HRESULT hr = pFindData.CreateInstance(CLSID_CddbPL2FindData);
+ if (FAILED(hr))
+ return count;
+
+ // empty FindData iterator means ALL FILES
+ hr = playlistMgr->FindOpen(pFindData);
+ if (FAILED(hr))
+ return count;
+
+ while (SUCCEEDED(playlistMgr->FindNext(pFindData, &pDisc)))
+ {
+ count++;
+ }
+ playlistMgr->FindClose(pFindData);
+ return count;
+
+}
+/*
+Pass 1 Algorithm
+
+Find all files with gnpl_crit_field_xdev1 == "1"
+for each:
+GetFileInfo "GracenoteFileID"
+if success
+{
+xdev1 = "2"
+GetFileInfo "GracenoteExtData"
+if success
+xdev1 = "done"
+}
+else
+xdev="3"
+*/
+
+void IDScanner::Pass1()
+{
+ // benski> this function REALLY SUCKS but there's not much we can do about it unfortunately.
+ // because Gracenote's Playlist SDK doesn't give us a good way to run queries like this
+
+ ICddbPL2FindDataPtr pFindData;
+ ICddbDisc2Ptr pDisc;
+
+ HRESULT hr;
+ filesTotal = PlaylistManagerCount(); // super slow, but hey this whole function is slow, anyway
+
+ hr = pFindData.CreateInstance(CLSID_CddbPL2FindData);
+ if (FAILED(hr))
+ return ;
+
+ // empty FindData iterator means ALL FILES
+ hr = playlistMgr->FindOpen(pFindData);
+ if (FAILED(hr))
+ return;
+
+ while (SUCCEEDED(playlistMgr->FindNext(pFindData, &pDisc)))
+ {
+ if (killswitch)
+ break;
+ CComBSTR path,filespec;
+ wchar_t file[MAX_PATH] = {0};
+ CComBSTR phase;
+
+ pDisc->GetProperty(PROP_Default, PLM_Filename, &filespec);
+ pDisc->GetProperty(PROP_Default, PLM_Pathname, &path);
+ PathCombineW(file, path, filespec);
+ playlistMgr->FileGetFieldVal(file, gnpl_crit_field_xdev1, &phase);
+ if (phase && phase[0] && phase[0]=='1')
+ {
+ wchar_t gracenoteFileId[256]=L"";
+ if (GetFileInfo(file, L"GracenoteFileID", gracenoteFileId, 256) && gracenoteFileId[0])
+ {
+ wchar_t gracenoteExtData[65536]=L"";
+ GetFileInfo(file, L"GracenoteExtData", gracenoteExtData, 65536);
+
+ // write back to Media Library database (since if we got here, it wasn't in the itemRecordList)
+ AGAVE_API_MLDB->SetField(file, "GracenoteFileID", gracenoteFileId);
+ if (gracenoteExtData[0]) AGAVE_API_MLDB->SetField(file, "GracenoteExtData", gracenoteExtData);
+
+ SetGracenoteData(file, gracenoteFileId, gracenoteExtData);
+ playlistMgr->FileSetFieldVal(file, gnpl_crit_field_xdev1, L"0"); // mark as done!
+ }
+ else // no Tag ID, so we'll have to use AlbumID
+ {
+ playlistMgr->FileSetFieldVal(file, gnpl_crit_field_xdev1, L"2"); // move to phase 2
+ }
+ }
+ filesComplete++;
+ }
+ playlistMgr->FindClose(pFindData);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/pass2.cpp b/Src/Plugins/Library/ml_plg/pass2.cpp
new file mode 100644
index 00000000..6a4f10b2
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/pass2.cpp
@@ -0,0 +1,72 @@
+#include "IDScanner.h"
+#include "playlist.h"
+#include <atlbase.h>
+#include "main.h"
+#include "api__ml_plg.h"
+
+static ICddbFileInfoList *CreateScanList(volatile int *killswitch)
+{
+ // benski> this function REALLY SUCKS but there's not much we can do about it unfortunately.
+ // because Gracenote's Playlist SDK doesn't give us a good way to run queries like this
+ ICddbPL2FindDataPtr pFindData;
+ ICddbDisc2Ptr pDisc;
+
+ if (FAILED(pFindData.CreateInstance(CLSID_CddbPL2FindData)))
+ return 0;
+
+ if (FAILED(playlistMgr->FindOpen(pFindData)))
+ return 0;
+
+ ICddbFileInfoListPtr infoList;
+ infoList.CreateInstance(CLSID_CddbFileInfoList);
+
+ while (SUCCEEDED(playlistMgr->FindNext(pFindData, &pDisc)))
+ {
+ if (*killswitch)
+ return 0;
+ CComBSTR path,filespec;
+ wchar_t file[MAX_PATH] = {0};
+ CComBSTR phase;
+
+ pDisc->GetProperty(PROP_Default, PLM_Filename, &filespec);
+ pDisc->GetProperty(PROP_Default, PLM_Pathname, &path);
+ PathCombineW(file, path, filespec);
+ playlistMgr->FileGetFieldVal(file, gnpl_crit_field_xdev1, &phase);
+ if (!phase || !phase[0] || phase[0]!='2')
+ continue;
+
+ ICddbFileInfoPtr info;
+ info.CreateInstance(CLSID_CddbFileInfo);
+ info->put_Filename(file);
+ infoList->AddFileInfo(info);
+ }
+
+ playlistMgr->FindClose(pFindData);
+ infoList->AddRef();
+ return infoList;
+}
+
+
+/*
+Pass 2 Algorithm
+Find all files with gnpl_crit_field_xdev1 == "2" and run them through MusicID
+*/
+void IDScanner::Pass2()
+{
+ if (SetupMusicID())
+ {
+ ICddbFileInfoList *infoList = CreateScanList(&killswitch);
+ if (infoList)
+ {
+ musicID->LibraryID(infoList, MUSICID_RETURN_EXACT_ONLY | MUSICID_GET_FP_FROM_APP | MUSICID_GET_TAG_FROM_APP | MUSICID_PREFER_WF_MATCHES);
+ infoList->Release();
+ }
+ }
+}
+
+int IDScanner::Pass2OnThread(HANDLE handle, void *user_data, intptr_t id)
+{
+ IDScanner *scanner = (IDScanner *)id;
+ scanner->Pass2();
+ return 0;
+}
diff --git a/Src/Plugins/Library/ml_plg/playlist.cpp b/Src/Plugins/Library/ml_plg/playlist.cpp
new file mode 100644
index 00000000..ac459ba3
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/playlist.cpp
@@ -0,0 +1,635 @@
+#include "playlist.h"
+#include <shlwapi.h>
+#include "main.h"
+#include "api__ml_plg.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "impl_playlist.h"
+//#import "../gracenote/CDDBControlWinamp.dll" no_namespace, named_guids, raw_interfaces_only
+#include "../gracenote/cddbcontrolwinamp.tlh"
+#include "../winamp/ipc_pe.h"
+#include "resource.h"
+
+#include <strsafe.h>
+
+//extern Playlist currentPlaylist;
+ICddbPlaylist25Mgr *playlistMgr;
+ICddbMLDBManager *mldbMgr;
+
+Playlist currentPlaylist;
+Playlist seedPlaylist;
+
+class PlaylistEventHandler : public DPlaylist2Events
+{
+ STDMETHODIMP STDMETHODCALLTYPE QueryInterface(REFIID riid, PVOID *ppvObject)
+ {
+ if (!ppvObject)
+ return E_POINTER;
+
+ else if (IsEqualIID(riid, __uuidof(DPlaylist2Events)))
+ *ppvObject = (DPlaylist2Events *)this;
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+ }
+
+ ULONG STDMETHODCALLTYPE AddRef(void)
+ {
+ return 1;
+ }
+
+ ULONG STDMETHODCALLTYPE Release(void)
+ {
+ return 0;
+ }
+
+ HRESULT STDMETHODCALLTYPE Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+ {
+ switch (dispid)
+ {
+ case 1:
+ break;
+ case 2:
+ break;
+ case 3:
+ break;
+ case 4:
+ break;
+ case 5:
+ break;
+
+ }
+ return DISP_E_MEMBERNOTFOUND;
+ }
+
+ HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+ {
+ *rgdispid = DISPID_UNKNOWN;
+ return DISP_E_UNKNOWNNAME;
+ }
+
+ HRESULT STDMETHODCALLTYPE GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT STDMETHODCALLTYPE GetTypeInfoCount(unsigned int FAR * pctinfo)
+ {
+ return E_NOTIMPL;
+ }
+
+};
+
+static IConnectionPoint *GetConnectionPoint(IUnknown *punk, REFIID riid)
+{
+ if (!punk)
+ return 0;
+
+ IConnectionPointContainer *pcpc;
+ IConnectionPoint *pcp = 0;
+
+ HRESULT hr = punk->QueryInterface(IID_IConnectionPointContainer, (void **) & pcpc);
+ if (SUCCEEDED(hr))
+ {
+ pcpc->FindConnectionPoint(riid, &pcp);
+ pcpc->Release();
+ }
+ return pcp;
+}
+
+
+static PlaylistEventHandler events;
+static DWORD m_dwCookie = 0;
+//static DWORD m_dwCookie_MldbMgr = 0;
+bool SetupPlaylistSDK()
+{
+ if (!playlistMgr)
+ {
+ //playlistMgr = AGAVE_API_GRACENOTE->GetPlaylistManager();
+ //AGAVE_API_GRACENOTE->GetPlaylistManagerWithMLDBManager(&playlistMgr, &mldbMgr);
+ AGAVE_API_GRACENOTE->GetPlaylistManager(&playlistMgr, &mldbMgr);
+
+ IConnectionPoint *icp = GetConnectionPoint(playlistMgr, DIID_DPlaylist2Events);
+ if (icp)
+ {
+ icp->Advise(static_cast<IDispatch *>(&events), &m_dwCookie);
+ icp->Release();
+ }
+ }
+
+ return !!playlistMgr;
+}
+
+void ShutdownPlaylistSDK()
+{
+ if (playlistMgr)
+ {
+ if (mldbMgr)
+ {
+ mldbMgr->Detach(playlistMgr); // Detach the mldb manager from the playlist manager if it is not null
+ }
+
+ IConnectionPoint *icp = GetConnectionPoint(playlistMgr, DIID_DPlaylist2Events);
+ if (icp)
+ {
+ icp->Unadvise(m_dwCookie);
+ icp->Release();
+ }
+
+ if (mldbMgr)
+ mldbMgr->Release(); // Release the mldb manager if its not null
+
+ playlistMgr->Shutdown();
+ playlistMgr->Release();
+ }
+ mldbMgr=0;
+ playlistMgr=0;
+}
+
+// Caller must cleanup the BSTR
+BSTR SetAndCreatePath(/*wchar_t *path_to_create,*/ const wchar_t *node)
+{
+ wchar_t path_to_create[MAX_PATH] = {0};
+
+ BSTR bPath = 0;
+
+ PathCombineW(path_to_create, WASABI_API_APP->path_getUserSettingsPath(), L"Plugins");
+ CreateDirectoryW(path_to_create, 0);
+ PathAppendW(path_to_create, node);
+ CreateDirectoryW(path_to_create, 0);
+
+ bPath = SysAllocString(path_to_create);
+ return bPath;
+ // modified path as return value
+}
+
+// IMPORTANT: Make sure to call this on the gracenote dedicated thread.
+int RestoreGracenoteMLDB(void)
+{
+ long restoreFlags = PL_MLDB_RESTORE_BASE | PL_MLDB_RESTORE_INDEX;
+ //wchar_t backupPath[MAX_PATH] = {0};
+ BSTR bDataPath = SetAndCreatePath(GRACENOTE_DB_BASE_PATH);
+ BSTR bBackupPath = SetAndCreatePath(GRACENOTE_DB_BACKUP_PATH);
+
+ // Restore the db files.
+ mldbMgr->RestoreDBFiles(restoreFlags, bDataPath, bBackupPath);
+
+ SysFreeString(bDataPath);
+ SysFreeString(bBackupPath);
+ return NErr_Success;
+}
+
+// Backs up the gracenote MLDB so that it can be restored on corruption
+// IMPORTANT: Make sure to call this on the gracenote dedicated thread.
+int BackupGracenoteMLDB(void)
+{
+ long backupFlags = PL_MLDB_BACKUP_BASE | PL_MLDB_BACKUP_INDEX;
+ //wchar_t backupPath[MAX_PATH] = {0};
+ BSTR bDataPath = SetAndCreatePath(GRACENOTE_DB_BASE_PATH);
+ BSTR bBackupPath = SetAndCreatePath(GRACENOTE_DB_BACKUP_PATH);
+
+ // Backup the db files.
+ mldbMgr->BackupDBFiles(backupFlags, bDataPath, bBackupPath);
+
+ SysFreeString(bDataPath);
+ SysFreeString(bBackupPath);
+ return NErr_Success;
+}
+
+/*BOOL DeleteGracenoteFile(char *filename)
+{
+ BOOL result;
+ char path[MAX_PATH] = {0};
+ //PathCombineA(path,mediaLibrary.GetIniDirectory(),"Plugins\\Gracenote");
+ PathCombineA(path,"C:\\Users\\bigg\\AppData\\Roaming\\Winamp\\","Plugins\\Gracenote");
+ PathAppendA(path, filename);
+ result = DeleteFileA(path);
+ return result;
+}*/
+
+void CheckForResetError(HRESULT error)
+{
+ if (error != S_OK)
+ {
+ MessageBoxW(0, WASABI_API_LNGSTRINGW(IDS_ERROR_RESET), (LPWSTR)plugin.description, MB_OK| MB_ICONERROR);
+ }
+}
+
+void CheckForShutdownError(HRESULT error)
+{
+ if (error != S_OK)
+ {
+ MessageBoxW(plugin.hwndWinampParent, WASABI_API_LNGSTRINGW(IDS_CANNOT_SHUT_DOWN), (LPWSTR)plugin.description, MB_OK| MB_ICONERROR);
+ }
+}
+
+// Deprecated: Currently not used, remove at some point
+/*
+INT_PTR CALLBACK ResettingProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ //ShowWindow(hwndDlg,SW_HIDE);
+ ShowWindow(hwndDlg, SW_SHOW);
+ return 0;
+ break;
+ case WM_QUIT:
+ EndDialog(hwndDlg,0);
+ return 0;
+ break;
+ }
+ return 0;
+}*/
+
+// IMPORTANT: Make sure to call this on the gracenote dedicated thread.
+int DeleteGracenoteMLDB(bool silent)
+{
+ long deleteFlags = PL_MLDB_DELETE_INDEX | PL_MLDB_DELETE_BASE | PL_MLDB_DELETE_OTHER | PL_MLDB_DELETE_BACKUPS;
+ //long deleteFlags = PL_MLDB_DELETE_INDEX | PL_MLDB_DELETE_BASE | PL_MLDB_DELETE_OTHER; // | PL_MLDB_DELETE_BACKUPS;
+ BSTR bDataPath = SetAndCreatePath(GRACENOTE_DB_BASE_PATH);
+
+ HRESULT error = 0;
+
+ if (playlistMgr)
+ error = playlistMgr->Shutdown();
+
+ if (!silent)
+ CheckForShutdownError(error);
+
+ // Spawn the working window
+ //HWND hwndResetWorking = WASABI_API_CREATEDIALOG(IDD_NAG, plugin.hwndWinampParent, ResettingProcedure);
+ if (mldbMgr)
+ error = mldbMgr->DeleteDBFiles(deleteFlags, bDataPath);
+ //SendMessage(hwndResetWorking, WM_QUIT, 0, 0);
+
+ if (!silent)
+ CheckForResetError(error);
+
+ SysFreeString(bDataPath);
+ return NErr_Success; // NOT the HRESULT so that non zero values return as false
+}
+
+int InitializeMLDBManager(void)
+{
+ // Other initializations for the MLDB manager can go here
+ ///BackupGracenoteMLDB();
+
+ return NErr_Success;
+}
+
+static void ConfigureGeneratorPrefs(ICddbPLMoreLikeThisCfg *config)
+{
+ // ToDo: (BigG) Consider using some of the different algorithms
+ // GNPL_MORELIKETHIS_ALG_20 (Playlist SDK 2.0)
+ // GNPL_MORELIKETHIS_ALG_25 (Playlist SDK 2.5)
+ // GNPL_MORELIKETHIS_ALG_DSP_1 (Playlist SDK 2.6)
+ // GNPL_MORELIKETHIS_ALG_DSP_25 (Playlist SDK 2.6)
+
+ config->put_Algorithm(GNPL_MORELIKETHIS_ALG_DEFAULT);
+ if(!multipleAlbums) config->put_MaxPerAlbum(1);
+ if(!multipleArtists) config->put_MaxPerArtist(1);
+ //config->put_TrackLimit(plLength);
+ config->put_TrackLimit(0); // Dont put a limit on gracenote (return all tracks)
+}
+
+//void playPlaylist(Playlist &pl, bool enqueue=false, int startplaybackat=0/*-1 for don't start playback*/, const wchar_t *seedfn=NULL, int useSeed=FALSE)
+void playPlaylist(Playlist &pl, bool enqueue=false, int startplaybackat=0/*-1 for don't start playback*/, int useSeed=FALSE)
+{
+ extern winampMediaLibraryPlugin plugin;
+
+ const size_t number_of_seeds = seedPlaylist.GetNumItems();
+ wchar_t seedFilename[MAX_PATH] = {0};
+ seedFilename[0] = 0;
+
+ if(!enqueue)
+ mediaLibrary.ClearPlaylist();
+
+ // Enqueue the seed tracks first
+ if(useSeed)
+ {
+ for (size_t i = 0; i < number_of_seeds; i++)
+ {
+ seedPlaylist.GetItem(i, seedFilename, MAX_PATH); // Get the playlist filename
+
+ enqueueFileWithMetaStructW s={seedFilename,NULL,PathFindExtensionW(seedFilename),-1};
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
+ }
+ }
+
+ size_t listLength = pl.GetNumItems();
+ for(size_t i=0; i<listLength; i++)
+ {
+ wchar_t filename[MAX_PATH] = {0};
+ pl.GetItem(i,filename,MAX_PATH);
+
+ //if(seedfn && !_wcsicmp(seedfn,filename)) // Not really sure this is necessary... just making sure that the same file doesnt get added twice
+ // continue;
+ enqueueFileWithMetaStructW s={filename,NULL,PathFindExtensionW(filename),-1};
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
+ }
+
+ if(!enqueue && startplaybackat != -1)
+ { //play item startplaybackat
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,startplaybackat,IPC_SETPLAYLISTPOS);
+ SendMessage(plugin.hwndWinampParent,WM_COMMAND,40047,0); //stop
+ SendMessage(plugin.hwndWinampParent,WM_COMMAND,40045,0); //play
+ }
+}
+
+
+// Tests wether an item can be added to a playlist and still stay under the current target
+bool PlaylistIsFull(Playlist *playlist, /*uint64_t*/ unsigned int currentItemLength, /*uint64_t*/ unsigned int currentItemSize)
+{
+#define POWER_2_TO_20 1048576 // 1024 * 1024
+ uint64_t measurement = 0;
+
+ // See what type we care about items, size, or length
+ switch (plLengthType)
+ {
+ case PL_ITEMS: // Check for number of items
+ {
+ measurement = currentPlaylist.GetNumItems() + ( (useSeed) ? seedPlaylist.GetNumItems() : 0 ); // Add the data from the seed track if we are using it
+
+ if ( (measurement + 1) > plItems ) // See if an extra item will put us over.
+ return true;
+ else
+ return false;
+ }
+ break;
+
+ case PL_MINUTES: // Check for minutes used
+ {
+ measurement = currentPlaylist.GetPlaylistLengthMilliseconds() + ( (useSeed) ? seedPlaylist.GetPlaylistLengthMilliseconds() : 0 );
+
+ if ( (measurement + (currentItemLength) ) > (plMinutes * 60 * 1000) )
+ return true;
+ else
+ return false;
+ }
+ break;
+ case PL_MEGABYTES: // Check for megabytes used
+ {
+ measurement = currentPlaylist.GetPlaylistSizeBytes() + ( (useSeed) ? seedPlaylist.GetPlaylistSizeBytes() : 0 );
+
+ if ( (measurement + (uint64_t)currentItemSize) > ((uint64_t)plMegabytes * POWER_2_TO_20) )
+ return true;
+ else
+ return false;
+ }
+ break;
+ }
+
+ return true;
+}
+
+bool MatchesQuery(const wchar_t *filename, const wchar_t *user_query)
+{
+ // Get an item that mathces both the filename and the query
+ itemRecordW *result = AGAVE_API_MLDB->GetFileIf(filename, user_query);
+
+ if (result)
+ {
+ AGAVE_API_MLDB->FreeRecord(result); // Clean up the records list since we are done using it
+ return true;
+ }
+ else
+ {
+ AGAVE_API_MLDB->FreeRecord(result); // Clean up the records list since we are done using it
+ return false;
+ }
+}
+
+
+// Callback for getting a tag for gracenote library items
+static wchar_t * TitleTagFuncGracenote(const wchar_t * tag, void * p)
+{ //return 0 if not found, -1 for empty tag
+ //tagItem * s = (tagItem *)p;
+ //wchar_t *filename = (wchar_t *)p;
+ ICddbPL2Result *gracenoteResult = (ICddbPL2Result *)p;
+
+ BSTR tag_data;
+
+ if (!_wcsicmp(tag, L"artist")) gracenoteResult->GetArtist(&tag_data)/*wsprintf(buf,L"%s",L"artist")*/;
+ else if (!_wcsicmp(tag, L"album")) gracenoteResult->GetAlbum(&tag_data);
+ else if (!_wcsicmp(tag, L"title")) gracenoteResult->GetTitle(&tag_data);
+ else if (!_wcsicmp(tag, L"filename")) gracenoteResult->GetFilename(&tag_data);
+ else
+ return 0;
+
+ //else if (!_wcsicmp(tag, L"genre")) -;
+ //else if (!_wcsicmp(tag, L"year")) -;
+ //else if (!_wcsicmp(tag, L"tracknumber")) -;
+ //else if (!_wcsicmp(tag, L"discnumber")) -;
+ //else if (!_wcsicmp(tag, L"bitrate")) -;
+
+ //else if (!_wcsicmp(tag, L"albumartist")) -;
+ //else if (!_wcsicmp(tag, L"composer")) -;
+ //else if (!_wcsicmp(tag, L"publisher")) -;
+
+ return tag_data;
+}
+
+// Callback for getting a tag for media library items
+static wchar_t * TitleTagFuncML(const wchar_t * tag, void * p)
+{ //return 0 if not found, -1 for empty tag
+ itemRecordW *mlResult = (itemRecordW *)p;
+
+ if (!mlResult)
+ return 0; // Return 0 because we dont have this ml object
+
+ wchar_t buf[128] = {0};
+
+ wchar_t *tag_data = 0;
+
+ if (!_wcsicmp(tag, L"artist")) tag_data = mlResult->artist;
+ else if (!_wcsicmp(tag, L"album")) tag_data = mlResult->album;
+ else if (!_wcsicmp(tag, L"title")) tag_data = mlResult->title;
+ else if (!_wcsicmp(tag, L"filename")) tag_data = mlResult->filename;
+ else if (!_wcsicmp(tag, L"genre")) tag_data = mlResult->genre;
+ else if (!_wcsicmp(tag, L"year")) if (mlResult->year > 0) { StringCchPrintfW(buf, 128, L"%04d", mlResult->year); tag_data = buf; }
+ else if (!_wcsicmp(tag, L"tracknumber") || !_wcsicmp(tag, L"track")) if (mlResult->track > 0) { StringCchPrintfW(buf, 128, L"%04d", mlResult->track); tag_data = buf; }
+ else if (!_wcsicmp(tag, L"discnumber")) if (mlResult->disc > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->disc); tag_data = buf; }
+ else if (!_wcsicmp(tag, L"bitrate")) if (mlResult->bitrate > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->bitrate); tag_data = buf; }
+ else if (!_wcsicmp(tag, L"albumartist")) tag_data = mlResult->albumartist;
+ else if (!_wcsicmp(tag, L"composer")) tag_data = mlResult->composer;
+ else if (!_wcsicmp(tag, L"publisher")) tag_data = mlResult->publisher;
+ else if (!_wcsicmp(tag, L"bpm")) if (mlResult->bpm > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->bpm); tag_data = buf; }
+ else if (!_wcsicmp(tag, L"comment")) tag_data = mlResult->comment;
+ else if (!_wcsicmp(tag, L"discs")) if (mlResult->discs > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->discs); tag_data = buf; }
+ else if (!_wcsicmp(tag, L"filesize")) if (mlResult->filesize > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->filesize); tag_data = buf; }
+ //else if (!_wcsicmp(tag, L"filetime")) tag_data = mlResult->filetime;
+ else if (!_wcsicmp(tag, L"length")) if (mlResult->length > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->length); tag_data = buf; }
+ else if (!_wcsicmp(tag, L"playcount")) if (mlResult->playcount > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->playcount); tag_data = buf; }
+ else if (!_wcsicmp(tag, L"rating")) if (mlResult->rating > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->rating); tag_data = buf; }
+ else
+ return 0;
+
+ return _wcsdup(tag_data);
+}
+
+// Callback to free a tag in gracenote library
+static void TitleTagFreeFuncGracenote(wchar_t *tag_data, void *p)
+{
+ if(tag_data)
+ SysFreeString(tag_data);
+}
+
+// Callback to free a tag in media library
+static void TitleTagFreeFuncML(wchar_t *tag_data, void *p)
+{
+ if(tag_data)
+ free(tag_data);
+}
+
+// Retreive the title formatting for gracenote library
+void GetTitleFormattingGracenote(const wchar_t *filename, ICddbPL2Result * gracenoteResult, wchar_t * buf, int len)
+{
+ waFormatTitleExtended fmt={ filename, 1, NULL, (void *)gracenoteResult, buf, len, TitleTagFuncGracenote, TitleTagFreeFuncGracenote };
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&fmt, IPC_FORMAT_TITLE_EXTENDED);
+}
+
+// Retreive the title formatting for media library
+void GetTitleFormattingML(const wchar_t *filename, itemRecordW *mlResult, wchar_t * buf, int len)
+{
+ waFormatTitleExtended fmt={ filename, 1, NULL, (void *)mlResult, buf, len, TitleTagFuncML, TitleTagFreeFuncML };
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&fmt, IPC_FORMAT_TITLE_EXTENDED);
+}
+
+static int MoreLikeTheseSongsFunc(HANDLE handle, void *user_data, intptr_t id)
+{
+ Playlist *pl = (Playlist *)user_data;
+ const int number_of_files = (int)pl->GetNumItems();
+
+ isGenerating = true;
+
+ //Sleep(501); // Wait for the update timer to finish its cycles, This is EVIL, keep it removed...
+ SetMarqueeProgress(true); // Turn the marquee off because we are actually generating the tracks
+ SetDlgItemText(hwndDlgCurrent, IDC_STATIC_PROGRESS_STATE, WASABI_API_LNGSTRINGW(IDS_GENERATING));
+ //EnableWindow (GetDlgItem(hwndDlgCurrent, IDC_BUTTON_REGENERATE), FALSE );
+ SetButtonsEnabledState(false); // Disable the buttons once we start generating here
+
+ if (SetupPlaylistSDK())
+ {
+ HRESULT hr;
+
+ ICddbPLMoreLikeThisCfgPtr cfg;
+ cfg.CreateInstance(CLSID_CddbPLMoreLikeThisCfg);
+ ConfigureGeneratorPrefs(cfg);
+
+ ICddbPL2ResultListPtr results;
+ wchar_t plFilename[MAX_PATH] = {0};
+
+ if (number_of_files == 1) // Call the regular morelikethis function if there is only the standard seed track
+ {
+ pl->GetItem(0, plFilename, MAX_PATH);
+ BSTR i_dunno = SysAllocString(plFilename);
+ hr=playlistMgr->MoreLikeThisSong(i_dunno, cfg, &results);
+ SysFreeString(i_dunno);
+ }
+ else if (number_of_files > 1) // We have more than 1 seed track
+ {
+ // Create the variant of an array of filenames for MoreLikeTheseSongs
+ VARIANT fileList;
+ VariantInit((VARIANTARG *)&fileList);
+ SAFEARRAY *psa = SafeArrayCreateVector (VT_BSTR, 0, number_of_files);
+ BSTR *data;
+ SafeArrayAccessData(psa, (void **)&data);
+ for (size_t i=0;i!=number_of_files;i++)
+ {
+ pl->GetItem(i, plFilename, MAX_PATH);
+ data[i] = SysAllocString(plFilename);
+ }
+ SafeArrayUnaccessData(psa);
+ V_VT(&fileList) = VT_ARRAY|VT_BSTR;
+ V_ARRAY(&fileList) = psa;
+
+
+ hr=playlistMgr->MoreLikeTheseSongs(fileList, cfg, &results);
+ }
+ else // We dont have any seed tracks (this should not happen because we shouldnt get this far.
+ {
+ return 1; // Failure
+ }
+
+ long count=-1;
+ if (results && SUCCEEDED(hr=results->get_Count(&count)) && count)
+ {
+ currentPlaylist.Clear();
+ for (long i=0;i<count;i++)
+ {
+ ICddbPL2Result *result;
+ if (SUCCEEDED(hr=results->GetResult(i+1, &result)))
+ {
+ BSTR filename = 0;
+ BSTR title = 0;
+
+ unsigned int length = -1;
+ unsigned int size = 0;
+
+ result->GetFilename(&filename);
+ result->GetTitle(&title);
+ result->GetTracklength(&length); // Gracenote is returning seconds here
+ result->GetFilesize(&size); // Gracenote took the size as kilobytes but it is returning it to us as bytes
+
+ length *= 1000; // Multiply length by 1000 to turn it into milliseconds from seconds
+
+ // Get the winamp user formatted title.
+ wchar_t winamp_title[512] = {0};
+ GetTitleFormattingGracenote(filename, result, winamp_title, 512);
+
+ // Only check for the query if the user wants to apply one
+ if ( useMLQuery == TRUE && MatchesQuery(filename, mlQuery ) == false )
+ {
+ SysFreeString(filename);
+ SysFreeString(title);
+ result->Release();
+ continue;
+ }
+
+ // Lets check for the playlist limit to see if we should add this track
+ if ( !PlaylistIsFull(&currentPlaylist, length, size) )
+ currentPlaylist.AppendWithInfo(filename, winamp_title, length, size);
+ // DONT break here if we are full, keep going as we may find something that will fit into the playlist eventually
+
+ SysFreeString(filename);
+ SysFreeString(title);
+ result->Release();
+ }
+ }
+ /*char cfg[1024 + 32] = {0};
+ char *dir = (char*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETINIDIRECTORY);
+ PathCombineA(cfg, dir, "Plugins\\gen_ml.ini");
+ int en = GetPrivateProfileIntA("gen_ml_config","enqueuedef",0,cfg);*/
+
+ PopulateResults(&currentPlaylist);
+ }
+ else /*if (count == 0)*/
+ {
+ PopulateResults(0); // Call populate with an empty playlist
+ CantPopulateResults(); // Display warning about not being able to generate any tracks
+ }
+ }
+
+ isGenerating = false;
+
+ return 0;
+}
+
+void MoreLikeTheseSongs(Playlist *pl)
+{
+ // Capture the stats, we dont care if its successful or not, we only care about the try
+ if (AGAVE_API_STATS)
+ AGAVE_API_STATS->IncrementStat(api_stats::PLG_COUNT);
+
+ // Call the the mor like this fuction on the gracenote reserved thread
+ WASABI_API_THREADPOOL->RunFunction(plg_thread, MoreLikeTheseSongsFunc, pl, 0, api_threadpool::FLAG_REQUIRE_COM_STA);
+}
diff --git a/Src/Plugins/Library/ml_plg/playlist.h b/Src/Plugins/Library/ml_plg/playlist.h
new file mode 100644
index 00000000..5af603ed
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/playlist.h
@@ -0,0 +1,40 @@
+#ifndef NULLSOFT_ML_PLG_PLAYLIST_H
+#define NULLSOFT_ML_PLG_PLAYLIST_H
+
+#include "../gracenote/gracenote.h"
+#include "../../General/gen_ml/ml.h"
+#include <bfc/error.h>
+
+#include "impl_playlist.h"
+
+extern ICddbPlaylist25Mgr *playlistMgr;
+extern ICddbMLDBManager *mldbMgr;
+extern Playlist currentPlaylist;
+bool SetupPlaylistSDK();
+void ShutdownPlaylistSDK();
+int InitializeMLDBManager(void);
+int DeleteGracenoteMLDB(bool silent);
+int BackupGracenoteMLDB(void);
+int RestoreGracenoteMLDB(void);
+void playPlaylist(Playlist &pl, bool enqueue, int startplaybackat, /*const wchar_t *seedfn,*/ int useSeed);
+
+void GetTitleFormattingGracenote(const wchar_t *filename, ICddbPL2Result * gracenoteResult, wchar_t * buf, int len);
+void GetTitleFormattingML(const wchar_t *filename, itemRecordW *mlResult, wchar_t * buf, int len);
+
+void MoreLikeThisSong(const wchar_t *filename);
+void MoreLikeTheseSongs(Playlist *pl);
+
+typedef enum
+{
+ PL_NOT_INITIALIZED = 0,
+ PL_ITEMS = 1,
+ PL_MINUTES = 2,
+ PL_MEGABYTES = 3
+} PlLengthTypeEnum;
+
+#define PLM_Filename L"PLM_rec_filename"
+#define PLM_Pathname L"PLM_rec_pathname"
+#define GRACENOTE_DB_BASE_PATH L"Gracenote"
+#define GRACENOTE_DB_BACKUP_PATH L"Gracenote/Backup"
+
+#endif
diff --git a/Src/Plugins/Library/ml_plg/prefs.cpp b/Src/Plugins/Library/ml_plg/prefs.cpp
new file mode 100644
index 00000000..f8ce2d75
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/prefs.cpp
@@ -0,0 +1,596 @@
+#include "../gracenote/gracenote.h"
+#include "api__ml_plg.h"
+#include <windows.h>
+#include "resource.h"
+#include "../../General/gen_ml/ml.h"
+#include "../winamp/wa_ipc.h"
+#include "../Agave/Language/api_language.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "../nu/ComboBox.h"
+
+#include "main.h"
+#include <shlwapi.h>
+#include <assert.h>
+#include "playlist.h"
+#include <atlbase.h>
+#include "IDScanner.h"
+#include <strsafe.h>
+
+extern bool pluginEnabled;
+extern int scanMode;
+extern int plLengthType;
+volatile bool is_scan_running=false;
+
+static bool IsScanRunning()
+{
+ return is_scan_running;
+}
+
+void ShowErrorDlg(HWND parent)
+{
+ wchar_t title[32] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_INITIALIZING,title,32);
+ MessageBox(parent, WASABI_API_LNGSTRINGW(IDS_INITFAILMSG),title,0);
+}
+
+IDScanner scanner;
+
+int ShutdownScanner(HANDLE handle, void *user_data, intptr_t id)
+{
+ HANDLE event = (HANDLE)user_data;
+ scanner.Shutdown();
+ ShutdownPlaylistSDK();
+ SetEvent(event);
+ return 0;
+}
+
+static int ScanThread(HANDLE handle, void *user_data, intptr_t id)
+{
+ is_scan_running=true;
+ scanner.ScanDatabase();
+ is_scan_running=false;
+ return 0;
+}
+
+static void doProgressBar(HWND h, int x, int t=-1) {
+ h = GetDlgItem(h,IDC_PROGRESS1);
+ if(t!=-1 && SendMessage(h,PBM_GETRANGE,0,0) != t)
+ SendMessage(h,PBM_SETRANGE32,0,t);
+ SendMessage(h,PBM_SETPOS,x,0);
+}
+
+static void FillStatus(HWND hwndDlg)
+{
+ long state, track, tracks;
+ if (scanner.GetStatus(&state, &track, &tracks))
+ {
+ static int x=0;
+ wchar_t *ticker;
+ switch (x++)
+ {
+ case 0: ticker=L""; break;
+ case 1: ticker=L"."; break;
+ case 2: ticker=L".."; break;
+ default: ticker=L"...";
+ }
+ x%=4;
+ wchar_t status[1024]=L"";
+ switch (state)
+ {
+ case IDScanner::STATE_ERROR:
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_INITIALIZING,status,1024);
+ KillTimer(hwndDlg, 1);
+ doProgressBar(hwndDlg,0,1);
+ ShowErrorDlg(hwndDlg);
+ break;
+ case IDScanner::STATE_IDLE:
+ WASABI_API_LNGSTRINGW_BUF(IDS_IDLE,status,1024);
+ doProgressBar(hwndDlg,0);
+ break;
+ case IDScanner::STATE_INITIALIZING:
+ StringCchPrintfW(status, 1024, WASABI_API_LNGSTRINGW(IDS_INITIALIZING), ticker);
+ doProgressBar(hwndDlg,0);
+ break;
+ case IDScanner::STATE_SYNC:
+ StringCchPrintfW(status, 1024, WASABI_API_LNGSTRINGW(IDS_SYNC), track, tracks, ticker);
+ doProgressBar(hwndDlg,track,tracks);
+ break;
+ case IDScanner::STATE_METADATA:
+ StringCchPrintfW(status, 1024, WASABI_API_LNGSTRINGW(IDS_METADATA), track, tracks, ticker);
+ doProgressBar(hwndDlg,track,tracks);
+ break;
+ case IDScanner::STATE_MUSICID:
+ StringCchPrintfW(status, 1024, WASABI_API_LNGSTRINGW(IDS_MUSICID), track, tracks, ticker);
+ doProgressBar(hwndDlg,track,tracks);
+ break;
+ case IDScanner::STATE_DONE:
+ WASABI_API_LNGSTRINGW_BUF(IDS_DONE,status,1024);
+ doProgressBar(hwndDlg,100,100);
+ KillTimer(hwndDlg, 1);
+ SetDlgItemText(hwndDlg,IDC_TEST,WASABI_API_LNGSTRINGW(IDS_SCAN));
+ break;
+ }
+ SetDlgItemTextW(hwndDlg, IDC_STATUS, status);
+ }
+}
+#if 0 // TODO: reimplement contract requirement
+LRESULT CALLBACK DeviceMsgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if (uMsg == WM_TIMER)
+ {
+ if (wParam == 0)
+ {
+ KillTimer(hwnd, 0);
+ if (CheckThread())
+ {
+ PostMessage(plugin.hwndLibraryParent, WM_USER+641, 0, 0);
+ SetTimer(hwnd, 1, 3000, 0);
+ }
+ else
+ DestroyWindow(hwnd);
+ }
+ else if (wParam == 1)
+ {
+ KillTimer(hwnd, 1);
+ PostMessage(plugin.hwndLibraryParent, WM_USER+641, 1, 0);
+ SetTimer(hwnd, 0, 27000, 0);
+ }
+ }
+ else if (uMsg == WM_DESTROY)
+ {
+ PostMessage(plugin.hwndLibraryParent, WM_USER+641, 1, 0);
+ }
+
+ return DefWindowProcW(hwnd, uMsg, wParam, lParam);
+}
+
+static bool classRegistered=0;
+HWND CreateDummyWindow()
+{
+ if (!classRegistered)
+ {
+ WNDCLASSW wc = {0, };
+
+ wc.style = 0;
+ wc.lpfnWndProc = DeviceMsgProc;
+ wc.hInstance = plugin.hDllInstance;
+ wc.hIcon = 0;
+ wc.hCursor = NULL;
+ wc.lpszClassName = L"ml_plg_window";
+
+ if (!RegisterClassW(&wc))
+ return 0;
+
+ classRegistered = true;
+ }
+ HWND dummy = CreateWindowW(L"ml_plg_window", L"ml_plg_window", 0, 0, 0, 0, 0, NULL, NULL, plugin.hDllInstance, NULL);
+
+ return dummy;
+}
+#endif
+
+bool StartScan()
+{
+ if (IsScanRunning())
+ {
+ //run_full_scan_flag = true; // Not really necessary since we are already running the scan
+ return false;
+ }
+
+ if (reset_db_flag) // See if we have the flag set to reset the DB.
+ {
+ SetDlgItemTextW(hwndDlgCurrent, IDC_STATIC_PROGRESS_STATE, WASABI_API_LNGSTRINGW(IDS_RESETDB));
+ NukeDB();
+ //ResetDBOnThread(true);
+ reset_db_flag = false;
+ }
+
+ if (run_full_scan_flag) // See if we have the flag set for running the whole scan
+ {
+ // Call the scan procedure on the reserved gracenote specific thread
+ if (WASABI_API_THREADPOOL->RunFunction(plg_thread, ScanThread, 0, 0, api_threadpool::FLAG_REQUIRE_COM_STA) == 0)
+ {
+ run_full_scan_flag = false; // Reset the flag to false since we (will) complete the run
+ return true;
+ }
+ }
+ else if (run_pass2_flag) // If we have the pass2 scan flag set then run the second pass only
+ {
+ if (WASABI_API_THREADPOOL->RunFunction(plg_thread, IDScanner::Pass2OnThread, 0, (intptr_t)&scanner, api_threadpool::FLAG_REQUIRE_COM_STA) == 0)
+ {
+ run_pass2_flag = false; // Set the flag back to false so we know that we wont have to run it again
+ return true;
+ }
+ }
+
+
+ return false;
+}
+
+void StopScan()
+{
+ if (IsScanRunning())
+ {
+ scanner.Kill();
+ while (IsScanRunning())
+ Sleep(500);
+ run_full_scan_flag = true; // Set the flag so that we rerun everything on a rescan
+ }
+}
+
+void SetPlLengthTypeComboToItems(HWND hwndDlg, int value)
+{
+ ComboBox combo(hwndDlg, IDC_COMBO_LENGTH);
+ combo.Clear();
+ combo.SetItemData(combo.AddString("10"),10);
+ combo.SetItemData(combo.AddString("20"),20);
+ combo.SetItemData(combo.AddString("50"),50);
+ combo.SetItemData(combo.AddString("100"),100);
+ combo.SetItemData(combo.AddString("200"),200);
+ wchar_t buf[32] = {0};
+ _itow(value,buf,10);
+ combo.SetEditText(buf);
+}
+
+void SetPlLengthTypeComboToMinutes(HWND hwndDlg, int value)
+{
+ ComboBox combo(hwndDlg, IDC_COMBO_LENGTH);
+ combo.Clear();
+ combo.SetItemData(combo.AddString("10"),10);
+ combo.SetItemData(combo.AddString("20"),20);
+ combo.SetItemData(combo.AddString("30"),30);
+ combo.SetItemData(combo.AddString("60"),60);
+ combo.SetItemData(combo.AddString("74"),60);
+ combo.SetItemData(combo.AddString("80"),60);
+ combo.SetItemData(combo.AddString("90"),60);
+ combo.SetItemData(combo.AddString("120"),120);
+ wchar_t buf[32] = {0};
+ _itow(value,buf,10);
+ combo.SetEditText(buf);
+}
+
+void SetPlLengthTypeComboToMegabytes(HWND hwndDlg, int value)
+{
+ ComboBox combo(hwndDlg, IDC_COMBO_LENGTH);
+ combo.Clear();
+ combo.SetItemData(combo.AddString("100"),10);
+ combo.SetItemData(combo.AddString("650"),20);
+ combo.SetItemData(combo.AddString("703"),30);
+ combo.SetItemData(combo.AddString("4489"),60);
+ combo.SetItemData(combo.AddString("8147"),120);
+
+ wchar_t buf[32] = {0};
+ _itow(value,buf,10);
+ combo.SetEditText(buf);
+}
+
+// Set a control to bold text and a more bold font
+void BoldStatusText(HWND hwndControl)
+{
+ HFONT hFont ;
+
+ LOGFONT lfFont;
+
+ memset(&lfFont, 0x00, sizeof(lfFont));
+ memcpy(lfFont.lfFaceName, TEXT("Microsoft Sans Serif"), 24);
+
+ lfFont.lfHeight = 14;
+ lfFont.lfWeight = FW_BOLD;
+ lfFont.lfCharSet = ANSI_CHARSET;
+ lfFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
+ lfFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
+ lfFont.lfQuality = DEFAULT_QUALITY;
+
+ // Create the font from the LOGFONT structure passed.
+ hFont = CreateFontIndirect (&lfFont);
+
+ //SendMessageW(GetDlgItem(hwndDlg,IDC_STATUS), WM_SETFONT, (LPARAM)hFont, MAKELONG(TRUE, 0 ) );
+ SendMessageW(hwndControl, WM_SETFONT, (LPARAM)hFont, MAKELONG(TRUE, 0 ) );
+}
+
+void getViewport(RECT *r, HWND wnd, int full, RECT *sr)
+{
+ POINT *p = NULL;
+ if (p || sr || wnd)
+ {
+ HMONITOR hm = NULL;
+ if (sr)
+ hm = MonitorFromRect(sr, MONITOR_DEFAULTTONEAREST);
+ else if (wnd)
+ hm = MonitorFromWindow(wnd, MONITOR_DEFAULTTONEAREST);
+ else if (p)
+ hm = MonitorFromPoint(*p, MONITOR_DEFAULTTONEAREST);
+
+ if (hm)
+ {
+ MONITORINFOEXW mi;
+ memset(&mi, 0, sizeof(mi));
+ mi.cbSize = sizeof(mi);
+
+ if (GetMonitorInfoW(hm, &mi))
+ {
+ if (!full)
+ *r = mi.rcWork;
+ else
+ *r = mi.rcMonitor;
+ return ;
+ }
+ }
+ }
+ if (full)
+ { // this might be borked =)
+ r->top = r->left = 0;
+ r->right = GetSystemMetrics(SM_CXSCREEN);
+ r->bottom = GetSystemMetrics(SM_CYSCREEN);
+ }
+ else
+ {
+ SystemParametersInfoW(SPI_GETWORKAREA, 0, r, 0);
+ }
+}
+
+BOOL windowOffScreen(HWND hwnd, POINT pt)
+{
+ RECT r = {0}, wnd = {0}, sr = {0};
+ GetWindowRect(hwnd, &wnd);
+ sr.left = pt.x;
+ sr.top = pt.y;
+ sr.right = sr.left + (wnd.right - wnd.left);
+ sr.bottom = sr.top + (wnd.bottom - wnd.top);
+ getViewport(&r, hwnd, 0, &sr);
+ return !PtInRect(&r, pt);
+}
+
+#define STATUS_MS 500
+INT_PTR CALLBACK PrefsProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ {
+ // this will make sure that we've got thr aacplus logo shown even when using a localised version
+ SendDlgItemMessage(hwndDlg,IDC_LOGO,STM_SETIMAGE,IMAGE_BITMAP,
+ (LPARAM)LoadImage(plugin.hDllInstance,MAKEINTRESOURCE(IDB_GN_LOGO),IMAGE_BITMAP,0,0,LR_SHARED));
+
+ SetDlgItemText(hwndDlg,IDC_BLURB,WASABI_API_LNGSTRINGW(IDS_SCANNER_BLURB));
+ if (IsScanRunning())
+ {
+ //EnableWindow(GetDlgItem(hwndDlg, IDC_TEST), FALSE);
+ SetDlgItemText(hwndDlg,IDC_TEST,WASABI_API_LNGSTRINGW(IDS_PAUSE));
+ SetTimer(hwndDlg, 1, STATUS_MS, 0);
+ }
+
+ BoldStatusText(GetDlgItem(hwndDlg, IDC_STATUS) );
+ FillStatus(hwndDlg);
+
+ /*if(!pluginEnabled)
+ CheckDlgButton(hwndDlg,IDC_SCANDISABLE,TRUE);
+ else */
+ if(scanMode == 1)
+ CheckDlgButton(hwndDlg,IDC_SCANLAUNCH,TRUE);
+ else
+ CheckDlgButton(hwndDlg,IDC_SCANONUSE,TRUE);
+
+ SetRadioControlsState(hwndDlg); // Set the playlist length radio buttons state
+
+ if(scanMode == 0 || pluginEnabled == false)
+ {
+ pluginEnabled = true;
+ scanMode = 2;
+ }
+ if(multipleArtists)
+ CheckDlgButton(hwndDlg,IDC_CHECK_MULTIPLE_ARTISTS,TRUE);
+ if(multipleAlbums)
+ CheckDlgButton(hwndDlg,IDC_CHECK_MULTIPLE_ALBUMS,TRUE);
+ if(useSeed)
+ CheckDlgButton(hwndDlg,IDC_CHECK_USE_SEED,TRUE);
+
+ // show config window and restore last position as applicable
+ POINT pt = {(LONG)GetPrivateProfileInt(L"ml_plg", L"prefs_x", -1, mediaLibrary.GetWinampIniW()),
+ (LONG)GetPrivateProfileInt(L"ml_plg", L"prefs_y", -1, mediaLibrary.GetWinampIniW())};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ }
+ break;
+ case WM_TIMER:
+ FillStatus(hwndDlg);
+ break;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_TEST:
+ {
+ if(StartScan()) // Start the scanning
+ {
+ SetDlgItemText(hwndDlg,IDC_TEST,WASABI_API_LNGSTRINGW(IDS_PAUSE));
+ //EnableWindow(GetDlgItem(hwndDlg, IDC_TEST), FALSE);
+ SetTimer(hwndDlg, 1, STATUS_MS, 0); // Start the timer ?
+ }
+ else // Stop the scanning
+ {
+ StopScan();
+ SetDlgItemText(hwndDlg,IDC_TEST,WASABI_API_LNGSTRINGW(IDS_SCAN));
+ SetDlgItemText(hwndDlg,IDC_STATUS,WASABI_API_LNGSTRINGW(IDS_PAUSE));
+ }
+ }
+ break;
+ case IDOK:
+ case IDCANCEL:
+ {
+ RECT rect = {0};
+ GetWindowRect(hwndDlg, &rect);
+ char buf[16] = {0};
+ StringCchPrintfA(buf, 16, "%d", rect.left);
+ WritePrivateProfileStringA("ml_plg", "prefs_x", buf, mediaLibrary.GetWinampIni());
+ StringCchPrintfA(buf, 16, "%d", rect.top);
+ WritePrivateProfileStringA("ml_plg", "prefs_y", buf, mediaLibrary.GetWinampIni());
+
+ EndDialog(hwndDlg, 0);
+
+ StringCchPrintfA(buf, 10, "%d", scanMode);
+ WritePrivateProfileStringA("ml_plg", "scanmode", buf, mediaLibrary.GetWinampIni());
+ WritePrivateProfileStringA("ml_plg", "enable", pluginEnabled ? "1" : "0", mediaLibrary.GetWinampIni());
+ }
+ break;
+ case IDC_SCANDISABLE:
+ pluginEnabled = false;
+ scanMode = 2;
+ break;
+ case IDC_SCANONUSE:
+ pluginEnabled = true;
+ scanMode = 2;
+ break;
+ case IDC_SCANLAUNCH:
+ pluginEnabled = true;
+ scanMode = 1;
+ break;
+
+ case IDC_RESETDB:
+ {
+ wchar_t title[32] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_RESETDB,title,32);
+ if(MessageBox(hwndDlg,WASABI_API_LNGSTRINGW(IDS_RESETDB_TEXT),title,MB_YESNO) == IDNO)
+ break;
+
+ StopScan();
+ SetDlgItemText(hwndDlg,IDC_TEST,WASABI_API_LNGSTRINGW(IDS_SCAN));
+ ResetDBOnThread(false);
+ //NukeDB();
+ }
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+int DeleteGracenoteMLDBOnThread(HANDLE handle, void *user_data, intptr_t id)
+{
+ HANDLE event = (HANDLE)user_data;
+ SetupPlaylistSDK(); // Need to set up the playlist SDK so that we have the MLDB manager ready to go, which is linked to the playlist manager
+ DeleteGracenoteMLDB(!!id); // This will Shutdown the playlist manager in order to allow for a delete, id is recreating the silent boolean double NOT is creating int -> bool
+ ShutdownPlaylistSDK(); // Hence we need to do a complete and proper shutdown afterwards
+ SetEvent(event);
+ return 0;
+}
+
+// silent - pass in true if the user should not be prompted on errors
+int ResetDBOnThread(bool silent)
+{
+ int silentInt = (silent) ? 1 : 0; // Convert silent to int to pass it to wasabi thread runner
+ /*HANDLE wait_event = CreateEvent(NULL, FALSE, FALSE, 0);
+ WASABI_API_THREADPOOL->RunFunction(plg_thread, ShutdownScanner, (void *)wait_event, 0, api_threadpool::FLAG_REQUIRE_COM_STA);
+ WaitForSingleObject(wait_event, INFINITE);*/
+
+ HANDLE wait_event = CreateEvent(NULL, FALSE, FALSE, 0);
+ WASABI_API_THREADPOOL->RunFunction(plg_thread, DeleteGracenoteMLDBOnThread, (void *)wait_event, silentInt, api_threadpool::FLAG_REQUIRE_COM_STA);
+ WaitForSingleObject(wait_event, INFINITE);
+
+ run_full_scan_flag = true; // Set the flag so that we rerun everything on a rescan
+
+ return 0;
+}
+
+int ResetDB(bool silent)
+{
+ int silentInt = silent;
+
+ HANDLE wait_event = CreateEvent(NULL, FALSE, FALSE, 0);
+ DeleteGracenoteMLDBOnThread (0, (void *)wait_event, silentInt);
+
+ run_full_scan_flag = true; // Set the flag so that we rerun everything on a rescan
+
+ return 0;
+}
+
+BOOL DeleteGracenoteFile(char *filename)
+{
+ BOOL result;
+ char path[MAX_PATH] = {0};
+ PathCombineA(path,mediaLibrary.GetIniDirectory(),"Plugins\\Gracenote");
+ PathAppendA(path, filename);
+ result = DeleteFileA(path);
+ return result;
+}
+
+int NukeDB(void)
+{
+ ShutdownPlaylistSDK();
+
+ DeleteGracenoteFile("cddb.db");
+ DeleteGracenoteFile("cddbplm.chk");
+ DeleteGracenoteFile("cddbplm.gcf");
+ DeleteGracenoteFile("cddbplm.idx");
+ DeleteGracenoteFile("cddbplm.pdb");
+ DeleteGracenoteFile("elists.db");
+
+ run_full_scan_flag = true; // Set the flag so that we rerun everything on a rescan
+
+ return 0;
+}
+
+
+
+INT_PTR CALLBACK BGScanProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam);
+ if(!StartScan())
+ {
+ EndDialog(hwndDlg,0);
+ return 0;
+ }
+ SetTimer(hwndDlg,1,STATUS_MS,NULL);
+ ShowWindow(hwndDlg,SW_HIDE);
+
+ break;
+ case WM_TIMER:
+ switch(wParam)
+ {
+ case 1:
+ {
+ long state, track, tracks;
+ if (scanner.GetStatus(&state, &track, &tracks))
+ {
+ if(state == IDScanner::STATE_MUSICID && tracks != 0 && track != 0)
+ {
+ KillTimer(hwndDlg,1);
+ ShowWindow(hwndDlg,SW_SHOWNA);
+ SetTimer(hwndDlg,2,3000,NULL);
+ }
+ else if(state == IDScanner::STATE_DONE)
+ EndDialog(hwndDlg,0);
+ else if(state == IDScanner::STATE_ERROR)
+ {
+ EndDialog(hwndDlg,0);
+ if(!GetWindowLongPtr(hwndDlg,GWLP_USERDATA)){
+ KillTimer(hwndDlg,1);
+ ShowErrorDlg(hwndDlg);
+ }
+ }
+ }
+ }
+ break;
+ case 2:
+ EndDialog(hwndDlg,0);
+ break;
+ }
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDC_BUTTON1:
+ EndDialog(hwndDlg,0);
+ WASABI_API_DIALOGBOXW(IDD_PREFS, plugin.hwndLibraryParent, PrefsProcedure);
+ break;
+ case IDCANCEL:
+ EndDialog(hwndDlg,0);
+ break;
+ }
+ break;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/resource.h b/Src/Plugins/Library/ml_plg/resource.h
new file mode 100644
index 00000000..34be1fbb
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/resource.h
@@ -0,0 +1,120 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_plg.rc
+//
+#define IDS_PLAY_TRACKS_SIMILAR_TO 1
+#define IDS_SCANNING_NEEDED 2
+#define IDS_SCANNER_BLURB 3
+#define IDS_ERROR_INITIALIZING 4
+#define IDS_IDLE 5
+#define IDS_INITIALIZING 6
+#define IDS_SYNC 7
+#define IDS_METADATA 8
+#define IDS_MUSICID 9
+#define IDS_DONE 10
+#define IDS_PAUSE 11
+#define IDS_SCAN 12
+#define IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS 13
+#define IDS_INITFAILMSG 14
+#define IDS_RESETDB 15
+#define IDS_RESETDB_TEXT 16
+#define IDS_MINUTES 17
+#define IDS_ITEMS 18
+#define IDS_ARTIST 19
+#define IDS_ALBUM 20
+#define IDS_TRACK 21
+#define IDS_TITLE 22
+#define IDS_SIZE 23
+#define IDS_LENGTH 24
+#define IDS_MEGABYTES 25
+#define IDS_STRING26 26
+#define IDD_PREFS 101
+#define IDD_DIALOG1 102
+#define IDD_NAG 107
+#define IDB_BITMAP1 108
+#define IDB_GN_LOGO 108
+#define IDD_FAIL 108
+#define IDD_GENERATE 113
+#define IDS_EXCUSE_ME 114
+#define IDS_STRING115 115
+#define IDS_SEED 115
+#define IDS_YES 116
+#define IDS_NO 117
+#define IDS_OPTIONS 118
+#define IDS_NO_OPTIONS 119
+#define IDS_STATS 120
+#define IDS_ML_QUERY 122
+#define IDD_ADD_PLAYLIST 123
+#define IDS_ENTER_A_NAME 123
+#define IDS_ERROR 124
+#define IDS_PL_NAME_PREFIX 126
+#define IDS_GENERATING 128
+#define IDS_CANT_USE_SEED 132
+#define IDS_UNKNOWN 133
+#define IDS_THERE_CAN_BE_ONLY_ONE 134
+#define IDS_DAYS 135
+#define IDS_DAY 136
+#define IDS_ERROR_RESET 138
+#define IDS_CANNOT_SHUT_DOWN 139
+#define IDC_TEST 1001
+#define IDC_BUTTON1 1002
+#define IDC_PLAYLIST 1003
+#define IDC_ENQUEUE 1004
+#define IDC_SAVE 1006
+#define IDC_PLAY 1007
+#define IDC_STATUS 1008
+#define IDC_SENDTO 1009
+#define IDC_PROGRESS1 1009
+#define IDC_BLURB 1010
+#define IDC_SCANONUSE 1012
+#define IDC_SCANLAUNCH 1013
+#define IDC_SCANDISABLE 1014
+#define IDC_LOGO 1015
+#define IDC_CHECK_USE_SEED 1016
+#define IDC_CHECK_MULTIPLE_ARTISTS 1017
+#define IDC_CHECK_MULTIPLE_ALBUMS 1018
+#define IDC_COMBO_LENGTH 1019
+#define IDC_RESETDB 1020
+#define IDC_RADIO_PLAYLIST_ITEMS 1021
+#define IDC_RADIO_PLAYLIST_LENGTH 1022
+#define IDC_RADIO_PLAYLIST_SIZE 1023
+#define IDC_CHECK_ML_QUERY 1024
+#define IDC_BUTTON_ML_QUERY 1026
+#define IDC_STATIC_GENERATING 1027
+#define IDC_BUTTON_GENERATE_CANCEL 1028
+#define IDC_LENGTH_TYPE 1029
+#define IDC_PROGRESS_GENERATE 1032
+#define IDC_STATIC_PROGRESS_STATE 1033
+#define IDC_BUTTON_CANCEL 1034
+#define IDC_LIST_RESULTS 1035
+#define IDC_BUTTON_OPTIONS 1035
+#define IDC_BUTTON_REGENERATE 1036
+#define IDC_BUTTON_SAVEAS 1037
+#define IDC_BUTTON_PLAY_NOW 1038
+#define IDC_BUTTON_ENQUEUE_NOW 1039
+#define IDC_LIST_RESULTS2 1040
+#define IDC_STATIC_UP_TO 1042
+#define IDC_STATIC_STATS 1043
+#define IDC_STATIC_STATUS_LABEL 1044
+#define IDC_EDIT_ML_QUERY 1045
+#define IDC_STATIC_QUERY_DESCRIPTION 1046
+#define IDC_STATIC_NAME 1047
+#define IDC_EDIT_NAME 1048
+#define IDC_BUTTON_QUERY_DEFAULT 1049
+#define IDC_BUTTON_RESTORE_QUERY_DEFAULT 1049
+#define IDC_GROUP_SEED 1050
+#define IDC_GROUP_PL_ENTRIES 1051
+#define IDC_GROUP_ML_QUERY 1052
+#define IDC_GROUP_STATUS 1053
+#define IDS_NULLSOFT_PLAYLIST_GENERATOR 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 140
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1054
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_plg/util.cpp b/Src/Plugins/Library/ml_plg/util.cpp
new file mode 100644
index 00000000..599512de
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/util.cpp
@@ -0,0 +1,36 @@
+#include "main.h"
+#include "../winamp/wa_ipc.h"
+
+int GetFileInfo(const wchar_t *filename, wchar_t *metadata, wchar_t *dest, int len)
+{
+ dest[0]=0;
+ extendedFileInfoStructW efis=
+ {
+ filename,
+ metadata,
+ dest,
+ (size_t)len,
+ };
+ int r = SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efis,IPC_GET_EXTENDED_FILE_INFOW); //will return 1 if wa2 supports this IPC call
+ return r;
+}
+
+int updateFileInfo(const wchar_t *filename, wchar_t *metadata, wchar_t *data)
+{
+ extendedFileInfoStructW efis =
+ {
+ filename,
+ metadata,
+ data ? data : L"",
+ data ? wcslen(data) : 0,
+ };
+ return SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&efis, IPC_SET_EXTENDED_FILE_INFOW);
+}
+
+
+void WriteFileInfo(const wchar_t *filename)
+{
+
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)filename, IPC_FILE_TAG_MAY_HAVE_UPDATEDW);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_plg/version.rc2 b/Src/Plugins/Library/ml_plg/version.rc2
new file mode 100644
index 00000000..967002b6
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,81,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,81,0,0"
+ VALUE "InternalName", "Nullsoft Playlist Generator"
+ VALUE "LegalCopyright", "Copyright © 2007-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_plg.dll"
+ VALUE "ProductName", "Winamp"
+ VALUE "ProductVersion", STR_WINAMP_PRODUCTVER
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/Src/Plugins/Library/ml_plg/view.cpp b/Src/Plugins/Library/ml_plg/view.cpp
new file mode 100644
index 00000000..9520a34b
--- /dev/null
+++ b/Src/Plugins/Library/ml_plg/view.cpp
@@ -0,0 +1,368 @@
+#include "resource.h"
+#include "main.h"
+#include "api.h"
+#include "../nu/listview.h"
+#include "../nu/ChildSizer.h"
+#include "../nu/DialogSkinner.h"
+#include "impl_playlist.h"
+#include "../nu/SendTo.h"
+#include <strsafe.h>
+
+static ChildWndResizeItem view_rlist[] =
+{
+ { IDC_PLAYLIST, ResizeBottom | ResizeRight},
+ { IDC_PLAY, DockToBottom},
+ { IDC_ENQUEUE, DockToBottom},
+ { IDC_SAVE, DockToBottom},
+ { IDC_SENDTO, DockToBottom},
+};
+
+W_ListView playlist_list;
+Playlist currentPlaylist;
+int playlist_skin;
+static SendToMenu sendTo(plugin);
+
+
+static void PlaySelection(int enqueue, int is_all)
+{
+ if (!enqueue)
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_DELETE);
+
+ int numTracks = playlist_list.GetCount();
+ for (int i = 0;i < numTracks;i++)
+ {
+ if (is_all || playlist_list.GetSelected(i))
+ {
+ if (currentPlaylist.IsCached(i))
+ {
+ enqueueFileWithMetaStructW s;
+ s.filename = currentPlaylist.ItemName(i);
+ s.title = currentPlaylist.ItemTitle(i);
+ s.length = currentPlaylist.GetItemLengthMilliseconds(i) / 1000;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
+ }
+ else
+ {
+ enqueueFileWithMetaStructW s;
+ s.filename = currentPlaylist.ItemName(i);
+ s.title = 0;
+ s.length = 0;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
+ }
+
+ }
+ }
+
+ if (!enqueue)
+ {
+ if (is_all)
+ {
+ int pos = playlist_list.GetNextSelected(-1);
+ if (pos != -1)
+ {
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, pos, IPC_SETPLAYLISTPOS);
+ SendMessage(plugin.hwndWinampParent, WM_COMMAND, 40047, 0); // stop button, literally
+ SendMessage(plugin.hwndWinampParent, WM_COMMAND, 40045, 0); // play button, literally
+ return ;
+ }
+ }
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_STARTPLAY);
+ }
+ /*
+ int cnt=0;
+ int l=PlayList_getlength();
+
+ int foo_all=0; // all but play the only selected
+ int foo_selected=-1;
+
+ if (g_config->ReadInt("plplaymode",1)||is_all)
+ {
+ int selcnt=0;
+ for(int i=0;i<l;i++)
+ {
+ if(PlayList_getselect(i)) selcnt++;
+ }
+ if ((selcnt == 1 && !enqueue) || selcnt == 0)
+ {
+ foo_all=-1;
+ }
+ }
+
+ */
+}
+
+
+static void playlist_DisplayChange()
+{
+ playlist_list.SetTextColors(dialogSkinner.Color(WADLG_ITEMFG), dialogSkinner.Color(WADLG_ITEMBG));
+ ListView_SetBkColor(playlist_list.getwnd(), dialogSkinner.Color(WADLG_ITEMBG));
+ playlist_list.SetFont(dialogSkinner.GetFont());
+}
+
+static BOOL playlist_GetDisplayInfo(NMLVDISPINFO *lpdi)
+{
+ size_t item = lpdi->item.iItem;
+ if (item < 0 || item >= currentPlaylist.GetNumItems()) return 0;
+
+ if (lpdi->item.mask & LVIF_TEXT)
+ {
+ switch (lpdi->item.iSubItem)
+ {
+ case 0:
+ {
+ bool cached = currentPlaylist.IsCached(item);
+
+ if (!cached)
+ {
+ wchar_t title[400];
+ int length;
+ mediaLibrary.GetFileInfo(currentPlaylist.ItemName(item), title, 400, &length);
+ if (length == 0)
+ currentPlaylist.SetItemLengthMilliseconds(item, -1000);
+ else
+ currentPlaylist.SetItemLengthMilliseconds(item, length*1000);
+ currentPlaylist.SetItemTitle(item, title);
+ }
+ // CUT: currentPlaylist.GetItemTitle(item, lpdi->item.pszText, lpdi->item.cchTextMax);
+ StringCchPrintf(lpdi->item.pszText, lpdi->item.cchTextMax, L"%d. %s", item + 1, currentPlaylist.ItemTitle(item));
+ }
+ break;
+ case 1:
+ {
+
+ if (currentPlaylist.GetItemLengthMilliseconds(item) == 0) // if the length is 0, then we'll re-read it
+ {
+ wchar_t title[400];
+ int length;
+ mediaLibrary.GetFileInfo(currentPlaylist.ItemName(item), title, 400, &length);
+ if (length == 0)
+ currentPlaylist.SetItemLengthMilliseconds(item, -1000);
+ else
+ {
+ currentPlaylist.SetItemLengthMilliseconds(item, length*1000);
+// currentPlaylist.SetItemTitle(item, AutoWide(title));
+ }
+ }
+
+ int length = currentPlaylist.GetItemLengthMilliseconds(item) / 1000;
+ if (length <= 0)
+ lpdi->item.pszText[0] = 0;
+ else
+ StringCchPrintf(lpdi->item.pszText, lpdi->item.cchTextMax, L"%d:%02d", length / 60, length % 60);
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+
+static INT_PTR playlist_Notify(HWND hwndDlg, WPARAM wParam, LPARAM lParam)
+{
+ LPNMHDR l = (LPNMHDR)lParam;
+ if (l->idFrom == IDC_PLAYLIST)
+ {
+ switch (l->code)
+ {
+ /*
+ case NM_RETURN:
+ if (!(GetAsyncKeyState(VK_SHIFT)&0x8000))
+ PlaySelection(g_config->ReadInt("enqueuedef", 0), g_config->ReadInt("plplaymode", 1));
+ else
+ PlaySelection(!g_config->ReadInt("enqueuedef", 0), 0);
+ break;
+
+ case NM_DBLCLK:
+ PlaySelection(g_config->ReadInt("enqueuedef", 0), g_config->ReadInt("plplaymode", 1));
+ break;*/
+ case LVN_GETDISPINFO:
+ return playlist_GetDisplayInfo((NMLVDISPINFO*) lParam);
+ /*case LVN_BEGINDRAG:
+ we_are_drag_and_dropping = 1;
+ SetCapture(hwndDlg);
+ break;
+ */
+ /*
+ case LVN_ITEMCHANGED:
+ case LVN_ODSTATECHANGED:
+ UpdatePlaylistTime(hwndDlg);
+ break;
+ */
+ /*
+ case LVN_KEYDOWN:
+ {
+ LPNMLVKEYDOWN pnkd = (LPNMLVKEYDOWN) lParam;
+ switch (pnkd->wVKey)
+ {
+ case VK_DELETE:
+ if (GetAsyncKeyState(VK_CONTROL)&0x8000)
+ Playlist_DeleteSelected(0);
+ else
+ Playlist_DeleteSelected(1);
+
+ break;
+
+ case '3':
+ if (GetAsyncKeyState(VK_MENU)&0x8000)
+ TagEditor(hwndDlg);
+ break;
+ case 'A':
+ if (GetAsyncKeyState(VK_CONTROL)&0x8000)
+ playlist_list.SelectAll();
+ break;
+ case 'I':
+ if (GetAsyncKeyState(VK_CONTROL)&0x8000)
+ playlist_list.InvertSelection();
+ break;
+ case 'L':
+ if (GetAsyncKeyState(VK_CONTROL)&0x8000)
+ CurrentPlaylist_AddLocation(hwndDlg);
+ else if (GetAsyncKeyState(VK_SHIFT)&0x8000)
+ CurrentPlaylist_AddDirectory(hwndDlg);
+ else
+ CurrentPlaylist_AddFiles(hwndDlg);
+ SyncPlaylist();
+ break;
+ case 'R':
+ if (GetAsyncKeyState(VK_CONTROL)&0x8000)
+ {
+ if (GetAsyncKeyState(VK_SHIFT)&0x8000)
+ AGAVE_API_PLAYLISTMANAGER->Randomize(&currentPlaylist);
+ else
+ AGAVE_API_PLAYLISTMANAGER->Reverse(&currentPlaylist);
+ playlist_list.RefreshAll();
+ }
+ break;
+ case 'E':
+ if (GetAsyncKeyState(VK_CONTROL)&0x8000)
+ {
+ if (GetAsyncKeyState(VK_MENU)&0x8000)
+ Playlist_ResetSelected();
+ else
+ EditEntry(l->hwndFrom);
+ }
+ break;
+
+ }
+ }
+ break;
+ */
+ }
+ }
+ return 0;
+}
+
+static wchar_t *BuildFilenameList(int is_all)
+{
+ wchar_t filename[MAX_PATH] = {0};
+ size_t len = 200;
+ wchar_t *str = (wchar_t *)malloc(len*sizeof(wchar_t));
+ size_t sofar = 0;
+
+ int numTracks = playlist_list.GetCount();
+ for (int i = 0;i < numTracks;i++)
+ {
+ if (is_all || playlist_list.GetSelected(i))
+ {
+ if (currentPlaylist.GetItem(i, filename, MAX_PATH))
+ {
+ size_t filenameLen = wcslen(filename);
+ if ((filenameLen + sofar) > len)
+ {
+ size_t newLen = filenameLen + sofar + 32; // add some cushion
+ wchar_t *newStr = (wchar_t *)malloc(newLen*sizeof(wchar_t));
+ memcpy(newStr, str, sofar*sizeof(wchar_t));
+ len = newLen;
+ free(str);
+ str = newStr;
+ }
+
+ StringCchCopyW(str + sofar, filenameLen, filename);
+ sofar += filenameLen + 1;
+ }
+ }
+ }
+ *(str + sofar) = 0;
+
+ return str;
+}
+
+INT_PTR CALLBACK ViewProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ INT_PTR a = dialogSkinner.Handle(hwndDlg, msg, wParam, lParam);
+ if (a)
+ return a;
+
+ switch (msg)
+ {
+ case WM_INITMENUPOPUP:
+ sendTo.InitPopupMenu(wParam);
+ return 0;
+ case WM_INITDIALOG:
+ childSizer.Init(hwndDlg, view_rlist, sizeof(view_rlist) / sizeof(view_rlist[0]));
+
+ playlist_list.setwnd(GetDlgItem(hwndDlg, IDC_PLAYLIST));
+ playlist_skin = mediaLibrary.SkinList(GetDlgItem(hwndDlg, IDC_PLAYLIST));
+ playlist_list.AddCol(L"Song",200);
+ playlist_list.AddCol(L"Length",100);
+ playlist_list.JustifyColumn(1, LVCFMT_RIGHT);
+ playlist_DisplayChange();
+ playlist_list.SetVirtualCount(currentPlaylist.GetNumItems());
+ break;
+ case WM_PAINT:
+ {
+ int tab[] = {IDC_PLAYLIST|DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwndDlg,tab,1);
+ }
+ return 0;
+ case WM_SIZE:
+ if (wParam != SIZE_MINIMIZED)
+ childSizer.Resize(hwndDlg, view_rlist, sizeof(view_rlist) / sizeof(view_rlist[0]));
+
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_PLAY:
+ if (playlist_list.GetSelectedCount() > 0)
+ PlaySelection(0, 0/*g_config->ReadInt("plplaymode", 1)*/);
+ else
+ PlaySelection(0, 1);
+ break;
+ case IDC_ENQUEUE:
+ if (playlist_list.GetSelectedCount() > 0)
+ PlaySelection(0, 0);
+ else
+ PlaySelection(1, 0);
+ break;
+ case IDC_SAVE:
+ break;
+ case IDC_SENDTO:
+ {
+ HMENU blah = CreatePopupMenu();
+ RECT r;
+ GetWindowRect(GetDlgItem(hwndDlg, IDC_SENDTO), &r);
+ sendTo.AddHere(hwndDlg, blah, ML_TYPE_FILENAMESW);
+ int x = TrackPopupMenu(blah, TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN | TPM_LEFTALIGN | TPM_RETURNCMD, r.left, r.top, 0, hwndDlg, NULL);
+ if (sendTo.WasClicked(x))
+ {
+ int is_all = playlist_list.GetSelectedCount() == 0;
+ wchar_t *names = BuildFilenameList(is_all);
+ sendTo.SendFilenames(names);
+ free(names);
+ }
+
+ sendTo.Cleanup();
+
+ }
+ break;
+ }
+ break;
+ case WM_DISPLAYCHANGE: playlist_DisplayChange(); return 0;
+ case WM_NOTIFY: return playlist_Notify(hwndDlg, wParam, lParam);
+ case WM_DESTROY:
+ mediaLibrary.UnskinList(playlist_skin);
+ break;
+ }
+ return 0;
+} \ No newline at end of file