Small clean up to http_open()
[cmus.git] / cache.c
blob03ce568fc4f8fa8862fa3542f7abc165d0d5ff23
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"
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <unistd.h>
15 #include <fcntl.h>
16 #include <errno.h>
18 #define CACHE_64_BIT 0x01
19 #define CACHE_BE 0x02
21 // Cmus Track Cache version 0 + 4 bytes flags
22 static char cache_header[8] = "CTC\0\0\0\0\0";
24 // host byte order
25 // mtime is either 32 or 64 bits
26 struct cache_entry {
27 // size of this struct including size itself
28 // NOTE: size does not include padding bytes
29 unsigned int size;
30 int duration;
31 time_t mtime;
33 // filename and N * (key, val)
34 char strings[0];
37 #define ALIGN(size) (((size) + sizeof(long) - 1) & ~(sizeof(long) - 1))
38 #define HASH_SIZE 1023
40 static struct track_info *hash_table[HASH_SIZE];
41 static char *cache_filename;
42 static int total;
43 static int removed;
44 static int new;
46 pthread_mutex_t cache_mutex = CMUS_MUTEX_INITIALIZER;
48 static unsigned int filename_hash(const char *filename)
50 unsigned int hash = 0;
51 int i;
53 for (i = 0; filename[i]; i++)
54 hash = (hash << 5) - hash + filename[i];
55 return hash;
58 static void add_ti(struct track_info *ti, unsigned int hash)
60 unsigned int pos = hash % HASH_SIZE;
61 struct track_info *next = hash_table[pos];
63 ti->next = next;
64 hash_table[pos] = ti;
65 total++;
68 static int valid_cache_entry(const struct cache_entry *e, unsigned int avail)
70 unsigned int min_size = sizeof(*e);
71 unsigned int str_size;
72 int i, count;
74 if (avail < min_size)
75 return 0;
77 if (e->size < min_size || e->size > avail)
78 return 0;
80 str_size = e->size - min_size;
81 count = 0;
82 for (i = 0; i < str_size; i++) {
83 if (!e->strings[i])
84 count++;
86 if (count % 2 == 0)
87 return 0;
88 if (e->strings[str_size - 1])
89 return 0;
90 return 1;
93 static struct track_info *cache_entry_to_ti(struct cache_entry *e)
95 const char *strings = e->strings;
96 struct track_info *ti = track_info_new(strings);
97 struct keyval *kv;
98 int str_size = e->size - sizeof(*e);
99 int pos, i, count;
101 ti->duration = e->duration;
102 ti->mtime = e->mtime;
104 // count strings (filename + key/val pairs)
105 count = 0;
106 for (i = 0; i < str_size; i++) {
107 if (!strings[i])
108 count++;
110 count = (count - 1) / 2;
112 // NOTE: filename already copied by track_info_new()
113 pos = strlen(strings) + 1;
114 ti->comments = xnew(struct keyval, count + 1);
115 kv = ti->comments;
116 for (i = 0; i < count; i++) {
117 int size;
119 size = strlen(strings + pos) + 1;
120 kv[i].key = xstrdup(strings + pos);
121 pos += size;
123 size = strlen(strings + pos) + 1;
124 kv[i].val = xstrdup(strings + pos);
125 pos += size;
127 kv[i].key = NULL;
128 kv[i].val = NULL;
129 return ti;
132 static struct track_info *lookup_cache_entry(const char *filename, unsigned int hash)
134 struct track_info *ti = hash_table[hash % HASH_SIZE];
136 while (ti) {
137 if (!strcmp(filename, ti->filename))
138 return ti;
139 ti = ti->next;
141 return NULL;
144 static void do_cache_remove_ti(struct track_info *ti, unsigned int hash)
146 unsigned int pos = hash % HASH_SIZE;
147 struct track_info *t = hash_table[pos];
148 struct track_info *next, *prev = NULL;
150 while (t) {
151 next = t->next;
152 if (t == ti) {
153 if (prev) {
154 prev->next = next;
155 } else {
156 hash_table[pos] = next;
158 total--;
159 removed++;
160 track_info_unref(ti);
161 return;
163 prev = t;
164 t = next;
168 void cache_remove_ti(struct track_info *ti)
170 do_cache_remove_ti(ti, filename_hash(ti->filename));
173 static int read_cache(void)
175 unsigned int size, offset = 0;
176 struct stat st;
177 char *buf;
178 int fd;
180 fd = open(cache_filename, O_RDONLY);
181 if (fd < 0) {
182 if (errno == ENOENT)
183 return 0;
184 return -1;
186 fstat(fd, &st);
187 if (st.st_size < sizeof(cache_header))
188 goto close;
189 size = st.st_size;
191 buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
192 if (buf == MAP_FAILED) {
193 close(fd);
194 return -1;
197 if (memcmp(buf, cache_header, sizeof(cache_header)))
198 goto corrupt;
200 offset = sizeof(cache_header);
201 while (offset < size) {
202 struct cache_entry *e = (struct cache_entry *)(buf + offset);
203 struct track_info *ti;
205 if (!valid_cache_entry(e, size - offset))
206 goto corrupt;
208 ti = cache_entry_to_ti(e);
209 add_ti(ti, filename_hash(ti->filename));
210 offset += ALIGN(e->size);
212 munmap(buf, size);
213 close(fd);
214 return 0;
215 corrupt:
216 munmap(buf, size);
217 close:
218 close(fd);
219 // corrupt
220 return -2;
223 int cache_init(void)
225 unsigned int flags = 0;
227 #ifdef WORDS_BIGENDIAN
228 flags |= CACHE_BE;
229 #endif
230 if (sizeof(long) == 8)
231 flags |= CACHE_64_BIT;
232 cache_header[7] = flags & 0xff; flags >>= 8;
233 cache_header[6] = flags & 0xff; flags >>= 8;
234 cache_header[5] = flags & 0xff; flags >>= 8;
235 cache_header[4] = flags & 0xff; flags >>= 8;
237 cache_filename = xstrjoin(cmus_config_dir, "/cache");
238 return read_cache();
241 static int ti_filename_cmp(const void *a, const void *b)
243 const struct track_info *ai = *(const struct track_info **)a;
244 const struct track_info *bi = *(const struct track_info **)b;
246 return strcmp(ai->filename, bi->filename);
249 static struct track_info **get_track_infos(void)
251 struct track_info **tis;
252 int i, c;
254 tis = xnew(struct track_info *, total);
255 c = 0;
256 for (i = 0; i < HASH_SIZE; i++) {
257 struct track_info *ti = hash_table[i];
259 while (ti) {
260 tis[c++] = ti;
261 ti = ti->next;
264 qsort(tis, total, sizeof(struct track_info *), ti_filename_cmp);
265 return tis;
268 struct growing_buffer {
269 char *buffer;
270 size_t alloc;
271 size_t count;
274 #define GROWING_BUFFER(name) struct growing_buffer name = { NULL, 0, 0 }
276 static size_t buf_avail(struct growing_buffer *buf)
278 return buf->alloc - buf->count;
281 static void buf_resize(struct growing_buffer *buf, size_t size)
283 size_t align = 4096 - 1;
285 buf->alloc = (size + align) & ~align;
286 buf->buffer = xrealloc(buf->buffer, buf->alloc);
289 static void buf_free(struct growing_buffer *buf)
291 free(buf->buffer);
292 buf->buffer = NULL;
293 buf->alloc = 0;
294 buf->count = 0;
297 static void buf_add(struct growing_buffer *buf, void *data, size_t count)
299 size_t avail = buf_avail(buf);
301 if (avail < count)
302 buf_resize(buf, buf->count + count);
303 memcpy(buf->buffer + buf->count, data, count);
304 buf->count += count;
307 static void buf_set(struct growing_buffer *buf, int c, size_t count)
309 size_t avail = buf_avail(buf);
311 if (avail < count)
312 buf_resize(buf, buf->count + count);
313 memset(buf->buffer + buf->count, c, count);
314 buf->count += count;
317 static void buf_consume(struct growing_buffer *buf, size_t count)
319 size_t n = buf->count - count;
321 if (!n) {
322 buf->count = 0;
323 return;
325 memmove(buf->buffer, buf->buffer + count, n);
328 static void flush_buffer(int fd, struct growing_buffer *buf)
330 write_all(fd, buf->buffer, buf->count);
331 buf_consume(buf, buf->count);
334 static void write_ti(int fd, struct growing_buffer *buf, struct track_info *ti, unsigned int *offsetp)
336 const struct keyval *kv = ti->comments;
337 unsigned int offset = *offsetp;
338 unsigned int pad;
339 struct cache_entry e;
340 int len[65], count, i;
342 count = 0;
343 e.size = sizeof(e);
344 e.duration = ti->duration;
345 e.mtime = ti->mtime;
346 len[count] = strlen(ti->filename) + 1;
347 e.size += len[count++];
348 for (i = 0; kv[i].key; i++) {
349 len[count] = strlen(kv[i].key) + 1;
350 e.size += len[count++];
351 len[count] = strlen(kv[i].val) + 1;
352 e.size += len[count++];
355 pad = ALIGN(offset) - offset;
356 if (buf_avail(buf) < pad + e.size)
357 flush_buffer(fd, buf);
359 count = 0;
360 if (pad)
361 buf_set(buf, 0, pad);
362 buf_add(buf, &e, sizeof(e));
363 buf_add(buf, ti->filename, len[count++]);
364 for (i = 0; kv[i].key; i++) {
365 buf_add(buf, kv[i].key, len[count++]);
366 buf_add(buf, kv[i].val, len[count++]);
369 *offsetp = offset + pad + e.size;
372 int cache_close(void)
374 GROWING_BUFFER(buf);
375 struct track_info **tis;
376 unsigned int offset;
377 int i, fd;
378 char *tmp;
380 if (!new && !removed)
381 return 0;
383 tmp = xstrjoin(cmus_config_dir, "/cache.tmp");
384 fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0666);
385 if (fd < 0)
386 return -1;
388 tis = get_track_infos();
390 buf_resize(&buf, 64 * 1024);
391 buf_add(&buf, cache_header, sizeof(cache_header));
392 offset = sizeof(cache_header);
393 for (i = 0; i < total; i++)
394 write_ti(fd, &buf, tis[i], &offset);
395 flush_buffer(fd, &buf);
396 buf_free(&buf);
398 close(fd);
399 if (rename(tmp, cache_filename))
400 return -1;
401 return 0;
404 static struct track_info *ip_get_ti(const char *filename)
406 struct track_info *ti = NULL;
407 struct input_plugin *ip;
408 struct keyval *comments;
409 int rc;
411 ip = ip_new(filename);
412 rc = ip_open(ip);
413 if (rc) {
414 ip_delete(ip);
415 return NULL;
418 rc = ip_read_comments(ip, &comments);
419 if (!rc) {
420 ti = track_info_new(filename);
421 ti->comments = comments;
422 ti->duration = ip_duration(ip);
423 ti->mtime = 0;
425 ip_delete(ip);
426 return ti;
429 struct track_info *cache_get_ti(const char *filename)
431 unsigned int hash = filename_hash(filename);
432 struct track_info *ti;
434 ti = lookup_cache_entry(filename, hash);
435 if (!ti) {
436 ti = ip_get_ti(filename);
437 if (!ti)
438 return NULL;
439 ti->mtime = file_get_mtime(filename);
440 add_ti(ti, hash);
441 new++;
443 track_info_ref(ti);
444 return ti;
447 struct track_info **cache_refresh(int *count)
449 struct track_info **tis = get_track_infos();
450 int i;
452 for (i = 0; i < total; i++) {
453 unsigned int hash;
454 struct track_info *ti = tis[i];
455 struct stat st;
456 int rc;
459 * If no-one else has reference to tis[i] then it is set to NULL
460 * otherwise:
462 * unchanged: tis[i] = NULL
463 * deleted: tis[i]->next = NULL
464 * changed: tis[i]->next = new
467 rc = stat(ti->filename, &st);
468 if (!rc && ti->mtime == st.st_mtime) {
469 // unchanged
470 tis[i] = NULL;
471 continue;
474 hash = filename_hash(ti->filename);
475 track_info_ref(ti);
476 do_cache_remove_ti(ti, hash);
478 if (!rc) {
479 // changed
480 struct track_info *new_ti = ip_get_ti(ti->filename);
482 if (new_ti) {
483 new_ti->mtime = st.st_mtime;
484 add_ti(new_ti, hash);
485 new++;
487 if (ti->ref == 1) {
488 track_info_unref(ti);
489 tis[i] = NULL;
490 } else {
491 track_info_ref(new_ti);
492 ti->next = new_ti;
494 continue;
496 // treat as deleted
499 // deleted
500 if (ti->ref == 1) {
501 track_info_unref(ti);
502 tis[i] = NULL;
503 } else {
504 ti->next = NULL;
507 *count = total;
508 return tis;