1 /*
2  * Copyright 2008-2013 Various Authors
3  * Copyright 2004-2005 Timo Hirvonen
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "track_info.h"
20 #include "comment.h"
21 #include "uchar.h"
22 #include "u_collate.h"
23 #include "misc.h"
24 #include "xmalloc.h"
25 #include "utils.h"
26 #include "debug.h"
27 #include "path.h"
28 #include "ui_curses.h"
29 
30 #include <string.h>
31 #include <stdatomic.h>
32 #include <math.h>
33 
34 struct track_info_priv {
35 	struct track_info ti;
36 	_Atomic uint32_t ref_count;
37 };
38 
track_info_to_priv(struct track_info * ti)39 static struct track_info_priv *track_info_to_priv(struct track_info *ti)
40 {
41 	return container_of(ti, struct track_info_priv, ti);
42 }
43 
track_info_new(const char * filename)44 struct track_info *track_info_new(const char *filename)
45 {
46 	static _Atomic uint64_t cur_uid = ATOMIC_VAR_INIT(1);
47 	uint64_t uid = atomic_fetch_add_explicit(&cur_uid, 1, memory_order_relaxed);
48 	BUG_ON(uid == 0);
49 
50 	struct track_info_priv *priv = xnew(struct track_info_priv, 1);
51 	atomic_init(&priv->ref_count, 1);
52 
53 	struct track_info *ti = &priv->ti;
54 	ti->uid = uid;
55 	ti->filename = xstrdup(filename);
56 	ti->play_count = 0;
57 	ti->comments = NULL;
58 	ti->bpm = -1;
59 	ti->codec = NULL;
60 	ti->codec_profile = NULL;
61 
62 	return ti;
63 }
64 
track_info_set_comments(struct track_info * ti,struct keyval * comments)65 void track_info_set_comments(struct track_info *ti, struct keyval *comments) {
66 	long int r128_track_gain;
67 	long int r128_album_gain;
68 
69 	ti->comments = comments;
70 	ti->artist = keyvals_get_val(comments, "artist");
71 	ti->album = keyvals_get_val(comments, "album");
72 	ti->title = keyvals_get_val(comments, "title");
73 	ti->tracknumber = comments_get_int(comments, "tracknumber");
74 	ti->discnumber = comments_get_int(comments, "discnumber");
75 	ti->date = comments_get_date(comments, "date");
76 	ti->originaldate = comments_get_date(comments, "originaldate");
77 	ti->genre = keyvals_get_val(comments, "genre");
78 	ti->comment = keyvals_get_val(comments, "comment");
79 	ti->albumartist = comments_get_albumartist(comments);
80 	ti->artistsort = comments_get_artistsort(comments);
81 	ti->albumsort = keyvals_get_val(comments, "albumsort");
82 	ti->is_va_compilation = track_is_va_compilation(comments);
83 	ti->media = keyvals_get_val(comments, "media");
84 
85 	int bpm = comments_get_int(comments, "bpm");
86 	if (ti->bpm == 0 || ti->bpm == -1) {
87 		ti->bpm = bpm;
88 	}
89 
90 	if (ti->artist == NULL && ti->albumartist != NULL) {
91 		/* best guess */
92 		ti->artist = ti->albumartist;
93 	}
94 
95 	if (track_info_has_tag(ti) && ti->title == NULL) {
96 		/* best guess */
97 		ti->title = path_basename(ti->filename);
98 	}
99 
100 	ti->rg_track_gain = comments_get_double(comments, "replaygain_track_gain");
101 	ti->rg_track_peak = comments_get_double(comments, "replaygain_track_peak");
102 	ti->rg_album_gain = comments_get_double(comments, "replaygain_album_gain");
103 	ti->rg_album_peak = comments_get_double(comments, "replaygain_album_peak");
104 
105 	if (comments_get_signed_int(comments, "r128_track_gain", &r128_track_gain) != -1) {
106 		ti->rg_track_gain = (r128_track_gain / 256.0) + 5;
107 	}
108 
109 	if (comments_get_signed_int(comments, "r128_album_gain", &r128_album_gain) != -1) {
110 		ti->rg_album_gain = (r128_album_gain / 256.0) + 5;
111 	}
112 
113 	ti->collkey_artist = u_strcasecoll_key0(ti->artist);
114 	ti->collkey_album = u_strcasecoll_key0(ti->album);
115 	ti->collkey_title = u_strcasecoll_key0(ti->title);
116 	ti->collkey_genre = u_strcasecoll_key0(ti->genre);
117 	ti->collkey_comment = u_strcasecoll_key0(ti->comment);
118 	ti->collkey_albumartist = u_strcasecoll_key0(ti->albumartist);
119 }
120 
track_info_ref(struct track_info * ti)121 void track_info_ref(struct track_info *ti)
122 {
123 	struct track_info_priv *priv = track_info_to_priv(ti);
124 	atomic_fetch_add_explicit(&priv->ref_count, 1, memory_order_relaxed);
125 }
126 
track_info_unref(struct track_info * ti)127 void track_info_unref(struct track_info *ti)
128 {
129 	struct track_info_priv *priv = track_info_to_priv(ti);
130 	uint32_t prev = atomic_fetch_sub_explicit(&priv->ref_count, 1,
131 			memory_order_acq_rel);
132 	if (prev == 1) {
133 		keyvals_free(ti->comments);
134 		free(ti->filename);
135 		free(ti->codec);
136 		free(ti->codec_profile);
137 		free(ti->collkey_artist);
138 		free(ti->collkey_album);
139 		free(ti->collkey_title);
140 		free(ti->collkey_genre);
141 		free(ti->collkey_comment);
142 		free(ti->collkey_albumartist);
143 		free(priv);
144 	}
145 }
146 
track_info_unique_ref(struct track_info * ti)147 bool track_info_unique_ref(struct track_info *ti)
148 {
149 	struct track_info_priv *priv = track_info_to_priv(ti);
150 	return atomic_load_explicit(&priv->ref_count, memory_order_relaxed) == 1;
151 }
152 
track_info_has_tag(const struct track_info * ti)153 int track_info_has_tag(const struct track_info *ti)
154 {
155 	return ti->artist || ti->album || ti->title;
156 }
157 
match_word(const struct track_info * ti,const char * word,unsigned int flags)158 static inline int match_word(const struct track_info *ti, const char *word, unsigned int flags)
159 {
160 	return ((flags & TI_MATCH_ARTIST) && ti->artist && u_strcasestr_base(ti->artist, word)) ||
161 	       ((flags & TI_MATCH_ALBUM) && ti->album && u_strcasestr_base(ti->album, word)) ||
162 	       ((flags & TI_MATCH_TITLE) && ti->title && u_strcasestr_base(ti->title, word)) ||
163 	       ((flags & TI_MATCH_ALBUMARTIST) && ti->albumartist && u_strcasestr_base(ti->albumartist, word));
164 }
165 
flags_set(const struct track_info * ti,unsigned int flags)166 static inline int flags_set(const struct track_info *ti, unsigned int flags)
167 {
168 	return ((flags & TI_MATCH_ARTIST) && ti->artist) ||
169 	       ((flags & TI_MATCH_ALBUM) && ti->album) ||
170 	       ((flags & TI_MATCH_TITLE) && ti->title) ||
171 	       ((flags & TI_MATCH_ALBUMARTIST) && ti->albumartist);
172 }
173 
track_info_matches_full(const struct track_info * ti,const char * text,unsigned int flags,unsigned int exclude_flags,int match_all_words)174 int track_info_matches_full(const struct track_info *ti, const char *text,
175 		unsigned int flags, unsigned int exclude_flags, int match_all_words)
176 {
177 	char **words;
178 	int i, matched = 0;
179 
180 	words = get_words(text);
181 	for (i = 0; words[i]; i++) {
182 		const char *word = words[i];
183 
184 		matched = 0;
185 		if (flags_set(ti, flags) && match_word(ti, word, flags)) {
186 			matched = 1;
187 		} else {
188 			/* compare with url or filename without path */
189 			const char *filename = ti->filename;
190 
191 			if (!is_url(filename))
192 				filename = path_basename(filename);
193 
194 			if (u_strcasestr_filename(filename, word))
195 				matched = 1;
196 		}
197 
198 		if (flags_set(ti, exclude_flags) && match_word(ti, word, exclude_flags))
199 			matched = 0;
200 
201 		if (match_all_words ? !matched : matched)
202 			break;
203 
204 	}
205 	free_str_array(words);
206 	return matched;
207 }
208 
track_info_matches(const struct track_info * ti,const char * text,unsigned int flags)209 int track_info_matches(const struct track_info *ti, const char *text, unsigned int flags)
210 {
211 	return track_info_matches_full(ti, text, flags, 0, 1);
212 }
213 
doublecmp0(double a,double b)214 static int doublecmp0(double a, double b)
215 {
216 	double x;
217 	/* fast check for NaN */
218 	int r = (b != b) - (a != a);
219 	if (r)
220 		return r;
221 	x = a - b;
222 	return (x > 0) - (x < 0);
223 }
224 
225 /* this function gets called *a lot*, it must be very fast */
track_info_cmp(const struct track_info * a,const struct track_info * b,const sort_key_t * keys)226 int track_info_cmp(const struct track_info *a, const struct track_info *b, const sort_key_t *keys)
227 {
228 	int i, rev = 0, res = 0;
229 
230 	for (i = 0; keys[i] != SORT_INVALID; i++) {
231 		sort_key_t key = keys[i];
232 		const char *av, *bv;
233 
234 		rev = 0;
235 		if (key >= REV_SORT__START) {
236 			rev = 1;
237 			key -= REV_SORT__START;
238 		}
239 
240 		switch (key) {
241 		case SORT_TRACKNUMBER:
242 		case SORT_DISCNUMBER:
243 		case SORT_DATE:
244 		case SORT_ORIGINALDATE:
245 		case SORT_PLAY_COUNT:
246 		case SORT_BPM:
247 			res = getentry(a, key, int) - getentry(b, key, int);
248 			break;
249 		case SORT_FILEMTIME:
250 			res = a->mtime - b->mtime;
251 			break;
252 		case SORT_FILENAME:
253 			/* NOTE: filenames are not necessarily UTF-8 */
254 			res = strcoll(a->filename, b->filename);
255 			break;
256 		case SORT_RG_TRACK_GAIN:
257 		case SORT_RG_TRACK_PEAK:
258 		case SORT_RG_ALBUM_GAIN:
259 		case SORT_RG_ALBUM_PEAK:
260 			res = doublecmp0(getentry(a, key, double), getentry(b, key, double));
261 			break;
262 		case SORT_BITRATE:
263 			res = getentry(a, key, long) - getentry(b, key, long);
264 			break;
265 		default:
266 			av = getentry(a, key, const char *);
267 			bv = getentry(b, key, const char *);
268 			res = strcmp0(av, bv);
269 			break;
270 		}
271 
272 		if (res)
273 			break;
274 	}
275 	return rev ? -res : res;
276 }
277 
278 static const struct {
279 	const char *str;
280 	sort_key_t key;
281 } sort_key_map[] = {
282 	{ "artist",		SORT_ARTIST		},
283 	{ "album",		SORT_ALBUM		},
284 	{ "title",		SORT_TITLE		},
285 	{ "play_count",		SORT_PLAY_COUNT		},
286 	{ "tracknumber",	SORT_TRACKNUMBER	},
287 	{ "discnumber",		SORT_DISCNUMBER		},
288 	{ "date",		SORT_DATE		},
289 	{ "originaldate",	SORT_ORIGINALDATE	},
290 	{ "genre",		SORT_GENRE		},
291 	{ "comment",		SORT_COMMENT		},
292 	{ "albumartist",	SORT_ALBUMARTIST	},
293 	{ "filename",		SORT_FILENAME		},
294 	{ "filemtime",		SORT_FILEMTIME		},
295 	{ "rg_track_gain",	SORT_RG_TRACK_GAIN	},
296 	{ "rg_track_peak",	SORT_RG_TRACK_PEAK	},
297 	{ "rg_album_gain",	SORT_RG_ALBUM_GAIN	},
298 	{ "rg_album_peak",	SORT_RG_ALBUM_PEAK	},
299 	{ "bitrate",		SORT_BITRATE		},
300 	{ "codec",		SORT_CODEC		},
301 	{ "codec_profile",	SORT_CODEC_PROFILE	},
302 	{ "media",		SORT_MEDIA		},
303 	{ "bpm",		SORT_BPM		},
304 	{ "-artist",		REV_SORT_ARTIST		},
305 	{ "-album",		REV_SORT_ALBUM		},
306 	{ "-title",		REV_SORT_TITLE		},
307 	{ "-play_count", 	REV_SORT_PLAY_COUNT	},
308 	{ "-tracknumber",	REV_SORT_TRACKNUMBER	},
309 	{ "-discnumber",	REV_SORT_DISCNUMBER	},
310 	{ "-date",		REV_SORT_DATE		},
311 	{ "-originaldate",	REV_SORT_ORIGINALDATE	},
312 	{ "-genre",		REV_SORT_GENRE		},
313 	{ "-comment",		REV_SORT_COMMENT	},
314 	{ "-albumartist",	REV_SORT_ALBUMARTIST	},
315 	{ "-filename",		REV_SORT_FILENAME	},
316 	{ "-filemtime",		REV_SORT_FILEMTIME	},
317 	{ "-rg_track_gain",	REV_SORT_RG_TRACK_GAIN	},
318 	{ "-rg_track_peak",	REV_SORT_RG_TRACK_PEAK	},
319 	{ "-rg_album_gain",	REV_SORT_RG_ALBUM_GAIN	},
320 	{ "-rg_album_peak",	REV_SORT_RG_ALBUM_PEAK	},
321 	{ "-bitrate",		REV_SORT_BITRATE	},
322 	{ "-codec",		REV_SORT_CODEC		},
323 	{ "-codec_profile",	REV_SORT_CODEC_PROFILE	},
324 	{ "-media",		REV_SORT_MEDIA		},
325 	{ "-bpm",		REV_SORT_BPM		},
326 	{ NULL,                 SORT_INVALID            }
327 };
328 
parse_sort_keys(const char * value)329 sort_key_t *parse_sort_keys(const char *value)
330 {
331 	sort_key_t *keys;
332 	const char *s, *e;
333 	int size = 4;
334 	int pos = 0;
335 
336 	keys = xnew(sort_key_t, size);
337 
338 	s = value;
339 	while (1) {
340 		char buf[32];
341 		int i, len;
342 
343 		while (*s == ' ')
344 			s++;
345 
346 		e = s;
347 		while (*e && *e != ' ')
348 			e++;
349 
350 		len = e - s;
351 		if (len == 0)
352 			break;
353 		if (len > 31)
354 			len = 31;
355 
356 		memcpy(buf, s, len);
357 		buf[len] = 0;
358 		s = e;
359 
360 		for (i = 0; ; i++) {
361 			if (sort_key_map[i].str == NULL) {
362 				error_msg("invalid sort key '%s'", buf);
363 				free(keys);
364 				return NULL;
365 			}
366 
367 			if (strcmp(buf, sort_key_map[i].str) == 0)
368 				break;
369 		}
370 		if (pos == size - 1) {
371 			size *= 2;
372 			keys = xrenew(sort_key_t, keys, size);
373 		}
374 		keys[pos++] = sort_key_map[i].key;
375 	}
376 	keys[pos] = SORT_INVALID;
377 	return keys;
378 }
379 
sort_key_to_str(sort_key_t key)380 const char *sort_key_to_str(sort_key_t key)
381 {
382 	int i;
383 	for (i = 0; sort_key_map[i].str; i++) {
384 		if (sort_key_map[i].key == key)
385 			return sort_key_map[i].str;
386 	}
387 	return NULL;
388 }
389 
sort_keys_to_str(const sort_key_t * keys,char * buf,size_t bufsize)390 void sort_keys_to_str(const sort_key_t *keys, char *buf, size_t bufsize)
391 {
392 	int i, pos = 0;
393 
394 	for (i = 0; keys[i] != SORT_INVALID; i++) {
395 		const char *key = sort_key_to_str(keys[i]);
396 		int len = strlen(key);
397 
398 		if ((int)bufsize - pos - len - 2 < 0)
399 			break;
400 
401 		memcpy(buf + pos, key, len);
402 		pos += len;
403 		buf[pos++] = ' ';
404 	}
405 	if (pos > 0)
406 		pos--;
407 	buf[pos] = 0;
408 }
409