1 /*
2  * MOC - music on console
3  * Copyright (C) 2004,2005 Damian Pietras <daper@daper.net>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * Author of title building code: Florian Kriener <me@leflo.de>
11  *
12  * Contributors:
13  *  - Florian Kriener <me@leflo.de> - title building code
14  *  - Kamil Tarkowski <kamilt@interia.pl> - plist_prev()
15  *
16  */
17 
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
21 
22 #include <stdio.h>
23 #include <string.h>
24 #include <stdint.h>
25 #include <assert.h>
26 
27 #define DEBUG
28 
29 #include "common.h"
30 #include "playlist.h"
31 #include "log.h"
32 #include "options.h"
33 #include "files.h"
34 #include "rbtree.h"
35 #include "utf8.h"
36 #include "rcc.h"
37 
38 /* Initial size of the table */
39 #define	INIT_SIZE	64
40 
tags_free(struct file_tags * tags)41 void tags_free (struct file_tags *tags)
42 {
43 	assert (tags != NULL);
44 
45 	if (tags->title)
46 		free (tags->title);
47 	if (tags->artist)
48 		free (tags->artist);
49 	if (tags->album)
50 		free (tags->album);
51 
52 	free (tags);
53 }
54 
tags_clear(struct file_tags * tags)55 void tags_clear (struct file_tags *tags)
56 {
57 	assert (tags != NULL);
58 
59 	if (tags->title)
60 		free (tags->title);
61 	if (tags->artist)
62 		free (tags->artist);
63 	if (tags->album)
64 		free (tags->album);
65 
66 	tags->title = NULL;
67 	tags->artist = NULL;
68 	tags->album = NULL;
69 	tags->track = -1;
70 	tags->time = -1;
71 }
72 
73 /* Copy the tags data from src to dst freeing old fields if necessary. */
tags_copy(struct file_tags * dst,const struct file_tags * src)74 void tags_copy (struct file_tags *dst, const struct file_tags *src)
75 {
76 	if (dst->title)
77 		free (dst->title);
78 	dst->title = xstrdup (src->title);
79 
80 	if (dst->artist)
81 		free (dst->artist);
82 	dst->artist = xstrdup (src->artist);
83 
84 	if (dst->album)
85 		free (dst->album);
86 	dst->album = xstrdup (src->album);
87 
88 	dst->track = src->track;
89 	dst->time = src->time;
90 	dst->filled = src->filled;
91 }
92 
tags_new()93 struct file_tags *tags_new ()
94 {
95 	struct file_tags *tags;
96 
97 	tags = (struct file_tags *)xmalloc (sizeof(struct file_tags));
98 	tags->title = NULL;
99 	tags->artist = NULL;
100 	tags->album = NULL;
101 	tags->track = -1;
102 	tags->time = -1;
103 	tags->filled = 0;
104 
105 	return tags;
106 }
107 
tags_dup(const struct file_tags * tags)108 struct file_tags *tags_dup (const struct file_tags *tags)
109 {
110 	struct file_tags *dtags;
111 
112 	assert (tags != NULL);
113 
114 	dtags = tags_new();
115 	tags_copy (dtags, tags);
116 
117 	return dtags;
118 }
119 
rb_compare(const void * a,const void * b,void * adata)120 static int rb_compare (const void *a, const void *b, void *adata)
121 {
122 	struct plist *plist = (struct plist *)adata;
123 	int pos_a = (intptr_t)a;
124 	int pos_b = (intptr_t)b;
125 
126 	return strcoll (plist->items[pos_a].file, plist->items[pos_b].file);
127 }
128 
rb_fname_compare(const void * key,const void * data,void * adata)129 static int rb_fname_compare (const void *key, const void *data, void *adata)
130 {
131 	struct plist *plist = (struct plist *)adata;
132 	const char *fname = (const char *)key;
133 	const int pos = (intptr_t)data;
134 
135 	return strcoll (fname, plist->items[pos].file);
136 }
137 
138 /* Return 1 if an item has 'deleted' flag. */
plist_deleted(const struct plist * plist,const int num)139 inline int plist_deleted (const struct plist *plist, const int num)
140 {
141 	assert (LIMIT(num, plist->num));
142 
143 	return plist->items[num].deleted;
144 }
145 
146 /* Initialize the playlist. */
plist_init(struct plist * plist)147 void plist_init (struct plist *plist)
148 {
149 	plist->num = 0;
150 	plist->allocated = INIT_SIZE;
151 	plist->not_deleted = 0;
152 	plist->items = (struct plist_item *)xmalloc (sizeof(struct plist_item)
153 			* INIT_SIZE);
154 	plist->serial = -1;
155 	rb_init_tree (&plist->search_tree, rb_compare, rb_fname_compare,
156 			plist);
157 	plist->total_time = 0;
158 	plist->items_with_time = 0;
159 }
160 
161 /* Create a new playlist item with empty fields. */
plist_new_item()162 struct plist_item *plist_new_item ()
163 {
164 	struct plist_item *item;
165 
166 	item = (struct plist_item *)xmalloc (sizeof(struct plist_item));
167 	item->file = NULL;
168 	item->type = F_OTHER;
169 	item->deleted = 0;
170 	item->title_file = NULL;
171 	item->title_tags = NULL;
172 	item->tags = NULL;
173 	item->mtime = (time_t)-1;
174 	item->queue_pos = 0;
175 
176 	return item;
177 }
178 
179 /* Add a file to the list. Return the index of the item. */
plist_add(struct plist * plist,const char * file_name)180 int plist_add (struct plist *plist, const char *file_name)
181 {
182 	assert (plist != NULL);
183 	assert (plist->items != NULL);
184 
185 	if (plist->allocated == plist->num) {
186 		plist->allocated *= 2;
187 		plist->items = (struct plist_item *)xrealloc (plist->items,
188 				sizeof(struct plist_item) * plist->allocated);
189 	}
190 
191 	plist->items[plist->num].file = xstrdup (file_name);
192 	plist->items[plist->num].type = file_name ? file_type (file_name)
193 		: F_OTHER;
194 	plist->items[plist->num].deleted = 0;
195 	plist->items[plist->num].title_file = NULL;
196 	plist->items[plist->num].title_tags = NULL;
197 	plist->items[plist->num].tags = NULL;
198 	plist->items[plist->num].mtime = (file_name ? get_mtime(file_name)
199 			: (time_t)-1);
200 	plist->items[plist->num].queue_pos = 0;
201 
202 	if (file_name) {
203 		rb_delete (&plist->search_tree, file_name);
204 		rb_insert (&plist->search_tree, (void *)(intptr_t)plist->num);
205 	}
206 
207 	plist->num++;
208 	plist->not_deleted++;
209 
210 	return plist->num - 1;
211 }
212 
213 /* Copy all fields of item src to dst. */
plist_item_copy(struct plist_item * dst,const struct plist_item * src)214 void plist_item_copy (struct plist_item *dst, const struct plist_item *src)
215 {
216 	if (dst->file)
217 		free (dst->file);
218 	dst->file = xstrdup (src->file);
219 	dst->type = src->type;
220 	dst->title_file = xstrdup (src->title_file);
221 	dst->title_tags = xstrdup (src->title_tags);
222 	dst->mtime = src->mtime;
223 	dst->queue_pos = src->queue_pos;
224 
225 	if (src->tags)
226 		dst->tags = tags_dup (src->tags);
227 	else
228 		dst->tags = NULL;
229 
230 	dst->deleted = src->deleted;
231 }
232 
233 /* Get the pointer to the element on the playlist.
234  * If the item number is not valid, return NULL.
235  * Returned memory is malloced.
236  */
plist_get_file(const struct plist * plist,int i)237 char *plist_get_file (const struct plist *plist, int i)
238 {
239 	char *file = NULL;
240 
241 	assert (i >= 0);
242 	assert (plist != NULL);
243 
244 	if (i < plist->num)
245 		file = xstrdup (plist->items[i].file);
246 
247 	return file;
248 }
249 
250 /* Get the number of the next item on the list (skipping deleted items).
251  * If num == -1, get the first item.
252  * Return -1 if there are no items left.
253  */
plist_next(struct plist * plist,int num)254 int plist_next (struct plist *plist, int num)
255 {
256 	int i = num + 1;
257 
258 	assert (plist != NULL);
259 	assert (num >= -1);
260 
261 	while (i < plist->num && plist->items[i].deleted)
262 		i++;
263 
264 	return i < plist->num ? i : -1;
265 }
266 
267 /* Get the number of the previous item on the list (skipping deleted items).
268  * If num == -1, get the first item.
269  * Return -1 if it is the beginning of the playlist.
270  */
plist_prev(struct plist * plist,int num)271 int plist_prev (struct plist *plist, int num)
272 {
273 	int i = num - 1;
274 
275 	assert (plist != NULL);
276 	assert (num >= -1);
277 
278 	while (i >= 0 && plist->items[i].deleted)
279 		i--;
280 
281 	return i >= 0 ? i : -1;
282 }
283 
plist_free_item_fields(struct plist_item * item)284 void plist_free_item_fields (struct plist_item *item)
285 {
286 	if (item->file) {
287 		free (item->file);
288 		item->file = NULL;
289 	}
290 	if (item->title_tags) {
291 		free (item->title_tags);
292 		item->title_tags = NULL;
293 	}
294 	if (item->title_file) {
295 		free (item->title_file);
296 		item->title_file = NULL;
297 	}
298 	if (item->tags) {
299 		tags_free (item->tags);
300 		item->tags = NULL;
301 	}
302 }
303 
304 /* Clear the list. */
plist_clear(struct plist * plist)305 void plist_clear (struct plist *plist)
306 {
307 	int i;
308 
309 	assert (plist != NULL);
310 
311 	for (i = 0; i < plist->num; i++)
312 		plist_free_item_fields (&plist->items[i]);
313 
314 	plist->items = (struct plist_item *)xrealloc (plist->items,
315 			sizeof(struct plist_item) * INIT_SIZE);
316 	plist->allocated = INIT_SIZE;
317 	plist->num = 0;
318 	plist->not_deleted = 0;
319 	rb_clear (&plist->search_tree);
320 	plist->total_time = 0;
321 	plist->items_with_time = 0;
322 }
323 
324 /* Destroy the list freeing memory; the list can't be used after that. */
plist_free(struct plist * plist)325 void plist_free (struct plist *plist)
326 {
327 	assert (plist != NULL);
328 
329 	plist_clear (plist);
330 	free (plist->items);
331 	plist->allocated = 0;
332 	plist->items = NULL;
333 }
334 
335 /* Sort the playlist by file names. */
plist_sort_fname(struct plist * plist)336 void plist_sort_fname (struct plist *plist)
337 {
338 	struct plist_item *sorted;
339 	struct rb_node *x;
340 	int n;
341 
342 	if (plist_count(plist) == 0)
343 		return;
344 
345 	sorted = (struct plist_item *)xmalloc (plist_count(plist) *
346 			sizeof(struct plist_item));
347 
348 	x = rb_min (&plist->search_tree);
349 	assert (!rb_is_null(x));
350 
351 	while (plist_deleted(plist, (intptr_t)x->data))
352 		x = rb_next (x);
353 
354 	sorted[0] = plist->items[(intptr_t)x->data];
355 	x->data = NULL;
356 
357 	n = 1;
358 	while (!rb_is_null(x = rb_next(x))) {
359 		if (!plist_deleted(plist, (intptr_t)x->data)) {
360 			sorted[n] = plist->items[(intptr_t)x->data];
361 			x->data = (void *)(intptr_t)n++;
362 		}
363 	}
364 
365 	plist->num = n;
366 	plist->not_deleted = n;
367 
368 	memcpy (plist->items, sorted, sizeof(struct plist_item) * n);
369 	free (sorted);
370 }
371 
372 /* Find an item in the list.  Return the index or -1 if not found. */
plist_find_fname(struct plist * plist,const char * file)373 int plist_find_fname (struct plist *plist, const char *file)
374 {
375 	struct rb_node *x;
376 
377 	assert (plist != NULL);
378 
379 	x = rb_search (&plist->search_tree, file);
380 
381 	if (rb_is_null(x))
382 		return -1;
383 
384 	return !plist_deleted(plist, (intptr_t)x->data) ? (intptr_t)x->data : -1;
385 }
386 
387 /* Find an item in the list; also find deleted items.  If there is more than
388  * one item for this file, return the non-deleted one or, if all are deleted,
389  * return the last of them.  Return the index or -1 if not found. */
plist_find_del_fname(const struct plist * plist,const char * file)390 int plist_find_del_fname (const struct plist *plist, const char *file)
391 {
392 	int i;
393 	int item = -1;
394 
395 	assert (plist != NULL);
396 
397 	for (i = 0; i < plist->num; i++) {
398 		if (plist->items[i].file
399 				&& !strcmp(plist->items[i].file, file)) {
400 			if (item == -1 || plist_deleted(plist, item))
401 				item = i;
402 		}
403 	}
404 
405 	return item;
406 }
407 
408 /* Returns the next filename that is a dead entry, or NULL if there are none
409  * left.
410  *
411  * It will set the index on success.
412  */
plist_get_next_dead_entry(const struct plist * plist,int * last_index)413 const char *plist_get_next_dead_entry (const struct plist *plist,
414                                        int *last_index)
415 {
416 	int i;
417 
418 	assert (last_index != NULL);
419 	assert (plist != NULL);
420 
421 	for (i = *last_index; i < plist->num; i++) {
422 		if (plist->items[i].file
423 			  && ! plist_deleted(plist, i)
424 			  && ! can_read_file(plist->items[i].file)) {
425 			*last_index = i + 1;
426 			return plist->items[i].file;
427 		}
428 	}
429 
430 	return NULL;
431 }
432 
433 #define if_not_empty(str)	(tags && (str) && (*str) ? (str) : NULL)
434 
title_expn_subs(char fmt,const struct file_tags * tags)435 static char *title_expn_subs(char fmt, const struct file_tags *tags)
436 {
437 	static char track[16];
438 
439 	switch (fmt) {
440 		case 'n':
441 			if (!tags || tags->track == -1)
442 				break;
443 			snprintf (track, sizeof(track), "%d", tags->track);
444 			return track;
445 		case 'a':
446 			return if_not_empty (tags->artist);
447 		case 'A':
448 			return if_not_empty (tags->album);
449 		case 't':
450 			return if_not_empty (tags->title);
451 		default:
452 			fatal ("Error parsing format string!");
453 	}
454 
455 	return NULL;
456 }
457 
458 /* Generate a title from fmt. */
459 #define check_zero(x) if((x) == '\0') \
460 		fatal ("Unexpected end of title expression!")
461 
do_title_expn(char * dest,int size,const char * fmt,const struct file_tags * tags)462 static void do_title_expn (char *dest, int size, const char *fmt,
463 		const struct file_tags *tags)
464 {
465 	const char *h;
466 	int free = --size;
467 	short escape = 0;
468 
469 	dest[0] = 0;
470 
471 	while (free > 0 && *fmt) {
472 		if (*fmt == '%' && !escape) {
473 			check_zero(*++fmt);
474 
475 			/* do ternary expansion
476 			 * format: %(x:true:false)
477 			 */
478 			if (*fmt == '(') {
479 				char separator, expr[256];
480 				int expr_pos = 0;
481 
482 				check_zero(*++fmt);
483 				h = title_expn_subs(*fmt, tags);
484 
485 				check_zero(*++fmt);
486 				separator = *fmt;
487 
488 				check_zero(*++fmt);
489 
490 				if(h) { /* true */
491 
492 					/* copy the expression */
493 					while (escape || *fmt != separator) {
494 						if (expr_pos == sizeof(expr)-2)
495 							fatal ("Nested ternary expression too long!");
496 						expr[expr_pos++] = *fmt;
497 						if (*fmt == '\\')
498 							escape = 1;
499 						else
500 							escape = 0;
501 						check_zero(*++fmt);
502 					}
503 					expr[expr_pos] = '\0';
504 
505 					/* eat the rest */
506 					while (escape || *fmt != ')') {
507 						if (escape)
508 							escape = 0;
509 						else if (*fmt == '\\')
510 							escape = 1;
511 						check_zero(*++fmt);
512 					}
513 				}
514 				else { /* false */
515 
516 					/* eat the truth :-) */
517 					while (escape || *fmt != separator) {
518 						if (escape)
519 							escape = 0;
520 						else if (*fmt == '\\')
521 							escape = 1;
522 						check_zero(*++fmt);
523 					}
524 
525 					check_zero(*++fmt);
526 
527 					/* copy the expression */
528 					while (escape || *fmt != ')') {
529 						if (expr_pos == sizeof(expr)-2)
530 							fatal ("Ternary expression too long!");
531 						expr[expr_pos++] = *fmt;
532 						if (*fmt == '\\')
533 							escape = 1;
534 						else
535 							escape = 0;
536 						check_zero(*++fmt);
537 					}
538 					expr[expr_pos] = '\0';
539 				}
540 
541 				do_title_expn((dest + size - free),
542 					      free, expr, tags);
543 				free -= strlen(dest + size - free);
544 			}
545 			else {
546 				h = title_expn_subs(*fmt, tags);
547 
548 				if (h) {
549 					strncat(dest, h, free-1);
550 					free -= strlen (h);
551 				}
552 			}
553 		}
554 		else if (*fmt == '\\' && !escape)
555 			escape = 1;
556 		else {
557 			dest[size - free] = *fmt;
558 			dest[size - free + 1] = 0;
559 			--free;
560 			escape = 0;
561 		}
562 		fmt++;
563 	}
564 
565 	free = MAX(free, 0); /* Possible integer overflow? */
566 	dest[size - free] = '\0';
567 }
568 
569 /* Build file title from struct file_tags. Returned memory is malloc()ed. */
build_title_with_format(const struct file_tags * tags,const char * fmt)570 char *build_title_with_format (const struct file_tags *tags, const char *fmt)
571 {
572 	char title[512];
573 
574 	do_title_expn (title, sizeof(title), fmt, tags);
575 	return xstrdup (title);
576 }
577 
578 /* Build file title from struct file_tags. Returned memory is malloc()ed. */
build_title(const struct file_tags * tags)579 char *build_title (const struct file_tags *tags)
580 {
581 	return build_title_with_format (tags, options_get_str ("FormatString"));
582 }
583 
584 /* Copy the item to the playlist. Return the index of the added item. */
plist_add_from_item(struct plist * plist,const struct plist_item * item)585 int plist_add_from_item (struct plist *plist, const struct plist_item *item)
586 {
587 	int pos = plist_add (plist, item->file);
588 
589 	plist_item_copy (&plist->items[pos], item);
590 
591 	if (item->tags && item->tags->time != -1) {
592 		plist->total_time += item->tags->time;
593 		plist->items_with_time++;
594 	}
595 
596 	return pos;
597 }
598 
plist_delete(struct plist * plist,const int num)599 void plist_delete (struct plist *plist, const int num)
600 {
601 	assert (plist != NULL);
602 	assert (!plist->items[num].deleted);
603 	assert (plist->not_deleted > 0);
604 
605 	if (num < plist->num) {
606 
607 		/* Free every field except the file, it is needed in deleted
608 		 * items. */
609 		char *file = plist->items[num].file;
610 
611 		plist->items[num].file = NULL;
612 
613 		if (plist->items[num].tags
614 				&& plist->items[num].tags->time != -1) {
615 			plist->total_time -= plist->items[num].tags->time;
616 			plist->items_with_time--;
617 		}
618 
619 		plist_free_item_fields (&plist->items[num]);
620 		plist->items[num].file = file;
621 
622 		plist->items[num].deleted = 1;
623 
624 		plist->not_deleted--;
625 	}
626 }
627 
628 /* Count non-deleted items. */
plist_count(const struct plist * plist)629 int plist_count (const struct plist *plist)
630 {
631 	assert (plist != NULL);
632 
633 	return plist->not_deleted;
634 }
635 
636 /* Set tags title of an item. */
plist_set_title_tags(struct plist * plist,const int num,const char * title)637 void plist_set_title_tags (struct plist *plist, const int num,
638 		const char *title)
639 {
640 	assert (LIMIT(num, plist->num));
641 
642 	if (plist->items[num].title_tags)
643 		free (plist->items[num].title_tags);
644 	plist->items[num].title_tags = xstrdup (title);
645 }
646 
647 /* Set file title of an item. */
plist_set_title_file(struct plist * plist,const int num,const char * title)648 void plist_set_title_file (struct plist *plist, const int num,
649 		const char *title)
650 {
651 	assert (LIMIT(num, plist->num));
652 
653 	if (plist->items[num].title_file)
654 		free (plist->items[num].title_file);
655 
656 #ifdef  HAVE_RCC
657 	if (options_get_int ("UseRCCForFilesystem")) {
658 		char *t_str = xstrdup (title);
659 		plist->items[num].title_file = rcc_reencode (t_str);
660 		return;
661 	}
662 #endif
663 
664 	plist->items[num].title_file = xstrdup (title);
665 }
666 
667 /* Set file for an item. */
plist_set_file(struct plist * plist,const int num,const char * file)668 void plist_set_file (struct plist *plist, const int num, const char *file)
669 {
670 	assert (LIMIT(num, plist->num));
671 	assert (file != NULL);
672 
673 	if (plist->items[num].file) {
674 		rb_delete (&plist->search_tree, file);
675 		free (plist->items[num].file);
676 		plist->items[num].type = file_type (file);
677 	}
678 
679 	plist->items[num].file = xstrdup (file);
680 	plist->items[num].type = file_type (file);
681 	plist->items[num].mtime = get_mtime (file);
682 	rb_insert (&plist->search_tree, (void *)(intptr_t)num);
683 }
684 
685 /* Add the content of playlist b to a by copying items. */
plist_cat(struct plist * a,struct plist * b)686 void plist_cat (struct plist *a, struct plist *b)
687 {
688 	int i;
689 
690 	assert (a != NULL);
691 	assert (b != NULL);
692 
693 	for (i = 0; i < b->num; i++) {
694 		assert (b->items[i].file != NULL);
695 
696 		if (!plist_deleted(b, i)
697 				&& plist_find_fname(a, b->items[i].file) == -1)
698 			plist_add_from_item (a, &b->items[i]);
699 	}
700 }
701 
702 /* Set the time tags field for the item. */
plist_set_item_time(struct plist * plist,const int num,const int time)703 void plist_set_item_time (struct plist *plist, const int num, const int time)
704 {
705 	int old_time;
706 
707 	assert (plist != NULL);
708 	assert (LIMIT(num, plist->num));
709 
710 	if (!plist->items[num].tags) {
711 		plist->items[num].tags = tags_new ();
712 		old_time = -1;
713 	}
714 	else if (plist->items[num].tags->time != -1)
715 		old_time = plist->items[num].tags->time;
716 	else
717 		old_time = -1;
718 
719 	if (old_time != -1) {
720 		plist->total_time -= old_time;
721 		plist->items_with_time--;
722 	}
723 
724 	if (time != -1) {
725 		plist->total_time += time;
726 		plist->items_with_time++;
727 	}
728 
729 	plist->items[num].tags->time = time;
730 	plist->items[num].tags->filled |= TAGS_TIME;
731 }
732 
get_item_time(const struct plist * plist,const int i)733 int get_item_time (const struct plist *plist, const int i)
734 {
735 	assert (plist != NULL);
736 
737 	if (plist->items[i].tags)
738 		return plist->items[i].tags->time;
739 
740 	return -1;
741 }
742 
743 /* Return the total time of all files on the playlist having the time tag.
744  * If the time information is missing for any file, all_files is set to 0,
745  * otherwise 1.
746  * Returned value is that counted by plist_count_time(), so may be not
747  * up-to-date. */
plist_total_time(const struct plist * plist,int * all_files)748 int plist_total_time (const struct plist *plist, int *all_files)
749 {
750 	*all_files = plist->not_deleted == plist->items_with_time;
751 
752 	return plist->total_time;
753 }
754 
755 /* Swap two items on the playlist. */
plist_swap(struct plist * plist,const int a,const int b)756 static void plist_swap (struct plist *plist, const int a, const int b)
757 {
758 	assert (plist != NULL);
759 	assert (LIMIT(a, plist->num));
760 	assert (LIMIT(b, plist->num));
761 
762 	if (a != b) {
763 		struct plist_item t;
764 
765 		t = plist->items[a];
766 		plist->items[a] = plist->items[b];
767 		plist->items[b] = t;
768 	}
769 }
770 
771 /* Shuffle the playlist. */
plist_shuffle(struct plist * plist)772 void plist_shuffle (struct plist *plist)
773 {
774 	int i;
775 
776 	for (i = 0; i < plist->num; i++)
777 		plist_swap (plist, i,
778 				(rand()/(float)RAND_MAX) * (plist->num - 1));
779 
780 	rb_clear (&plist->search_tree);
781 
782 	for (i = 0; i < plist->num; i++)
783 		rb_insert (&plist->search_tree, (void *)(intptr_t)i);
784 }
785 
786 /* Swap the first item on the playlist with the item with file fname. */
plist_swap_first_fname(struct plist * plist,const char * fname)787 void plist_swap_first_fname (struct plist *plist, const char *fname)
788 {
789 	int i;
790 
791 	assert (plist != NULL);
792 	assert (fname != NULL);
793 
794 	i = plist_find_fname (plist, fname);
795 
796 	if (i != -1 && i != 0) {
797 		rb_delete (&plist->search_tree, fname);
798 		rb_delete (&plist->search_tree, plist->items[0].file);
799 		plist_swap (plist, 0, i);
800 		rb_insert (&plist->search_tree, NULL);
801 		rb_insert (&plist->search_tree, (void *)(intptr_t)i);
802 	}
803 }
804 
plist_set_serial(struct plist * plist,const int serial)805 void plist_set_serial (struct plist *plist, const int serial)
806 {
807 	plist->serial = serial;
808 }
809 
plist_get_serial(const struct plist * plist)810 int plist_get_serial (const struct plist *plist)
811 {
812 	return plist->serial;
813 }
814 
815 /* Return the index of the last non-deleted item from the playlist.
816  * Return -1 if there are no items. */
plist_last(const struct plist * plist)817 int plist_last (const struct plist *plist)
818 {
819 	int i;
820 
821 	i = plist->num - 1;
822 
823 	while (i > 0 && plist_deleted(plist, i))
824 		i--;
825 
826 	return i;
827 }
828 
plist_file_type(const struct plist * plist,const int num)829 enum file_type plist_file_type (const struct plist *plist, const int num)
830 {
831 	assert (plist != NULL);
832 	assert (num < plist->num);
833 
834 	return plist->items[num].type;
835 }
836 
837 /* Remove items from playlist 'a' that are also present on playlist 'b'. */
plist_remove_common_items(struct plist * a,struct plist * b)838 void plist_remove_common_items (struct plist *a, struct plist *b)
839 {
840 	int i;
841 
842 	assert (a != NULL);
843 	assert (b != NULL);
844 
845 	for (i = 0; i < a->num; i++)
846 		if (plist_find_fname(b, a->items[i].file) != -1)
847 			plist_delete (a, i);
848 }
849 
plist_discard_tags(struct plist * plist)850 void plist_discard_tags (struct plist *plist)
851 {
852 	int i;
853 
854 	assert (plist != NULL);
855 
856 	for (i = 0; i < plist->num; i++)
857 		if (!plist_deleted(plist, i) && plist->items[i].tags) {
858 			tags_free (plist->items[i].tags);
859 			plist->items[i].tags = NULL;
860 		}
861 
862 	plist->items_with_time = 0;
863 	plist->total_time = 0;
864 }
865 
plist_set_tags(struct plist * plist,const int num,const struct file_tags * tags)866 void plist_set_tags (struct plist *plist, const int num,
867 		const struct file_tags *tags)
868 {
869 	int old_time;
870 
871 	assert (plist != NULL);
872 	assert (LIMIT(num, plist->num));
873 	assert (tags != NULL);
874 
875 	if (plist->items[num].tags && plist->items[num].tags->time != -1)
876 		old_time = plist->items[num].tags->time;
877 	else
878 		old_time = -1;
879 
880 	if (plist->items[num].tags)
881 		tags_free (plist->items[num].tags);
882 	plist->items[num].tags = tags_dup (tags);
883 
884 	if (old_time != -1) {
885 		plist->total_time -= old_time;
886 		plist->items_with_time--;
887 	}
888 
889 	if (tags->time != -1) {
890 		plist->total_time += tags->time;
891 		plist->items_with_time++;
892 	}
893 }
894 
plist_get_tags(const struct plist * plist,const int num)895 struct file_tags *plist_get_tags (const struct plist *plist, const int num)
896 {
897 	assert (plist != NULL);
898 	assert (LIMIT(num, plist->num));
899 
900 	if (plist->items[num].tags)
901 		return tags_dup (plist->items[num].tags);
902 
903 	return NULL;
904 }
905 
906 /* Swap two files on the playlist. */
plist_swap_files(struct plist * plist,const char * file1,const char * file2)907 void plist_swap_files (struct plist *plist, const char *file1,
908 		const char *file2)
909 {
910 	struct rb_node *x1, *x2;
911 
912 	assert (plist != NULL);
913 	assert (file1 != NULL);
914 	assert (file2 != NULL);
915 
916 	x1 = rb_search (&plist->search_tree, file1);
917 	x2 = rb_search (&plist->search_tree, file2);
918 
919 	if (!rb_is_null(x1) && !rb_is_null(x2)) {
920 		void *t;
921 
922 		plist_swap (plist, (intptr_t)x1->data, (intptr_t)x2->data);
923 
924 		t = x1->data;
925 		x1->data = x2->data;
926 		x2->data = t;
927 	}
928 }
929 
930 /* Return the position of a file in the list, starting with 1. */
plist_get_position(const struct plist * plist,int num)931 int plist_get_position (const struct plist *plist, int num)
932 {
933 	int i, pos = 1;
934 
935 	assert (LIMIT(num, plist->num));
936 
937 	for (i = 0; i < num; i++) {
938 		if(!plist->items[i].deleted)
939 			pos++;
940 	}
941 
942 	return pos;
943 }
944