winamp/Src/Plugins/General/gen_ml/fileview_metadata.cpp

789 lines
23 KiB
C++

#include "main.h"
#include "./fileview.h"
#include "./fileview_internal.h"
#include <vector>
#include <deque>
#include "../nu/threadname.h"
#include <api/service/waServiceFactory.h>
#include "../playlist/api_playlistmanager.h"
#include "../playlist/ifc_playlistloadercallback.h"
#include "../Agave/Metadata/api_metadata.h"
#include <shlwapi.h>
#include <strsafe.h>
#include <algorithm>
typedef struct _METASEARCHKEY
{
INT pathId;
LPCWSTR pszFileName;
} METASEARCHKEY;
typedef struct _PATHID
{
INT id;
LPWSTR pszPath;
} PATHID;
typedef struct _METAINFO
{
INT pathId;
LPWSTR pszFileName;
FILETIME lastWriteTime;
DWORD fileSizeLow;
DWORD fileSizeHigh;
BOOL bBusy;
FILEMETARECORD *pFileMeta;
} METAINFO;
typedef std::vector<PATHID> PATHLIST;
typedef std::vector<METAINFO*> METARECORS;
typedef struct _METADB
{
UINT ref;
PATHLIST pPath;
METARECORS pRec;
size_t lastPathIndex;
INT maxPathId;
HANDLE hDiscoverThread;
HANDLE hDiscoverWake;
HANDLE hDiscoverKill;
} METADB;
#define METATHREAD_KILL 0
#define METATHREAD_WAKE 1
typedef void (CALLBACK *DISCOVERCALLBACK)(LPCWSTR /*pszFileName*/, ULONG_PTR /*param*/);
typedef struct _DISCOVERJOB
{
METAINFO *pMeta;
HANDLE hCaller;
DISCOVERCALLBACK fnCallback;
ULONG_PTR param;
UINT fileType;
WCHAR szFileName[2*MAX_PATH];
} DISCOVERJOB;
typedef std::deque<DISCOVERJOB*> DISCOVERDEQUE;
static DISCOVERDEQUE discoverJobs;
static api_metadata *apiMetaData = NULL;
static api_playlistmanager *apiPlaylistManager = NULL;
static METADB metaDb = { 0, };
static CRITICAL_SECTION g_cs_discover;
static BOOL MetaDiscovery_InitializeThread(METADB *pMetaDb);
static void MetaDiscovery_KillThread(METADB *pMetaDb);
static BOOL MetaDiscovery_ScheduleJob(HANDLE hWakeEvent, LPCWSTR pszPath, METAINFO *pMeta, UINT fileType, DISCOVERCALLBACK fnCallback, ULONG_PTR param);
class PlMetaLoader : public ifc_playlistloadercallback
{
public:
PlMetaLoader( PLAYLISTMETA *plm )
{
this->plm = plm; plm->nCount = 0; plm->nLength = 0;
}
~PlMetaLoader()
{};
void OnFile( const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info )
{
if ( plm->nCount < sizeof( plm->szEntries ) / sizeof( plm->szEntries[ 0 ] ) )
{
PLENTRY *pe = &plm->szEntries[ plm->nCount ];
if ( title && *title )
pe->pszTitle = _wcsdup( title );
else if ( filename && *filename )
{
pe->pszTitle = _wcsdup( filename );
}
else
pe->pszTitle = NULL;
pe->nLength = lengthInMS / 1000;
}
plm->nCount++;
plm->nLength += lengthInMS;
}
void OnPlaylistInfo( const wchar_t *playlistName, size_t numEntries, ifc_plentryinfo *info )
{
plm->pszTitle = ( playlistName && *playlistName ) ? _wcsdup( playlistName ) : NULL;
}
const wchar_t *GetBasePath()
{
return L".";
}
protected:
RECVS_DISPATCH;
private:
PLAYLISTMETA *plm;
};
#define CBCLASS PlMetaLoader
START_DISPATCH;
VCB( IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile )
VCB( IFC_PLAYLISTLOADERCALLBACK_ONPLAYLISTINFO, OnPlaylistInfo )
CB( IFC_PLAYLISTLOADERCALLBACK_GETBASEPATH, GetBasePath )
END_DISPATCH;
void FileViewMeta_InitializeStorage(HWND hView)
{
if (0 == metaDb.ref)
{
InitializeCriticalSection(&g_cs_discover);
if (WASABI_API_SVC)
{
waServiceFactory *factory;
factory = WASABI_API_SVC->service_getServiceByGuid(api_metadataGUID);
if (factory) apiMetaData = (api_metadata*) factory->getInterface();
else apiMetaData = NULL;
factory = WASABI_API_SVC->service_getServiceByGuid(api_playlistmanagerGUID);
if (factory) apiPlaylistManager = (api_playlistmanager*) factory->getInterface();
else apiPlaylistManager = NULL;
}
}
metaDb.ref++;
}
static void FileViewMeta_FreeAudioMeta(AUDIOMETA *pMeta)
{
if (pMeta->pszAlbum) { free(pMeta->pszAlbum); pMeta->pszAlbum = NULL; }
if (pMeta->pszArtist) { free(pMeta->pszArtist); pMeta->pszArtist = NULL; }
if (pMeta->pszTitle) { free(pMeta->pszTitle); pMeta->pszTitle = NULL; }
if (pMeta->pszAlbumArtist) { free(pMeta->pszAlbumArtist); pMeta->pszAlbumArtist = NULL; }
if (pMeta->pszComment) { free(pMeta->pszComment); pMeta->pszComment = NULL; }
if (pMeta->pszComposer) { free(pMeta->pszComposer); pMeta->pszComposer = NULL; }
if (pMeta->pszGenre) { free(pMeta->pszGenre); pMeta->pszGenre = NULL; }
if (pMeta->pszPublisher) { free(pMeta->pszPublisher); pMeta->pszPublisher = NULL; }
}
static void FileViewMeta_FreeVideoMeta(VIDEOMETA *pMeta)
{
if (pMeta->pszAlbum) { free(pMeta->pszAlbum); pMeta->pszAlbum = NULL; }
if (pMeta->pszArtist) { free(pMeta->pszArtist); pMeta->pszArtist = NULL; }
if (pMeta->pszTitle) { free(pMeta->pszTitle); pMeta->pszTitle = NULL; }
if (pMeta->pszAlbumArtist) { free(pMeta->pszAlbumArtist); pMeta->pszAlbumArtist = NULL; }
if (pMeta->pszComment) { free(pMeta->pszComment); pMeta->pszComment = NULL; }
if (pMeta->pszComposer) { free(pMeta->pszComposer); pMeta->pszComposer = NULL; }
if (pMeta->pszGenre) { free(pMeta->pszGenre); pMeta->pszGenre = NULL; }
if (pMeta->pszPublisher) { free(pMeta->pszPublisher); pMeta->pszPublisher = NULL; }
}
static void FileViewMeta_FreePlaylistMeta(PLAYLISTMETA *pMeta)
{
if (!pMeta) return;
if (pMeta->pszTitle) { free(pMeta->pszTitle); pMeta->pszTitle = NULL; }
for (int i = 0; i < sizeof(pMeta->szEntries)/sizeof(pMeta->szEntries[0]); i++)
{
if (pMeta->szEntries[i].pszTitle) { free(pMeta->szEntries[i].pszTitle); pMeta->szEntries[i].pszTitle = NULL; }
}
if (pMeta->pszTitle) { free(pMeta->pszTitle); pMeta->pszTitle = NULL; }
}
static void FileViewMeta_FreeFileMeta(FILEMETARECORD *pFileMeta)
{
if (pFileMeta)
{
switch(pFileMeta->type)
{
case METATYPE_AUDIO:
FileViewMeta_FreeAudioMeta(&pFileMeta->audio); break;
case METATYPE_VIDEO:
FileViewMeta_FreeVideoMeta(&pFileMeta->video); break;
case METATYPE_PLAYLIST:
FileViewMeta_FreePlaylistMeta(&pFileMeta->playlist); break;
}
free(pFileMeta);
}
}
static void FileViewMeta_FreeMetaInfo(METAINFO *pMeta)
{
if (pMeta)
{
if (pMeta->pszFileName) { free(pMeta->pszFileName); pMeta->pszFileName = NULL; }
FileViewMeta_FreeFileMeta(pMeta->pFileMeta);
}
}
void FileViewMeta_TruncateQueue(size_t max)
{
EnterCriticalSection(&g_cs_discover);
while(discoverJobs.size() > max)
{
DISCOVERJOB *pdj = discoverJobs.front();
if (pdj)
{
if (pdj->pMeta)
{
ZeroMemory(&pdj->pMeta->lastWriteTime, sizeof(FILETIME));
pdj->pMeta->fileSizeLow = 0;
pdj->pMeta->bBusy = FALSE;
if (pdj->hCaller) CloseHandle(pdj->hCaller);
}
free(pdj);
}
discoverJobs.pop_front();
}
LeaveCriticalSection(&g_cs_discover);
}
void FileViewMeta_ReleaseStorage(HWND hView)
{
if (0 == metaDb.ref) return;
metaDb.ref--;
if (0 == metaDb.ref)
{
FileViewMeta_TruncateQueue(0);
MetaDiscovery_KillThread(&metaDb);
if (WASABI_API_SVC)
{
if(apiMetaData)
{
waServiceFactory *factory = WASABI_API_SVC->service_getServiceByGuid(api_metadataGUID);
if (factory) factory->releaseInterface(apiMetaData);
}
if(apiPlaylistManager)
{
waServiceFactory *factory = WASABI_API_SVC->service_getServiceByGuid(api_playlistmanagerGUID);
if (factory) factory->releaseInterface(apiPlaylistManager);
}
}
while(metaDb.pPath.size() > 0)
{
if (metaDb.pPath.back().pszPath) free(metaDb.pPath.back().pszPath);
metaDb.pPath.pop_back();
}
while(metaDb.pRec.size() > 0)
{
FileViewMeta_FreeMetaInfo(metaDb.pRec.back());
metaDb.pRec.pop_back();
}
metaDb.maxPathId = 0;
metaDb.lastPathIndex = 0;
DeleteCriticalSection(&g_cs_discover);
}
}
__inline static int __cdecl FileViewMeta_SortPathId(const void *elem1, const void *elem2)
{
return (CompareStringW(CSTR_INVARIANT, NORM_IGNORECASE,
((PATHID*)elem1)->pszPath, -1, ((PATHID*)elem2)->pszPath, -1) - 2);
}
__inline static int __cdecl FileViewMeta_SearhPath(const void *elem1, const void *elem2)
{
return (CompareStringW(CSTR_INVARIANT, NORM_IGNORECASE,
(LPCWSTR)elem1, -1, ((PATHID*)elem2)->pszPath, -1) - 2);
}
__inline static int __cdecl FileViewMeta_SortMetaInfo(const void *elem1, const void *elem2)
{
METAINFO *pmi1 = ((METAINFO*)elem1);
METAINFO *pmi2 = ((METAINFO*)elem2);
if (pmi1->pathId != pmi2->pathId) return (pmi1->pathId - pmi2->pathId);
return (CompareStringW(CSTR_INVARIANT, NORM_IGNORECASE, pmi1->pszFileName, -1, pmi2->pszFileName, -1) - 2);
}
__inline static int __cdecl FileViewMeta_SortMetaInfo_V2(const void* elem1, const void* elem2)
{
return FileViewMeta_SortMetaInfo(elem1, elem2) < 0;
}
__inline static int __cdecl FileViewMeta_SearhMetaInfo(const void *elem1, const void *elem2)
{
METASEARCHKEY *pKey = (METASEARCHKEY*)elem1;
if (pKey->pathId != (((METAINFO*)elem2))->pathId)
return (pKey->pathId - (((METAINFO*)elem2))->pathId);
return (CompareStringW(CSTR_INVARIANT, NORM_IGNORECASE,
pKey->pszFileName, -1, (((METAINFO*)elem2))->pszFileName, -1) - 2);
}
static INT FileViewMeta_GetPathId(LPCWSTR pszPath)
{
if (!pszPath || L'\0' == pszPath) return 0;
//PATHID *psr;
PATHLIST::iterator psr;
if (metaDb.lastPathIndex < metaDb.pPath.size() &&
CSTR_EQUAL == CompareStringW(CSTR_INVARIANT, NORM_IGNORECASE, pszPath, -1,
metaDb.pPath.at(metaDb.lastPathIndex).pszPath, -1))
{
//psr = &metaDb.pPath.at(metaDb.lastPathIndex);
psr = metaDb.pPath.begin() + metaDb.lastPathIndex;
}
else
{
//psr = (PATHID*)bsearch(pszPath, metaDb.pPath.begin(), metaDb.pPath.size(), sizeof(PATHID), FileViewMeta_SearhPath);
psr = std::find_if(metaDb.pPath.begin(), metaDb.pPath.end(),
[&](PATHID &path) -> bool
{
return FileViewMeta_SearhPath(pszPath, &path) == 0;
}
);
}
//if (!psr)
if(psr == metaDb.pPath.end())
{
PATHID pid;
pid.id = metaDb.maxPathId+1;
pid.pszPath = _wcsdup(pszPath);
metaDb.pPath.push_back(pid);
metaDb.maxPathId++;
//psr = &pid;
psr = metaDb.pPath.end() - 1;
//qsort(metaDb.pPath.begin(), metaDb.pPath.size(), sizeof(PATHID), FileViewMeta_SortPathId);
std::sort(metaDb.pPath.begin(), metaDb.pPath.end(),
[&](const PATHID& lhs, const PATHID& rhs) -> bool
{
return FileViewMeta_SortPathId(&lhs, &rhs) == CSTR_LESS_THAN;
}
);
}
metaDb.lastPathIndex = (size_t)(ULONG_PTR)(psr - metaDb.pPath.begin());
return psr->id;
}
static METAINFO *FileViewMeta_GetMeta(LPCWSTR pszPath, LPCWSTR pszFileName, LPCWSTR pszExt, BOOL bCreateIfNotFound)
{
WCHAR szBuffer[MAX_PATH] = {0};
METASEARCHKEY key;
key.pathId = FileViewMeta_GetPathId(pszPath);
key.pszFileName = pszFileName;
if (pszExt == (pszFileName + lstrlenW(pszFileName) + 1))
{
StringCchCopyW(szBuffer, sizeof(szBuffer)/sizeof(szBuffer[0]), pszFileName);
if (pszExt)
{
StringCchCatW(szBuffer, sizeof(szBuffer)/sizeof(szBuffer[0]), L".");
StringCchCatW(szBuffer, sizeof(szBuffer)/sizeof(szBuffer[0]), pszExt);
}
key.pszFileName = szBuffer;
}
//METAINFO **pr = (METAINFO**)bsearch(&key, metaDb.pRec.at(0), metaDb.pRec.size(), sizeof(METAINFO*), FileViewMeta_SearhMetaInfo);
auto it = std::find_if(metaDb.pRec.begin(), metaDb.pRec.end(),
[&](const METAINFO* info) -> bool
{
return FileViewMeta_SearhMetaInfo(&key, info) == 0;
}
);
if (it != metaDb.pRec.end())
{
return *it;
}
//if (pr)
// return *pr;
if (!bCreateIfNotFound)
return NULL;
METAINFO *pInfo = (METAINFO*)calloc(1, sizeof(METAINFO));
if (pInfo)
{
pInfo->pathId = key.pathId;
pInfo->pszFileName = _wcsdup(key.pszFileName);
metaDb.pRec.push_back(pInfo);
//qsort(metaDb.pRec.first(), metaDb.pRec.size(), sizeof(METAINFO*), FileViewMeta_SortMetaInfo);
std::sort(metaDb.pRec.begin(), metaDb.pRec.end(), FileViewMeta_SortMetaInfo_V2);
}
return pInfo;
}
// sets part and parts to -1 or 0 on fail/missing (e.g. parts will be -1 on "1", but 0 on "1/")
static void ParseIntSlashInt(wchar_t *string, int *part, int *parts)
{
*part = -1;
*parts = -1;
if (string && string[0])
{
*part = _wtoi(string);
while (*string && *string != '/')
{
string++;
}
if (*string == '/')
{
string++;
*parts = _wtoi(string);
}
}
}
#define READFILEINFO(__fileName, __tag, __result, __pszBuffer, __cchBuffer)\
(apiMetaData->GetExtendedFileInfo((__fileName), (__tag), (__pszBuffer), (__cchBuffer)) && L'\0' != *(__pszBuffer))
static void FileViewMeta_ReadAudioMeta(LPCWSTR pszFullPath, AUDIOMETA *pa)
{
#define GETFILEINFO_STR(__tag, __result) { szBuffer[0] = L'\0';\
if (READFILEINFO(pszFullPath, __tag, __result, szBuffer, sizeof(szBuffer)/sizeof(szBuffer[0])))\
{(__result) = _wcsdup(szBuffer); }}
#define GETFILEINFO_INT(__tag, __result) { szBuffer[0] = L'\0';\
if (READFILEINFO(pszFullPath, __tag, __result, szBuffer, sizeof(szBuffer)/sizeof(szBuffer[0])))\
{(__result) = _wtoi(szBuffer); }}
#define GETFILEINFO_INTINT(__tag, __result1, __result2) { szBuffer[0] = L'\0';\
if (READFILEINFO(pszFullPath, __tag, __result, szBuffer, sizeof(szBuffer)/sizeof(szBuffer[0])))\
{ParseIntSlashInt(szBuffer, (__result1), (__result2)); }}
if (AGAVE_API_MLDB)
{
itemRecordW *record = AGAVE_API_MLDB->GetFile(pszFullPath);
if (record)
{
if (record->artist) pa->pszArtist = _wcsdup(record->artist);
if (record->album) pa->pszAlbum = _wcsdup(record->album);
if (record->title) pa->pszTitle = _wcsdup(record->title);
if (record->albumartist) pa->pszAlbumArtist = _wcsdup(record->albumartist);
if (record->comment) pa->pszComment = _wcsdup(record->comment);
if (record->composer) pa->pszComposer = _wcsdup(record->composer);
if (record->genre) pa->pszGenre = _wcsdup(record->genre);
if (record->publisher) pa->pszPublisher = _wcsdup(record->publisher);
pa->nLength = record->length;
pa->nBitrate = record->bitrate;
pa->nYear = record->year;
pa->nDiscNum = record->disc;
pa->nDiscCount = record->discs;
pa->nTrackNum = record->track;
pa->nTrackCount = record->tracks;
pa->nSource = METADATA_SOURCE_MLDB;
AGAVE_API_MLDB->FreeRecord(record);
return;
}
}
WCHAR szBuffer[2048] = {0};
GETFILEINFO_STR(L"artist", pa->pszArtist);
GETFILEINFO_STR(L"album", pa->pszAlbum);
GETFILEINFO_STR(L"title", pa->pszTitle);
GETFILEINFO_STR(L"albumartist", pa->pszAlbumArtist);
GETFILEINFO_STR(L"comment", pa->pszComment);
GETFILEINFO_STR(L"composer", pa->pszComposer);
GETFILEINFO_STR(L"genre", pa->pszGenre);
GETFILEINFO_STR(L"publisher", pa->pszPublisher);
GETFILEINFO_INT(L"bitrate", pa->nBitrate);
GETFILEINFO_INT(L"year", pa->nYear);
GETFILEINFO_INT(L"length", pa->nLength);
pa->nLength = pa->nLength/1000;
GETFILEINFO_INTINT(L"disc", &pa->nDiscNum, &pa->nDiscCount);
GETFILEINFO_INTINT(L"track",&pa->nTrackNum, &pa->nTrackCount);
pa->nSource = METADATA_SOURCE_FILEINFO;
}
static void FileViewMeta_ReadPlaylistMeta(LPCWSTR pszFullPath, PLAYLISTMETA *plm)
{
if (!apiPlaylistManager) return;
PlMetaLoader plLoaderCb(plm);
apiPlaylistManager->Load(pszFullPath, &plLoaderCb);
plm->nLength = plm->nLength/1000;
}
static FILEMETARECORD* FileViewMeta_ReadFileMeta(LPCWSTR pszFullPath, UINT uType)
{
FILEMETARECORD *pmr = (FILEMETARECORD*)calloc(1, sizeof(FILEMETARECORD));
if (!pmr) return NULL;
switch(uType)
{
case FVFT_AUDIO:
pmr->type = METATYPE_AUDIO;
FileViewMeta_ReadAudioMeta(pszFullPath, &pmr->audio);
break;
case FVFT_PLAYLIST:
pmr->type = METATYPE_PLAYLIST;
FileViewMeta_ReadPlaylistMeta(pszFullPath, &pmr->playlist);
break;
}
return pmr;
}
FILEMETARECORD *FileViewMeta_GetFromCache(LPCWSTR pszPath, FILERECORD *pfr)
{
if ((FVFT_AUDIO != pfr->fileType && FVFT_VIDEO != pfr->fileType) || !pszPath) return NULL;
METAINFO *pMeta = FileViewMeta_GetMeta(pszPath, pfr->Info.cFileName, &pfr->Info.cFileName[pfr->extOffset], FALSE);
if (pMeta && !pMeta->bBusy && pMeta->fileSizeLow == pfr->Info.nFileSizeLow &&
pMeta->fileSizeHigh == pfr->Info.nFileSizeHigh && 0 == CompareFileTime(&pMeta->lastWriteTime, &pfr->Info.ftLastWriteTime))
{
return pMeta->pFileMeta;
}
return NULL;
}
BOOL FileViewMeta_Discover(LPCWSTR pszPath, FILERECORD *pfr, DISCOVERCALLBACK fnCallback, ULONG_PTR param, INT queueMax)
{
if (!apiMetaData) return FALSE;
if ((FVFT_AUDIO != pfr->fileType &&
FVFT_VIDEO != pfr->fileType &&
FVFT_PLAYLIST != pfr->fileType) || !pszPath) return FALSE;
METAINFO *pMeta = FileViewMeta_GetMeta(pszPath, pfr->Info.cFileName, &pfr->Info.cFileName[pfr->extOffset], TRUE);
if (!pMeta || pMeta->bBusy) return FALSE;
if (0 == CompareFileTime(&pMeta->lastWriteTime, &pfr->Info.ftLastWriteTime) &&
pMeta->fileSizeLow == pfr->Info.nFileSizeLow && pMeta->fileSizeHigh == pfr->Info.nFileSizeHigh)
{
pfr->pMeta = pMeta->pFileMeta;
return TRUE;
}
FileViewMeta_FreeFileMeta(pMeta->pFileMeta);
pMeta->lastWriteTime = pfr->Info.ftLastWriteTime;
pMeta->fileSizeLow = pfr->Info.nFileSizeLow;
pMeta->fileSizeHigh = pfr->Info.nFileSizeHigh;
pMeta->pFileMeta = NULL;
pMeta->bBusy = TRUE;
if (!fnCallback && !param)
{
WCHAR szFullPath[MAX_PATH] = {0};
FileViewMeta_TruncateQueue(0);
PathCombineW(szFullPath, pszPath, pMeta->pszFileName);
pMeta->pFileMeta = FileViewMeta_ReadFileMeta(szFullPath, pfr->fileType);
pMeta->bBusy = FALSE;
pfr->pMeta = pMeta->pFileMeta;
return TRUE;
}
if (queueMax > 0)
{
FileViewMeta_TruncateQueue(--queueMax);
}
if (!MetaDiscovery_InitializeThread(&metaDb) ||
!MetaDiscovery_ScheduleJob(metaDb.hDiscoverWake, pszPath, pMeta, pfr->fileType, fnCallback, param))
{
ZeroMemory(&pMeta->lastWriteTime, sizeof(FILETIME));
pMeta->fileSizeLow = 0;
pMeta->bBusy = FALSE;
return FALSE;
}
return FALSE;
}
static BOOL MetaDiscovery_ScheduleJob(HANDLE hWakeEvent, LPCWSTR pszPath, METAINFO *pMeta, UINT fileType, DISCOVERCALLBACK fnCallback, ULONG_PTR param)
{
if (!pszPath || !pMeta || !pMeta->pszFileName || !fnCallback || !hWakeEvent)
{
if (pMeta)
{
ZeroMemory(&pMeta->lastWriteTime, sizeof(FILETIME));
pMeta->fileSizeLow = 0;
pMeta->bBusy = FALSE;
}
return FALSE;
}
DISCOVERJOB *pJob = (DISCOVERJOB*)calloc(1, sizeof(DISCOVERJOB));
if (pJob)
{
pJob->pMeta = pMeta;
HANDLE hp = GetCurrentProcess();
if (DuplicateHandle(hp, GetCurrentThread(), hp, &pJob->hCaller, 0, FALSE, DUPLICATE_SAME_ACCESS))
{
pJob->fnCallback = fnCallback;
pJob->param = param;
pJob->fileType = fileType;
PathCombineW(pJob->szFileName, pszPath, pMeta->pszFileName);
EnterCriticalSection(&g_cs_discover);
discoverJobs.push_front(pJob);
LeaveCriticalSection(&g_cs_discover);
if (SetEvent(hWakeEvent)) return TRUE;
}
}
if (pJob)
{
if (pJob->hCaller) CloseHandle(pJob->hCaller);
free(pJob);
}
if (pMeta)
{
ZeroMemory(&pMeta->lastWriteTime, sizeof(FILETIME));
pMeta->fileSizeLow = 0;
pMeta->bBusy = FALSE;
}
return FALSE;
}
static void CALLBACK MetaDiscovery_ApcCallback(ULONG_PTR param)
{
DISCOVERJOB *pJob = (DISCOVERJOB*)param;
if (!pJob) return;
if (pJob->pMeta) pJob->pMeta->bBusy = FALSE;
if (pJob->fnCallback) pJob->fnCallback(pJob->szFileName, pJob->param);
if (pJob->hCaller) CloseHandle(pJob->hCaller);
free(pJob);
}
static void MetaDiscovery_ExecuteJob(DISCOVERJOB *pJob)
{
pJob->pMeta->pFileMeta = FileViewMeta_ReadFileMeta(pJob->szFileName, pJob->fileType);
if (pJob->hCaller)
{
QueueUserAPC(MetaDiscovery_ApcCallback, pJob->hCaller, (ULONG_PTR)pJob);
SleepEx(1, TRUE);
}
else MetaDiscovery_ApcCallback((ULONG_PTR)pJob);
}
static DWORD CALLBACK MetaDiscovery_ThreadProc(LPVOID param)
{
METADB *pMetaDb = (METADB*)param;
HANDLE hEvents[] = { pMetaDb->hDiscoverKill, pMetaDb->hDiscoverWake };
SetThreadName(-1, "FileView meta discovery");
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);
SetEvent(hEvents[METATHREAD_WAKE]);
for(;;)
{
switch (WaitForMultipleObjectsEx(2, hEvents, FALSE, INFINITE, TRUE))
{
case WAIT_OBJECT_0: // kill switch
TRACE_FMT(TEXT("Kill Meta Discovery"));
return 0;
case WAIT_OBJECT_0+1: // job
EnterCriticalSection(&g_cs_discover);
if (discoverJobs.empty())
{
ResetEvent(hEvents[METATHREAD_WAKE]);
LeaveCriticalSection(&g_cs_discover);
}
else
{
DISCOVERJOB *pj;
pj = discoverJobs.front();
discoverJobs.pop_front();
LeaveCriticalSection(&g_cs_discover);
MetaDiscovery_ExecuteJob(pj);
}
break;
}
}
return 0;
}
static BOOL MetaDiscovery_InitializeThread(METADB *pMetaDb)
{
if (!pMetaDb) return FALSE;
if (pMetaDb->hDiscoverThread) return TRUE;
DWORD threadId;
if (!pMetaDb->hDiscoverWake) pMetaDb->hDiscoverWake = CreateEvent(0, TRUE, FALSE, 0);
if (!pMetaDb->hDiscoverKill) pMetaDb->hDiscoverKill = CreateEvent(0, TRUE, FALSE, 0);
if (pMetaDb->hDiscoverKill && pMetaDb->hDiscoverWake)
{
pMetaDb->hDiscoverThread = CreateThread(NULL, 0, MetaDiscovery_ThreadProc, (LPVOID)pMetaDb, 0, &threadId);
if (pMetaDb->hDiscoverThread)
{
WaitForSingleObject(pMetaDb->hDiscoverWake, INFINITE);
ResetEvent(pMetaDb->hDiscoverWake);
return TRUE;
}
}
MetaDiscovery_KillThread(pMetaDb);
return FALSE;
}
static void MetaDiscovery_KillThread(METADB *pMetaDb)
{
if (!pMetaDb) return;
if (pMetaDb->hDiscoverThread)
{
if (pMetaDb->hDiscoverKill)
{
SetEvent(metaDb.hDiscoverKill);
WaitForSingleObject(metaDb.hDiscoverThread, INFINITE);
}
CloseHandle(pMetaDb->hDiscoverThread);
pMetaDb->hDiscoverThread = NULL;
}
if (pMetaDb->hDiscoverKill) { CloseHandle(pMetaDb->hDiscoverKill); pMetaDb->hDiscoverKill = NULL; }
if (pMetaDb->hDiscoverWake) { CloseHandle(pMetaDb->hDiscoverWake); pMetaDb->hDiscoverWake = NULL; }
}
static BOOL FileViewMeta_GetAudioString(AUDIOMETA *pam, UINT uMetaField, LPCWSTR *ppszOut)
{
switch(uMetaField)
{
case MF_ARTIST: *ppszOut = pam->pszArtist; return TRUE;
case MF_ALBUM: *ppszOut = pam->pszAlbum; return TRUE;
case MF_TITLE: *ppszOut = pam->pszTitle; return TRUE;
case MF_GENRE: *ppszOut = pam->pszGenre; return TRUE;
case MF_COMMENT: *ppszOut = pam->pszComment; return TRUE;
case MF_PUBLISHER: *ppszOut = pam->pszPublisher; return TRUE;
case MF_COMPOSER: *ppszOut = pam->pszComposer; return TRUE;
case MF_ALBUMARTIST: *ppszOut = pam->pszAlbumArtist; return TRUE;
}
return FALSE;
}
static BOOL FileViewMeta_GetAudioInt(AUDIOMETA *pam, UINT uMetaField, INT *pOut)
{
switch(uMetaField)
{
case MF_BITRATE: *pOut = pam->nBitrate; return TRUE;
case MF_DISCCOUNT: *pOut = pam->nDiscCount; return TRUE;
case MF_DISCNUM: *pOut = pam->nDiscNum; return TRUE;
case MF_LENGTH: *pOut = pam->nLength; return TRUE;
case MF_SOURCE: *pOut = pam->nSource; return TRUE;
case MF_TRACKCOUNT: *pOut = pam->nTrackCount; return TRUE;
case MF_TRACKNUM: *pOut = pam->nTrackNum; return TRUE;
case MF_YEAR: *pOut = pam->nYear; return TRUE;
}
return FALSE;
}
BOOL FileViewMeta_GetString(FILEMETARECORD *pMeta, UINT uMetaField, LPCWSTR *ppszOut)
{
if (!pMeta) return FALSE;
switch(pMeta->type)
{
case METATYPE_AUDIO: return FileViewMeta_GetAudioString(&pMeta->audio, uMetaField, ppszOut);
}
return FALSE;
}
BOOL FileViewMeta_GetInt(FILEMETARECORD *pMeta, UINT uMetaField, INT *pOut)
{
if (!pMeta) return FALSE;
switch(pMeta->type)
{
case METATYPE_AUDIO: return FileViewMeta_GetAudioInt(&pMeta->audio, uMetaField, pOut);
}
return FALSE;
}