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