fileview_metadata.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. #include "main.h"
  2. #include "./fileview.h"
  3. #include "./fileview_internal.h"
  4. #include <vector>
  5. #include <deque>
  6. #include "../nu/threadname.h"
  7. #include <api/service/waServiceFactory.h>
  8. #include "../playlist/api_playlistmanager.h"
  9. #include "../playlist/ifc_playlistloadercallback.h"
  10. #include "../Agave/Metadata/api_metadata.h"
  11. #include <shlwapi.h>
  12. #include <strsafe.h>
  13. #include <algorithm>
  14. typedef struct _METASEARCHKEY
  15. {
  16. INT pathId;
  17. LPCWSTR pszFileName;
  18. } METASEARCHKEY;
  19. typedef struct _PATHID
  20. {
  21. INT id;
  22. LPWSTR pszPath;
  23. } PATHID;
  24. typedef struct _METAINFO
  25. {
  26. INT pathId;
  27. LPWSTR pszFileName;
  28. FILETIME lastWriteTime;
  29. DWORD fileSizeLow;
  30. DWORD fileSizeHigh;
  31. BOOL bBusy;
  32. FILEMETARECORD *pFileMeta;
  33. } METAINFO;
  34. typedef std::vector<PATHID> PATHLIST;
  35. typedef std::vector<METAINFO*> METARECORS;
  36. typedef struct _METADB
  37. {
  38. UINT ref;
  39. PATHLIST pPath;
  40. METARECORS pRec;
  41. size_t lastPathIndex;
  42. INT maxPathId;
  43. HANDLE hDiscoverThread;
  44. HANDLE hDiscoverWake;
  45. HANDLE hDiscoverKill;
  46. } METADB;
  47. #define METATHREAD_KILL 0
  48. #define METATHREAD_WAKE 1
  49. typedef void (CALLBACK *DISCOVERCALLBACK)(LPCWSTR /*pszFileName*/, ULONG_PTR /*param*/);
  50. typedef struct _DISCOVERJOB
  51. {
  52. METAINFO *pMeta;
  53. HANDLE hCaller;
  54. DISCOVERCALLBACK fnCallback;
  55. ULONG_PTR param;
  56. UINT fileType;
  57. WCHAR szFileName[2*MAX_PATH];
  58. } DISCOVERJOB;
  59. typedef std::deque<DISCOVERJOB*> DISCOVERDEQUE;
  60. static DISCOVERDEQUE discoverJobs;
  61. static api_metadata *apiMetaData = NULL;
  62. static api_playlistmanager *apiPlaylistManager = NULL;
  63. static METADB metaDb = { 0, };
  64. static CRITICAL_SECTION g_cs_discover;
  65. static BOOL MetaDiscovery_InitializeThread(METADB *pMetaDb);
  66. static void MetaDiscovery_KillThread(METADB *pMetaDb);
  67. static BOOL MetaDiscovery_ScheduleJob(HANDLE hWakeEvent, LPCWSTR pszPath, METAINFO *pMeta, UINT fileType, DISCOVERCALLBACK fnCallback, ULONG_PTR param);
  68. class PlMetaLoader : public ifc_playlistloadercallback
  69. {
  70. public:
  71. PlMetaLoader( PLAYLISTMETA *plm )
  72. {
  73. this->plm = plm; plm->nCount = 0; plm->nLength = 0;
  74. }
  75. ~PlMetaLoader()
  76. {};
  77. void OnFile( const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info )
  78. {
  79. if ( plm->nCount < sizeof( plm->szEntries ) / sizeof( plm->szEntries[ 0 ] ) )
  80. {
  81. PLENTRY *pe = &plm->szEntries[ plm->nCount ];
  82. if ( title && *title )
  83. pe->pszTitle = _wcsdup( title );
  84. else if ( filename && *filename )
  85. {
  86. pe->pszTitle = _wcsdup( filename );
  87. }
  88. else
  89. pe->pszTitle = NULL;
  90. pe->nLength = lengthInMS / 1000;
  91. }
  92. plm->nCount++;
  93. plm->nLength += lengthInMS;
  94. }
  95. void OnPlaylistInfo( const wchar_t *playlistName, size_t numEntries, ifc_plentryinfo *info )
  96. {
  97. plm->pszTitle = ( playlistName && *playlistName ) ? _wcsdup( playlistName ) : NULL;
  98. }
  99. const wchar_t *GetBasePath()
  100. {
  101. return L".";
  102. }
  103. protected:
  104. RECVS_DISPATCH;
  105. private:
  106. PLAYLISTMETA *plm;
  107. };
  108. #define CBCLASS PlMetaLoader
  109. START_DISPATCH;
  110. VCB( IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile )
  111. VCB( IFC_PLAYLISTLOADERCALLBACK_ONPLAYLISTINFO, OnPlaylistInfo )
  112. CB( IFC_PLAYLISTLOADERCALLBACK_GETBASEPATH, GetBasePath )
  113. END_DISPATCH;
  114. void FileViewMeta_InitializeStorage(HWND hView)
  115. {
  116. if (0 == metaDb.ref)
  117. {
  118. InitializeCriticalSection(&g_cs_discover);
  119. if (WASABI_API_SVC)
  120. {
  121. waServiceFactory *factory;
  122. factory = WASABI_API_SVC->service_getServiceByGuid(api_metadataGUID);
  123. if (factory) apiMetaData = (api_metadata*) factory->getInterface();
  124. else apiMetaData = NULL;
  125. factory = WASABI_API_SVC->service_getServiceByGuid(api_playlistmanagerGUID);
  126. if (factory) apiPlaylistManager = (api_playlistmanager*) factory->getInterface();
  127. else apiPlaylistManager = NULL;
  128. }
  129. }
  130. metaDb.ref++;
  131. }
  132. static void FileViewMeta_FreeAudioMeta(AUDIOMETA *pMeta)
  133. {
  134. if (pMeta->pszAlbum) { free(pMeta->pszAlbum); pMeta->pszAlbum = NULL; }
  135. if (pMeta->pszArtist) { free(pMeta->pszArtist); pMeta->pszArtist = NULL; }
  136. if (pMeta->pszTitle) { free(pMeta->pszTitle); pMeta->pszTitle = NULL; }
  137. if (pMeta->pszAlbumArtist) { free(pMeta->pszAlbumArtist); pMeta->pszAlbumArtist = NULL; }
  138. if (pMeta->pszComment) { free(pMeta->pszComment); pMeta->pszComment = NULL; }
  139. if (pMeta->pszComposer) { free(pMeta->pszComposer); pMeta->pszComposer = NULL; }
  140. if (pMeta->pszGenre) { free(pMeta->pszGenre); pMeta->pszGenre = NULL; }
  141. if (pMeta->pszPublisher) { free(pMeta->pszPublisher); pMeta->pszPublisher = NULL; }
  142. }
  143. static void FileViewMeta_FreeVideoMeta(VIDEOMETA *pMeta)
  144. {
  145. if (pMeta->pszAlbum) { free(pMeta->pszAlbum); pMeta->pszAlbum = NULL; }
  146. if (pMeta->pszArtist) { free(pMeta->pszArtist); pMeta->pszArtist = NULL; }
  147. if (pMeta->pszTitle) { free(pMeta->pszTitle); pMeta->pszTitle = NULL; }
  148. if (pMeta->pszAlbumArtist) { free(pMeta->pszAlbumArtist); pMeta->pszAlbumArtist = NULL; }
  149. if (pMeta->pszComment) { free(pMeta->pszComment); pMeta->pszComment = NULL; }
  150. if (pMeta->pszComposer) { free(pMeta->pszComposer); pMeta->pszComposer = NULL; }
  151. if (pMeta->pszGenre) { free(pMeta->pszGenre); pMeta->pszGenre = NULL; }
  152. if (pMeta->pszPublisher) { free(pMeta->pszPublisher); pMeta->pszPublisher = NULL; }
  153. }
  154. static void FileViewMeta_FreePlaylistMeta(PLAYLISTMETA *pMeta)
  155. {
  156. if (!pMeta) return;
  157. if (pMeta->pszTitle) { free(pMeta->pszTitle); pMeta->pszTitle = NULL; }
  158. for (int i = 0; i < sizeof(pMeta->szEntries)/sizeof(pMeta->szEntries[0]); i++)
  159. {
  160. if (pMeta->szEntries[i].pszTitle) { free(pMeta->szEntries[i].pszTitle); pMeta->szEntries[i].pszTitle = NULL; }
  161. }
  162. if (pMeta->pszTitle) { free(pMeta->pszTitle); pMeta->pszTitle = NULL; }
  163. }
  164. static void FileViewMeta_FreeFileMeta(FILEMETARECORD *pFileMeta)
  165. {
  166. if (pFileMeta)
  167. {
  168. switch(pFileMeta->type)
  169. {
  170. case METATYPE_AUDIO:
  171. FileViewMeta_FreeAudioMeta(&pFileMeta->audio); break;
  172. case METATYPE_VIDEO:
  173. FileViewMeta_FreeVideoMeta(&pFileMeta->video); break;
  174. case METATYPE_PLAYLIST:
  175. FileViewMeta_FreePlaylistMeta(&pFileMeta->playlist); break;
  176. }
  177. free(pFileMeta);
  178. }
  179. }
  180. static void FileViewMeta_FreeMetaInfo(METAINFO *pMeta)
  181. {
  182. if (pMeta)
  183. {
  184. if (pMeta->pszFileName) { free(pMeta->pszFileName); pMeta->pszFileName = NULL; }
  185. FileViewMeta_FreeFileMeta(pMeta->pFileMeta);
  186. }
  187. }
  188. void FileViewMeta_TruncateQueue(size_t max)
  189. {
  190. EnterCriticalSection(&g_cs_discover);
  191. while(discoverJobs.size() > max)
  192. {
  193. DISCOVERJOB *pdj = discoverJobs.front();
  194. if (pdj)
  195. {
  196. if (pdj->pMeta)
  197. {
  198. ZeroMemory(&pdj->pMeta->lastWriteTime, sizeof(FILETIME));
  199. pdj->pMeta->fileSizeLow = 0;
  200. pdj->pMeta->bBusy = FALSE;
  201. if (pdj->hCaller) CloseHandle(pdj->hCaller);
  202. }
  203. free(pdj);
  204. }
  205. discoverJobs.pop_front();
  206. }
  207. LeaveCriticalSection(&g_cs_discover);
  208. }
  209. void FileViewMeta_ReleaseStorage(HWND hView)
  210. {
  211. if (0 == metaDb.ref) return;
  212. metaDb.ref--;
  213. if (0 == metaDb.ref)
  214. {
  215. FileViewMeta_TruncateQueue(0);
  216. MetaDiscovery_KillThread(&metaDb);
  217. if (WASABI_API_SVC)
  218. {
  219. if(apiMetaData)
  220. {
  221. waServiceFactory *factory = WASABI_API_SVC->service_getServiceByGuid(api_metadataGUID);
  222. if (factory) factory->releaseInterface(apiMetaData);
  223. }
  224. if(apiPlaylistManager)
  225. {
  226. waServiceFactory *factory = WASABI_API_SVC->service_getServiceByGuid(api_playlistmanagerGUID);
  227. if (factory) factory->releaseInterface(apiPlaylistManager);
  228. }
  229. }
  230. while(metaDb.pPath.size() > 0)
  231. {
  232. if (metaDb.pPath.back().pszPath) free(metaDb.pPath.back().pszPath);
  233. metaDb.pPath.pop_back();
  234. }
  235. while(metaDb.pRec.size() > 0)
  236. {
  237. FileViewMeta_FreeMetaInfo(metaDb.pRec.back());
  238. metaDb.pRec.pop_back();
  239. }
  240. metaDb.maxPathId = 0;
  241. metaDb.lastPathIndex = 0;
  242. DeleteCriticalSection(&g_cs_discover);
  243. }
  244. }
  245. __inline static int __cdecl FileViewMeta_SortPathId(const void *elem1, const void *elem2)
  246. {
  247. return (CompareStringW(CSTR_INVARIANT, NORM_IGNORECASE,
  248. ((PATHID*)elem1)->pszPath, -1, ((PATHID*)elem2)->pszPath, -1) - 2);
  249. }
  250. __inline static int __cdecl FileViewMeta_SearhPath(const void *elem1, const void *elem2)
  251. {
  252. return (CompareStringW(CSTR_INVARIANT, NORM_IGNORECASE,
  253. (LPCWSTR)elem1, -1, ((PATHID*)elem2)->pszPath, -1) - 2);
  254. }
  255. __inline static int __cdecl FileViewMeta_SortMetaInfo(const void *elem1, const void *elem2)
  256. {
  257. METAINFO *pmi1 = ((METAINFO*)elem1);
  258. METAINFO *pmi2 = ((METAINFO*)elem2);
  259. if (pmi1->pathId != pmi2->pathId) return (pmi1->pathId - pmi2->pathId);
  260. return (CompareStringW(CSTR_INVARIANT, NORM_IGNORECASE, pmi1->pszFileName, -1, pmi2->pszFileName, -1) - 2);
  261. }
  262. __inline static int __cdecl FileViewMeta_SortMetaInfo_V2(const void* elem1, const void* elem2)
  263. {
  264. return FileViewMeta_SortMetaInfo(elem1, elem2) < 0;
  265. }
  266. __inline static int __cdecl FileViewMeta_SearhMetaInfo(const void *elem1, const void *elem2)
  267. {
  268. METASEARCHKEY *pKey = (METASEARCHKEY*)elem1;
  269. if (pKey->pathId != (((METAINFO*)elem2))->pathId)
  270. return (pKey->pathId - (((METAINFO*)elem2))->pathId);
  271. return (CompareStringW(CSTR_INVARIANT, NORM_IGNORECASE,
  272. pKey->pszFileName, -1, (((METAINFO*)elem2))->pszFileName, -1) - 2);
  273. }
  274. static INT FileViewMeta_GetPathId(LPCWSTR pszPath)
  275. {
  276. if (!pszPath || L'\0' == pszPath) return 0;
  277. //PATHID *psr;
  278. PATHLIST::iterator psr;
  279. if (metaDb.lastPathIndex < metaDb.pPath.size() &&
  280. CSTR_EQUAL == CompareStringW(CSTR_INVARIANT, NORM_IGNORECASE, pszPath, -1,
  281. metaDb.pPath.at(metaDb.lastPathIndex).pszPath, -1))
  282. {
  283. //psr = &metaDb.pPath.at(metaDb.lastPathIndex);
  284. psr = metaDb.pPath.begin() + metaDb.lastPathIndex;
  285. }
  286. else
  287. {
  288. //psr = (PATHID*)bsearch(pszPath, metaDb.pPath.begin(), metaDb.pPath.size(), sizeof(PATHID), FileViewMeta_SearhPath);
  289. psr = std::find_if(metaDb.pPath.begin(), metaDb.pPath.end(),
  290. [&](PATHID &path) -> bool
  291. {
  292. return FileViewMeta_SearhPath(pszPath, &path) == 0;
  293. }
  294. );
  295. }
  296. //if (!psr)
  297. if(psr == metaDb.pPath.end())
  298. {
  299. PATHID pid;
  300. pid.id = metaDb.maxPathId+1;
  301. pid.pszPath = _wcsdup(pszPath);
  302. metaDb.pPath.push_back(pid);
  303. metaDb.maxPathId++;
  304. //psr = &pid;
  305. psr = metaDb.pPath.end() - 1;
  306. //qsort(metaDb.pPath.begin(), metaDb.pPath.size(), sizeof(PATHID), FileViewMeta_SortPathId);
  307. std::sort(metaDb.pPath.begin(), metaDb.pPath.end(),
  308. [&](const PATHID& lhs, const PATHID& rhs) -> bool
  309. {
  310. return FileViewMeta_SortPathId(&lhs, &rhs) == CSTR_LESS_THAN;
  311. }
  312. );
  313. }
  314. metaDb.lastPathIndex = (size_t)(ULONG_PTR)(psr - metaDb.pPath.begin());
  315. return psr->id;
  316. }
  317. static METAINFO *FileViewMeta_GetMeta(LPCWSTR pszPath, LPCWSTR pszFileName, LPCWSTR pszExt, BOOL bCreateIfNotFound)
  318. {
  319. WCHAR szBuffer[MAX_PATH] = {0};
  320. METASEARCHKEY key;
  321. key.pathId = FileViewMeta_GetPathId(pszPath);
  322. key.pszFileName = pszFileName;
  323. if (pszExt == (pszFileName + lstrlenW(pszFileName) + 1))
  324. {
  325. StringCchCopyW(szBuffer, sizeof(szBuffer)/sizeof(szBuffer[0]), pszFileName);
  326. if (pszExt)
  327. {
  328. StringCchCatW(szBuffer, sizeof(szBuffer)/sizeof(szBuffer[0]), L".");
  329. StringCchCatW(szBuffer, sizeof(szBuffer)/sizeof(szBuffer[0]), pszExt);
  330. }
  331. key.pszFileName = szBuffer;
  332. }
  333. //METAINFO **pr = (METAINFO**)bsearch(&key, metaDb.pRec.at(0), metaDb.pRec.size(), sizeof(METAINFO*), FileViewMeta_SearhMetaInfo);
  334. auto it = std::find_if(metaDb.pRec.begin(), metaDb.pRec.end(),
  335. [&](const METAINFO* info) -> bool
  336. {
  337. return FileViewMeta_SearhMetaInfo(&key, info) == 0;
  338. }
  339. );
  340. if (it != metaDb.pRec.end())
  341. {
  342. return *it;
  343. }
  344. //if (pr)
  345. // return *pr;
  346. if (!bCreateIfNotFound)
  347. return NULL;
  348. METAINFO *pInfo = (METAINFO*)calloc(1, sizeof(METAINFO));
  349. if (pInfo)
  350. {
  351. pInfo->pathId = key.pathId;
  352. pInfo->pszFileName = _wcsdup(key.pszFileName);
  353. metaDb.pRec.push_back(pInfo);
  354. //qsort(metaDb.pRec.first(), metaDb.pRec.size(), sizeof(METAINFO*), FileViewMeta_SortMetaInfo);
  355. std::sort(metaDb.pRec.begin(), metaDb.pRec.end(), FileViewMeta_SortMetaInfo_V2);
  356. }
  357. return pInfo;
  358. }
  359. // sets part and parts to -1 or 0 on fail/missing (e.g. parts will be -1 on "1", but 0 on "1/")
  360. static void ParseIntSlashInt(wchar_t *string, int *part, int *parts)
  361. {
  362. *part = -1;
  363. *parts = -1;
  364. if (string && string[0])
  365. {
  366. *part = _wtoi(string);
  367. while (*string && *string != '/')
  368. {
  369. string++;
  370. }
  371. if (*string == '/')
  372. {
  373. string++;
  374. *parts = _wtoi(string);
  375. }
  376. }
  377. }
  378. #define READFILEINFO(__fileName, __tag, __result, __pszBuffer, __cchBuffer)\
  379. (apiMetaData->GetExtendedFileInfo((__fileName), (__tag), (__pszBuffer), (__cchBuffer)) && L'\0' != *(__pszBuffer))
  380. static void FileViewMeta_ReadAudioMeta(LPCWSTR pszFullPath, AUDIOMETA *pa)
  381. {
  382. #define GETFILEINFO_STR(__tag, __result) { szBuffer[0] = L'\0';\
  383. if (READFILEINFO(pszFullPath, __tag, __result, szBuffer, sizeof(szBuffer)/sizeof(szBuffer[0])))\
  384. {(__result) = _wcsdup(szBuffer); }}
  385. #define GETFILEINFO_INT(__tag, __result) { szBuffer[0] = L'\0';\
  386. if (READFILEINFO(pszFullPath, __tag, __result, szBuffer, sizeof(szBuffer)/sizeof(szBuffer[0])))\
  387. {(__result) = _wtoi(szBuffer); }}
  388. #define GETFILEINFO_INTINT(__tag, __result1, __result2) { szBuffer[0] = L'\0';\
  389. if (READFILEINFO(pszFullPath, __tag, __result, szBuffer, sizeof(szBuffer)/sizeof(szBuffer[0])))\
  390. {ParseIntSlashInt(szBuffer, (__result1), (__result2)); }}
  391. if (AGAVE_API_MLDB)
  392. {
  393. itemRecordW *record = AGAVE_API_MLDB->GetFile(pszFullPath);
  394. if (record)
  395. {
  396. if (record->artist) pa->pszArtist = _wcsdup(record->artist);
  397. if (record->album) pa->pszAlbum = _wcsdup(record->album);
  398. if (record->title) pa->pszTitle = _wcsdup(record->title);
  399. if (record->albumartist) pa->pszAlbumArtist = _wcsdup(record->albumartist);
  400. if (record->comment) pa->pszComment = _wcsdup(record->comment);
  401. if (record->composer) pa->pszComposer = _wcsdup(record->composer);
  402. if (record->genre) pa->pszGenre = _wcsdup(record->genre);
  403. if (record->publisher) pa->pszPublisher = _wcsdup(record->publisher);
  404. pa->nLength = record->length;
  405. pa->nBitrate = record->bitrate;
  406. pa->nYear = record->year;
  407. pa->nDiscNum = record->disc;
  408. pa->nDiscCount = record->discs;
  409. pa->nTrackNum = record->track;
  410. pa->nTrackCount = record->tracks;
  411. pa->nSource = METADATA_SOURCE_MLDB;
  412. AGAVE_API_MLDB->FreeRecord(record);
  413. return;
  414. }
  415. }
  416. WCHAR szBuffer[2048] = {0};
  417. GETFILEINFO_STR(L"artist", pa->pszArtist);
  418. GETFILEINFO_STR(L"album", pa->pszAlbum);
  419. GETFILEINFO_STR(L"title", pa->pszTitle);
  420. GETFILEINFO_STR(L"albumartist", pa->pszAlbumArtist);
  421. GETFILEINFO_STR(L"comment", pa->pszComment);
  422. GETFILEINFO_STR(L"composer", pa->pszComposer);
  423. GETFILEINFO_STR(L"genre", pa->pszGenre);
  424. GETFILEINFO_STR(L"publisher", pa->pszPublisher);
  425. GETFILEINFO_INT(L"bitrate", pa->nBitrate);
  426. GETFILEINFO_INT(L"year", pa->nYear);
  427. GETFILEINFO_INT(L"length", pa->nLength);
  428. pa->nLength = pa->nLength/1000;
  429. GETFILEINFO_INTINT(L"disc", &pa->nDiscNum, &pa->nDiscCount);
  430. GETFILEINFO_INTINT(L"track",&pa->nTrackNum, &pa->nTrackCount);
  431. pa->nSource = METADATA_SOURCE_FILEINFO;
  432. }
  433. static void FileViewMeta_ReadPlaylistMeta(LPCWSTR pszFullPath, PLAYLISTMETA *plm)
  434. {
  435. if (!apiPlaylistManager) return;
  436. PlMetaLoader plLoaderCb(plm);
  437. apiPlaylistManager->Load(pszFullPath, &plLoaderCb);
  438. plm->nLength = plm->nLength/1000;
  439. }
  440. static FILEMETARECORD* FileViewMeta_ReadFileMeta(LPCWSTR pszFullPath, UINT uType)
  441. {
  442. FILEMETARECORD *pmr = (FILEMETARECORD*)calloc(1, sizeof(FILEMETARECORD));
  443. if (!pmr) return NULL;
  444. switch(uType)
  445. {
  446. case FVFT_AUDIO:
  447. pmr->type = METATYPE_AUDIO;
  448. FileViewMeta_ReadAudioMeta(pszFullPath, &pmr->audio);
  449. break;
  450. case FVFT_PLAYLIST:
  451. pmr->type = METATYPE_PLAYLIST;
  452. FileViewMeta_ReadPlaylistMeta(pszFullPath, &pmr->playlist);
  453. break;
  454. }
  455. return pmr;
  456. }
  457. FILEMETARECORD *FileViewMeta_GetFromCache(LPCWSTR pszPath, FILERECORD *pfr)
  458. {
  459. if ((FVFT_AUDIO != pfr->fileType && FVFT_VIDEO != pfr->fileType) || !pszPath) return NULL;
  460. METAINFO *pMeta = FileViewMeta_GetMeta(pszPath, pfr->Info.cFileName, &pfr->Info.cFileName[pfr->extOffset], FALSE);
  461. if (pMeta && !pMeta->bBusy && pMeta->fileSizeLow == pfr->Info.nFileSizeLow &&
  462. pMeta->fileSizeHigh == pfr->Info.nFileSizeHigh && 0 == CompareFileTime(&pMeta->lastWriteTime, &pfr->Info.ftLastWriteTime))
  463. {
  464. return pMeta->pFileMeta;
  465. }
  466. return NULL;
  467. }
  468. BOOL FileViewMeta_Discover(LPCWSTR pszPath, FILERECORD *pfr, DISCOVERCALLBACK fnCallback, ULONG_PTR param, INT queueMax)
  469. {
  470. if (!apiMetaData) return FALSE;
  471. if ((FVFT_AUDIO != pfr->fileType &&
  472. FVFT_VIDEO != pfr->fileType &&
  473. FVFT_PLAYLIST != pfr->fileType) || !pszPath) return FALSE;
  474. METAINFO *pMeta = FileViewMeta_GetMeta(pszPath, pfr->Info.cFileName, &pfr->Info.cFileName[pfr->extOffset], TRUE);
  475. if (!pMeta || pMeta->bBusy) return FALSE;
  476. if (0 == CompareFileTime(&pMeta->lastWriteTime, &pfr->Info.ftLastWriteTime) &&
  477. pMeta->fileSizeLow == pfr->Info.nFileSizeLow && pMeta->fileSizeHigh == pfr->Info.nFileSizeHigh)
  478. {
  479. pfr->pMeta = pMeta->pFileMeta;
  480. return TRUE;
  481. }
  482. FileViewMeta_FreeFileMeta(pMeta->pFileMeta);
  483. pMeta->lastWriteTime = pfr->Info.ftLastWriteTime;
  484. pMeta->fileSizeLow = pfr->Info.nFileSizeLow;
  485. pMeta->fileSizeHigh = pfr->Info.nFileSizeHigh;
  486. pMeta->pFileMeta = NULL;
  487. pMeta->bBusy = TRUE;
  488. if (!fnCallback && !param)
  489. {
  490. WCHAR szFullPath[MAX_PATH] = {0};
  491. FileViewMeta_TruncateQueue(0);
  492. PathCombineW(szFullPath, pszPath, pMeta->pszFileName);
  493. pMeta->pFileMeta = FileViewMeta_ReadFileMeta(szFullPath, pfr->fileType);
  494. pMeta->bBusy = FALSE;
  495. pfr->pMeta = pMeta->pFileMeta;
  496. return TRUE;
  497. }
  498. if (queueMax > 0)
  499. {
  500. FileViewMeta_TruncateQueue(--queueMax);
  501. }
  502. if (!MetaDiscovery_InitializeThread(&metaDb) ||
  503. !MetaDiscovery_ScheduleJob(metaDb.hDiscoverWake, pszPath, pMeta, pfr->fileType, fnCallback, param))
  504. {
  505. ZeroMemory(&pMeta->lastWriteTime, sizeof(FILETIME));
  506. pMeta->fileSizeLow = 0;
  507. pMeta->bBusy = FALSE;
  508. return FALSE;
  509. }
  510. return FALSE;
  511. }
  512. static BOOL MetaDiscovery_ScheduleJob(HANDLE hWakeEvent, LPCWSTR pszPath, METAINFO *pMeta, UINT fileType, DISCOVERCALLBACK fnCallback, ULONG_PTR param)
  513. {
  514. if (!pszPath || !pMeta || !pMeta->pszFileName || !fnCallback || !hWakeEvent)
  515. {
  516. if (pMeta)
  517. {
  518. ZeroMemory(&pMeta->lastWriteTime, sizeof(FILETIME));
  519. pMeta->fileSizeLow = 0;
  520. pMeta->bBusy = FALSE;
  521. }
  522. return FALSE;
  523. }
  524. DISCOVERJOB *pJob = (DISCOVERJOB*)calloc(1, sizeof(DISCOVERJOB));
  525. if (pJob)
  526. {
  527. pJob->pMeta = pMeta;
  528. HANDLE hp = GetCurrentProcess();
  529. if (DuplicateHandle(hp, GetCurrentThread(), hp, &pJob->hCaller, 0, FALSE, DUPLICATE_SAME_ACCESS))
  530. {
  531. pJob->fnCallback = fnCallback;
  532. pJob->param = param;
  533. pJob->fileType = fileType;
  534. PathCombineW(pJob->szFileName, pszPath, pMeta->pszFileName);
  535. EnterCriticalSection(&g_cs_discover);
  536. discoverJobs.push_front(pJob);
  537. LeaveCriticalSection(&g_cs_discover);
  538. if (SetEvent(hWakeEvent)) return TRUE;
  539. }
  540. }
  541. if (pJob)
  542. {
  543. if (pJob->hCaller) CloseHandle(pJob->hCaller);
  544. free(pJob);
  545. }
  546. if (pMeta)
  547. {
  548. ZeroMemory(&pMeta->lastWriteTime, sizeof(FILETIME));
  549. pMeta->fileSizeLow = 0;
  550. pMeta->bBusy = FALSE;
  551. }
  552. return FALSE;
  553. }
  554. static void CALLBACK MetaDiscovery_ApcCallback(ULONG_PTR param)
  555. {
  556. DISCOVERJOB *pJob = (DISCOVERJOB*)param;
  557. if (!pJob) return;
  558. if (pJob->pMeta) pJob->pMeta->bBusy = FALSE;
  559. if (pJob->fnCallback) pJob->fnCallback(pJob->szFileName, pJob->param);
  560. if (pJob->hCaller) CloseHandle(pJob->hCaller);
  561. free(pJob);
  562. }
  563. static void MetaDiscovery_ExecuteJob(DISCOVERJOB *pJob)
  564. {
  565. pJob->pMeta->pFileMeta = FileViewMeta_ReadFileMeta(pJob->szFileName, pJob->fileType);
  566. if (pJob->hCaller)
  567. {
  568. QueueUserAPC(MetaDiscovery_ApcCallback, pJob->hCaller, (ULONG_PTR)pJob);
  569. SleepEx(1, TRUE);
  570. }
  571. else MetaDiscovery_ApcCallback((ULONG_PTR)pJob);
  572. }
  573. static DWORD CALLBACK MetaDiscovery_ThreadProc(LPVOID param)
  574. {
  575. METADB *pMetaDb = (METADB*)param;
  576. HANDLE hEvents[] = { pMetaDb->hDiscoverKill, pMetaDb->hDiscoverWake };
  577. SetThreadName(-1, "FileView meta discovery");
  578. SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);
  579. SetEvent(hEvents[METATHREAD_WAKE]);
  580. for(;;)
  581. {
  582. switch (WaitForMultipleObjectsEx(2, hEvents, FALSE, INFINITE, TRUE))
  583. {
  584. case WAIT_OBJECT_0: // kill switch
  585. TRACE_FMT(TEXT("Kill Meta Discovery"));
  586. return 0;
  587. case WAIT_OBJECT_0+1: // job
  588. EnterCriticalSection(&g_cs_discover);
  589. if (discoverJobs.empty())
  590. {
  591. ResetEvent(hEvents[METATHREAD_WAKE]);
  592. LeaveCriticalSection(&g_cs_discover);
  593. }
  594. else
  595. {
  596. DISCOVERJOB *pj;
  597. pj = discoverJobs.front();
  598. discoverJobs.pop_front();
  599. LeaveCriticalSection(&g_cs_discover);
  600. MetaDiscovery_ExecuteJob(pj);
  601. }
  602. break;
  603. }
  604. }
  605. return 0;
  606. }
  607. static BOOL MetaDiscovery_InitializeThread(METADB *pMetaDb)
  608. {
  609. if (!pMetaDb) return FALSE;
  610. if (pMetaDb->hDiscoverThread) return TRUE;
  611. DWORD threadId;
  612. if (!pMetaDb->hDiscoverWake) pMetaDb->hDiscoverWake = CreateEvent(0, TRUE, FALSE, 0);
  613. if (!pMetaDb->hDiscoverKill) pMetaDb->hDiscoverKill = CreateEvent(0, TRUE, FALSE, 0);
  614. if (pMetaDb->hDiscoverKill && pMetaDb->hDiscoverWake)
  615. {
  616. pMetaDb->hDiscoverThread = CreateThread(NULL, 0, MetaDiscovery_ThreadProc, (LPVOID)pMetaDb, 0, &threadId);
  617. if (pMetaDb->hDiscoverThread)
  618. {
  619. WaitForSingleObject(pMetaDb->hDiscoverWake, INFINITE);
  620. ResetEvent(pMetaDb->hDiscoverWake);
  621. return TRUE;
  622. }
  623. }
  624. MetaDiscovery_KillThread(pMetaDb);
  625. return FALSE;
  626. }
  627. static void MetaDiscovery_KillThread(METADB *pMetaDb)
  628. {
  629. if (!pMetaDb) return;
  630. if (pMetaDb->hDiscoverThread)
  631. {
  632. if (pMetaDb->hDiscoverKill)
  633. {
  634. SetEvent(metaDb.hDiscoverKill);
  635. WaitForSingleObject(metaDb.hDiscoverThread, INFINITE);
  636. }
  637. CloseHandle(pMetaDb->hDiscoverThread);
  638. pMetaDb->hDiscoverThread = NULL;
  639. }
  640. if (pMetaDb->hDiscoverKill) { CloseHandle(pMetaDb->hDiscoverKill); pMetaDb->hDiscoverKill = NULL; }
  641. if (pMetaDb->hDiscoverWake) { CloseHandle(pMetaDb->hDiscoverWake); pMetaDb->hDiscoverWake = NULL; }
  642. }
  643. static BOOL FileViewMeta_GetAudioString(AUDIOMETA *pam, UINT uMetaField, LPCWSTR *ppszOut)
  644. {
  645. switch(uMetaField)
  646. {
  647. case MF_ARTIST: *ppszOut = pam->pszArtist; return TRUE;
  648. case MF_ALBUM: *ppszOut = pam->pszAlbum; return TRUE;
  649. case MF_TITLE: *ppszOut = pam->pszTitle; return TRUE;
  650. case MF_GENRE: *ppszOut = pam->pszGenre; return TRUE;
  651. case MF_COMMENT: *ppszOut = pam->pszComment; return TRUE;
  652. case MF_PUBLISHER: *ppszOut = pam->pszPublisher; return TRUE;
  653. case MF_COMPOSER: *ppszOut = pam->pszComposer; return TRUE;
  654. case MF_ALBUMARTIST: *ppszOut = pam->pszAlbumArtist; return TRUE;
  655. }
  656. return FALSE;
  657. }
  658. static BOOL FileViewMeta_GetAudioInt(AUDIOMETA *pam, UINT uMetaField, INT *pOut)
  659. {
  660. switch(uMetaField)
  661. {
  662. case MF_BITRATE: *pOut = pam->nBitrate; return TRUE;
  663. case MF_DISCCOUNT: *pOut = pam->nDiscCount; return TRUE;
  664. case MF_DISCNUM: *pOut = pam->nDiscNum; return TRUE;
  665. case MF_LENGTH: *pOut = pam->nLength; return TRUE;
  666. case MF_SOURCE: *pOut = pam->nSource; return TRUE;
  667. case MF_TRACKCOUNT: *pOut = pam->nTrackCount; return TRUE;
  668. case MF_TRACKNUM: *pOut = pam->nTrackNum; return TRUE;
  669. case MF_YEAR: *pOut = pam->nYear; return TRUE;
  670. }
  671. return FALSE;
  672. }
  673. BOOL FileViewMeta_GetString(FILEMETARECORD *pMeta, UINT uMetaField, LPCWSTR *ppszOut)
  674. {
  675. if (!pMeta) return FALSE;
  676. switch(pMeta->type)
  677. {
  678. case METATYPE_AUDIO: return FileViewMeta_GetAudioString(&pMeta->audio, uMetaField, ppszOut);
  679. }
  680. return FALSE;
  681. }
  682. BOOL FileViewMeta_GetInt(FILEMETARECORD *pMeta, UINT uMetaField, INT *pOut)
  683. {
  684. if (!pMeta) return FALSE;
  685. switch(pMeta->type)
  686. {
  687. case METATYPE_AUDIO: return FileViewMeta_GetAudioInt(&pMeta->audio, uMetaField, pOut);
  688. }
  689. return FALSE;
  690. }