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