1 /*
2 * Copyright (c) 2011 Tim van der Molen <tim@kariliq.nl>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 /* Let glibc expose strcasestr(). */
18 #define _GNU_SOURCE
19
20 /* Let NetBSD expose strtonum(). */
21 #define _OPENBSD_SOURCE
22
23 #ifdef __OpenBSD__
24 #include <sys/tree.h>
25 #else
26 #include "compat/tree.h"
27 #endif
28
29 #include <limits.h>
30 #include <stdint.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <strings.h>
34 #include <unistd.h>
35
36 #include "siren.h"
37
38 struct track_entry {
39 struct track track;
40 int delete;
41 RB_ENTRY(track_entry) entries;
42 };
43
44 RB_HEAD(track_tree, track_entry);
45
46 static int track_cmp_entry(struct track_entry *,
47 struct track_entry *);
48 static int track_cmp_number(const char *, const char *);
49 static int track_cmp_string(const char *, const char *);
50 static void track_free_entry(struct track_entry *);
51 static void track_free_metadata(struct track_entry *);
52 static void track_init_metadata(struct track_entry *);
53 static void track_read_cache(void);
54
55 RB_PROTOTYPE(track_tree, track_entry, entries, track_cmp_entry)
56
57 static pthread_mutex_t track_metadata_mtx = PTHREAD_MUTEX_INITIALIZER;
58 static struct track_tree track_tree = RB_INITIALIZER(track_tree);
59 static size_t track_nentries;
60 static int track_tree_modified;
61
RB_GENERATE(track_tree,track_entry,entries,track_cmp_entry)62 RB_GENERATE(track_tree, track_entry, entries, track_cmp_entry)
63
64 static int
65 track_add_entry(struct track_entry *te)
66 {
67 if (track_nentries == SIZE_MAX)
68 return -1;
69
70 if (RB_INSERT(track_tree, &track_tree, te) != NULL) {
71 /* This should not happen. */
72 LOG_ERRX("%s: track already in tree", te->track.path);
73 return -1;
74 }
75
76 te->track.filename = strrchr(te->track.path, '/');
77 if (te->track.filename != NULL)
78 te->track.filename++;
79 else
80 te->track.filename = te->track.path;
81
82 track_nentries++;
83 return 0;
84 }
85
86 static struct track *
track_add_new_entry(char * path,const struct ip * ip)87 track_add_new_entry(char *path, const struct ip *ip)
88 {
89 struct track_entry *te;
90
91 te = xmalloc(sizeof *te);
92 te->delete = 0;
93 te->track.path = xstrdup(path);
94 te->track.ip = (ip != NULL) ? ip : plugin_find_ip(path);
95 te->track.ipdata = NULL;
96 track_init_metadata(te);
97
98 if (te->track.ip != NULL)
99 te->track.ip->get_metadata(&te->track);
100
101 if (track_add_entry(te) == -1) {
102 track_free_entry(te);
103 return NULL;
104 }
105
106 track_tree_modified = 1;
107 return &te->track;
108 }
109
110 void
track_copy_vorbis_comment(struct track * t,const char * com)111 track_copy_vorbis_comment(struct track *t, const char *com)
112 {
113 char *number, *total;
114
115 number = NULL;
116 total = NULL;
117 if (!strncasecmp(com, "album=", 6)) {
118 free(t->album);
119 t->album = xstrdup(com + 6);
120 } else if (!strncasecmp(com, "albumartist=", 12)) {
121 free(t->albumartist);
122 t->albumartist = xstrdup(com + 12);
123 } else if (!strncasecmp(com, "album artist=", 13) ||
124 !strncasecmp(com, "album_artist=", 13)) {
125 free(t->albumartist);
126 t->albumartist = xstrdup(com + 13);
127 } else if (!strncasecmp(com, "artist=", 7)) {
128 free(t->artist);
129 t->artist = xstrdup(com + 7);
130 } else if (!strncasecmp(com, "comment=", 8)) {
131 free(t->comment);
132 t->comment = xstrdup(com + 8);
133 } else if (!strncasecmp(com, "date=", 5)) {
134 free(t->date);
135 t->date = xstrdup(com + 5);
136 } else if (!strncasecmp(com, "discnumber=", 11)) {
137 track_split_tag(com + 11, &number, &total);
138 if (number != NULL) {
139 free(t->discnumber);
140 t->discnumber = number;
141 }
142 if (total != NULL) {
143 free(t->disctotal);
144 t->disctotal = total;
145 }
146 } else if (!strncasecmp(com, "disctotal=", 10)) {
147 free(t->disctotal);
148 t->disctotal = xstrdup(com + 10);
149 } else if (!strncasecmp(com, "genre=", 6)) {
150 free(t->genre);
151 t->genre = xstrdup(com + 6);
152 } else if (!strncasecmp(com, "title=", 6)) {
153 free(t->title);
154 t->title = xstrdup(com + 6);
155 } else if (!strncasecmp(com, "totaldiscs=", 11)) {
156 free(t->disctotal);
157 t->disctotal = xstrdup(com + 11);
158 } else if (!strncasecmp(com, "totaltracks=", 12)) {
159 free(t->tracktotal);
160 t->tracktotal = xstrdup(com + 12);
161 } else if (!strncasecmp(com, "tracknumber=", 12)) {
162 track_split_tag(com + 12, &number, &total);
163 if (number != NULL) {
164 free(t->tracknumber);
165 t->tracknumber = number;
166 }
167 if (total != NULL) {
168 free(t->tracktotal);
169 t->tracktotal = total;
170 }
171 } else if (!strncasecmp(com, "tracktotal=", 11)) {
172 free(t->tracktotal);
173 t->tracktotal = xstrdup(com + 11);
174 }
175 }
176
177 int
track_cmp(const struct track * t1,const struct track * t2)178 track_cmp(const struct track *t1, const struct track *t2)
179 {
180 int ret;
181 const char *artist1, *artist2;
182
183 artist1 = (t1->albumartist != NULL) ? t1->albumartist : t1->artist;
184 artist2 = (t2->albumartist != NULL) ? t2->albumartist : t2->artist;
185
186 if ((ret = track_cmp_string(artist1, artist2)))
187 return ret;
188 if ((ret = track_cmp_number(t1->date, t2->date)))
189 return ret;
190 if ((ret = track_cmp_string(t1->album, t2->album)))
191 return ret;
192 if ((ret = track_cmp_number(t1->discnumber, t2->discnumber)))
193 return ret;
194 if ((ret = track_cmp_number(t1->tracknumber, t2->tracknumber)))
195 return ret;
196 if ((ret = track_cmp_string(t1->title, t2->title)))
197 return ret;
198 return strcmp(t1->path, t2->path);
199 }
200
201 static int
track_cmp_entry(struct track_entry * t1,struct track_entry * t2)202 track_cmp_entry(struct track_entry *t1, struct track_entry *t2)
203 {
204 return strcmp(t1->track.path, t2->track.path);
205 }
206
207 static int
track_cmp_number(const char * s1,const char * s2)208 track_cmp_number(const char *s1, const char *s2)
209 {
210 int i1, i2;
211 const char *errstr;
212
213 if (s1 == NULL)
214 return (s2 == NULL) ? 0 : -1;
215 if (s2 == NULL)
216 return 1;
217
218 i1 = strtonum(s1, 0, INT_MAX, &errstr);
219 if (errstr != NULL)
220 return strcasecmp(s1, s2);
221
222 i2 = strtonum(s2, 0, INT_MAX, &errstr);
223 if (errstr != NULL)
224 return strcasecmp(s1, s2);
225
226 return (i1 < i2) ? -1 : (i1 > i2);
227 }
228
229 static int
track_cmp_string(const char * s1,const char * s2)230 track_cmp_string(const char *s1, const char *s2)
231 {
232 if (s1 == NULL)
233 return (s2 == NULL) ? 0: -1;
234 if (s2 == NULL)
235 return 1;
236 return strcasecmp(s1, s2);
237 }
238
239 void
track_end(void)240 track_end(void)
241 {
242 struct track_entry *te;
243
244 if (track_tree_modified)
245 track_write_cache();
246
247 while ((te = RB_ROOT(&track_tree)) != NULL) {
248 RB_REMOVE(track_tree, &track_tree, te);
249 track_free_entry(te);
250 }
251 }
252
253 static struct track_entry *
track_find_entry(char * path,const struct ip * ip)254 track_find_entry(char *path, const struct ip *ip)
255 {
256 struct track_entry search, *te;
257
258 search.track.path = path;
259 te = RB_FIND(track_tree, &track_tree, &search);
260 if (te != NULL && te->track.ip == NULL)
261 te->track.ip = (ip != NULL) ? ip : plugin_find_ip(path);
262 return te;
263 }
264
265 static void
track_free_entry(struct track_entry * te)266 track_free_entry(struct track_entry *te)
267 {
268 track_free_metadata(te);
269 free(te->track.path);
270 free(te);
271 }
272
273 static void
track_free_metadata(struct track_entry * te)274 track_free_metadata(struct track_entry *te)
275 {
276 free(te->track.album);
277 free(te->track.albumartist);
278 free(te->track.artist);
279 free(te->track.comment);
280 free(te->track.date);
281 free(te->track.discnumber);
282 free(te->track.disctotal);
283 free(te->track.genre);
284 free(te->track.title);
285 free(te->track.tracknumber);
286 free(te->track.tracktotal);
287 }
288
289 struct track *
track_get(char * path,const struct ip * ip)290 track_get(char *path, const struct ip *ip)
291 {
292 struct track_entry *te;
293
294 te = track_find_entry(path, ip);
295 if (te != NULL) {
296 if (te->track.ip != NULL)
297 return &te->track;
298 else {
299 msg_errx("%s: Unsupported file format", path);
300 return NULL;
301 }
302 }
303
304 if (ip == NULL) {
305 ip = plugin_find_ip(path);
306 if (ip == NULL) {
307 msg_errx("%s: Unsupported file format", path);
308 return NULL;
309 }
310 }
311
312 return track_add_new_entry(path, ip);
313 }
314
315 void
track_init(void)316 track_init(void)
317 {
318 track_read_cache();
319 }
320
321 static void
track_init_metadata(struct track_entry * te)322 track_init_metadata(struct track_entry *te)
323 {
324 te->track.album = NULL;
325 te->track.albumartist = NULL;
326 te->track.artist = NULL;
327 te->track.comment = NULL;
328 te->track.date = NULL;
329 te->track.discnumber = NULL;
330 te->track.disctotal = NULL;
331 te->track.genre = NULL;
332 te->track.title = NULL;
333 te->track.tracknumber = NULL;
334 te->track.tracktotal = NULL;
335 te->track.duration = 0;
336 }
337
338 void
track_lock_metadata(void)339 track_lock_metadata(void)
340 {
341 XPTHREAD_MUTEX_LOCK(&track_metadata_mtx);
342 }
343
344 static void
track_read_cache(void)345 track_read_cache(void)
346 {
347 struct track_entry *te;
348
349 if (cache_open(CACHE_MODE_READ) == -1)
350 return;
351
352 for (;;) {
353 te = xmalloc(sizeof *te);
354 te->delete = 0;
355 if (cache_read_entry(&te->track) == -1) {
356 track_free_entry(te);
357 break;
358 }
359 if (track_add_entry(te) == -1)
360 track_free_entry(te);
361 }
362
363 cache_close();
364 }
365
366 struct track *
track_require(char * path)367 track_require(char *path)
368 {
369 struct track_entry *te;
370
371 te = track_find_entry(path, NULL);
372 return (te != NULL) ? &te->track : track_add_new_entry(path, NULL);
373 }
374
375 int
track_search(const struct track * t,const char * search)376 track_search(const struct track *t, const char *search)
377 {
378 if (t->album != NULL && strcasestr(t->album, search))
379 return 0;
380 if (t->artist != NULL && strcasestr(t->artist, search))
381 return 0;
382 if (t->date != NULL && strcasestr(t->date, search))
383 return 0;
384 if (t->genre != NULL && strcasestr(t->genre, search))
385 return 0;
386 if (t->title != NULL && strcasestr(t->title, search))
387 return 0;
388 if (t->tracknumber != NULL && strcasestr(t->tracknumber, search))
389 return 0;
390 if (strcasestr(t->path, search))
391 return 0;
392 return -1;
393 }
394
395 void
track_split_tag(const char * tag,char ** fld1,char ** fld2)396 track_split_tag(const char *tag, char **fld1, char **fld2)
397 {
398 size_t pos;
399
400 pos = strcspn(tag, "/");
401 if (pos > 0)
402 *fld1 = xstrndup(tag, pos);
403 if (tag[pos] == '/' && tag[pos + 1] != '\0')
404 *fld2 = xstrdup(tag + pos + 1);
405 }
406
407 void
track_unlock_metadata(void)408 track_unlock_metadata(void)
409 {
410 XPTHREAD_MUTEX_UNLOCK(&track_metadata_mtx);
411 }
412
413 void
track_update_metadata(int delete)414 track_update_metadata(int delete)
415 {
416 struct track_entry *te;
417 size_t i;
418
419 i = 1;
420 RB_FOREACH(te, track_tree, &track_tree) {
421 msg_info("Updating track %zu of %zu (%zu%%)", i,
422 track_nentries, 100 * i / track_nentries);
423 i++;
424
425 if (access(te->track.path, F_OK) == -1) {
426 if (delete)
427 te->delete = 1;
428 continue;
429 }
430
431 if (te->track.ip == NULL) {
432 te->track.ip = plugin_find_ip(te->track.path);
433 if (te->track.ip == NULL) {
434 LOG_ERRX("%s: no ip found", te->track.path);
435 continue;
436 }
437 }
438
439 track_lock_metadata();
440 track_free_metadata(te);
441 track_init_metadata(te);
442 te->track.ip->get_metadata(&te->track);
443 track_unlock_metadata();
444 }
445
446 msg_clear();
447 track_tree_modified = 1;
448 }
449
450 int
track_write_cache(void)451 track_write_cache(void)
452 {
453 struct track_entry *te;
454
455 if (cache_open(CACHE_MODE_WRITE) == -1)
456 return -1;
457
458 RB_FOREACH(te, track_tree, &track_tree)
459 if (!te->delete)
460 cache_write_entry(&te->track);
461
462 cache_close();
463 track_tree_modified = 0;
464 return 0;
465 }
466