Initial community commit

This commit is contained in:
Jef
2024-09-24 14:54:57 +02:00
parent 537bcbc862
commit 20d28e80a5
16810 changed files with 4640254 additions and 2 deletions

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
};

View File

@ -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

View File

@ -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);
}

View File

@ -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__

View File

@ -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
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}
};

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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);
}

View File

@ -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 <20> 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

View File

@ -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;
}