5 #include "track_info.h"
13 #include <sys/types.h>
19 #define CACHE_64_BIT 0x01
22 // Cmus Track Cache version X + 4 bytes flags
23 static char cache_header
[8] = "CTC\0\0\0\0\0";
26 // mtime is either 32 or 64 bits
28 // size of this struct including size itself
29 // NOTE: size does not include padding bytes
34 // filename and N * (key, val)
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
;
47 pthread_mutex_t cache_mutex
= CMUS_MUTEX_INITIALIZER
;
49 static unsigned int filename_hash(const char *filename
)
51 unsigned int hash
= 0;
54 for (i
= 0; filename
[i
]; i
++)
55 hash
= (hash
<< 5) - hash
+ filename
[i
];
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
];
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
;
78 if (e
->size
< min_size
|| e
->size
> avail
)
81 str_size
= e
->size
- min_size
;
83 for (i
= 0; i
< str_size
; i
++) {
89 if (e
->strings
[str_size
- 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
);
99 int str_size
= e
->size
- sizeof(*e
);
102 ti
->duration
= e
->duration
;
103 ti
->mtime
= e
->mtime
;
105 // count strings (filename + key/val pairs)
107 for (i
= 0; i
< str_size
; i
++) {
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);
117 for (i
= 0; i
< count
; i
++) {
120 size
= strlen(strings
+ pos
) + 1;
121 kv
[i
].key
= xstrdup(strings
+ pos
);
124 size
= strlen(strings
+ pos
) + 1;
125 kv
[i
].val
= xstrdup(strings
+ pos
);
133 static struct track_info
*lookup_cache_entry(const char *filename
, unsigned int hash
)
135 struct track_info
*ti
= hash_table
[hash
% HASH_SIZE
];
138 if (!strcmp(filename
, ti
->filename
))
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
;
157 hash_table
[pos
] = next
;
161 track_info_unref(ti
);
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;
181 fd
= open(cache_filename
, O_RDONLY
);
188 if (st
.st_size
< sizeof(cache_header
))
192 buf
= mmap(NULL
, size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
193 if (buf
== MAP_FAILED
) {
198 if (memcmp(buf
, cache_header
, sizeof(cache_header
)))
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
))
209 ti
= cache_entry_to_ti(e
);
210 add_ti(ti
, filename_hash(ti
->filename
));
211 offset
+= ALIGN(e
->size
);
226 unsigned int flags
= 0;
228 #ifdef WORDS_BIGENDIAN
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");
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
;
258 tis
= xnew(struct track_info
*, total
);
260 for (i
= 0; i
< HASH_SIZE
; i
++) {
261 struct track_info
*ti
= hash_table
[i
];
268 qsort(tis
, total
, sizeof(struct track_info
*), ti_filename_cmp
);
272 static void flush_buffer(int fd
, struct gbuf
*buf
)
275 write_all(fd
, buf
->buffer
, buf
->len
);
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
;
285 struct cache_entry e
;
286 int len
[65], count
, i
;
290 e
.duration
= ti
->duration
;
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
);
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)
321 struct track_info
**tis
;
326 if (!new && !removed
)
329 tmp
= xstrjoin(cmus_config_dir
, "/cache.tmp");
330 fd
= open(tmp
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0666);
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
);
345 if (rename(tmp
, cache_filename
))
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
;
357 ip
= ip_new(filename
);
364 rc
= ip_read_comments(ip
, &comments
);
366 ti
= track_info_new(filename
);
367 ti
->comments
= comments
;
368 ti
->duration
= ip_duration(ip
);
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
);
382 ti
= ip_get_ti(filename
);
385 ti
->mtime
= file_get_mtime(filename
);
393 struct track_info
**cache_refresh(int *count
)
395 struct track_info
**tis
= get_track_infos();
398 for (i
= 0; i
< total
; i
++) {
400 struct track_info
*ti
= tis
[i
];
405 * If no-one else has reference to tis[i] then it is set to NULL
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
) {
420 hash
= filename_hash(ti
->filename
);
422 do_cache_remove_ti(ti
, hash
);
426 struct track_info
*new_ti
= ip_get_ti(ti
->filename
);
429 new_ti
->mtime
= st
.st_mtime
;
430 add_ti(new_ti
, hash
);
434 track_info_unref(ti
);
437 track_info_ref(new_ti
);
447 track_info_unref(ti
);