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