1 /*
2 DeaDBeeF -- the music player
3 Copyright (C) 2009-2015 Alexey Yakovenko and other contributors
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17
18 2. Altered source versions must be plainly marked as such, and must not be
19 misrepresented as being the original software.
20
21 3. This notice may not be removed or altered from any source distribution.
22 */
23
24 #include <sys/time.h>
25 #include <string.h>
26 #include <stdlib.h>
27 #include <pthread.h>
28 #include <unistd.h>
29 #include <sys/stat.h>
30 #ifdef __linux__
31 #include <sys/prctl.h>
32 #endif
33 #include "../artwork/artwork.h"
34 #include "gtkui.h"
35
36 //#define trace(...) { fprintf(stderr, __VA_ARGS__); }
37 #define trace(...)
38
39 static DB_artwork_plugin_t *artwork_plugin;
40
41 typedef struct {
42 struct timeval tm;
43 time_t file_time;
44 char *fname;
45 int width;
46 int height;
47 GdkPixbuf *pixbuf;
48 } cached_pixbuf_t;
49
50 typedef enum {
51 CACHE_TYPE_PRIMARY = 0,
52 CACHE_TYPE_THUMB
53 } cache_type_t;
54
55 #define PRIMARY_CACHE_SIZE 1
56 static size_t thumb_cache_size;
57 static cached_pixbuf_t primary_cache[PRIMARY_CACHE_SIZE];
58 static cached_pixbuf_t *thumb_cache;
59 static GdkPixbuf *pixbuf_default;
60 static size_t thrash_count;
61
62 typedef struct cover_callback_s {
63 void (*cb)(void *ud);
64 void *ud;
65 struct cover_callback_s *next;
66 } cover_callback_t;
67
68 typedef struct load_query_s {
69 cache_type_t cache_type;
70 char *fname;
71 int width;
72 int height;
73 cover_callback_t *callback;
74 struct load_query_s *next;
75 } load_query_t;
76
77 static int terminate;
78 static uintptr_t mutex;
79 static uintptr_t cond;
80 static uintptr_t tid;
81 static load_query_t *queue;
82 static load_query_t *tail;
83
84 typedef struct {
85 cache_type_t cache_type;
86 char *cache_path;
87 int width;
88 int height;
89 void (*callback)(void *user_data);
90 void *user_data;
91 } cover_avail_info_t;
92
93 GdkPixbuf *
cover_get_default_pixbuf(void)94 cover_get_default_pixbuf(void)
95 {
96 if (!artwork_plugin) {
97 return NULL;
98 }
99
100 /* get_default_cover=NULL means it was reset and we call again to get the new value */
101 if (!artwork_plugin->get_default_cover() && pixbuf_default) {
102 g_object_unref(pixbuf_default);
103 pixbuf_default = NULL;
104 }
105
106 /* Load the default cover image into a pixbuf */
107 if (!pixbuf_default) {
108 const char *defpath = artwork_plugin->get_default_cover();
109 if (defpath && defpath[0]) {
110 pixbuf_default = gdk_pixbuf_new_from_file(defpath, NULL);
111 #if 0
112 GError *error = NULL;
113 pixbuf_default = gdk_pixbuf_new_from_file(defpath, &error);
114 if (!pixbuf_default) {
115 fprintf (stderr, "default cover: gdk_pixbuf_new_from_file %s failed, error: %s\n", defpath, error->message);
116 }
117 if (error) {
118 g_error_free (error);
119 error = NULL;
120 }
121 #endif
122 }
123
124 /* If we have a blank path or an error, just create a transparent pixbuf */
125 if (!pixbuf_default) {
126 pixbuf_default = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 2, 2);
127 gdk_pixbuf_fill(pixbuf_default, 0x00000000);
128 }
129 }
130
131 g_object_ref(pixbuf_default);
132 return pixbuf_default;
133 }
134
135 int
gtkui_is_default_pixbuf(GdkPixbuf * pb)136 gtkui_is_default_pixbuf (GdkPixbuf *pb) {
137 return pb == pixbuf_default;
138 }
139
140 static cover_callback_t *
add_callback(void (* cb)(void *),void * ud)141 add_callback(void (*cb)(void *), void *ud)
142 {
143 if (!cb) {
144 return NULL;
145 }
146
147 cover_callback_t *callback = malloc(sizeof(cover_callback_t));
148 if (!callback) {
149 trace("coverart: callback alloc failed\n");
150 return NULL;
151 }
152
153 callback->cb = cb;
154 callback->ud = ud;
155 callback->next = NULL;
156 return callback;
157 }
158
159 static void
queue_add(cache_type_t cache_type,char * fname,const int width,const int height,void (* cb)(void *),void * ud)160 queue_add (cache_type_t cache_type, char *fname, const int width, const int height, void (*cb)(void *), void *ud)
161 {
162 trace("coverart: queue_add %s @ %ix%i pixels\n", fname, width, height);
163 load_query_t *q = malloc(sizeof(load_query_t));
164 if (!q) {
165 free(fname);
166 return;
167 }
168
169 q->cache_type = cache_type;
170 q->fname = fname;
171 q->width = width;
172 q->height = height;
173 q->callback = add_callback(cb, ud);
174 q->next = NULL;
175
176 if (tail) {
177 tail->next = q;
178 tail = q;
179 }
180 else {
181 queue = tail = q;
182 }
183 deadbeef->cond_signal (cond);
184 }
185
186 static void
queue_add_load(cache_type_t cache_type,char * fname,const int width,const int height,void (* cb)(void *),void * ud)187 queue_add_load (cache_type_t cache_type, char *fname, const int width, const int height, void (*cb)(void *), void *ud)
188 {
189 for (load_query_t *q = queue; q; q = q->next) {
190 if (q->fname && !strcmp (q->fname, fname) && width == q->width && height == q->height) {
191 trace("coverart: %s already in queue, add to callbacks\n", fname);
192 cover_callback_t **last_callback = &q->callback;
193 while (*last_callback) {
194 last_callback = &(*last_callback)->next;
195 }
196 *last_callback = add_callback(cb, ud);
197 free(fname);
198 return;
199 }
200 }
201
202 queue_add(cache_type, fname, width, height, cb, ud);
203 }
204
205 static load_query_t *
queue_remove(load_query_t * q)206 queue_remove(load_query_t *q)
207 {
208 load_query_t *next = q->next;
209 if (q->fname) {
210 free(q->fname);
211 }
212 free(q);
213 return next;
214 }
215
216 static void
queue_pop(void)217 queue_pop (void)
218 {
219 queue = queue_remove(queue);
220 if (!queue) {
221 tail = NULL;
222 }
223 }
224
225 static void
send_query_callbacks(cover_callback_t * callback)226 send_query_callbacks(cover_callback_t *callback)
227 {
228 if (callback) {
229 trace("coverart: make callback to %p (next=%p)\n", callback->cb, callback->next);
230 callback->cb(callback->ud);
231 send_query_callbacks(callback->next);
232 free(callback);
233 }
234 }
235
236 static cached_pixbuf_t *
cache_location(cache_type_t cache_type)237 cache_location(cache_type_t cache_type)
238 {
239 return cache_type == CACHE_TYPE_PRIMARY ? primary_cache : thumb_cache;
240 }
241
242 static size_t
cache_elements(cache_type_t cache_type)243 cache_elements(cache_type_t cache_type)
244 {
245 return cache_type == CACHE_TYPE_PRIMARY ? PRIMARY_CACHE_SIZE : thumb_cache_size;
246 }
247
248 static int
cache_qsort(const void * a,const void * b)249 cache_qsort(const void *a, const void *b)
250 {
251 const cached_pixbuf_t *x = (cached_pixbuf_t *)a;
252 const cached_pixbuf_t *y = (cached_pixbuf_t *)b;
253 if (x->pixbuf && y->pixbuf) {
254 const int cmp = strcmp(x->fname, y->fname);
255 if (cmp) {
256 return cmp;
257 }
258
259 if (y->width != x->width) {
260 return y->width - x->width;
261 }
262
263 return y->height - x->height;
264 }
265
266 return x->pixbuf ? -1 : y->pixbuf ? 1 : 0;
267 }
268
269 static int
timeval_older(const struct timeval * tm1,const struct timeval * tm2)270 timeval_older(const struct timeval *tm1, const struct timeval *tm2)
271 {
272 return tm1->tv_sec < tm2->tv_sec || tm1->tv_sec == tm2->tv_sec && tm1->tv_usec < tm2->tv_usec;
273 }
274
275 static void
evict_pixbuf(cached_pixbuf_t * cached)276 evict_pixbuf(cached_pixbuf_t *cached)
277 {
278 trace("covercache: evict %s\n", cached->fname);
279 g_object_unref(cached->pixbuf);
280 cached->pixbuf = NULL;
281 free(cached->fname);
282 }
283
284 static cached_pixbuf_t *
oldest_slot(cached_pixbuf_t * cache,const size_t cache_size)285 oldest_slot(cached_pixbuf_t *cache, const size_t cache_size)
286 {
287 cached_pixbuf_t *oldest_slot = cache;
288 for (size_t i = 1; i < cache_size; i++) {
289 if (timeval_older(&cache[i].tm, &oldest_slot->tm)) {
290 oldest_slot = cache + i;
291 }
292 }
293 return oldest_slot;
294 }
295
296 static int
adjust_cache(struct timeval * oldest)297 adjust_cache(struct timeval *oldest)
298 {
299 /* A possible thrash is when the oldest pixbuf is still fresh */
300 struct timeval now;
301 gettimeofday(&now, NULL);
302 now.tv_sec -= 10 + thumb_cache_size / 10;
303 thrash_count = timeval_older(&now, oldest) ? thrash_count+1 : 0;
304
305 /* If the whole cache has been thrashed through then we need more space */
306 if (thrash_count > thumb_cache_size) {
307 cached_pixbuf_t *new_thumb_cache = realloc(thumb_cache, sizeof(cached_pixbuf_t) * thumb_cache_size * 2);
308 if (new_thumb_cache) {
309 memset(&new_thumb_cache[thumb_cache_size], '\0', sizeof(cached_pixbuf_t) * thumb_cache_size);
310 thumb_cache_size *= 2;
311 thumb_cache = new_thumb_cache;
312 trace("coverart: pixbuf cache size increased to %d\n", (int)thumb_cache_size);
313 return 1;
314 }
315 }
316
317 return 0;
318 }
319
320 static void
cache_add(cache_type_t cache_type,GdkPixbuf * pixbuf,char * fname,const time_t file_time,const int width,const int height)321 cache_add(cache_type_t cache_type, GdkPixbuf *pixbuf, char *fname, const time_t file_time, const int width, const int height)
322 {
323 cached_pixbuf_t *cache = cache_location(cache_type);
324 size_t cache_size = cache_elements(cache_type);
325 cached_pixbuf_t *cache_slot = &cache[cache_size-1];
326
327 /* If the last slot is filled then evict the oldest entry */
328 if (cache_slot->pixbuf) {
329 if (cache_type == CACHE_TYPE_THUMB) {
330 cache_slot = oldest_slot(cache, cache_size);
331 if (adjust_cache(&cache_slot->tm)) {
332 cache = cache_location(cache_type);
333 cache_slot = &cache[cache_size];
334 cache_size = cache_elements(cache_type);
335 }
336 }
337 if (cache_slot->pixbuf) {
338 evict_pixbuf(cache_slot);
339 }
340 }
341
342 /* Set the pixbuf in the cache slot */
343 cache_slot->pixbuf = pixbuf;
344 cache_slot->fname = fname;
345 cache_slot->file_time = file_time;
346 gettimeofday(&cache_slot->tm, NULL);
347 cache_slot->width = width;
348 cache_slot->height = height;
349
350 /* Sort by fname, largest first, with empty slots at the end */
351 qsort(cache, cache_size, sizeof(cached_pixbuf_t), cache_qsort);
352 }
353
354 static void
load_image(load_query_t * query)355 load_image(load_query_t *query)
356 {
357 deadbeef->mutex_unlock(mutex);
358 struct stat stat_buf;
359 if (stat(query->fname, &stat_buf)) {
360 deadbeef->mutex_lock(mutex);
361 return;
362 }
363
364 /* Create a new pixbuf from this file */
365 int width = query->width;
366 int height = query->height;
367 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_size(query->fname, width, height, NULL);
368 #if 0
369 GError *error = NULL;
370 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_size(query->fname, width, height, &error);
371 if (error) {
372 fprintf (stderr, "gdk_pixbuf_new_from_file_at_size %s %d failed, error: %s\n", query->fname, width, error ? error->message : "n/a");
373 g_error_free(error);
374 }
375 #endif
376 if (!pixbuf) {
377 trace("covercache: unable to create pixbuf from cached image file %s, use default\n", query->fname);
378 pixbuf = cover_get_default_pixbuf();
379 width = -1;
380 height = -1;
381 }
382 trace("covercache: loaded pixbuf from %s\n", query->fname);
383
384 /* Cache the pixbuf */
385 deadbeef->mutex_lock(mutex);
386 cache_add(query->cache_type, pixbuf, query->fname, stat_buf.st_mtime, width, height);
387 query->fname = NULL;
388 }
389
390 static void
loading_thread(void * none)391 loading_thread (void *none) {
392 #ifdef __linux__
393 prctl (PR_SET_NAME, "deadbeef-gtkui-artwork", 0, 0, 0, 0);
394 #endif
395
396 deadbeef->mutex_lock(mutex);
397
398 while (!terminate) {
399 trace("covercache: waiting for signal...\n");
400 pthread_cond_wait((pthread_cond_t *)cond, (pthread_mutex_t *)mutex);
401 trace("covercache: signal received (terminate=%d, queue=%p)\n", terminate, queue);
402
403 while (!terminate && queue) {
404 if (queue->fname) {
405 load_image(queue);
406 }
407 if (artwork_plugin) {
408 send_query_callbacks(queue->callback);
409 }
410 queue_pop();
411 }
412 }
413
414 deadbeef->mutex_unlock(mutex);
415 }
416
417 static GdkPixbuf *
get_pixbuf(cache_type_t cache_type,const char * fname,const int width,const int height)418 get_pixbuf (cache_type_t cache_type, const char *fname, const int width, const int height) {
419 /* Look in the pixbuf cache */
420 cached_pixbuf_t *cache = cache_location(cache_type);
421 const size_t cache_size = cache_elements(cache_type);
422 for (size_t i = 0; i < cache_size && cache[i].pixbuf; i++) {
423 /* Look for a cached pixbuf that matches the filename and size required */
424 if (!strcmp(cache[i].fname, fname) && (cache[i].width == -1 || cache[i].width == width && cache[i].height == height)) {
425 struct stat stat_buf;
426 /* Keep the pixbuf for now if the disk file is missing */
427 if (stat(fname, &stat_buf) || stat_buf.st_mtime == cache[i].file_time) {
428 gettimeofday(&cache[i].tm, NULL);
429 return cache[i].pixbuf;
430 }
431 /* Discard the poxbuf if the disk file modification time doesn't match */
432 evict_pixbuf(cache+i);
433 qsort(cache, cache_size, sizeof(cached_pixbuf_t), cache_qsort);
434 }
435 }
436 return NULL;
437 }
438
439 void
queue_cover_callback(void (* callback)(void * user_data),void * user_data)440 queue_cover_callback (void (*callback)(void *user_data), void *user_data) {
441 if (artwork_plugin && callback) {
442 deadbeef->mutex_lock (mutex);
443 queue_add(-1, NULL, -1, -1, callback, user_data);
444 deadbeef->mutex_unlock (mutex);
445 }
446 }
447
448 static void
cover_avail_callback(const char * fname,const char * artist,const char * album,void * user_data)449 cover_avail_callback(const char *fname, const char *artist, const char *album, void *user_data)
450 {
451 if (!user_data) {
452 return;
453 }
454
455 cover_avail_info_t *dt = user_data;
456
457 deadbeef->mutex_lock(mutex);
458 if (fname) {
459 /* An image file is in the disk cache, load it to the pixbuf cache */
460 trace("cover_avail_callback: add to queue %s, %s\n", fname, dt->cache_path);
461 queue_add_load(dt->cache_type, dt->cache_path, dt->width, dt->height, dt->callback, dt->user_data);
462 }
463 else if (get_pixbuf(dt->cache_type, dt->cache_path, dt->width, dt->height)) {
464 /* Pixbuf (usually the default) already cached */
465 trace("cover_avail_callback: pixbuf already in cache, do nothing for %s\n", dt->cache_path);
466 free(dt->cache_path);
467 }
468 else {
469 /* Put the default pixbuf in the cache because no artwork was found */
470 struct stat stat_buf;
471 if (!stat(dt->cache_path, &stat_buf)) {
472 trace("cover_avail_callback: cache default pixbuf for %s\n", dt->cache_path);
473 cache_add(dt->cache_type, cover_get_default_pixbuf(), dt->cache_path, stat_buf.st_mtime, -1, -1);
474 }
475 else {
476 /* Image file unexpectedly missing or not empty (unlucky timing on cache expiry, reset, etc) */
477 trace("cover_avail_callback: file gone, do nothing for %s\n", dt->cache_path);
478 free(dt->cache_path);
479 }
480 if (dt->callback) {
481 dt->callback(dt->user_data);
482 }
483 }
484 deadbeef->mutex_unlock(mutex);
485
486 free(user_data);
487 }
488
489 static GdkPixbuf *
best_cached_pixbuf(cache_type_t cache_type,const char * path)490 best_cached_pixbuf(cache_type_t cache_type, const char *path)
491 {
492 /* Find the largest pixbuf in the cache for this file */
493 cached_pixbuf_t *cache = cache_location(cache_type);
494 const size_t cache_size = cache_elements(cache_type);
495 for (size_t i = 0; i < cache_size && cache[i].pixbuf; i++) {
496 if (!strcmp(cache[i].fname, path)) {
497 g_object_ref(cache[i].pixbuf);
498 return cache[i].pixbuf;
499 }
500 }
501
502 return NULL;
503 }
504
505 static cover_avail_info_t *
cover_avail_info(cache_type_t cache_type,char * cache_path,const int width,const int height,void (* callback)(void *),void * user_data)506 cover_avail_info(cache_type_t cache_type, char *cache_path, const int width, const int height, void (*callback)(void *), void *user_data)
507 {
508 if (cache_path) {
509 cover_avail_info_t *dt = malloc(sizeof(cover_avail_info_t));
510 if (dt) {
511 dt->cache_type = cache_type;
512 dt->cache_path = cache_path;
513 dt->width = width;
514 dt->height = height;
515 dt->callback = callback;
516 dt->user_data = user_data;
517 return dt;
518 }
519 }
520
521 if (callback) {
522 callback(user_data);
523 }
524 return NULL;
525 }
526
527 static GdkPixbuf *
get_cover_art_int(cache_type_t cache_type,const char * fname,const char * artist,const char * album,int width,int height,void (* callback)(void *),void * user_data)528 get_cover_art_int(cache_type_t cache_type, const char *fname, const char *artist, const char *album, int width, int height, void (*callback)(void *), void *user_data)
529 {
530 if (!artwork_plugin) {
531 return NULL;
532 }
533
534 char cache_path[PATH_MAX];
535 artwork_plugin->make_cache_path2(cache_path, PATH_MAX, fname, album, artist, -1);
536
537 if (width == -1) {
538 /* Find the largest cached pixmap for this file */
539 trace("coverart: get largest pixbuf matching %s\n", cache_path);
540 deadbeef->mutex_lock(mutex);
541 GdkPixbuf *best_pixbuf = best_cached_pixbuf(cache_type, cache_path);
542 deadbeef->mutex_unlock(mutex);
543 return best_pixbuf;
544 }
545
546 /* Get a pixbuf of an exact size (or the default pixbuf) */
547 trace("coverart: get_album_art for %s %s %s %ix%i\n", fname, artist, album, width, height);
548 cover_avail_info_t *dt = cover_avail_info(cache_type, strdup(cache_path), width, height, callback, user_data);
549 char *image_fname = artwork_plugin->get_album_art(fname, artist, album, -1, cover_avail_callback, dt);
550 if (image_fname) {
551 /* There will be no callback */
552 free(dt->cache_path);
553 free(dt);
554 }
555
556 deadbeef->mutex_lock(mutex);
557 GdkPixbuf *pb = get_pixbuf(cache_type, cache_path, width, height);
558 if (pb) {
559 /* We already have the proper pixbuf in memory */
560 g_object_ref(pb);
561 if (image_fname) {
562 free(image_fname);
563 }
564 }
565 else if (image_fname) {
566 /* Got a cached file, need to load a pixbuf into memory */
567 queue_add_load(cache_type, image_fname, width, height, callback, user_data);
568 }
569 deadbeef->mutex_unlock(mutex);
570 return pb;
571 }
572
573 // Deprecated
574 GdkPixbuf *
get_cover_art_callb(const char * fname,const char * artist,const char * album,int width,void (* callback)(void *),void * user_data)575 get_cover_art_callb (const char *fname, const char *artist, const char *album, int width, void (*callback) (void *), void *user_data)
576 {
577 return get_cover_art_int(CACHE_TYPE_THUMB, fname, artist, album, width, -1, callback, user_data);
578 }
579
580 GdkPixbuf *
get_cover_art_primary(const char * fname,const char * artist,const char * album,int width,void (* callback)(void *),void * user_data)581 get_cover_art_primary (const char *fname, const char *artist, const char *album, int width, void (*callback) (void *), void *user_data)
582 {
583 return get_cover_art_int(CACHE_TYPE_PRIMARY, fname, artist, album, width, -1, callback, user_data);
584 }
585
586 GdkPixbuf *
get_cover_art_primary_by_size(const char * fname,const char * artist,const char * album,int width,int height,void (* callback)(void *),void * user_data)587 get_cover_art_primary_by_size (const char *fname, const char *artist, const char *album, int width, int height, void (*callback) (void *), void *user_data)
588 {
589 return get_cover_art_int(CACHE_TYPE_PRIMARY, fname, artist, album, width, height, callback, user_data);
590 }
591
592 GdkPixbuf *
get_cover_art_thumb(const char * fname,const char * artist,const char * album,int width,void (* callback)(void *),void * user_data)593 get_cover_art_thumb (const char *fname, const char *artist, const char *album, int width, void (*callback) (void *), void *user_data)
594 {
595 return get_cover_art_int(CACHE_TYPE_THUMB, fname, artist, album, width, -1, callback, user_data);
596 }
597
598 void
coverart_reset_queue(void)599 coverart_reset_queue (void) {
600 if (!artwork_plugin) {
601 return;
602 }
603 trace("coverart: reset queue\n");
604 deadbeef->mutex_lock (mutex);
605 if (queue) {
606 load_query_t *q = queue->next;
607 while (q) {
608 q = queue_remove(q);
609 }
610 queue->next = NULL;
611 tail = queue;
612 }
613 thrash_count /= 2;
614 deadbeef->mutex_unlock (mutex);
615
616 if (artwork_plugin) {
617 artwork_plugin->reset (1);
618 }
619 }
620
621 void
cover_art_init(void)622 cover_art_init (void) {
623 const DB_plugin_t *plugin = deadbeef->plug_get_for_id("artwork");
624 if (plugin && PLUG_TEST_COMPAT(plugin, 1, DDB_ARTWORK_VERSION)) {
625 artwork_plugin = (DB_artwork_plugin_t *)plugin;
626 }
627 if (!artwork_plugin) {
628 return;
629 }
630 thumb_cache_size = 2;
631 thumb_cache = calloc(2, sizeof(cached_pixbuf_t));
632 if (!thumb_cache) {
633 return;
634 }
635
636 terminate = 0;
637 mutex = deadbeef->mutex_create_nonrecursive ();
638 cond = deadbeef->cond_create ();
639 if (mutex && cond) {
640 tid = deadbeef->thread_start_low_priority (loading_thread, NULL);
641 }
642 }
643
644 void
cover_art_disconnect(void)645 cover_art_disconnect (void) {
646 if (artwork_plugin) {
647 const DB_artwork_plugin_t *plugin = artwork_plugin;
648 artwork_plugin = NULL;
649 trace("coverart: resetting artwork plugin...\n");
650 plugin->reset(0);
651 }
652 }
653
654 static void
clear_pixbuf_cache(cached_pixbuf_t * cache,const size_t cache_size)655 clear_pixbuf_cache(cached_pixbuf_t *cache, const size_t cache_size)
656 {
657 for (size_t i = 0; i < cache_size && cache[i].pixbuf; i++) {
658 evict_pixbuf(cache+i);
659 }
660 }
661
662 void
cover_art_free(void)663 cover_art_free (void) {
664 trace ("coverart: terminating cover art loader...\n");
665
666 if (tid) {
667 deadbeef->mutex_lock(mutex);
668 terminate = 1;
669 trace("coverart: sending terminate signal to art loader thread...\n");
670 deadbeef->cond_signal(cond);
671 deadbeef->mutex_unlock(mutex);
672 deadbeef->thread_join(tid);
673 tid = 0;
674 }
675
676 while (queue) {
677 queue = queue_remove(queue);
678 }
679 tail = NULL;
680
681 if (cond) {
682 deadbeef->cond_free(cond);
683 cond = 0;
684 }
685 if (mutex) {
686 deadbeef->mutex_free(mutex);
687 mutex = 0;
688 }
689
690 clear_pixbuf_cache(primary_cache, PRIMARY_CACHE_SIZE);
691 clear_pixbuf_cache(thumb_cache, thumb_cache_size);
692 free(thumb_cache);
693 thumb_cache_size = 0;
694
695 if (pixbuf_default) {
696 g_object_unref(pixbuf_default);
697 pixbuf_default = NULL;
698 }
699
700 trace("coverart: objects all freed\n");
701 }
702