5 #include "track_info.h"
12 #include <sys/types.h>
18 #define CACHE_64_BIT 0x01
21 // Cmus Track Cache version 0 + 4 bytes flags
22 static char cache_header
[8] = "CTC\0\0\0\0\0";
25 // mtime is either 32 or 64 bits
27 // size of this struct including size itself
28 // NOTE: size does not include padding bytes
33 // filename and N * (key, val)
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
;
46 pthread_mutex_t cache_mutex
= CMUS_MUTEX_INITIALIZER
;
48 static unsigned int filename_hash(const char *filename
)
50 unsigned int hash
= 0;
53 for (i
= 0; filename
[i
]; i
++)
54 hash
= (hash
<< 5) - hash
+ filename
[i
];
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
];
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
;
77 if (e
->size
< min_size
|| e
->size
> avail
)
80 str_size
= e
->size
- min_size
;
82 for (i
= 0; i
< str_size
; i
++) {
88 if (e
->strings
[str_size
- 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
);
98 int str_size
= e
->size
- sizeof(*e
);
101 ti
->duration
= e
->duration
;
102 ti
->mtime
= e
->mtime
;
104 // count strings (filename + key/val pairs)
106 for (i
= 0; i
< str_size
; i
++) {
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);
116 for (i
= 0; i
< count
; i
++) {
119 size
= strlen(strings
+ pos
) + 1;
120 kv
[i
].key
= xstrdup(strings
+ pos
);
123 size
= strlen(strings
+ pos
) + 1;
124 kv
[i
].val
= xstrdup(strings
+ pos
);
132 static struct track_info
*lookup_cache_entry(const char *filename
, unsigned int hash
)
134 struct track_info
*ti
= hash_table
[hash
% HASH_SIZE
];
137 if (!strcmp(filename
, ti
->filename
))
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
;
156 hash_table
[pos
] = next
;
160 track_info_unref(ti
);
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;
180 fd
= open(cache_filename
, O_RDONLY
);
187 if (st
.st_size
< sizeof(cache_header
))
191 buf
= mmap(NULL
, size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
192 if (buf
== MAP_FAILED
) {
197 if (memcmp(buf
, cache_header
, sizeof(cache_header
)))
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
))
208 ti
= cache_entry_to_ti(e
);
209 add_ti(ti
, filename_hash(ti
->filename
));
210 offset
+= ALIGN(e
->size
);
225 unsigned int flags
= 0;
227 #ifdef WORDS_BIGENDIAN
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");
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
;
254 tis
= xnew(struct track_info
*, total
);
256 for (i
= 0; i
< HASH_SIZE
; i
++) {
257 struct track_info
*ti
= hash_table
[i
];
264 qsort(tis
, total
, sizeof(struct track_info
*), ti_filename_cmp
);
268 struct growing_buffer
{
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
)
297 static void buf_add(struct growing_buffer
*buf
, void *data
, size_t count
)
299 size_t avail
= buf_avail(buf
);
302 buf_resize(buf
, buf
->count
+ count
);
303 memcpy(buf
->buffer
+ buf
->count
, data
, count
);
307 static void buf_set(struct growing_buffer
*buf
, int c
, size_t count
)
309 size_t avail
= buf_avail(buf
);
312 buf_resize(buf
, buf
->count
+ count
);
313 memset(buf
->buffer
+ buf
->count
, c
, count
);
317 static void buf_consume(struct growing_buffer
*buf
, size_t count
)
319 size_t n
= buf
->count
- count
;
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
;
339 struct cache_entry e
;
340 int len
[65], count
, i
;
344 e
.duration
= ti
->duration
;
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
);
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)
375 struct track_info
**tis
;
380 if (!new && !removed
)
383 tmp
= xstrjoin(cmus_config_dir
, "/cache.tmp");
384 fd
= open(tmp
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0666);
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
);
399 if (rename(tmp
, cache_filename
))
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
;
411 ip
= ip_new(filename
);
418 rc
= ip_read_comments(ip
, &comments
);
420 ti
= track_info_new(filename
);
421 ti
->comments
= comments
;
422 ti
->duration
= ip_duration(ip
);
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
);
436 ti
= ip_get_ti(filename
);
439 ti
->mtime
= file_get_mtime(filename
);
447 struct track_info
**cache_refresh(int *count
)
449 struct track_info
**tis
= get_track_infos();
452 for (i
= 0; i
< total
; i
++) {
454 struct track_info
*ti
= tis
[i
];
459 * If no-one else has reference to tis[i] then it is set to NULL
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
) {
474 hash
= filename_hash(ti
->filename
);
476 do_cache_remove_ti(ti
, hash
);
480 struct track_info
*new_ti
= ip_get_ti(ti
->filename
);
483 new_ti
->mtime
= st
.st_mtime
;
484 add_ti(new_ti
, hash
);
488 track_info_unref(ti
);
491 track_info_ref(new_ti
);
501 track_info_unref(ti
);