AlbumArtCache.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. #include "main.h"
  2. #include <nde/nde_c.h>
  3. #include "../nu/threadname.h"
  4. #include "AlbumArtContainer.h"
  5. #include <tataki/bitmap/bitmap.h>
  6. #include "MD5.h"
  7. #include "../replicant/nu/AutoLock.h"
  8. #include <strsafe.h>
  9. #include <map>
  10. #include <deque>
  11. static bool GetArtFromCache(const wchar_t *filename, int size, ARGB32 **bits);
  12. static bool SetArtCache(const wchar_t *filename, int size, const ARGB32 *bits, uint8_t hash[16]);
  13. static void CloseArtCache();
  14. static HANDLE artWake=0;
  15. static HANDLE mainThread=0;
  16. struct CreateCacheParameters
  17. {
  18. AlbumArtContainer *container;
  19. int w;
  20. int h;
  21. SkinBitmap *cache;
  22. AlbumArtContainer::CacheStatus status;
  23. };
  24. static std::deque<CreateCacheParameters*> artQueue;
  25. static nu::LockGuard queueGuard;
  26. static void Adjust(int bmpw, int bmph, int &x, int &y, int &w, int &h)
  27. {
  28. // maintain 'square' stretching
  29. double aspX = (double)(w)/(double)bmpw;
  30. double aspY = (double)(h)/(double)bmph;
  31. double asp = min(aspX, aspY);
  32. int newW = (int)(bmpw*asp);
  33. int newH = (int)(bmph*asp);
  34. x = (w - newW)/2;
  35. y = (h - newH)/2;
  36. w = newW;
  37. h = newH;
  38. }
  39. static void CALLBACK CreateCacheCallbackAPC(ULONG_PTR parameter)
  40. {
  41. CreateCacheParameters *parameters = (CreateCacheParameters *)parameter;
  42. parameters->container->SetCache(parameters->cache, parameters->status);
  43. parameters->container->Release();
  44. WASABI_API_MEMMGR->sysFree(parameters);
  45. }
  46. static void CreateCache(CreateCacheParameters *parameters)
  47. {
  48. // let's hope this doesn't overflow the stack
  49. const wchar_t *filename = parameters->container->filename;
  50. if ((unsigned int)(ULONG_PTR)filename < 65536)
  51. {
  52. parameters->cache = 0;
  53. parameters->status = AlbumArtContainer::CACHE_NOTFOUND;
  54. QueueUserAPC(CreateCacheCallbackAPC, mainThread, (ULONG_PTR)parameters);
  55. return;
  56. }
  57. int w = parameters->w, h = parameters->h;
  58. ARGB32 *cacheBits = 0;
  59. if (GetArtFromCache(filename, w, &cacheBits))
  60. {
  61. SkinBitmap *cache = new SkinBitmap(cacheBits, w, h, true);
  62. parameters->cache = cache;
  63. parameters->status = AlbumArtContainer::CACHE_CACHED;
  64. }
  65. else
  66. {
  67. int artsize = w;
  68. int bmp_w = 0, bmp_h = 0;
  69. ARGB32 *bits = 0;
  70. if (AGAVE_API_ALBUMART && AGAVE_API_ALBUMART->GetAlbumArt(filename, L"Cover", &bmp_w, &bmp_h, &bits) == ALBUMART_SUCCESS)
  71. {
  72. uint8_t hash[16] = {0};
  73. MD5_CTX hashCtx;
  74. MD5Init(&hashCtx);
  75. MD5Update(&hashCtx, (uint8_t *)bits, bmp_w*bmp_h*sizeof(ARGB32));
  76. MD5Final(hash, &hashCtx);
  77. BltCanvas canvas(w,h);
  78. HQSkinBitmap temp(bits, bmp_w,bmp_h); // wrap into a SkinBitmap (no copying involved)
  79. int x = 0, y = 0;
  80. Adjust(bmp_w, bmp_h, x,y,w,h);
  81. temp.stretch(&canvas,x,y,w,h);
  82. SkinBitmap *cache = new SkinBitmap(&canvas);
  83. parameters->cache = cache;
  84. SetArtCache(filename, artsize, (ARGB32 *)canvas.getBits(), hash);
  85. WASABI_API_MEMMGR->sysFree(bits);
  86. parameters->status = AlbumArtContainer::CACHE_CACHED;
  87. }
  88. else
  89. {
  90. parameters->cache = 0;
  91. parameters->status = AlbumArtContainer::CACHE_NOTFOUND;
  92. }
  93. }
  94. QueueUserAPC(CreateCacheCallbackAPC, mainThread, (ULONG_PTR)parameters);
  95. return;
  96. }
  97. static int ArtLoadThread(HANDLE handle, void *user_data, intptr_t id)
  98. {
  99. // TODO? SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);
  100. queueGuard.Lock();
  101. if (artQueue.empty())
  102. {
  103. queueGuard.Unlock();
  104. return 0;
  105. }
  106. CreateCacheParameters *parameters = artQueue.front();
  107. artQueue.pop_front();
  108. queueGuard.Unlock();
  109. CreateCache(parameters);
  110. return 0;
  111. }
  112. enum
  113. {
  114. ARTHASH_FILENAME = 0,
  115. ARTHASH_HASH = 1,
  116. ARTTABLE_HASH = 0,
  117. ARTTABLE_ARGB32 = 1,
  118. };
  119. typedef std::map<int, nde_table_t> ArtCache;
  120. static ArtCache artcache;
  121. static nde_database_t art_db = 0;
  122. static nde_table_t artHashes = 0;
  123. static ThreadID *artThread=0;
  124. static void InitArtThread()
  125. {
  126. if (!artThread)
  127. {
  128. artWake = CreateSemaphore(0, 0, 65536, 0);
  129. mainThread = WASABI_API_APP->main_getMainThreadHandle();
  130. artThread = WASABI_API_THREADPOOL->ReserveThread(0);
  131. WASABI_API_THREADPOOL->AddHandle(artThread, artWake, ArtLoadThread, 0, 0, 0);
  132. }
  133. }
  134. static int ArtKillThreadPoolFunc(HANDLE handle, void *user_data, intptr_t id)
  135. {
  136. WASABI_API_THREADPOOL->RemoveHandle(artThread, artWake);
  137. CloseArtCache();
  138. SetEvent((HANDLE)user_data);
  139. return 0;
  140. }
  141. void KillArtThread()
  142. {
  143. if (artThread)
  144. {
  145. HANDLE artKill = CreateEvent(0, FALSE, FALSE, 0);
  146. WASABI_API_THREADPOOL->RunFunction(artThread, ArtKillThreadPoolFunc, (void *)artKill, 0, 0);
  147. WaitForSingleObject(artKill, INFINITE);
  148. CloseHandle(mainThread); mainThread = 0;
  149. CloseHandle(artKill); artKill = 0;
  150. CloseHandle(artWake); artWake = 0;
  151. WASABI_API_THREADPOOL->ReleaseThread(artThread);
  152. }
  153. }
  154. void MigrateArtCache()
  155. {
  156. int size[] = {0, 60, 90, 120, 180};
  157. for (int i = 0; i < sizeof(size)/sizeof(size[0]); i++)
  158. {
  159. if (!size[i])
  160. {
  161. const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath();
  162. art_db = NDE_CreateDatabase(plugin.hDllInstance);
  163. wchar_t tableName[MAX_PATH] = {0}, oldTableName[MAX_PATH] = {0}, indexName[MAX_PATH] = {0}, oldIndexName[MAX_PATH] = {0};
  164. PathCombineW(oldIndexName, inidir, L"Plugins\\ml");
  165. PathCombineW(indexName, inidir, L"Plugins\\ml\\art");
  166. CreateDirectoryW(indexName, NULL);
  167. PathCombineW(tableName, indexName, L"art.dat");
  168. PathCombineW(oldTableName, oldIndexName, L"art.dat");
  169. PathAppendW(indexName, L"art.idx");
  170. PathAppendW(oldIndexName, L"art.idx");
  171. // migrate files to their own 'art' sub-folder
  172. if (PathFileExistsW(oldIndexName) && !PathFileExistsW(indexName))
  173. {
  174. MoveFileW(oldIndexName, indexName);
  175. MoveFileW(oldTableName, tableName);
  176. }
  177. }
  178. else if (size[i])
  179. {
  180. const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath();
  181. wchar_t temp[MAX_PATH] = {0}, tableName[MAX_PATH] = {0}, oldTableName[MAX_PATH] = {0}, indexName[MAX_PATH] = {0}, oldIndexName[MAX_PATH] = {0};
  182. PathCombineW(temp, inidir, L"Plugins\\ml");
  183. StringCchPrintfW(tableName, MAX_PATH, L"%s\\art\\art_%d.dat", temp, size[i]);
  184. StringCchPrintfW(oldTableName, MAX_PATH, L"%s\\art_%d.dat", temp, size[i]);
  185. StringCchPrintfW(indexName, MAX_PATH, L"%s\\art\\art_%d.idx", temp, size[i]);
  186. StringCchPrintfW(oldIndexName, MAX_PATH, L"%s\\art_%d.idx", temp, size[i]);
  187. // migrate files to their own 'art' sub-folder
  188. if (PathFileExistsW(oldIndexName) && !PathFileExistsW(indexName))
  189. {
  190. MoveFileW(oldIndexName, indexName);
  191. MoveFileW(oldTableName, tableName);
  192. }
  193. }
  194. }
  195. }
  196. static bool InitArtCache(int size)
  197. {
  198. if (!art_db)
  199. {
  200. const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath();
  201. art_db = NDE_CreateDatabase(plugin.hDllInstance);
  202. wchar_t tableName[MAX_PATH] = {0}, indexName[MAX_PATH] = {0};
  203. PathCombineW(indexName, inidir, L"Plugins\\ml\\art");
  204. PathCombineW(tableName, indexName, L"art.dat");
  205. PathAppendW(indexName, L"art.idx");
  206. artHashes = NDE_Database_OpenTable(art_db, tableName, indexName, NDE_OPEN_ALWAYS, NDE_CACHE);
  207. NDE_Table_NewColumnW(artHashes, ARTHASH_FILENAME, DB_FIELDNAME_filename, FIELD_FILENAME);
  208. NDE_Table_NewColumnW(artHashes, ARTHASH_HASH, L"hash", FIELD_INT128);
  209. NDE_Table_PostColumns(artHashes);
  210. NDE_Table_AddIndexByIDW( artHashes, ARTHASH_FILENAME, DB_FIELDNAME_filename );
  211. }
  212. if (size && !artcache[size])
  213. {
  214. const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath();
  215. wchar_t temp[MAX_PATH] = {0}, tableName[MAX_PATH] = {0}, indexName[MAX_PATH] = {0};
  216. PathCombineW(temp, inidir, L"Plugins\\ml");
  217. StringCchPrintfW(tableName, MAX_PATH, L"%s\\art\\art_%d.dat", temp, size);
  218. StringCchPrintfW(indexName, MAX_PATH, L"%s\\art\\art_%d.idx", temp, size);
  219. nde_table_t artTable = NDE_Database_OpenTable(art_db, tableName, indexName, NDE_OPEN_ALWAYS, NDE_NOCACHE);
  220. NDE_Table_NewColumnW(artTable, ARTTABLE_HASH, L"hash", FIELD_INT128);
  221. NDE_Table_NewColumnW(artTable, ARTTABLE_ARGB32, L"art", FIELD_BINARY32);
  222. NDE_Table_PostColumns(artTable);
  223. NDE_Table_AddIndexByIDW(artTable, ARTTABLE_HASH, L"hash");
  224. artcache[size] = artTable;
  225. }
  226. else
  227. return artHashes != 0;
  228. return artcache[size] != 0;
  229. }
  230. static void CloseArtHashes()
  231. {
  232. __try
  233. {
  234. if (artHashes)
  235. {
  236. NDE_Database_CloseTable(art_db, artHashes);
  237. }
  238. artHashes = 0;
  239. NDE_DestroyDatabase(art_db);
  240. art_db = 0;
  241. }
  242. __except (EXCEPTION_EXECUTE_HANDLER)
  243. {
  244. artHashes = 0;
  245. art_db = 0;
  246. }
  247. }
  248. static void CloseArtCache()
  249. {
  250. ArtCache::iterator itr;
  251. for (itr = artcache.begin(); itr != artcache.end(); itr++)
  252. {
  253. if (itr->second)
  254. NDE_Database_CloseTable(art_db, itr->second);
  255. itr->second = 0;
  256. }
  257. artcache.clear();
  258. CloseArtHashes();
  259. }
  260. /*
  261. @param size = art dimensions. e.g. size==120 is for 120x120 album art
  262. @param bits better be allocated to size*size*sizeof(ARGB32) or you're in for a world of hurt
  263. */
  264. static bool GetArtFromCache(const wchar_t *filename, int size, ARGB32 **bits)
  265. {
  266. if (InitArtCache(size))
  267. {
  268. nde_scanner_t hashscanner = NDE_Table_CreateScanner(artHashes);
  269. if (NDE_Scanner_LocateFilename(hashscanner, ARTHASH_FILENAME, FIRST_RECORD, filename))
  270. {
  271. nde_field_t field = NDE_Scanner_GetFieldByID(hashscanner, ARTHASH_HASH);
  272. if (field)
  273. {
  274. nde_scanner_t artscanner = NDE_Table_CreateScanner(artcache[size]);
  275. if (NDE_Scanner_LocateField(artscanner, ARTTABLE_HASH, FIRST_RECORD, field))
  276. {
  277. nde_field_t field = NDE_Scanner_GetFieldByID(artscanner, ARTTABLE_ARGB32);
  278. if (field)
  279. {
  280. size_t len = 0;
  281. void *data = NDE_BinaryField_GetData(field, &len);
  282. if (data && len == size*size*sizeof(ARGB32))
  283. {
  284. *bits = (ARGB32 *)WASABI_API_MEMMGR->sysMalloc(len);
  285. memcpy(*bits, data, len);
  286. NDE_Table_DestroyScanner(artcache[size], artscanner);
  287. NDE_Table_DestroyScanner(artHashes, hashscanner);
  288. return true;
  289. }
  290. }
  291. }
  292. NDE_Table_DestroyScanner(artcache[size], artscanner);
  293. }
  294. }
  295. NDE_Table_DestroyScanner(artHashes, hashscanner);
  296. }
  297. return false;
  298. }
  299. static bool SetArtCache(const wchar_t *filename, int size, const ARGB32 *bits, uint8_t hash[16])
  300. {
  301. if (InitArtCache(size))
  302. {
  303. nde_scanner_t hashscanner = NDE_Table_CreateScanner(artHashes);
  304. if (!NDE_Scanner_LocateFilename(hashscanner, ARTHASH_FILENAME, FIRST_RECORD, filename))
  305. {
  306. NDE_Scanner_New(hashscanner);
  307. db_setFieldStringW(hashscanner, ARTHASH_FILENAME, filename);
  308. }
  309. else
  310. NDE_Scanner_Edit(hashscanner);
  311. nde_field_t field = NDE_Scanner_GetFieldByID(hashscanner, ARTHASH_HASH);
  312. if (!field)
  313. field = NDE_Scanner_NewFieldByID(hashscanner, ARTHASH_HASH);
  314. NDE_Int128Field_SetValue(field, hash);
  315. NDE_Scanner_Post(hashscanner);
  316. nde_scanner_t artscanner = NDE_Table_CreateScanner(artcache[size]);
  317. if (!NDE_Scanner_LocateField(artscanner, ARTTABLE_HASH, FIRST_RECORD, field))
  318. {
  319. NDE_Scanner_New(artscanner);
  320. field = NDE_Scanner_NewFieldByID(artscanner, ARTTABLE_HASH);
  321. NDE_Int128Field_SetValue(field, hash);
  322. field = NDE_Scanner_NewFieldByID(artscanner, ARTTABLE_ARGB32);
  323. // TODO when size is possibly zero, this is causing a crash in nde.dll
  324. if (size > 0) NDE_BinaryField_SetData(field, bits, size*size*sizeof(ARGB32));
  325. NDE_Scanner_Post(artscanner);
  326. NDE_Table_DestroyScanner(artcache[size], artscanner);
  327. NDE_Table_DestroyScanner(artHashes, hashscanner);
  328. NDE_Table_Sync(artHashes);
  329. return true;
  330. }
  331. NDE_Table_DestroyScanner(artcache[size], artscanner);
  332. NDE_Table_DestroyScanner(artHashes, hashscanner);
  333. NDE_Table_Sync(artHashes);
  334. }
  335. return false;
  336. }
  337. size_t maxCache = 65536/*100*/;
  338. void HintCacheSize(int _cachesize)
  339. {
  340. maxCache = _cachesize;
  341. }
  342. void CreateCache(AlbumArtContainer *container, int w, int h)
  343. {
  344. InitArtThread();
  345. assert(artThread);
  346. container->AddRef();
  347. CreateCacheParameters *parameters = (CreateCacheParameters *)WASABI_API_MEMMGR->sysMalloc(sizeof(CreateCacheParameters));
  348. parameters->container = container;
  349. parameters->w = w;
  350. parameters->h = h;
  351. parameters->status = AlbumArtContainer::CACHE_UNKNOWN;
  352. nu::AutoLock lock(queueGuard);
  353. if (artQueue.size() > maxCache)
  354. {
  355. CreateCacheParameters *kill = artQueue.back();
  356. artQueue.pop_back();
  357. kill->cache = 0;
  358. kill->status = AlbumArtContainer::CACHE_UNKNOWN;
  359. QueueUserAPC(CreateCacheCallbackAPC, mainThread, (ULONG_PTR)kill);
  360. }
  361. artQueue.push_front(parameters);
  362. ReleaseSemaphore(artWake, 1, 0);
  363. }
  364. void FlushCache()
  365. {
  366. nu::AutoLock lock(queueGuard);
  367. while (!artQueue.empty())
  368. {
  369. CreateCacheParameters *kill = artQueue.front();
  370. kill->container->SetCache(0, AlbumArtContainer::CACHE_UNKNOWN);
  371. artQueue.pop_front();
  372. WASABI_API_MEMMGR->sysFree(kill);
  373. }
  374. }
  375. void ResumeCache()
  376. {
  377. }
  378. static int ClearFilenameCacheAPC(HANDLE handle, void *param, intptr_t id)
  379. {
  380. wchar_t *filename = (wchar_t *)param;
  381. if (InitArtCache(0))
  382. {
  383. nde_scanner_t hashscanner = NDE_Table_CreateScanner(artHashes);
  384. if (NDE_Scanner_LocateNDEFilename(hashscanner, ARTHASH_FILENAME, FIRST_RECORD, filename))
  385. {
  386. nde_field_t field = NDE_Scanner_GetFieldByID(hashscanner, ARTHASH_HASH);
  387. if (field)
  388. {
  389. nde_scanner_t deleteAllScanner = NDE_Table_CreateScanner(artHashes);
  390. while (NDE_Scanner_LocateField(deleteAllScanner, ARTHASH_HASH, FIRST_RECORD, field))
  391. {
  392. NDE_Scanner_Delete(deleteAllScanner);
  393. NDE_Scanner_Post(deleteAllScanner);
  394. }
  395. NDE_Table_DestroyScanner(artHashes, deleteAllScanner);
  396. NDE_Table_Sync(artHashes);
  397. /* delete it from the art table as well, but we'll just
  398. use the already-opened ones */
  399. for (ArtCache::iterator itr=artcache.begin();itr!=artcache.end();itr++)
  400. {
  401. nde_table_t table = itr->second;
  402. if (table)
  403. {
  404. nde_scanner_t s= NDE_Table_CreateScanner(table);
  405. while (NDE_Scanner_LocateField(s, ARTTABLE_HASH, FIRST_RECORD, field))
  406. {
  407. NDE_Scanner_Delete(s);
  408. NDE_Scanner_Post(s);
  409. }
  410. NDE_Table_DestroyScanner(table, s);
  411. NDE_Table_Sync(table);
  412. }
  413. }
  414. }
  415. }
  416. NDE_Table_DestroyScanner(artHashes, hashscanner);
  417. }
  418. ndestring_release(filename);
  419. return 0;
  420. }
  421. static int DeleteDatabaseAPC(HANDLE handle, void *user_data, intptr_t id)
  422. {
  423. CloseArtCache();
  424. wchar_t search_mask[MAX_PATH] = {0};
  425. StringCchPrintfW(search_mask, MAX_PATH, L"%s\\art_*.*", g_tableDir);
  426. wchar_t fn[MAX_PATH] = {0};
  427. StringCchPrintfW(fn, MAX_PATH, L"%s\\art.idx", g_tableDir);
  428. DeleteFileW(fn);
  429. StringCchPrintfW(fn, MAX_PATH, L"%s\\art.dat", g_tableDir);
  430. DeleteFileW(fn);
  431. WIN32_FIND_DATAW findData;
  432. HANDLE hFind = FindFirstFileW(search_mask, &findData);
  433. if (hFind != INVALID_HANDLE_VALUE)
  434. {
  435. do
  436. {
  437. if (!_wcsnicmp(findData.cFileName, L"art_", 4))
  438. {
  439. StringCchPrintfW(fn, MAX_PATH, L"%s\\%s", g_tableDir, findData.cFileName);
  440. DeleteFileW(fn);
  441. }
  442. }
  443. while (FindNextFileW(hFind, &findData));
  444. FindClose(hFind);
  445. }
  446. return 0;
  447. }
  448. void DumpArtCache()
  449. {
  450. InitArtThread();
  451. assert(artThread);
  452. WASABI_API_THREADPOOL->RunFunction(artThread, DeleteDatabaseAPC, 0, 0, 0);
  453. }
  454. void ClearCache(const wchar_t *filename)
  455. {
  456. InitArtThread();
  457. assert(artThread);
  458. WASABI_API_THREADPOOL->RunFunction(artThread, ClearFilenameCacheAPC, ndestring_wcsdup(filename), 0, 0);
  459. }