1 /*
2  * art.c
3  * Copyright 2011-2012 John Lindgren
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  *    this list of conditions, and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  *    this list of conditions, and the following disclaimer in the documentation
13  *    provided with the distribution.
14  *
15  * This software is provided "as is" and without any warranty, express or
16  * implied. In no event shall the authors be liable for any damages arising from
17  * the use of this software.
18  */
19 
20 #include "internal.h"
21 #include "probe.h"
22 
23 #include <assert.h>
24 #include <errno.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 
29 #include <glib/gstdio.h>
30 
31 #include "audstrings.h"
32 #include "hook.h"
33 #include "mainloop.h"
34 #include "multihash.h"
35 #include "runtime.h"
36 #include "scanner.h"
37 #include "threads.h"
38 #include "vfs.h"
39 
40 #define FLAG_DONE 1
41 #define FLAG_SENT 2
42 
43 struct AudArtItem
44 {
45     String filename;
46     int refcount;
47     int flag;
48 
49     /* album art as JPEG or PNG data */
50     Index<char> data;
51 
52     /* album art as (possibly a temporary) file */
53     String art_file;
54     bool is_temp;
55 };
56 
57 static aud::mutex mutex;
58 static SimpleHash<String, AudArtItem> art_items;
59 static AudArtItem * current_item;
60 static QueuedFunc queued_requests;
61 
get_queued()62 static Index<AudArtItem *> get_queued()
63 {
64     auto mh = mutex.take();
65     Index<AudArtItem *> queued;
66 
67     art_items.iterate([&](const String &, AudArtItem & item) {
68         if (item.flag == FLAG_DONE)
69         {
70             queued.append(&item);
71             item.flag = FLAG_SENT;
72         }
73     });
74 
75     queued_requests.stop();
76 
77     return queued;
78 }
79 
send_requests()80 static void send_requests()
81 {
82     auto queued = get_queued();
83     for (AudArtItem * item : queued)
84     {
85         hook_call("art ready", (void *)(const char *)item->filename);
86         aud_art_unref(item); /* release temporary reference */
87     }
88 }
89 
finish_item(aud::mutex::holder &,AudArtItem * item,Index<char> && data,String && art_file)90 static void finish_item(aud::mutex::holder &, AudArtItem * item,
91                         Index<char> && data, String && art_file)
92 {
93     /* already finished? */
94     if (item->flag)
95         return;
96 
97     item->data = std::move(data);
98     item->art_file = std::move(art_file);
99     item->flag = FLAG_DONE;
100 
101     queued_requests.queue(send_requests);
102 }
103 
request_callback(ScanRequest * request)104 static void request_callback(ScanRequest * request)
105 {
106     auto mh = mutex.take();
107     AudArtItem * item = art_items.lookup(request->filename);
108 
109     if (item)
110         finish_item(mh, item, std::move(request->image_data),
111                     std::move(request->image_file));
112 }
113 
art_item_get(aud::mutex::holder &,const String & filename,bool * queued)114 static AudArtItem * art_item_get(aud::mutex::holder &, const String & filename,
115                                  bool * queued)
116 {
117     if (queued)
118         *queued = false;
119 
120     // blacklist stdin
121     if (!strncmp(filename, "stdin://", 8))
122         return nullptr;
123 
124     AudArtItem * item = art_items.lookup(filename);
125 
126     if (item && item->flag)
127     {
128         item->refcount++;
129         return item;
130     }
131 
132     if (!item)
133     {
134         item = art_items.add(filename, AudArtItem());
135         item->filename = filename;
136         item->refcount = 1; /* temporary reference */
137 
138         scanner_request(
139             new ScanRequest(filename, SCAN_IMAGE, request_callback));
140     }
141 
142     if (queued)
143         *queued = true;
144 
145     return nullptr;
146 }
147 
art_item_unref(aud::mutex::holder &,AudArtItem * item)148 static void art_item_unref(aud::mutex::holder &, AudArtItem * item)
149 {
150     if (!--item->refcount)
151     {
152         /* delete temporary file */
153         if (item->art_file && item->is_temp)
154         {
155             StringBuf local = uri_to_filename(item->art_file);
156             if (local)
157                 g_unlink(local);
158         }
159 
160         art_items.remove(item->filename);
161     }
162 }
163 
clear_current(aud::mutex::holder & mh)164 static void clear_current(aud::mutex::holder & mh)
165 {
166     if (current_item)
167     {
168         art_item_unref(mh, current_item);
169         current_item = nullptr;
170     }
171 }
172 
art_cache_current(const String & filename,Index<char> && data,String && art_file)173 void art_cache_current(const String & filename, Index<char> && data,
174                        String && art_file)
175 {
176     auto mh = mutex.take();
177     clear_current(mh);
178 
179     AudArtItem * item = art_items.lookup(filename);
180 
181     if (!item)
182     {
183         item = art_items.add(filename, AudArtItem());
184         item->filename = filename;
185         item->refcount = 1; /* temporary reference */
186     }
187 
188     finish_item(mh, item, std::move(data), std::move(art_file));
189 
190     item->refcount++;
191     current_item = item;
192 }
193 
art_clear_current()194 void art_clear_current()
195 {
196     auto mh = mutex.take();
197     clear_current(mh);
198 }
199 
art_cleanup()200 void art_cleanup()
201 {
202     auto queued = get_queued();
203     for (AudArtItem * item : queued)
204         aud_art_unref(item); /* release temporary reference */
205 
206     /* playback should already be stopped */
207     assert(!current_item);
208 
209     if (art_items.n_items())
210         AUDWARN("Album art reference count not zero at exit!\n");
211 }
212 
aud_art_request(const char * file,int format,bool * queued)213 EXPORT AudArtPtr aud_art_request(const char * file, int format, bool * queued)
214 {
215     auto mh = mutex.take();
216     AudArtItem * item = art_item_get(mh, String(file), queued);
217 
218     if (!item)
219         return AudArtPtr();
220 
221     if (format & AUD_ART_DATA)
222     {
223         /* load data from external image file */
224         if (!item->data.len() && item->art_file)
225         {
226             VFSFile file(item->art_file, "r");
227             if (file)
228                 item->data = file.read_all();
229         }
230 
231         if (!item->data.len())
232         {
233             art_item_unref(mh, item);
234             return AudArtPtr();
235         }
236     }
237 
238     if (format & AUD_ART_FILE)
239     {
240         /* save data to temporary file */
241         if (item->data.len() && !item->art_file)
242         {
243             String local =
244                 write_temp_file(item->data.begin(), item->data.len());
245             if (local)
246             {
247                 item->art_file = String(filename_to_uri(local));
248                 item->is_temp = true;
249             }
250         }
251 
252         if (!item->art_file)
253         {
254             art_item_unref(mh, item);
255             return AudArtPtr();
256         }
257     }
258 
259     return AudArtPtr(item);
260 }
261 
aud_art_data(const AudArtItem * item)262 EXPORT const Index<char> * aud_art_data(const AudArtItem * item)
263 {
264     return &item->data;
265 }
aud_art_file(const AudArtItem * item)266 EXPORT const char * aud_art_file(const AudArtItem * item)
267 {
268     return item->art_file;
269 }
270 
aud_art_unref(AudArtItem * item)271 EXPORT void aud_art_unref(AudArtItem * item)
272 {
273     auto mh = mutex.take();
274     art_item_unref(mh, item);
275 }
276