8080fcd69b682a4c0a94051a6cfb659294acc6dc
[profile/ivi/pulseaudio.git] / 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 <polyp/xmalloc.h>
48
49 #include <polypcore/sink-input.h>
50 #include <polypcore/sample-util.h>
51 #include <polypcore/play-memchunk.h>
52 #include <polypcore/core-subscribe.h>
53 #include <polypcore/namereg.h>
54 #include <polypcore/sound-file.h>
55 #include <polypcore/util.h>
56 #include <polypcore/log.h>
57
58 #include "core-scache.h"
59
60 #define UNLOAD_POLL_TIME 2
61
62 static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) {
63     pa_core *c = userdata;
64     struct timeval ntv;
65     assert(c && c->mainloop == m && c->scache_auto_unload_event == e);
66
67     pa_scache_unload_unused(c);
68
69     pa_gettimeofday(&ntv);
70     ntv.tv_sec += UNLOAD_POLL_TIME;
71     m->time_restart(e, &ntv);
72 }
73
74 static void free_entry(pa_scache_entry *e) {
75     assert(e);
76     pa_namereg_unregister(e->core, e->name);
77     pa_subscription_post(e->core, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_REMOVE, e->index);
78     pa_xfree(e->name);
79     pa_xfree(e->filename);
80     if (e->memchunk.memblock)
81         pa_memblock_unref(e->memchunk.memblock);
82     pa_xfree(e);
83 }
84
85 static pa_scache_entry* scache_add_item(pa_core *c, const char *name) {
86     pa_scache_entry *e;
87     assert(c && name);
88
89     if ((e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE, 0))) {
90         if (e->memchunk.memblock)
91             pa_memblock_unref(e->memchunk.memblock);
92
93         pa_xfree(e->filename);
94         
95         assert(e->core == c);
96
97         pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index);
98     } else {
99         e = pa_xmalloc(sizeof(pa_scache_entry));
100
101         if (!pa_namereg_register(c, name, PA_NAMEREG_SAMPLE, e, 1)) {
102             pa_xfree(e);
103             return NULL;
104         }
105
106         e->name = pa_xstrdup(name);
107         e->core = c;
108
109         if (!c->scache) {
110             c->scache = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
111             assert(c->scache);
112         }
113
114         pa_idxset_put(c->scache, e, &e->index);
115
116         pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_NEW, e->index);
117     }
118
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(e->sample_spec));
127     pa_channel_map_init(&e->channel_map);
128     pa_cvolume_reset(&e->volume, PA_CHANNELS_MAX);
129
130     return e;
131 }
132
133 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) {
134     pa_scache_entry *e;
135     assert(c && name);
136
137     if (chunk && chunk->length > PA_SCACHE_ENTRY_SIZE_MAX)
138         return -1;
139
140     if (!(e = scache_add_item(c, name)))
141         return -1;
142
143     if (ss) {
144         e->sample_spec = *ss;
145         pa_channel_map_init_auto(&e->channel_map, ss->channels, PA_CHANNEL_MAP_DEFAULT);
146         e->volume.channels = e->sample_spec.channels;
147     }
148
149     if (map)
150         e->channel_map = *map;
151
152     if (chunk) {
153         e->memchunk = *chunk;
154         pa_memblock_ref(e->memchunk.memblock);
155     }
156
157     if (idx)
158         *idx = e->index;
159
160     return 0;
161 }
162
163 int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint32_t *idx) {
164     pa_sample_spec ss;
165     pa_channel_map map;
166     pa_memchunk chunk;
167     int r;
168
169 #ifdef OS_IS_WIN32
170     char buf[MAX_PATH];
171
172     if (ExpandEnvironmentStrings(filename, buf, MAX_PATH))
173         filename = buf;
174 #endif
175
176     if (pa_sound_file_load(filename, &ss, &map, &chunk, c->memblock_stat) < 0)
177         return -1;
178         
179     r = pa_scache_add_item(c, name, &ss, &map, &chunk, idx);
180     pa_memblock_unref(chunk.memblock);
181
182     return r;
183 }
184
185 int pa_scache_add_file_lazy(pa_core *c, const char *name, const char *filename, uint32_t *idx) {
186     pa_scache_entry *e;
187
188 #ifdef OS_IS_WIN32
189     char buf[MAX_PATH];
190
191     if (ExpandEnvironmentStrings(filename, buf, MAX_PATH))
192         filename = buf;
193 #endif
194
195     assert(c && name);
196
197     if (!(e = scache_add_item(c, name)))
198         return -1;
199
200     e->lazy = 1;
201     e->filename = pa_xstrdup(filename);
202     
203     if (!c->scache_auto_unload_event) {
204         struct timeval ntv;
205         pa_gettimeofday(&ntv);
206         ntv.tv_sec += UNLOAD_POLL_TIME;
207         c->scache_auto_unload_event = c->mainloop->time_new(c->mainloop, &ntv, timeout_callback, c);
208     }
209
210     if (idx)
211         *idx = e->index;
212
213     return 0;
214 }
215
216 int pa_scache_remove_item(pa_core *c, const char *name) {
217     pa_scache_entry *e;
218     assert(c && name);
219
220     if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE, 0)))
221         return -1;
222
223     if (pa_idxset_remove_by_data(c->scache, e, NULL) != e)
224         assert(0);
225
226     free_entry(e);
227     return 0;
228 }
229
230 static void free_cb(void *p, PA_GCC_UNUSED void *userdata) {
231     pa_scache_entry *e = p;
232     assert(e);
233     free_entry(e);
234 }
235
236 void pa_scache_free(pa_core *c) {
237     assert(c);
238
239     if (c->scache) {
240         pa_idxset_free(c->scache, free_cb, NULL);
241         c->scache = NULL;
242     }
243
244     if (c->scache_auto_unload_event)
245         c->mainloop->time_free(c->scache_auto_unload_event);
246 }
247
248 int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t volume) {
249     pa_scache_entry *e;
250     char *t;
251     pa_cvolume r;
252     
253     assert(c);
254     assert(name);
255     assert(sink);
256
257     if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE, 1)))
258         return -1;
259
260     if (e->lazy && !e->memchunk.memblock) {
261         if (pa_sound_file_load(e->filename, &e->sample_spec, &e->channel_map, &e->memchunk, c->memblock_stat) < 0)
262             return -1;
263
264         pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index);
265
266         if (e->volume.channels > e->sample_spec.channels)
267             e->volume.channels = e->sample_spec.channels;
268     }
269     
270     if (!e->memchunk.memblock)
271         return -1;
272
273     t = pa_sprintf_malloc("sample:%s", name);
274
275     pa_cvolume_set(&r, e->volume.channels, volume);
276     pa_sw_cvolume_multiply(&r, &r, &e->volume);
277
278     if (pa_play_memchunk(sink, t, &e->sample_spec, &e->channel_map, &e->memchunk, &r) < 0) {
279         pa_xfree(t);
280         return -1;
281     }
282
283     pa_xfree(t);
284
285     if (e->lazy)
286         time(&e->last_used_time);
287     
288     return 0;
289 }
290
291 const char * pa_scache_get_name_by_id(pa_core *c, uint32_t id) {
292     pa_scache_entry *e;
293     assert(c && id != PA_IDXSET_INVALID);
294
295     if (!c->scache || !(e = pa_idxset_get_by_index(c->scache, id)))
296         return NULL;
297
298     return e->name;
299 }
300
301 uint32_t pa_scache_get_id_by_name(pa_core *c, const char *name) {
302     pa_scache_entry *e;
303     assert(c && name);
304
305     if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE, 0)))
306         return PA_IDXSET_INVALID;
307
308     return e->index;
309 }
310
311 uint32_t pa_scache_total_size(pa_core *c) {
312     pa_scache_entry *e;
313     uint32_t idx, sum = 0;
314     assert(c);
315
316     if (!c->scache || !pa_idxset_size(c->scache))
317         return 0;
318     
319     for (e = pa_idxset_first(c->scache, &idx); e; e = pa_idxset_next(c->scache, &idx))
320         if (e->memchunk.memblock)
321             sum += e->memchunk.length;
322
323     return sum;
324 }
325
326 void pa_scache_unload_unused(pa_core *c) {
327     pa_scache_entry *e;
328     time_t now;
329     uint32_t idx;
330     assert(c);
331
332     if (!c->scache || !pa_idxset_size(c->scache))
333         return;
334     
335     time(&now);
336
337     for (e = pa_idxset_first(c->scache, &idx); e; e = pa_idxset_next(c->scache, &idx)) {
338
339         if (!e->lazy || !e->memchunk.memblock)
340             continue;
341
342         if (e->last_used_time + c->scache_idle_time > now)
343             continue;
344         
345         pa_memblock_unref(e->memchunk.memblock);
346         e->memchunk.memblock = NULL;
347         e->memchunk.index = e->memchunk.length = 0;
348
349         pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index);
350     }
351 }
352
353 static void add_file(pa_core *c, const char *pathname) {
354     struct stat st;
355     const char *e;
356
357     e = pa_path_get_filename(pathname);
358     
359     if (stat(pathname, &st) < 0) {
360         pa_log(__FILE__": stat('%s') failed: %s", pathname, strerror(errno));
361         return;
362     }
363
364 #if defined(S_ISREG) && defined(S_ISLNK)
365     if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
366 #endif
367         pa_scache_add_file_lazy(c, e, pathname, NULL);
368 }
369
370 int pa_scache_add_directory_lazy(pa_core *c, const char *pathname) {
371     DIR *dir;
372     assert(c && pathname);
373
374     /* First try to open this as directory */
375     if (!(dir = opendir(pathname))) {
376 #ifdef HAVE_GLOB_H
377         glob_t p;
378         unsigned int i;
379         /* If that fails, try to open it as shell glob */
380
381         if (glob(pathname, GLOB_ERR|GLOB_NOSORT, NULL, &p) < 0) {
382             pa_log(__FILE__": Failed to open directory: %s", strerror(errno));
383             return -1;
384         }
385
386         for (i = 0; i < p.gl_pathc; i++)
387             add_file(c, p.gl_pathv[i]);
388         
389         globfree(&p);
390 #else
391         return -1;
392 #endif
393     } else {
394         struct dirent *e;
395
396         while ((e = readdir(dir))) {
397             char p[PATH_MAX];
398
399             if (e->d_name[0] == '.')
400                 continue;
401
402             snprintf(p, sizeof(p), "%s/%s", pathname, e->d_name);
403             add_file(c, p);
404         }
405     }
406
407     closedir(dir);
408     return 0;
409 }