]> code.delx.au - pulseaudio/blob - src/polypcore/core-scache.c
Cleaned up the includes after the restructuring. Indicate which headers are
[pulseaudio] / src / polypcore / core-scache.c
1 /* $Id$ */
2
3 /***
4 This file is part of polypaudio.
5
6 polypaudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published
8 by the Free Software Foundation; either version 2 of the License,
9 or (at your option) any later version.
10
11 polypaudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with polypaudio; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19 USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <assert.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <stdio.h>
30 #include <sys/types.h>
31 #include <dirent.h>
32 #include <sys/stat.h>
33 #include <errno.h>
34 #include <limits.h>
35
36 #ifdef HAVE_GLOB_H
37 #include <glob.h>
38 #endif
39
40 #ifdef HAVE_WINDOWS_H
41 #include <windows.h>
42 #endif
43
44 #include <polyp/mainloop.h>
45 #include <polyp/channelmap.h>
46 #include <polyp/volume.h>
47 #include <polypcore/sink-input.h>
48 #include <polypcore/sample-util.h>
49 #include <polypcore/play-memchunk.h>
50 #include <polypcore/xmalloc.h>
51 #include <polypcore/core-subscribe.h>
52 #include <polypcore/namereg.h>
53 #include <polypcore/sound-file.h>
54 #include <polypcore/util.h>
55 #include <polypcore/log.h>
56
57 #include "core-scache.h"
58
59 #define UNLOAD_POLL_TIME 2
60
61 static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) {
62 pa_core *c = userdata;
63 struct timeval ntv;
64 assert(c && c->mainloop == m && c->scache_auto_unload_event == e);
65
66 pa_scache_unload_unused(c);
67
68 pa_gettimeofday(&ntv);
69 ntv.tv_sec += UNLOAD_POLL_TIME;
70 m->time_restart(e, &ntv);
71 }
72
73 static void free_entry(pa_scache_entry *e) {
74 assert(e);
75 pa_namereg_unregister(e->core, e->name);
76 pa_subscription_post(e->core, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_REMOVE, e->index);
77 pa_xfree(e->name);
78 pa_xfree(e->filename);
79 if (e->memchunk.memblock)
80 pa_memblock_unref(e->memchunk.memblock);
81 pa_xfree(e);
82 }
83
84 static pa_scache_entry* scache_add_item(pa_core *c, const char *name) {
85 pa_scache_entry *e;
86 assert(c && name);
87
88 if ((e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE, 0))) {
89 if (e->memchunk.memblock)
90 pa_memblock_unref(e->memchunk.memblock);
91
92 pa_xfree(e->filename);
93
94 assert(e->core == c);
95
96 pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index);
97 } else {
98 e = pa_xmalloc(sizeof(pa_scache_entry));
99
100 if (!pa_namereg_register(c, name, PA_NAMEREG_SAMPLE, e, 1)) {
101 pa_xfree(e);
102 return NULL;
103 }
104
105 e->name = pa_xstrdup(name);
106 e->core = c;
107
108 if (!c->scache) {
109 c->scache = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
110 assert(c->scache);
111 }
112
113 pa_idxset_put(c->scache, e, &e->index);
114
115 pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_NEW, e->index);
116 }
117
118 pa_cvolume_reset(&e->volume, PA_CHANNELS_MAX);
119 e->last_used_time = 0;
120 e->memchunk.memblock = NULL;
121 e->memchunk.index = e->memchunk.length = 0;
122 e->filename = NULL;
123 e->lazy = 0;
124 e->last_used_time = 0;
125
126 memset(&e->sample_spec, 0, sizeof(pa_sample_spec));
127
128 return e;
129 }
130
131 int pa_scache_add_item(pa_core *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map, const pa_memchunk *chunk, uint32_t *idx) {
132 pa_scache_entry *e;
133 assert(c && name);
134
135 if (!(e = scache_add_item(c, name)))
136 return -1;
137
138 if (ss) {
139 e->sample_spec = *ss;
140 pa_channel_map_init_auto(&e->channel_map, ss->channels);
141 }
142
143 if (map)
144 e->channel_map = *map;
145
146 if (chunk) {
147 e->memchunk = *chunk;
148 pa_memblock_ref(e->memchunk.memblock);
149 }
150
151 if (idx)
152 *idx = e->index;
153
154 return 0;
155 }
156
157 int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint32_t *idx) {
158 pa_sample_spec ss;
159 pa_memchunk chunk;
160 int r;
161
162 #ifdef OS_IS_WIN32
163 char buf[MAX_PATH];
164
165 if (ExpandEnvironmentStrings(filename, buf, MAX_PATH))
166 filename = buf;
167 #endif
168
169 if (pa_sound_file_load(filename, &ss, &chunk, c->memblock_stat) < 0)
170 return -1;
171
172 r = pa_scache_add_item(c, name, &ss, NULL, &chunk, idx);
173 pa_memblock_unref(chunk.memblock);
174
175 return r;
176 }
177
178 int pa_scache_add_file_lazy(pa_core *c, const char *name, const char *filename, uint32_t *idx) {
179 pa_scache_entry *e;
180
181 #ifdef OS_IS_WIN32
182 char buf[MAX_PATH];
183
184 if (ExpandEnvironmentStrings(filename, buf, MAX_PATH))
185 filename = buf;
186 #endif
187
188 assert(c && name);
189
190 if (!(e = scache_add_item(c, name)))
191 return -1;
192
193 e->lazy = 1;
194 e->filename = pa_xstrdup(filename);
195
196 if (!c->scache_auto_unload_event) {
197 struct timeval ntv;
198 pa_gettimeofday(&ntv);
199 ntv.tv_sec += UNLOAD_POLL_TIME;
200 c->scache_auto_unload_event = c->mainloop->time_new(c->mainloop, &ntv, timeout_callback, c);
201 }
202
203 if (idx)
204 *idx = e->index;
205
206 return 0;
207 }
208
209 int pa_scache_remove_item(pa_core *c, const char *name) {
210 pa_scache_entry *e;
211 assert(c && name);
212
213 if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE, 0)))
214 return -1;
215
216 if (pa_idxset_remove_by_data(c->scache, e, NULL) != e)
217 assert(0);
218
219 free_entry(e);
220 return 0;
221 }
222
223 static void free_cb(void *p, PA_GCC_UNUSED void *userdata) {
224 pa_scache_entry *e = p;
225 assert(e);
226 free_entry(e);
227 }
228
229 void pa_scache_free(pa_core *c) {
230 assert(c);
231
232 if (c->scache) {
233 pa_idxset_free(c->scache, free_cb, NULL);
234 c->scache = NULL;
235 }
236
237 if (c->scache_auto_unload_event)
238 c->mainloop->time_free(c->scache_auto_unload_event);
239 }
240
241 int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, const pa_cvolume *volume) {
242 pa_scache_entry *e;
243 char *t;
244 pa_cvolume r;
245 assert(c && name && sink);
246
247 if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE, 1)))
248 return -1;
249
250 if (e->lazy && !e->memchunk.memblock) {
251 if (pa_sound_file_load(e->filename, &e->sample_spec, &e->memchunk, c->memblock_stat) < 0)
252 return -1;
253
254 pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index);
255 }
256
257 if (!e->memchunk.memblock)
258 return -1;
259
260 t = pa_sprintf_malloc("sample:%s", name);
261
262 if (pa_play_memchunk(sink, t, &e->sample_spec, &e->channel_map, &e->memchunk, pa_sw_cvolume_multiply(&r, volume, &e->volume)) < 0) {
263 free(t);
264 return -1;
265 }
266
267 free(t);
268
269 if (e->lazy)
270 time(&e->last_used_time);
271
272 return 0;
273 }
274
275 const char * pa_scache_get_name_by_id(pa_core *c, uint32_t id) {
276 pa_scache_entry *e;
277 assert(c && id != PA_IDXSET_INVALID);
278
279 if (!c->scache || !(e = pa_idxset_get_by_index(c->scache, id)))
280 return NULL;
281
282 return e->name;
283 }
284
285 uint32_t pa_scache_get_id_by_name(pa_core *c, const char *name) {
286 pa_scache_entry *e;
287 assert(c && name);
288
289 if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE, 0)))
290 return PA_IDXSET_INVALID;
291
292 return e->index;
293 }
294
295 uint32_t pa_scache_total_size(pa_core *c) {
296 pa_scache_entry *e;
297 uint32_t idx, sum = 0;
298 assert(c);
299
300 if (!c->scache || !pa_idxset_size(c->scache))
301 return 0;
302
303 for (e = pa_idxset_first(c->scache, &idx); e; e = pa_idxset_next(c->scache, &idx))
304 if (e->memchunk.memblock)
305 sum += e->memchunk.length;
306
307 return sum;
308 }
309
310 void pa_scache_unload_unused(pa_core *c) {
311 pa_scache_entry *e;
312 time_t now;
313 uint32_t idx;
314 assert(c);
315
316 if (!c->scache || !pa_idxset_size(c->scache))
317 return;
318
319 time(&now);
320
321 for (e = pa_idxset_first(c->scache, &idx); e; e = pa_idxset_next(c->scache, &idx)) {
322
323 if (!e->lazy || !e->memchunk.memblock)
324 continue;
325
326 if (e->last_used_time + c->scache_idle_time > now)
327 continue;
328
329 pa_memblock_unref(e->memchunk.memblock);
330 e->memchunk.memblock = NULL;
331 e->memchunk.index = e->memchunk.length = 0;
332
333 pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index);
334 }
335 }
336
337 static void add_file(pa_core *c, const char *pathname) {
338 struct stat st;
339 const char *e;
340
341 e = pa_path_get_filename(pathname);
342
343 if (stat(pathname, &st) < 0) {
344 pa_log(__FILE__": stat('%s') failed: %s\n", pathname, strerror(errno));
345 return;
346 }
347
348 #if defined(S_ISREG) && defined(S_ISLNK)
349 if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
350 #endif
351 pa_scache_add_file_lazy(c, e, pathname, NULL);
352 }
353
354 int pa_scache_add_directory_lazy(pa_core *c, const char *pathname) {
355 DIR *dir;
356 assert(c && pathname);
357
358 /* First try to open this as directory */
359 if (!(dir = opendir(pathname))) {
360 #ifdef HAVE_GLOB_H
361 glob_t p;
362 unsigned int i;
363 /* If that fails, try to open it as shell glob */
364
365 if (glob(pathname, GLOB_ERR|GLOB_NOSORT, NULL, &p) < 0) {
366 pa_log(__FILE__": Failed to open directory: %s\n", strerror(errno));
367 return -1;
368 }
369
370 for (i = 0; i < p.gl_pathc; i++)
371 add_file(c, p.gl_pathv[i]);
372
373 globfree(&p);
374 #else
375 return -1;
376 #endif
377 } else {
378 struct dirent *e;
379
380 while ((e = readdir(dir))) {
381 char p[PATH_MAX];
382
383 if (e->d_name[0] == '.')
384 continue;
385
386 snprintf(p, sizeof(p), "%s/%s", pathname, e->d_name);
387 add_file(c, p);
388 }
389 }
390
391 closedir(dir);
392 return 0;
393 }