flac: Saner EOF handling
[cmus.git] / cache.c
blob1eae256c4ffe7fb81cd3596647da452d28fb0e11
1 #include "cache.h"
2 #include "misc.h"
3 #include "file.h"
4 #include "input.h"
5 #include "track_info.h"
6 #include "utils.h"
7 #include "xmalloc.h"
8 #include "xstrjoin.h"
9 #include "gbuf.h"
11 #include <stdlib.h>
12 #include <stdio.h>
13 #include <sys/types.h>
14 #include <sys/stat.h>
15 #include <unistd.h>
16 #include <fcntl.h>
17 #include <errno.h>
19 #define CACHE_64_BIT 0x01
20 #define CACHE_BE 0x02
22 // Cmus Track Cache version X + 4 bytes flags
23 static char cache_header[8] = "CTC\0\0\0\0\0";
25 // host byte order
26 // mtime is either 32 or 64 bits
27 struct cache_entry {
28 // size of this struct including size itself
29 // NOTE: size does not include padding bytes
30 unsigned int size;
31 int duration;
32 time_t mtime;
34 // filename and N * (key, val)
35 char strings[0];
38 #define ALIGN(size) (((size) + sizeof(long) - 1) & ~(sizeof(long) - 1))
39 #define HASH_SIZE 1023
41 static struct track_info *hash_table[HASH_SIZE];
42 static char *cache_filename;
43 static int total;
44 static int removed;
45 static int new;
47 pthread_mutex_t cache_mutex = CMUS_MUTEX_INITIALIZER;
49 static unsigned int filename_hash(const char *filename)
51 unsigned int hash = 0;
52 int i;
54 for (i = 0; filename[i]; i++)
55 hash = (hash << 5) - hash + filename[i];
56 return hash;
59 static void add_ti(struct track_info *ti, unsigned int hash)
61 unsigned int pos = hash % HASH_SIZE;
62 struct track_info *next = hash_table[pos];
64 ti->next = next;
65 hash_table[pos] = ti;
66 total++;
69 static int valid_cache_entry(const struct cache_entry *e, unsigned int avail)
71 unsigned int min_size = sizeof(*e);
72 unsigned int str_size;
73 int i, count;
75 if (avail < min_size)
76 return 0;
78 if (e->size < min_size || e->size > avail)
79 return 0;
81 str_size = e->size - min_size;
82 count = 0;
83 for (i = 0; i < str_size; i++) {
84 if (!e->strings[i])
85 count++;
87 if (count % 2 == 0)
88 return 0;
89 if (e->strings[str_size - 1])
90 return 0;
91 return 1;
94 static struct track_info *cache_entry_to_ti(struct cache_entry *e)
96 const char *strings = e->strings;
97 struct track_info *ti = track_info_new(strings);
98 struct keyval *kv;
99 int str_size = e->size - sizeof(*e);
100 int pos, i, count;
102 ti->duration = e->duration;
103 ti->mtime = e->mtime;
105 // count strings (filename + key/val pairs)
106 count = 0;
107 for (i = 0; i < str_size; i++) {
108 if (!strings[i])
109 count++;
111 count = (count - 1) / 2;
113 // NOTE: filename already copied by track_info_new()
114 pos = strlen(strings) + 1;
115 ti->comments = xnew(struct keyval, count + 1);
116 kv = ti->comments;
117 for (i = 0; i < count; i++) {
118 int size;
120 size = strlen(strings + pos) + 1;
121 kv[i].key = xstrdup(strings + pos);
122 pos += size;
124 size = strlen(strings + pos) + 1;
125 kv[i].val = xstrdup(strings + pos);
126 pos += size;
128 kv[i].key = NULL;
129 kv[i].val = NULL;
130 return ti;
133 static struct track_info *lookup_cache_entry(const char *filename, unsigned int hash)
135 struct track_info *ti = hash_table[hash % HASH_SIZE];
137 while (ti) {
138 if (!strcmp(filename, ti->filename))
139 return ti;
140 ti = ti->next;
142 return NULL;
145 static void do_cache_remove_ti(struct track_info *ti, unsigned int hash)
147 unsigned int pos = hash % HASH_SIZE;
148 struct track_info *t = hash_table[pos];
149 struct track_info *next, *prev = NULL;
151 while (t) {
152 next = t->next;
153 if (t == ti) {
154 if (prev) {
155 prev->next = next;
156 } else {
157 hash_table[pos] = next;
159 total--;
160 removed++;
161 track_info_unref(ti);
162 return;
164 prev = t;
165 t = next;
169 void cache_remove_ti(struct track_info *ti)
171 do_cache_remove_ti(ti, filename_hash(ti->filename));
174 static int read_cache(void)
176 unsigned int size, offset = 0;
177 struct stat st;
178 char *buf;
179 int fd;
181 fd = open(cache_filename, O_RDONLY);
182 if (fd < 0) {
183 if (errno == ENOENT)
184 return 0;
185 return -1;
187 fstat(fd, &st);
188 if (st.st_size < sizeof(cache_header))
189 goto close;
190 size = st.st_size;
192 buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
193 if (buf == MAP_FAILED) {
194 close(fd);
195 return -1;
198 if (memcmp(buf, cache_header, sizeof(cache_header)))
199 goto corrupt;
201 offset = sizeof(cache_header);
202 while (offset < size) {
203 struct cache_entry *e = (struct cache_entry *)(buf + offset);
204 struct track_info *ti;
206 if (!valid_cache_entry(e, size - offset))
207 goto corrupt;
209 ti = cache_entry_to_ti(e);
210 add_ti(ti, filename_hash(ti->filename));
211 offset += ALIGN(e->size);
213 munmap(buf, size);
214 close(fd);
215 return 0;
216 corrupt:
217 munmap(buf, size);
218 close:
219 close(fd);
220 // corrupt
221 return -2;
224 int cache_init(void)
226 unsigned int flags = 0;
228 #ifdef WORDS_BIGENDIAN
229 flags |= CACHE_BE;
230 #endif
231 if (sizeof(long) == 8)
232 flags |= CACHE_64_BIT;
233 cache_header[7] = flags & 0xff; flags >>= 8;
234 cache_header[6] = flags & 0xff; flags >>= 8;
235 cache_header[5] = flags & 0xff; flags >>= 8;
236 cache_header[4] = flags & 0xff; flags >>= 8;
238 /* assumed version */
239 cache_header[3] = 0x01;
241 cache_filename = xstrjoin(cmus_config_dir, "/cache");
242 return read_cache();
245 static int ti_filename_cmp(const void *a, const void *b)
247 const struct track_info *ai = *(const struct track_info **)a;
248 const struct track_info *bi = *(const struct track_info **)b;
250 return strcmp(ai->filename, bi->filename);
253 static struct track_info **get_track_infos(void)
255 struct track_info **tis;
256 int i, c;
258 tis = xnew(struct track_info *, total);
259 c = 0;
260 for (i = 0; i < HASH_SIZE; i++) {
261 struct track_info *ti = hash_table[i];
263 while (ti) {
264 tis[c++] = ti;
265 ti = ti->next;
268 qsort(tis, total, sizeof(struct track_info *), ti_filename_cmp);
269 return tis;
272 static void flush_buffer(int fd, struct gbuf *buf)
274 if (buf->len) {
275 write_all(fd, buf->buffer, buf->len);
276 gbuf_clear(buf);
280 static void write_ti(int fd, struct gbuf *buf, struct track_info *ti, unsigned int *offsetp)
282 const struct keyval *kv = ti->comments;
283 unsigned int offset = *offsetp;
284 unsigned int pad;
285 struct cache_entry e;
286 int len[65], count, i;
288 count = 0;
289 e.size = sizeof(e);
290 e.duration = ti->duration;
291 e.mtime = ti->mtime;
292 len[count] = strlen(ti->filename) + 1;
293 e.size += len[count++];
294 for (i = 0; kv[i].key; i++) {
295 len[count] = strlen(kv[i].key) + 1;
296 e.size += len[count++];
297 len[count] = strlen(kv[i].val) + 1;
298 e.size += len[count++];
301 pad = ALIGN(offset) - offset;
302 if (gbuf_avail(buf) < pad + e.size)
303 flush_buffer(fd, buf);
305 count = 0;
306 if (pad)
307 gbuf_set(buf, 0, pad);
308 gbuf_add_bytes(buf, &e, sizeof(e));
309 gbuf_add_bytes(buf, ti->filename, len[count++]);
310 for (i = 0; kv[i].key; i++) {
311 gbuf_add_bytes(buf, kv[i].key, len[count++]);
312 gbuf_add_bytes(buf, kv[i].val, len[count++]);
315 *offsetp = offset + pad + e.size;
318 int cache_close(void)
320 GBUF(buf);
321 struct track_info **tis;
322 unsigned int offset;
323 int i, fd;
324 char *tmp;
326 if (!new && !removed)
327 return 0;
329 tmp = xstrjoin(cmus_config_dir, "/cache.tmp");
330 fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0666);
331 if (fd < 0)
332 return -1;
334 tis = get_track_infos();
336 gbuf_grow(&buf, 64 * 1024 - 1);
337 gbuf_add_bytes(&buf, cache_header, sizeof(cache_header));
338 offset = sizeof(cache_header);
339 for (i = 0; i < total; i++)
340 write_ti(fd, &buf, tis[i], &offset);
341 flush_buffer(fd, &buf);
342 gbuf_free(&buf);
344 close(fd);
345 if (rename(tmp, cache_filename))
346 return -1;
347 return 0;
350 static struct track_info *ip_get_ti(const char *filename)
352 struct track_info *ti = NULL;
353 struct input_plugin *ip;
354 struct keyval *comments;
355 int rc;
357 ip = ip_new(filename);
358 rc = ip_open(ip);
359 if (rc) {
360 ip_delete(ip);
361 return NULL;
364 rc = ip_read_comments(ip, &comments);
365 if (!rc) {
366 ti = track_info_new(filename);
367 ti->comments = comments;
368 ti->duration = ip_duration(ip);
369 ti->mtime = 0;
371 ip_delete(ip);
372 return ti;
375 struct track_info *cache_get_ti(const char *filename)
377 unsigned int hash = filename_hash(filename);
378 struct track_info *ti;
380 ti = lookup_cache_entry(filename, hash);
381 if (!ti) {
382 ti = ip_get_ti(filename);
383 if (!ti)
384 return NULL;
385 ti->mtime = file_get_mtime(filename);
386 add_ti(ti, hash);
387 new++;
389 track_info_ref(ti);
390 return ti;
393 struct track_info **cache_refresh(int *count)
395 struct track_info **tis = get_track_infos();
396 int i;
398 for (i = 0; i < total; i++) {
399 unsigned int hash;
400 struct track_info *ti = tis[i];
401 struct stat st;
402 int rc;
405 * If no-one else has reference to tis[i] then it is set to NULL
406 * otherwise:
408 * unchanged: tis[i] = NULL
409 * deleted: tis[i]->next = NULL
410 * changed: tis[i]->next = new
413 rc = stat(ti->filename, &st);
414 if (!rc && ti->mtime == st.st_mtime) {
415 // unchanged
416 tis[i] = NULL;
417 continue;
420 hash = filename_hash(ti->filename);
421 track_info_ref(ti);
422 do_cache_remove_ti(ti, hash);
424 if (!rc) {
425 // changed
426 struct track_info *new_ti = ip_get_ti(ti->filename);
428 if (new_ti) {
429 new_ti->mtime = st.st_mtime;
430 add_ti(new_ti, hash);
431 new++;
433 if (ti->ref == 1) {
434 track_info_unref(ti);
435 tis[i] = NULL;
436 } else {
437 track_info_ref(new_ti);
438 ti->next = new_ti;
440 continue;
442 // treat as deleted
445 // deleted
446 if (ti->ref == 1) {
447 track_info_unref(ti);
448 tis[i] = NULL;
449 } else {
450 ti->next = NULL;
453 *count = total;
454 return tis;