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