1 /*
2 This file is part of Deadbeef Player source code
3 http://deadbeef.sourceforge.net
4
5 playlist management
6
7 Copyright (C) 2009-2013 Alexey Yakovenko
8
9 This software is provided 'as-is', without any express or implied
10 warranty. In no event will the authors be held liable for any damages
11 arising from the use of this software.
12
13 Permission is granted to anyone to use this software for any purpose,
14 including commercial applications, and to alter it and redistribute it
15 freely, subject to the following restrictions:
16
17 1. The origin of this software must not be misrepresented; you must not
18 claim that you wrote the original software. If you use this software
19 in a product, an acknowledgment in the product documentation would be
20 appreciated but is not required.
21 2. Altered source versions must be plainly marked as such, and must not be
22 misrepresented as being the original software.
23 3. This notice may not be removed or altered from any source distribution.
24
25 Alexey Yakovenko waker@users.sourceforge.net
26 */
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30 #ifdef HAVE_ALLOCA_H
31 # include <alloca.h>
32 #endif
33 #include <stdlib.h>
34 #include <string.h>
35 #include <dirent.h>
36 #include <fnmatch.h>
37 #include <stdio.h>
38 #include <ctype.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <unistd.h>
42 #include <assert.h>
43 #include <time.h>
44 #include <sys/time.h>
45 #ifndef __linux__
46 #define _POSIX_C_SOURCE 1
47 #endif
48 #include <limits.h>
49 #include <errno.h>
50 #include <math.h>
51 #include "gettext.h"
52 #include "playlist.h"
53 #include "streamer.h"
54 #include "messagepump.h"
55 #include "plugins.h"
56 #include "junklib.h"
57 #include "vfs.h"
58 #include "conf.h"
59 #include "utf8.h"
60 #include "common.h"
61 #include "threading.h"
62 #include "metacache.h"
63 #include "volume.h"
64 #include "pltmeta.h"
65 #include "escape.h"
66 #include "strdupa.h"
67 #include "tf.h"
68 #include "playqueue.h"
69
70 // disable custom title function, until we have new title formatting (0.7)
71 #define DISABLE_CUSTOM_TITLE
72
73 #define DISABLE_LOCKING 0
74 #define DEBUG_LOCKING 0
75 #define DETECT_PL_LOCK_RC 0
76
77 // file format revision history
78 // 1.1->1.2 changelog:
79 // added flags field
80 // 1.0->1.1 changelog:
81 // added sample-accurate seek positions for sub-tracks
82 // 1.1->1.2 changelog:
83 // added flags field
84 // 1.2->1.3 changelog:
85 // removed legacy data used for compat with 0.4.4
86 // note: ddb-0.5.0 should keep using 1.2 playlist format
87 // 1.3 support is designed for transition to ddb-0.6.0
88 #define PLAYLIST_MAJOR_VER 1
89 #define PLAYLIST_MINOR_VER 2
90
91 #if (PLAYLIST_MINOR_VER<2)
92 #error writing playlists in format <1.2 is not supported
93 #endif
94
95 //#define trace(...) { fprintf(stderr, __VA_ARGS__); }
96 #define trace(fmt,...)
97
98 #define SKIP_BLANK_CUE_TRACKS 0
99 #define MAX_CUE_TRACKS 99
100
101 #define min(x,y) ((x)<(y)?(x):(y))
102
103 static int playlists_count = 0;
104 static playlist_t *playlists_head = NULL;
105 static playlist_t *playlist = NULL; // current playlist
106 static int plt_loading = 0; // disable sending event about playlist switch, config regen, etc
107
108 #if !DISABLE_LOCKING
109 static uintptr_t mutex;
110 #endif
111
112 #define LOCK {pl_lock();}
113 #define UNLOCK {pl_unlock();}
114
115 // used at startup to prevent crashes
116 static playlist_t dummy_playlist = {
117 .refc = 1
118 };
119
120 static int pl_order = -1; // mirrors "playback.order" config variable
121
122 static int no_remove_notify;
123
124 static playlist_t *addfiles_playlist; // current playlist for adding files/folders; set in pl_add_files_begin
125
126 typedef struct ddb_fileadd_listener_s {
127 int id;
128 int (*callback)(ddb_fileadd_data_t *data, void *user_data);
129 void *user_data;
130 struct ddb_fileadd_listener_s *next;
131 } ddb_fileadd_listener_t;
132
133 static ddb_fileadd_listener_t *file_add_listeners;
134
135 typedef struct ddb_fileadd_beginend_listener_s {
136 int id;
137 void (*callback_begin)(ddb_fileadd_data_t *data, void *user_data);
138 void (*callback_end)(ddb_fileadd_data_t *data, void *user_data);
139 void *user_data;
140 struct ddb_fileadd_beginend_listener_s *next;
141 } ddb_fileadd_beginend_listener_t;
142
143 static ddb_fileadd_beginend_listener_t *file_add_beginend_listeners;
144
145 void
pl_set_order(int order)146 pl_set_order (int order) {
147 int prev_order = pl_order;
148
149 if (pl_order != order) {
150 pl_order = order;
151 for (playlist_t *plt = playlists_head; plt; plt = plt->next) {
152 plt_reshuffle (plt, NULL, NULL);
153 }
154 }
155
156 streamer_notify_order_changed (prev_order, pl_order);
157 }
158
159 int
pl_get_order(void)160 pl_get_order (void) {
161 return pl_order;
162 }
163
164 int
pl_init(void)165 pl_init (void) {
166 if (playlist) {
167 return 0; // avoid double init
168 }
169 playlist = &dummy_playlist;
170 #if !DISABLE_LOCKING
171 mutex = mutex_create ();
172 #endif
173 return 0;
174 }
175
176 void
pl_free(void)177 pl_free (void) {
178 trace ("pl_free\n");
179 LOCK;
180 playqueue_clear ();
181 plt_loading = 1;
182 while (playlists_head) {
183
184 for (playItem_t *it = playlists_head->head[PL_MAIN]; it; it = it->next[PL_MAIN]) {
185 if (it->_refc > 1) {
186 fprintf (stderr, "\033[0;31mWARNING: playitem %p %s(%s) has refc=%d at delete time\033[37;0m\n", it, pl_find_meta_raw (it, ":URI"), pl_find_meta_raw (it, "track"), it->_refc);
187 }
188 }
189
190 //fprintf (stderr, "\033[0;31mplt refc %d\033[37;0m\n", playlists_head->refc);
191
192 plt_remove (0);
193 }
194 plt_loading = 0;
195 UNLOCK;
196 #if !DISABLE_LOCKING
197 if (mutex) {
198 mutex_free (mutex);
199 mutex = 0;
200 }
201 #endif
202 playlist = NULL;
203 }
204
205 #if DEBUG_LOCKING
206 volatile int pl_lock_cnt = 0;
207 #endif
208 #if DETECT_PL_LOCK_RC
209 static pthread_t tids[1000];
210 static int ntids = 0;
211 pthread_t pl_lock_tid = 0;
212 #endif
213 void
pl_lock(void)214 pl_lock (void) {
215 #if !DISABLE_LOCKING
216 mutex_lock (mutex);
217 #if DETECT_PL_LOCK_RC
218 pl_lock_tid = pthread_self ();
219 tids[ntids++] = pl_lock_tid;
220 #endif
221
222 #if DEBUG_LOCKING
223 pl_lock_cnt++;
224 printf ("pcnt: %d\n", pl_lock_cnt);
225 #endif
226 #endif
227 }
228
229 void
pl_unlock(void)230 pl_unlock (void) {
231 #if !DISABLE_LOCKING
232 #if DETECT_PL_LOCK_RC
233 if (ntids > 0) {
234 ntids--;
235 }
236 if (ntids > 0) {
237 pl_lock_tid = tids[ntids-1];
238 }
239 else {
240 pl_lock_tid = 0;
241 }
242 #endif
243 mutex_unlock (mutex);
244 #if DEBUG_LOCKING
245 pl_lock_cnt--;
246 printf ("pcnt: %d\n", pl_lock_cnt);
247 #endif
248 #endif
249 }
250
251 static void
252 pl_item_free (playItem_t *it);
253
254 static void
plt_gen_conf(void)255 plt_gen_conf (void) {
256 if (plt_loading) {
257 return;
258 }
259 LOCK;
260 int cnt = plt_get_count ();
261 int i;
262 conf_remove_items ("playlist.tab.");
263
264 playlist_t *p = playlists_head;
265 for (i = 0; i < cnt; i++, p = p->next) {
266 char s[100];
267 snprintf (s, sizeof (s), "playlist.tab.%05d", i);
268 conf_set_str (s, p->title);
269 snprintf (s, sizeof (s), "playlist.cursor.%d", i);
270 conf_set_int (s, p->current_row[PL_MAIN]);
271 snprintf (s, sizeof (s), "playlist.scroll.%d", i);
272 conf_set_int (s, p->scroll);
273 }
274 UNLOCK;
275 }
276
277 playlist_t *
plt_get_list(void)278 plt_get_list (void) {
279 return playlists_head;
280 }
281
282 playlist_t *
plt_get_curr(void)283 plt_get_curr (void) {
284 LOCK;
285 playlist_t *plt = playlist;
286 if (plt) {
287 plt_ref (plt);
288 assert (plt->refc > 1);
289 }
290 UNLOCK;
291 return plt;
292 }
293
294 playlist_t *
plt_get_for_idx(int idx)295 plt_get_for_idx (int idx) {
296 LOCK;
297 playlist_t *p = playlists_head;
298 for (int i = 0; p && i <= idx; i++, p = p->next) {
299 if (i == idx) {
300 plt_ref (p);
301 UNLOCK;
302 return p;
303 }
304 }
305 UNLOCK;
306 return NULL;
307 }
308
309 void
plt_ref(playlist_t * plt)310 plt_ref (playlist_t *plt) {
311 LOCK;
312 plt->refc++;
313 UNLOCK;
314 }
315
316 void
plt_unref(playlist_t * plt)317 plt_unref (playlist_t *plt) {
318 LOCK;
319 assert (plt->refc > 0);
320 plt->refc--;
321 if (plt->refc < 0) {
322 trace ("\033[0;31mplaylist: bad refcount on playlist %p (%s)\033[37;0m\n", plt, plt->title);
323 }
324 if (plt->refc <= 0) {
325 plt_free (plt);
326 }
327 UNLOCK;
328 }
329
330 int
plt_get_count(void)331 plt_get_count (void) {
332 return playlists_count;
333 }
334
335 playItem_t *
plt_get_head(int plt)336 plt_get_head (int plt) {
337 playlist_t *p = playlists_head;
338 for (int i = 0; p && i <= plt; i++, p = p->next) {
339 if (i == plt) {
340 if (p->head[PL_MAIN]) {
341 pl_item_ref (p->head[PL_MAIN]);
342 }
343 return p->head[PL_MAIN];
344 }
345 }
346 return NULL;
347 }
348
349 int
plt_get_sel_count(int plt)350 plt_get_sel_count (int plt) {
351 playlist_t *p = playlists_head;
352 for (int i = 0; p && i <= plt; i++, p = p->next) {
353 if (i == plt) {
354 return plt_getselcount (p);
355 }
356 }
357 return 0;
358 }
359
360 playlist_t *
plt_alloc(const char * title)361 plt_alloc (const char *title) {
362 playlist_t *plt = malloc (sizeof (playlist_t));
363 memset (plt, 0, sizeof (playlist_t));
364 plt->refc = 1;
365 plt->title = strdup (title);
366 return plt;
367 }
368
369 int
plt_add(int before,const char * title)370 plt_add (int before, const char *title) {
371 assert (before >= 0);
372 trace ("plt_add\n");
373 playlist_t *plt = plt_alloc (title);
374 plt_modified (plt);
375
376 LOCK;
377 playlist_t *p_before = NULL;
378 playlist_t *p_after = playlists_head;
379
380 int i;
381 for (i = 0; i < before; i++) {
382 if (i >= before+1) {
383 break;
384 }
385 p_before = p_after;
386 if (p_after) {
387 p_after = p_after->next;
388 }
389 else {
390 p_after = playlists_head;
391 }
392 }
393
394 if (p_before) {
395 p_before->next = plt;
396 }
397 else {
398 playlists_head = plt;
399 }
400 plt->next = p_after;
401 playlists_count++;
402
403 if (!playlist) {
404 playlist = plt;
405 if (!plt_loading) {
406 // shift files
407 for (int i = playlists_count-1; i >= before+1; i--) {
408 char path1[PATH_MAX];
409 char path2[PATH_MAX];
410 if (snprintf (path1, sizeof (path1), "%s/playlists/%d.dbpl", dbconfdir, i) > sizeof (path1)) {
411 fprintf (stderr, "error: failed to make path string for playlist file\n");
412 continue;
413 }
414 if (snprintf (path2, sizeof (path2), "%s/playlists/%d.dbpl", dbconfdir, i+1) > sizeof (path2)) {
415 fprintf (stderr, "error: failed to make path string for playlist file\n");
416 continue;
417 }
418 int err = rename (path1, path2);
419 if (err != 0) {
420 fprintf (stderr, "playlist rename failed: %s\n", strerror (errno));
421 }
422 }
423 }
424 }
425 UNLOCK;
426
427 plt_gen_conf ();
428 if (!plt_loading) {
429 plt_save_n (before);
430 conf_save ();
431 messagepump_push (DB_EV_PLAYLISTSWITCHED, 0, 0, 0);
432 messagepump_push (DB_EV_PLAYLISTCHANGED, 0, DDB_PLAYLIST_CHANGE_CREATED, 0);
433 }
434 return before;
435 }
436
437 // NOTE: caller must ensure that configuration is saved after that call
438 void
plt_remove(int plt)439 plt_remove (int plt) {
440 int i;
441 assert (plt >= 0 && plt < playlists_count);
442 LOCK;
443
444 // find playlist and notify streamer
445 playlist_t *p = playlists_head;
446 playlist_t *prev = NULL;
447 for (i = 0; p && i < plt; i++) {
448 prev = p;
449 p = p->next;
450 }
451 streamer_notify_playlist_deleted (p);
452 if (!plt_loading) {
453 // move files (will decrease number of files by 1)
454 for (int i = plt+1; i < playlists_count; i++) {
455 char path1[PATH_MAX];
456 char path2[PATH_MAX];
457 if (snprintf (path1, sizeof (path1), "%s/playlists/%d.dbpl", dbconfdir, i-1) > sizeof (path1)) {
458 fprintf (stderr, "error: failed to make path string for playlist file\n");
459 continue;
460 }
461 if (snprintf (path2, sizeof (path2), "%s/playlists/%d.dbpl", dbconfdir, i) > sizeof (path2)) {
462 fprintf (stderr, "error: failed to make path string for playlist file\n");
463 continue;
464 }
465 int err = rename (path2, path1);
466 if (err != 0) {
467 fprintf (stderr, "playlist rename failed: %s\n", strerror (errno));
468 }
469 }
470 }
471
472 if (!plt_loading && (playlists_head && !playlists_head->next)) {
473 trace ("warning: deleting last playlist\n");
474 pl_clear ();
475 free (playlist->title);
476 playlist->title = strdup (_("Default"));
477 UNLOCK;
478 plt_gen_conf ();
479 conf_save ();
480 plt_save_n (0);
481 messagepump_push (DB_EV_PLAYLISTSWITCHED, 0, 0, 0);
482 messagepump_push (DB_EV_PLAYLISTCHANGED, 0, DDB_PLAYLIST_CHANGE_DELETED, 0);
483 return;
484 }
485 if (i != plt) {
486 trace ("plt_remove %d failed\n", i);
487 }
488 if (p) {
489 if (!prev) {
490 playlists_head = p->next;
491 }
492 else {
493 prev->next = p->next;
494 }
495 }
496 playlist_t *next = p->next;
497 playlist_t *old = playlist;
498 playlist = p;
499 playlist = old;
500 if (p == playlist) {
501 if (next) {
502 playlist = next;
503 }
504 else {
505 playlist = prev ? prev : playlists_head;
506 }
507 }
508
509 plt_unref (p);
510 playlists_count--;
511 UNLOCK;
512
513 plt_gen_conf ();
514 conf_save ();
515 if (!plt_loading) {
516 messagepump_push (DB_EV_PLAYLISTSWITCHED, 0, 0, 0);
517 messagepump_push (DB_EV_PLAYLISTCHANGED, 0, DDB_PLAYLIST_CHANGE_DELETED, 0);
518 }
519 }
520
521 int
plt_find(const char * name)522 plt_find (const char *name) {
523 playlist_t *p = playlists_head;
524 int i = -1;
525 for (i = 0; p; i++, p = p->next) {
526 if (!strcmp (p->title, name)) {
527 return i;
528 }
529 }
530 return -1;
531 }
532
533
534 void
plt_set_curr(playlist_t * plt)535 plt_set_curr (playlist_t *plt) {
536 LOCK;
537 if (plt != playlist) {
538 playlist = plt;
539 if (!plt_loading) {
540 messagepump_push (DB_EV_PLAYLISTSWITCHED, 0, 0, 0);
541 conf_set_int ("playlist.current", plt_get_curr_idx ());
542 conf_save ();
543 }
544 }
545 UNLOCK;
546 }
547
548 void
plt_set_curr_idx(int plt)549 plt_set_curr_idx (int plt) {
550 int i;
551 LOCK;
552 playlist_t *p = playlists_head;
553 for (i = 0; p && p->next && i < plt; i++) {
554 p = p->next;
555 }
556 if (p != playlist) {
557 playlist = p;
558 if (!plt_loading) {
559 messagepump_push (DB_EV_PLAYLISTSWITCHED, 0, 0, 0);
560 conf_set_int ("playlist.current", plt);
561 conf_save ();
562 }
563 }
564 UNLOCK;
565 }
566
567 int
plt_get_curr_idx(void)568 plt_get_curr_idx(void) {
569 int i;
570 LOCK;
571 playlist_t *p = playlists_head;
572 for (i = 0; p && i < playlists_count; i++) {
573 if (p == playlist) {
574 UNLOCK;
575 return i;
576 }
577 p = p->next;
578 }
579 UNLOCK;
580 return -1;
581 }
582
583 int
plt_get_idx_of(playlist_t * plt)584 plt_get_idx_of (playlist_t *plt) {
585 int i;
586 LOCK;
587 playlist_t *p = playlists_head;
588 for (i = 0; p && i < playlists_count; i++) {
589 if (p == plt) {
590 UNLOCK;
591 return i;
592 }
593 p = p->next;
594 }
595 UNLOCK;
596 return -1;
597 }
598
599 int
plt_get_title(playlist_t * p,char * buffer,int bufsize)600 plt_get_title (playlist_t *p, char *buffer, int bufsize) {
601 int i;
602 LOCK;
603 if (!buffer) {
604 int l = strlen (p->title);
605 UNLOCK;
606 return l;
607 }
608 strncpy (buffer, p->title, bufsize);
609 buffer[bufsize-1] = 0;
610 UNLOCK;
611 return 0;
612 }
613
614 int
plt_set_title(playlist_t * p,const char * title)615 plt_set_title (playlist_t *p, const char *title) {
616 int i;
617 LOCK;
618 free (p->title);
619 p->title = strdup (title);
620 plt_gen_conf ();
621 UNLOCK;
622 conf_save ();
623 if (!plt_loading) {
624 // not sending DB_EV_PLAYLISTSWITCHED here may cause compatibility problem
625 // messagepump_push (DB_EV_PLAYLISTSWITCHED, 0, 0, 0);
626 messagepump_push (DB_EV_PLAYLISTCHANGED, 0, DDB_PLAYLIST_CHANGE_TITLE, 0);
627 }
628 return 0;
629 }
630
631 void
plt_modified(playlist_t * plt)632 plt_modified (playlist_t *plt) {
633 pl_lock ();
634 plt->modification_idx++;
635 pl_unlock ();
636 }
637
638 int
plt_get_modification_idx(playlist_t * plt)639 plt_get_modification_idx (playlist_t *plt) {
640 pl_lock ();
641 int idx = plt->modification_idx;
642 pl_unlock ();
643 return idx;
644 }
645
646 void
plt_free(playlist_t * plt)647 plt_free (playlist_t *plt) {
648 LOCK;
649 plt_clear (plt);
650 free (plt->title);
651
652 while (plt->meta) {
653 DB_metaInfo_t *m = plt->meta;
654 plt->meta = m->next;
655 metacache_remove_string (m->key);
656 metacache_remove_string (m->value);
657 free (m);
658 }
659
660 free (plt);
661 UNLOCK;
662 }
663
664 void
plt_move(int from,int to)665 plt_move (int from, int to) {
666 trace ("%d -> %d\n", from, to);
667 if (from == to) {
668 return;
669 }
670 trace ("plt_move %d -> %d\n", from, to);
671 int i;
672 LOCK;
673 playlist_t *p = playlists_head;
674
675 playlist_t *pfrom = NULL;
676 playlist_t *prev = NULL;
677 playlist_t *ins = NULL;
678
679 // move 'from' to temp file
680 char path1[PATH_MAX];
681 char temp[PATH_MAX];
682 if (snprintf (path1, sizeof (path1), "%s/playlists/%d.dbpl", dbconfdir, from) > sizeof (path1)) {
683 fprintf (stderr, "error: failed to make path string for playlist file\n");
684 UNLOCK;
685 return;
686 }
687 if (snprintf (temp, sizeof (temp), "%s/playlists/temp.dbpl", dbconfdir) > sizeof (temp)) {
688 fprintf (stderr, "error: failed to make path string for playlist file\n");
689 UNLOCK;
690 return;
691 }
692
693 // trace ("will rename %s->%s\n", path1, temp);
694 struct stat st;
695 int err = stat (path1, &st);
696 if (!err) {
697 trace ("rename %s->%s\n", path1, temp);
698
699 int err = rename (path1, temp);
700 if (err != 0) {
701 fprintf (stderr, "playlist rename %s->%s failed: %s\n", path1, temp, strerror (errno));
702 UNLOCK;
703 return;
704 }
705 }
706
707 // remove 'from' from list
708 for (i = 0; p && i <= from; i++) {
709 if (i == from) {
710 pfrom = p;
711 if (prev) {
712 prev->next = p->next;
713 }
714 else {
715 playlists_head = p->next;
716 }
717 break;
718 }
719 prev = p;
720 p = p->next;
721 }
722
723 // shift files to fill the gap
724 for (int i = from; i < playlists_count-1; i++) {
725 char path2[PATH_MAX];
726 if (snprintf (path1, sizeof (path1), "%s/playlists/%d.dbpl", dbconfdir, i) > sizeof (path1)) {
727 fprintf (stderr, "error: failed to make path string for playlist file\n");
728 continue;
729 }
730 if (snprintf (path2, sizeof (path2), "%s/playlists/%d.dbpl", dbconfdir, i+1) > sizeof (path2)) {
731 fprintf (stderr, "error: failed to make path string for playlist file\n");
732 continue;
733 }
734 // trace ("will rename %s->%s\n", path2, path1);
735 int err = stat (path2, &st);
736 if (!err) {
737 trace ("rename %s->%s\n", path2, path1);
738 int err = rename (path2, path1);
739 if (err != 0) {
740 fprintf (stderr, "playlist rename %s->%s failed: %s\n", path2, path1, strerror (errno));
741 }
742 }
743 }
744 // open new gap
745 for (int i = playlists_count-2; i >= to; i--) {
746 char path2[PATH_MAX];
747 if (snprintf (path1, sizeof (path1), "%s/playlists/%d.dbpl", dbconfdir, i) > sizeof (path1)) {
748 fprintf (stderr, "error: failed to make path string for playlist file\n");
749 continue;
750 }
751 if (snprintf (path2, sizeof (path2), "%s/playlists/%d.dbpl", dbconfdir, i+1) > sizeof (path2)) {
752 fprintf (stderr, "error: failed to make path string for playlist file\n");
753 continue;
754 }
755 trace ("will rename %s->%s\n", path1, path2);
756 int err = stat (path1, &st);
757 if (!err) {
758 trace ("rename %s->%s\n", path1, path2);
759 int err = rename (path1, path2);
760 if (err != 0) {
761 fprintf (stderr, "playlist rename %s->%s failed: %s\n", path1, path2, strerror (errno));
762 }
763 }
764 }
765 // move temp file
766 if (snprintf (path1, sizeof (path1), "%s/playlists/%d.dbpl", dbconfdir, to) > sizeof (path1)) {
767 fprintf (stderr, "error: failed to make path string for playlist file\n");
768 }
769 else {
770 int err = stat (temp, &st);
771 if (!err) {
772 trace ("move %s->%s\n", temp, path1);
773 int err = rename (temp, path1);
774 if (err != 0) {
775 fprintf (stderr, "playlist rename %s->%s failed: %s\n", temp, path1, strerror (errno));
776 }
777 }
778 }
779
780 // move pointers
781 if (to == 0) {
782 // insert 'from' as head
783 pfrom->next = playlists_head;
784 playlists_head = pfrom;
785 }
786 else {
787 // insert 'from' in the middle
788 p = playlists_head;
789 for (i = 0; p && i < to; i++) {
790 if (i == to-1) {
791 playlist_t *next = p->next;
792 p->next = pfrom;
793 pfrom->next = next;
794 break;
795 }
796 prev = p;
797 p = p->next;
798 }
799 }
800
801 UNLOCK;
802 plt_gen_conf ();
803 conf_save ();
804 }
805
806 void
plt_clear(playlist_t * plt)807 plt_clear (playlist_t *plt) {
808 pl_lock ();
809 while (plt->head[PL_MAIN]) {
810 plt_remove_item (plt, plt->head[PL_MAIN]);
811 }
812 plt->current_row[PL_MAIN] = -1;
813 plt->current_row[PL_SEARCH] = -1;
814 plt_modified (plt);
815 pl_unlock ();
816 }
817
818 void
pl_clear(void)819 pl_clear (void) {
820 LOCK;
821 plt_clear (playlist);
822 UNLOCK;
823 }
824
825 static const uint8_t *
pl_str_skipspaces(const uint8_t * p,const uint8_t * end)826 pl_str_skipspaces (const uint8_t *p, const uint8_t *end) {
827 while (p < end && *p <= ' ') {
828 p++;
829 }
830 return p;
831 }
832 static const uint8_t *
pl_cue_skipspaces(const uint8_t * p)833 pl_cue_skipspaces (const uint8_t *p) {
834 while (*p && *p <= ' ') {
835 p++;
836 }
837 return p;
838 }
839
840 static void
pl_get_qvalue_from_cue(const uint8_t * p,int sz,char * out,const char * charset)841 pl_get_qvalue_from_cue (const uint8_t *p, int sz, char *out, const char *charset) {
842 char *str = out;
843 if (*p == 0) {
844 *out = 0;
845 return;
846 }
847 p = pl_cue_skipspaces (p);
848 if (*p == 0) {
849 *out = 0;
850 return;
851 }
852
853 if (*p == '"') {
854 p++;
855 p = pl_cue_skipspaces (p);
856 while (*p && *p != '"' && sz > 1) {
857 sz--;
858 *out++ = *p++;
859 }
860 *out = 0;
861 }
862 else {
863 while (*p && *p >= 0x20) {
864 sz--;
865 *out++ = *p++;
866 }
867 out--;
868 while (out > str && *out == 0x20) {
869 out--;
870 }
871 out++;
872 *out = 0;
873 }
874
875 if (!charset) {
876 return;
877 }
878
879 // recode
880 size_t l = strlen (str);
881 if (l == 0) {
882 return;
883 }
884
885 char recbuf[l*10];
886 int res = junk_recode (str, (int)l, recbuf, (int)(sizeof (recbuf)-1), charset);
887 if (res >= 0) {
888 strcpy (str, recbuf);
889 }
890 else
891 {
892 strcpy (str, "<UNRECOGNIZED CHARSET>");
893 }
894 }
895
896 static void
pl_get_value_from_cue(const char * p,int sz,char * out)897 pl_get_value_from_cue (const char *p, int sz, char *out) {
898 while (*p >= ' ' && sz > 1) {
899 sz--;
900 *out++ = *p++;
901 }
902 while (out > p && (*(out-1) == 0x20 || *(out-1) == 0x8)) {
903 out--;
904 }
905 *out = 0;
906 }
907
908 static float
pl_cue_parse_time(const char * p)909 pl_cue_parse_time (const char *p) {
910 char *endptr;
911 long mins = strtol(p, &endptr, 10);
912 if (endptr - p < 1 || *endptr != ':') {
913 return -1;
914 }
915 p = endptr + 1;
916 long sec = strtol(p, &endptr, 10);
917 if (endptr - p != 2 || *endptr != ':') {
918 return -1;
919 }
920 p = endptr + 1;
921 long frm = strtol(p, &endptr, 10);
922 if (endptr - p != 2 || *endptr != '\0') {
923 return -1;
924 }
925 return mins * 60.f + sec + frm / 75.f;
926 }
927
928 static playItem_t *
plt_process_cue_track(playlist_t * playlist,const char * fname,const int startsample,playItem_t ** prev,char * track,char * index00,char * index01,char * pregap,char * title,char * albumperformer,char * performer,char * albumtitle,char * genre,char * date,char * replaygain_album_gain,char * replaygain_album_peak,char * replaygain_track_gain,char * replaygain_track_peak,const char * decoder_id,const char * ftype,int samplerate)929 plt_process_cue_track (playlist_t *playlist, const char *fname, const int startsample, playItem_t **prev, char *track, char *index00, char *index01, char *pregap, char *title, char *albumperformer, char *performer, char *albumtitle, char *genre, char *date, char *replaygain_album_gain, char *replaygain_album_peak, char *replaygain_track_gain, char *replaygain_track_peak, const char *decoder_id, const char *ftype, int samplerate) {
930 if (!track[0]) {
931 trace ("pl_process_cue_track: invalid track (file=%s, title=%s)\n", fname, title);
932 return NULL;
933 }
934 if (!index00[0] && !index01[0]) {
935 trace ("pl_process_cue_track: invalid index (file=%s, title=%s, track=%s)\n", fname, title, track);
936 return NULL;
937 }
938 #if SKIP_BLANK_CUE_TRACKS
939 if (!title[0]) {
940 trace ("pl_process_cue_track: invalid title (file=%s, title=%s, track=%s)\n", fname, title, track);
941 return NULL;
942 }
943 #endif
944 // fix track number
945 char *p = track;
946 while (*p && isdigit (*p)) {
947 p++;
948 }
949 *p = 0;
950 // check that indexes have valid timestamps
951 //float f_index00 = index00[0] ? pl_cue_parse_time (index00) : 0;
952 float f_index01 = index01[0] ? pl_cue_parse_time (index01) : 0;
953 float f_pregap = pregap[0] ? pl_cue_parse_time (pregap) : 0;
954 if (*prev) {
955 float prevtime = 0;
956 if (pregap[0] && index01[0]) {
957 // PREGAP command
958 prevtime = f_index01 - f_pregap;
959 }
960 // else if (index00[0] && index01[0]) {
961 // // pregap in index 00
962 // prevtime = f_index00;
963 // }
964 else if (index01[0]) {
965 // no pregap
966 prevtime = f_index01;
967 }
968 else {
969 trace ("pl_process_cue_track: invalid pregap or index01 (pregap=%s, index01=%s)\n", pregap, index01);
970 return NULL;
971 }
972 (*prev)->endsample = startsample + (prevtime * samplerate) - 1;
973 plt_set_item_duration (playlist, *prev, (float)((*prev)->endsample - (*prev)->startsample + 1) / samplerate);
974 if (pl_get_item_duration (*prev) < 0) {
975 // might be bad cuesheet file, try to fix
976 trace ("cuesheet seems to be corrupted, trying workaround\n");
977 //trace ("[bad:] calc endsample=%d, prevtime=%f, samplerate=%d, prev track duration=%f\n", (*prev)->endsample, prevtime, samplerate, (*prev)->duration);
978 prevtime = f_index01;
979 (*prev)->endsample = startsample + (prevtime * samplerate) - 1;
980 float dur = (float)((*prev)->endsample - (*prev)->startsample + 1) / samplerate;
981 plt_set_item_duration (playlist, *prev, dur);
982 if (dur > 0) {
983 trace ("success :-D\n");
984 }
985 else {
986 trace ("fail :-(\n");
987 }
988 }
989 trace ("startsample=%d, endsample=%d, prevtime=%f, samplerate=%d, prev track duration=%f\n", (*prev)->startsample, (*prev)->endsample, prevtime, samplerate, (*prev)->_duration);
990 }
991 // non-compliant hack to handle tracks which only store pregap info
992 if (!index01[0]) {
993 *prev = NULL;
994 trace ("pl_process_cue_track: invalid index01 (pregap=%s, index01=%s)\n", pregap, index01);
995 return NULL;
996 }
997 playItem_t *it = pl_item_alloc_init (fname, decoder_id);
998 pl_set_meta_int (it, ":TRACKNUM", atoi (track));
999 it->startsample = index01[0] ? startsample + f_index01 * samplerate : startsample;
1000 it->endsample = -1; // will be filled by next read, or by decoder
1001 pl_replace_meta (it, ":FILETYPE", ftype);
1002 if (performer[0]) {
1003 pl_add_meta (it, "artist", performer);
1004 if (albumperformer[0] && strcmp (albumperformer, performer)) {
1005 pl_add_meta (it, "album artist", albumperformer);
1006 }
1007 }
1008 else if (albumperformer[0]) {
1009 pl_add_meta (it, "artist", albumperformer);
1010 }
1011 if (albumtitle[0]) {
1012 pl_add_meta (it, "album", albumtitle);
1013 }
1014 if (track[0]) {
1015 pl_add_meta (it, "track", track);
1016 }
1017 if (title[0]) {
1018 pl_add_meta (it, "title", title);
1019 }
1020 if (genre[0]) {
1021 pl_add_meta (it, "genre", genre);
1022 }
1023 if (date[0]) {
1024 pl_add_meta (it, "year", date);
1025 }
1026 if (replaygain_album_gain[0]) {
1027 pl_set_item_replaygain (it, DDB_REPLAYGAIN_ALBUMGAIN, atof (replaygain_album_gain));
1028 }
1029 if (replaygain_album_peak[0]) {
1030 pl_set_item_replaygain (it, DDB_REPLAYGAIN_ALBUMPEAK, atof (replaygain_album_peak));
1031 }
1032 if (replaygain_track_gain[0]) {
1033 pl_set_item_replaygain (it, DDB_REPLAYGAIN_TRACKGAIN, atof (replaygain_track_gain));
1034 }
1035 if (replaygain_track_peak[0]) {
1036 pl_set_item_replaygain (it, DDB_REPLAYGAIN_TRACKPEAK, atof (replaygain_track_peak));
1037 }
1038 it->_flags |= DDB_IS_SUBTRACK | DDB_TAG_CUESHEET;
1039 *prev = it;
1040 return it;
1041 }
1042
1043 playItem_t *
plt_insert_cue_from_buffer(playlist_t * playlist,playItem_t * after,playItem_t * origin,const uint8_t * buffer,int buffersize,int numsamples,int samplerate)1044 plt_insert_cue_from_buffer (playlist_t *playlist, playItem_t *after, playItem_t *origin, const uint8_t *buffer, int buffersize, int numsamples, int samplerate) {
1045 if (buffersize >= 3 && buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf) {
1046 buffer += 3;
1047 buffersize -= 3;
1048 }
1049
1050 // go through the file, and verify that it's not for multiple tracks
1051 int fcount = 0;
1052 uint8_t *p = (uint8_t *)buffer;
1053 uint8_t *e = (uint8_t *)(buffer + buffersize);
1054 while (*p) {
1055 while (*p <= 0x20 && *p) {
1056 p++;
1057 }
1058 if (e-p > 4 && !memcmp ((char *)p, "FILE", 4) && p[4] == 0x20) {
1059 fcount++;
1060 if (fcount > 1) {
1061 return NULL;
1062 }
1063 }
1064 while (*p >= 0x20 && *p) {
1065 p++;
1066 }
1067 }
1068
1069 const char *charset = junk_detect_charset_len (buffer, buffersize);
1070
1071 LOCK;
1072 playItem_t *ins = after;
1073 trace ("plt_insert_cue_from_buffer numsamples=%d, samplerate=%d\n", numsamples, samplerate);
1074 char albumperformer[256] = "";
1075 char performer[256] = "";
1076 char albumtitle[256] = "";
1077 char genre[256] = "";
1078 char date[256] = "";
1079 char track[256] = "";
1080 char title[256] = "";
1081 char pregap[256] = "";
1082 char index00[256] = "";
1083 char index01[256] = "";
1084 char replaygain_album_gain[256] = "";
1085 char replaygain_album_peak[256] = "";
1086 char replaygain_track_gain[256] = "";
1087 char replaygain_track_peak[256] = "";
1088 const char *uri = pl_find_meta_raw (origin, ":URI");
1089 const char *dec = pl_find_meta_raw (origin, ":DECODER");
1090 const char *filetype = pl_find_meta_raw (origin, ":FILETYPE");
1091
1092 int have_track = 0;
1093
1094 playItem_t *cuetracks[MAX_CUE_TRACKS];
1095 int ncuetracks = 0;
1096
1097 playItem_t *prev = NULL;
1098 while (buffersize > 0) {
1099 const uint8_t *p = buffer;
1100 // find end of line
1101 while (p - buffer < buffersize && *p >= 0x20) {
1102 p++;
1103 }
1104 // skip linebreak(s)
1105 while (p - buffer < buffersize && *p < 0x20) {
1106 p++;
1107 }
1108 if (p-buffer > 2048) { // huge string, ignore
1109 buffersize -= p-buffer;
1110 buffer = p;
1111 continue;
1112 }
1113 char str[p-buffer+1];
1114 strncpy (str, buffer, p-buffer);
1115 str[p-buffer] = 0;
1116 buffersize -= p-buffer;
1117 buffer = p;
1118 p = pl_cue_skipspaces (str);
1119 // trace ("cue line: %s\n", p);
1120 if (!strncmp (p, "PERFORMER ", 10)) {
1121 if (!track[0]) {
1122 pl_get_qvalue_from_cue (p + 10, sizeof (albumperformer), albumperformer, charset);
1123 }
1124 else {
1125 pl_get_qvalue_from_cue (p + 10, sizeof (performer), performer, charset);
1126 }
1127 trace ("cue: got performer: %s\n", performer);
1128 }
1129 else if (!strncmp (p, "TITLE ", 6)) {
1130 if (str[0] > ' ' && !albumtitle[0]) {
1131 pl_get_qvalue_from_cue (p + 6, sizeof (albumtitle), albumtitle, charset);
1132 trace ("cue: got albumtitle: %s\n", albumtitle);
1133 }
1134 else {
1135 pl_get_qvalue_from_cue (p + 6, sizeof (title), title, charset);
1136 trace ("cue: got title: %s\n", title);
1137 }
1138 }
1139 else if (!strncmp (p, "REM GENRE ", 10)) {
1140 pl_get_qvalue_from_cue (p + 10, sizeof (genre), genre, charset);
1141 }
1142 else if (!strncmp (p, "REM DATE ", 9)) {
1143 pl_get_value_from_cue (p + 9, sizeof (date), date);
1144 }
1145 else if (!strncmp (p, "TRACK ", 6)) {
1146 trace ("cue: adding track: %s %s %s\n", uri, title, track);
1147 if (have_track) {
1148 // add previous track
1149 playItem_t *it = plt_process_cue_track (playlist, uri, origin->startsample, &prev, track, index00, index01, pregap, title, albumperformer, performer, albumtitle, genre, date, replaygain_album_gain, replaygain_album_peak, replaygain_track_gain, replaygain_track_peak, dec, filetype, samplerate);
1150 trace ("cue: added %p\n", it);
1151 if (it) {
1152 if ((it->startsample-origin->startsample) >= numsamples || (it->endsample-origin->startsample) >= numsamples) {
1153 trace ("cue: the track is shorter than cue timeline\n");
1154 goto error;
1155 }
1156 cuetracks[ncuetracks++] = it;
1157 }
1158 }
1159
1160 have_track = 1;
1161 track[0] = 0;
1162 title[0] = 0;
1163 pregap[0] = 0;
1164 index00[0] = 0;
1165 index01[0] = 0;
1166 replaygain_track_gain[0] = 0;
1167 replaygain_track_peak[0] = 0;
1168 performer[0] = 0;
1169 pl_get_value_from_cue (p + 6, sizeof (track), track);
1170 trace ("cue: got track: %s\n", track);
1171 }
1172 else if (!strncmp (p, "REM REPLAYGAIN_ALBUM_GAIN ", 26)) {
1173 pl_get_value_from_cue (p + 26, sizeof (replaygain_album_gain), replaygain_album_gain);
1174 }
1175 else if (!strncmp (p, "REM REPLAYGAIN_ALBUM_PEAK ", 26)) {
1176 pl_get_value_from_cue (p + 26, sizeof (replaygain_album_peak), replaygain_album_peak);
1177 }
1178 else if (!strncmp (p, "REM REPLAYGAIN_TRACK_GAIN ", 26)) {
1179 pl_get_value_from_cue (p + 26, sizeof (replaygain_track_gain), replaygain_track_gain);
1180 }
1181 else if (!strncmp (p, "REM REPLAYGAIN_TRACK_PEAK ", 26)) {
1182 pl_get_value_from_cue (p + 26, sizeof (replaygain_track_peak), replaygain_track_peak);
1183 }
1184 else if (!strncmp (p, "PREGAP ", 7)) {
1185 pl_get_value_from_cue (p + 7, sizeof (pregap), pregap);
1186 }
1187 else if (!strncmp (p, "INDEX 00 ", 9)) {
1188 pl_get_value_from_cue (p + 9, sizeof (index00), index00);
1189 }
1190 else if (!strncmp (p, "INDEX 01 ", 9)) {
1191 pl_get_value_from_cue (p + 9, sizeof (index01), index01);
1192 }
1193 else {
1194 // fprintf (stderr, "got unknown line:\n%s\n", p);
1195 }
1196 }
1197 if (have_track) {
1198 // handle last track
1199 playItem_t *it = plt_process_cue_track (playlist, uri, origin->startsample, &prev, track, index00, index01, pregap, title, albumperformer, performer, albumtitle, genre, date, replaygain_album_gain, replaygain_album_peak, replaygain_track_gain, replaygain_track_peak, dec, filetype, samplerate);
1200 if (it) {
1201 trace ("last track endsample: %d\n", origin->startsample+numsamples-1);
1202 it->endsample = origin->startsample + numsamples - 1;
1203 if ((it->endsample-origin->startsample) >= numsamples || (it->startsample-origin->startsample) >= numsamples) {
1204 goto error;
1205 }
1206 plt_set_item_duration (playlist, it, (float)(it->endsample - it->startsample + 1) / samplerate);
1207 cuetracks[ncuetracks++] = it;
1208 }
1209 }
1210
1211 if (!ncuetracks) {
1212 UNLOCK;
1213 return NULL;
1214 }
1215
1216 playItem_t *last = cuetracks[ncuetracks-1];
1217 pl_item_ref (last);
1218
1219 for (int i = 0; i < ncuetracks; i++) {
1220 after = plt_insert_item (playlist, after, cuetracks[i]);
1221 pl_item_unref (cuetracks[i]);
1222 }
1223 playItem_t *first = ins ? ins->next[PL_MAIN] : playlist->head[PL_MAIN];
1224 if (!first) {
1225 UNLOCK;
1226 return NULL;
1227 }
1228 // copy metadata from embedded tags
1229 uint32_t f = pl_get_item_flags (origin);
1230 f |= DDB_TAG_CUESHEET | DDB_IS_SUBTRACK;
1231 if (pl_find_meta_raw (origin, "cuesheet")) {
1232 f |= DDB_HAS_EMBEDDED_CUESHEET;
1233 }
1234 pl_set_item_flags (origin, f);
1235 pl_items_copy_junk (origin, first, after);
1236 UNLOCK;
1237 return after;
1238 error:
1239 trace ("cue parsing error occured\n");
1240 for (int i = 0; i < ncuetracks; i++) {
1241 pl_item_unref (cuetracks[i]);
1242 }
1243 UNLOCK;
1244 return NULL;
1245 }
1246
1247 playItem_t *
plt_insert_cue(playlist_t * plt,playItem_t * after,playItem_t * origin,int numsamples,int samplerate)1248 plt_insert_cue (playlist_t *plt, playItem_t *after, playItem_t *origin, int numsamples, int samplerate) {
1249 trace ("pl_insert_cue numsamples=%d, samplerate=%d\n", numsamples, samplerate);
1250 pl_lock ();
1251 const char *fname = pl_find_meta_raw (origin, ":URI");
1252 int len = strlen (fname);
1253 char cuename[len+5];
1254 strcpy (cuename, fname);
1255 pl_unlock ();
1256 strcpy (cuename+len, ".cue");
1257 DB_FILE *fp = vfs_fopen (cuename);
1258 if (!fp) {
1259 strcpy (cuename+len, ".CUE");
1260 fp = vfs_fopen (cuename);
1261 }
1262 if (!fp) {
1263 char *ptr = cuename + len-1;
1264 while (ptr >= cuename && *ptr != '.') {
1265 ptr--;
1266 }
1267 if (ptr < cuename) {
1268 return NULL;
1269 }
1270 strcpy (ptr+1, "cue");
1271 fp = vfs_fopen (cuename);
1272 if (!fp) {
1273 strcpy (ptr+1, "CUE");
1274 fp = vfs_fopen (cuename);
1275 }
1276 }
1277 if (!fp) {
1278 return NULL;
1279 }
1280 size_t sz = vfs_fgetlength (fp);
1281 if (sz == 0) {
1282 vfs_fclose (fp);
1283 return NULL;
1284 }
1285 uint8_t *buf = alloca(sz);
1286 if (!buf) {
1287 vfs_fclose (fp);
1288 return NULL;
1289 }
1290 if (vfs_fread (buf, 1, sz, fp) != sz) {
1291 vfs_fclose (fp);
1292 return NULL;
1293 }
1294 vfs_fclose (fp);
1295 return plt_insert_cue_from_buffer (plt, after, origin, buf, sz, numsamples, samplerate);
1296 }
1297
1298 // FIXME: this is not thread-safe
1299 static int follow_symlinks = 0;
1300 static int ignore_archives = 0;
1301
1302 static playItem_t *
1303 plt_insert_dir_int (int visibility, playlist_t *playlist, DB_vfs_t *vfs, playItem_t *after, const char *dirname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data);
1304
1305 static playItem_t *
1306 plt_load_int (int visibility, playlist_t *plt, playItem_t *after, const char *fname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data);
1307
1308 static playItem_t *
plt_insert_file_int(int visibility,playlist_t * playlist,playItem_t * after,const char * fname,int * pabort,int (* cb)(playItem_t * it,void * data),void * user_data)1309 plt_insert_file_int (int visibility, playlist_t *playlist, playItem_t *after, const char *fname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) {
1310 trace ("count: %d\n", playlist->count[PL_MAIN]);
1311 trace ("pl_insert_file %s\n", fname);
1312 if (!fname || !(*fname)) {
1313 return NULL;
1314 }
1315
1316 // check if that is supported container format
1317 if (!ignore_archives) {
1318 DB_vfs_t **vfsplugs = plug_get_vfs_list ();
1319 for (int i = 0; vfsplugs[i]; i++) {
1320 if (vfsplugs[i]->is_container) {
1321 if (vfsplugs[i]->is_container (fname)) {
1322 trace ("inserting %s via vfs %s\n", fname, vfsplugs[i]->plugin.id);
1323 playItem_t *it = plt_insert_dir_int (visibility, playlist, vfsplugs[i], after, fname, pabort, cb, user_data);
1324 if (it) {
1325 return it;
1326 }
1327 }
1328 }
1329 }
1330 }
1331
1332 const char *fn = strrchr (fname, '/');
1333 if (!fn) {
1334 fn = fname;
1335 }
1336 else {
1337 fn++;
1338 }
1339
1340 // add all posible streams as special-case:
1341 // set decoder to NULL, and filetype to "content"
1342 // streamer is responsible to determine content type on 1st access and
1343 // update decoder and filetype fields
1344 if (strncasecmp (fname, "file://", 7)) {
1345 const char *p = fname;
1346 int detect_on_access = 1;
1347
1348 // check if it's URI
1349 for (; *p; p++) {
1350 if (!strncmp (p, "://", 3)) {
1351 break;
1352 }
1353 if (!isalpha (*p)) {
1354 detect_on_access = 0;
1355 break;
1356 }
1357 }
1358
1359 if (detect_on_access) {
1360 // find vfs plugin
1361 DB_vfs_t **vfsplugs = plug_get_vfs_list ();
1362 for (int i = 0; vfsplugs[i]; i++) {
1363 if (vfsplugs[i]->get_schemes) {
1364 const char **sch = vfsplugs[i]->get_schemes ();
1365 int s = 0;
1366 for (s = 0; sch[s]; s++) {
1367 if (!strncasecmp (sch[s], fname, strlen (sch[s]))) {
1368 break;
1369 }
1370 }
1371 if (sch[s]) {
1372 if (!vfsplugs[i]->is_streaming || !vfsplugs[i]->is_streaming()) {
1373 detect_on_access = 0;
1374 }
1375 break;
1376 }
1377 }
1378 }
1379 }
1380
1381 if (detect_on_access && *p == ':') {
1382 // check for wrong chars like CR/LF, TAB, etc
1383 // they are not allowed and might lead to corrupt display in GUI
1384 int32_t i = 0;
1385 while (fname[i]) {
1386 uint32_t c = u8_nextchar (fname, &i);
1387 if (c < 0x20) {
1388 return NULL;
1389 }
1390 }
1391
1392 playItem_t *it = pl_item_alloc_init (fname, NULL);
1393 pl_replace_meta (it, ":FILETYPE", "content");
1394 after = plt_insert_item (addfiles_playlist ? addfiles_playlist : playlist, after, it);
1395 pl_item_unref (it);
1396 return after;
1397 }
1398 }
1399 else {
1400 char *escaped = uri_unescape (fname, strlen (fname));
1401 if (escaped) {
1402 fname = strdupa (escaped);
1403 free (escaped);
1404 }
1405 fname += 7;
1406 }
1407
1408 // detect decoder
1409 const char *eol = strrchr (fname, '.');
1410 if (!eol) {
1411 return NULL;
1412 }
1413 eol++;
1414
1415 ddb_fileadd_data_t d;
1416 memset (&d, 0, sizeof (d));
1417 d.visibility = visibility;
1418 d.plt = (ddb_playlist_t *)playlist;
1419
1420 DB_decoder_t **decoders = plug_get_decoder_list ();
1421 // match by decoder
1422 for (int i = 0; decoders[i]; i++) {
1423 trace ("matching decoder %d(%s)...\n", i, decoders[i]->plugin.id);
1424 if (decoders[i]->exts && decoders[i]->insert) {
1425 const char **exts = decoders[i]->exts;
1426 for (int e = 0; exts[e]; e++) {
1427 if (!strcasecmp (exts[e], eol) || !strcmp (exts[e], "*")) {
1428 playItem_t *inserted = (playItem_t *)decoders[i]->insert ((ddb_playlist_t *)playlist, DB_PLAYITEM (after), fname);
1429 if (inserted != NULL) {
1430 if (cb && cb (inserted, user_data) < 0) {
1431 *pabort = 1;
1432 }
1433 if (file_add_listeners) {
1434 d.track = (ddb_playItem_t *)inserted;
1435 for (ddb_fileadd_listener_t *l = file_add_listeners; l; l = l->next) {
1436 if (l->callback (&d, l->user_data) < 0) {
1437 *pabort = 1;
1438 break;
1439 }
1440 }
1441 }
1442 trace ("file has been added by decoder: %s\n", decoders[i]->plugin.id);
1443 return inserted;
1444 }
1445 }
1446 }
1447 }
1448 if (decoders[i]->prefixes && decoders[i]->insert) {
1449 const char **prefixes = decoders[i]->prefixes;
1450 for (int e = 0; prefixes[e]; e++) {
1451 if (!strncasecmp (prefixes[e], fn, strlen(prefixes[e])) && *(fn + strlen (prefixes[e])) == '.') {
1452 playItem_t *inserted = (playItem_t *)decoders[i]->insert ((ddb_playlist_t *)playlist, DB_PLAYITEM (after), fname);
1453 if (inserted != NULL) {
1454 if (cb && cb (inserted, user_data) < 0) {
1455 *pabort = 1;
1456 }
1457 if (file_add_listeners) {
1458 d.track = (ddb_playItem_t *)inserted;
1459 for (ddb_fileadd_listener_t *l = file_add_listeners; l; l = l->next) {
1460 if (l->callback (&d, l->user_data) < 0) {
1461 *pabort = 1;
1462 break;
1463 }
1464 }
1465 }
1466 return inserted;
1467 }
1468 }
1469 }
1470 }
1471 }
1472 trace ("no decoder found for %s\n", fname);
1473 return NULL;
1474 }
1475
1476 playItem_t *
plt_insert_file(playlist_t * playlist,playItem_t * after,const char * fname,int * pabort,int (* cb)(playItem_t * it,void * data),void * user_data)1477 plt_insert_file (playlist_t *playlist, playItem_t *after, const char *fname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) {
1478 return plt_insert_file_int (0, playlist, after, fname, pabort, cb, user_data);
1479 }
1480
dirent_alphasort(const struct dirent ** a,const struct dirent ** b)1481 static int dirent_alphasort (const struct dirent **a, const struct dirent **b) {
1482 return strcmp ((*a)->d_name, (*b)->d_name);
1483 }
1484
1485 static playItem_t *
plt_insert_dir_int(int visibility,playlist_t * playlist,DB_vfs_t * vfs,playItem_t * after,const char * dirname,int * pabort,int (* cb)(playItem_t * it,void * data),void * user_data)1486 plt_insert_dir_int (int visibility, playlist_t *playlist, DB_vfs_t *vfs, playItem_t *after, const char *dirname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) {
1487 if (!strncmp (dirname, "file://", 7)) {
1488 dirname += 7;
1489 }
1490 if (!follow_symlinks && !vfs) {
1491 struct stat buf;
1492 lstat (dirname, &buf);
1493 if (S_ISLNK(buf.st_mode)) {
1494 return NULL;
1495 }
1496 }
1497 struct dirent **namelist = NULL;
1498 int n;
1499
1500 if (vfs && vfs->scandir) {
1501 n = vfs->scandir (dirname, &namelist, NULL, dirent_alphasort);
1502 }
1503 else {
1504 n = scandir (dirname, &namelist, NULL, dirent_alphasort);
1505 }
1506 if (n < 0)
1507 {
1508 if (namelist)
1509 free (namelist);
1510 return NULL; // not a dir or no read access
1511 }
1512 else
1513 {
1514 int i;
1515 for (i = 0; i < n; i++)
1516 {
1517 // no hidden files
1518 if (namelist[i]->d_name[0] != '.')
1519 {
1520 playItem_t *inserted = NULL;
1521 if (!vfs) {
1522 char fullname[PATH_MAX];
1523 snprintf (fullname, sizeof (fullname), "%s/%s", dirname, namelist[i]->d_name);
1524 inserted = plt_insert_dir_int (visibility, playlist, vfs, after, fullname, pabort, cb, user_data);
1525 if (!inserted) {
1526 inserted = plt_insert_file_int (visibility, playlist, after, fullname, pabort, cb, user_data);
1527 }
1528 }
1529 else {
1530 char fullname[PATH_MAX];
1531 const char *sch = NULL;
1532 if (vfs->plugin.api_vminor >= 6 && vfs->get_scheme_for_name) {
1533 sch = vfs->get_scheme_for_name (dirname);
1534 }
1535 if (sch && strncmp (sch, namelist[i]->d_name, strlen (sch))) {
1536 snprintf (fullname, sizeof (fullname), "%s%s:%s", sch, dirname, namelist[i]->d_name);
1537 }
1538 else {
1539 strcpy (fullname, namelist[i]->d_name);
1540 }
1541 inserted = plt_insert_file_int (visibility, playlist, after, fullname, pabort, cb, user_data);
1542 // NOTE: adding archive to playlist is the same as adding a
1543 // folder, so we don't load any playlists.
1544 // the code below is kept for reference
1545 // if (!inserted) {
1546 // // special case for loading playlists in zip files
1547 // inserted = plt_load_int (visibility, playlist, after, fullname, pabort, cb, user_data);
1548 // }
1549 }
1550 if (inserted) {
1551 after = inserted;
1552 }
1553 if (*pabort) {
1554 break;
1555 }
1556 }
1557 free (namelist[i]);
1558 }
1559 free (namelist);
1560 }
1561 return after;
1562 }
1563
1564 playItem_t *
plt_insert_dir(playlist_t * playlist,playItem_t * after,const char * dirname,int * pabort,int (* cb)(playItem_t * it,void * data),void * user_data)1565 plt_insert_dir (playlist_t *playlist, playItem_t *after, const char *dirname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) {
1566 follow_symlinks = conf_get_int ("add_folders_follow_symlinks", 0);
1567 ignore_archives = conf_get_int ("ignore_archives", 1);
1568
1569 playItem_t *ret = plt_insert_dir_int (0, playlist, NULL, after, dirname, pabort, cb, user_data);
1570
1571 ignore_archives = 0;
1572
1573 return ret;
1574 }
1575
1576 static int
plt_add_file_int(int visibility,playlist_t * plt,const char * fname,int (* cb)(playItem_t * it,void * data),void * user_data)1577 plt_add_file_int (int visibility, playlist_t *plt, const char *fname, int (*cb)(playItem_t *it, void *data), void *user_data) {
1578 int abort = 0;
1579 playItem_t *it = plt_insert_file_int (visibility, plt, plt->tail[PL_MAIN], fname, &abort, cb, user_data);
1580 if (it) {
1581 // pl_insert_file doesn't hold reference, don't unref here
1582 return 0;
1583 }
1584 return -1;
1585 }
1586
1587 int
plt_add_file(playlist_t * plt,const char * fname,int (* cb)(playItem_t * it,void * data),void * user_data)1588 plt_add_file (playlist_t *plt, const char *fname, int (*cb)(playItem_t *it, void *data), void *user_data) {
1589 return plt_add_file_int (0, plt, fname, cb, user_data);
1590 }
1591
1592 int
plt_add_dir(playlist_t * plt,const char * dirname,int (* cb)(playItem_t * it,void * data),void * user_data)1593 plt_add_dir (playlist_t *plt, const char *dirname, int (*cb)(playItem_t *it, void *data), void *user_data) {
1594 int abort = 0;
1595 playItem_t *it = plt_insert_dir (plt, plt->tail[PL_MAIN], dirname, &abort, cb, user_data);
1596 if (it) {
1597 // pl_insert_file doesn't hold reference, don't unref here
1598 return 0;
1599 }
1600 return -1;
1601 }
1602
1603 int
pl_add_files_begin(playlist_t * plt)1604 pl_add_files_begin (playlist_t *plt) {
1605 return plt_add_files_begin (plt, 0);
1606 }
1607
1608 void
pl_add_files_end(void)1609 pl_add_files_end (void) {
1610 return plt_add_files_end (addfiles_playlist, 0);
1611 }
1612
1613 int
plt_remove_item(playlist_t * playlist,playItem_t * it)1614 plt_remove_item (playlist_t *playlist, playItem_t *it) {
1615 if (!it)
1616 return -1;
1617
1618 if (!no_remove_notify) {
1619 streamer_song_removed_notify (it);
1620 playqueue_remove (it);
1621 }
1622
1623 // remove from both lists
1624 LOCK;
1625 for (int iter = PL_MAIN; iter <= PL_SEARCH; iter++) {
1626 if (it->prev[iter] || it->next[iter] || playlist->head[iter] == it || playlist->tail[iter] == it) {
1627 playlist->count[iter]--;
1628 }
1629 if (it->prev[iter]) {
1630 it->prev[iter]->next[iter] = it->next[iter];
1631 }
1632 else {
1633 playlist->head[iter] = it->next[iter];
1634 }
1635 if (it->next[iter]) {
1636 it->next[iter]->prev[iter] = it->prev[iter];
1637 }
1638 else {
1639 playlist->tail[iter] = it->prev[iter];
1640 }
1641 }
1642
1643 // totaltime
1644 float dur = pl_get_item_duration (it);
1645 if (dur > 0) {
1646 playlist->totaltime -= dur;
1647 if (playlist->totaltime < 0) {
1648 playlist->totaltime = 0;
1649 }
1650 }
1651 plt_modified (playlist);
1652 pl_item_unref (it);
1653 UNLOCK;
1654 return 0;
1655 }
1656
1657 int
plt_get_item_count(playlist_t * plt,int iter)1658 plt_get_item_count (playlist_t *plt, int iter) {
1659 return plt->count[iter];
1660 }
1661
1662 int
pl_getcount(int iter)1663 pl_getcount (int iter) {
1664 LOCK;
1665 if (!playlist) {
1666 UNLOCK;
1667 return 0;
1668 }
1669
1670 int cnt = playlist->count[iter];
1671 UNLOCK;
1672 return cnt;
1673 }
1674
1675 int
plt_getselcount(playlist_t * playlist)1676 plt_getselcount (playlist_t *playlist) {
1677 // FIXME: slow!
1678 int cnt = 0;
1679 for (playItem_t *it = playlist->head[PL_MAIN]; it; it = it->next[PL_MAIN]) {
1680 if (it->selected) {
1681 cnt++;
1682 }
1683 }
1684 return cnt;
1685 }
1686
1687 int
pl_getselcount(void)1688 pl_getselcount (void) {
1689 LOCK;
1690 int cnt = plt_getselcount (playlist);
1691 UNLOCK;
1692 return cnt;
1693 }
1694
1695 playItem_t *
plt_get_item_for_idx(playlist_t * playlist,int idx,int iter)1696 plt_get_item_for_idx (playlist_t *playlist, int idx, int iter) {
1697 LOCK;
1698 playItem_t *it = playlist->head[iter];
1699 while (idx--) {
1700 if (!it) {
1701 UNLOCK;
1702 return NULL;
1703 }
1704 it = it->next[iter];
1705 }
1706 if (it) {
1707 pl_item_ref (it);
1708 }
1709 UNLOCK;
1710 return it;
1711 }
1712
1713 playItem_t *
pl_get_for_idx_and_iter(int idx,int iter)1714 pl_get_for_idx_and_iter (int idx, int iter) {
1715 LOCK;
1716 playItem_t *it = plt_get_item_for_idx (playlist, idx, iter);
1717 UNLOCK;
1718 return it;
1719 }
1720
1721 playItem_t *
pl_get_for_idx(int idx)1722 pl_get_for_idx (int idx) {
1723 return pl_get_for_idx_and_iter (idx, PL_MAIN);
1724 }
1725
1726 int
plt_get_item_idx(playlist_t * playlist,playItem_t * it,int iter)1727 plt_get_item_idx (playlist_t *playlist, playItem_t *it, int iter) {
1728 LOCK;
1729 playItem_t *c = playlist->head[iter];
1730 int idx = 0;
1731 while (c && c != it) {
1732 c = c->next[iter];
1733 idx++;
1734 }
1735 if (!c) {
1736 UNLOCK;
1737 return -1;
1738 }
1739 UNLOCK;
1740 return idx;
1741 }
1742
1743 int
pl_get_idx_of(playItem_t * it)1744 pl_get_idx_of (playItem_t *it) {
1745 return pl_get_idx_of_iter (it, PL_MAIN);
1746 }
1747
1748 int
pl_get_idx_of_iter(playItem_t * it,int iter)1749 pl_get_idx_of_iter (playItem_t *it, int iter) {
1750 LOCK;
1751 int idx = plt_get_item_idx (playlist, it, iter);
1752 UNLOCK;
1753 return idx;
1754 }
1755
1756 playItem_t *
plt_insert_item(playlist_t * playlist,playItem_t * after,playItem_t * it)1757 plt_insert_item (playlist_t *playlist, playItem_t *after, playItem_t *it) {
1758 LOCK;
1759 pl_item_ref (it);
1760 if (!after) {
1761 it->next[PL_MAIN] = playlist->head[PL_MAIN];
1762 it->prev[PL_MAIN] = NULL;
1763 if (playlist->head[PL_MAIN]) {
1764 playlist->head[PL_MAIN]->prev[PL_MAIN] = it;
1765 }
1766 else {
1767 playlist->tail[PL_MAIN] = it;
1768 }
1769 playlist->head[PL_MAIN] = it;
1770 }
1771 else {
1772 it->prev[PL_MAIN] = after;
1773 it->next[PL_MAIN] = after->next[PL_MAIN];
1774 if (after->next[PL_MAIN]) {
1775 after->next[PL_MAIN]->prev[PL_MAIN] = it;
1776 }
1777 after->next[PL_MAIN] = it;
1778 if (after == playlist->tail[PL_MAIN]) {
1779 playlist->tail[PL_MAIN] = it;
1780 }
1781 }
1782 it->in_playlist = 1;
1783
1784 playlist->count[PL_MAIN]++;
1785
1786 // shuffle
1787 playItem_t *prev = it->prev[PL_MAIN];
1788 const char *aa = NULL, *prev_aa = NULL;
1789 if (prev) {
1790 aa = pl_find_meta_raw (it, "band");
1791 if (!aa) {
1792 aa = pl_find_meta_raw (it, "album artist");
1793 }
1794 if (!aa) {
1795 aa = pl_find_meta_raw (it, "albumartist");
1796 }
1797 prev_aa = pl_find_meta_raw (prev, "band");
1798 if (!prev_aa) {
1799 prev_aa = pl_find_meta_raw (prev, "album artist");
1800 }
1801 if (!prev_aa) {
1802 prev_aa = pl_find_meta_raw (prev, "albumartist");
1803 }
1804 }
1805 if (pl_order == PLAYBACK_ORDER_SHUFFLE_ALBUMS && prev && pl_find_meta_raw (prev, "album") == pl_find_meta_raw (it, "album") && ((aa && prev_aa && aa == prev_aa) || pl_find_meta_raw (prev, "artist") == pl_find_meta_raw (it, "artist"))) {
1806 it->shufflerating = prev->shufflerating;
1807 }
1808 else {
1809 it->shufflerating = rand ();
1810 }
1811 it->played = 0;
1812
1813 // totaltime
1814 float dur = pl_get_item_duration (it);
1815 if (dur > 0) {
1816 playlist->totaltime += dur;
1817 }
1818
1819 plt_modified (playlist);
1820
1821 UNLOCK;
1822 return it;
1823 }
1824
1825 playItem_t *
pl_insert_item(playItem_t * after,playItem_t * it)1826 pl_insert_item (playItem_t *after, playItem_t *it) {
1827 return plt_insert_item (addfiles_playlist ? addfiles_playlist : playlist, after, it);
1828 }
1829
1830 void
pl_item_copy(playItem_t * out,playItem_t * it)1831 pl_item_copy (playItem_t *out, playItem_t *it) {
1832 LOCK;
1833 out->startsample = it->startsample;
1834 out->endsample = it->endsample;
1835 out->shufflerating = it->shufflerating;
1836 out->_duration = it->_duration;
1837 out->next[PL_MAIN] = it->next[PL_MAIN];
1838 out->prev[PL_MAIN] = it->prev[PL_MAIN];
1839 out->next[PL_SEARCH] = it->next[PL_SEARCH];
1840 out->prev[PL_SEARCH] = it->prev[PL_SEARCH];
1841 out->_refc = 1;
1842 // copy metainfo
1843 DB_metaInfo_t *prev = NULL;
1844 DB_metaInfo_t *meta = it->meta;
1845 while (meta) {
1846 DB_metaInfo_t *m = malloc (sizeof (DB_metaInfo_t));
1847 memset (m, 0, sizeof (DB_metaInfo_t));
1848 m->key = metacache_add_string (meta->key);
1849 m->value = metacache_add_string (meta->value);
1850 m->next = NULL;
1851 if (prev) {
1852 prev->next = m;
1853 }
1854 else {
1855 out->meta = m;
1856 }
1857 prev = m;
1858 meta = meta->next;
1859 }
1860 UNLOCK;
1861 }
1862
1863 playItem_t *
pl_item_alloc(void)1864 pl_item_alloc (void) {
1865 playItem_t *it = malloc (sizeof (playItem_t));
1866 memset (it, 0, sizeof (playItem_t));
1867 it->_duration = -1;
1868 it->_refc = 1;
1869 return it;
1870 }
1871
1872 playItem_t *
pl_item_alloc_init(const char * fname,const char * decoder_id)1873 pl_item_alloc_init (const char *fname, const char *decoder_id) {
1874 playItem_t *it = pl_item_alloc ();
1875 pl_add_meta (it, ":URI", fname);
1876 pl_add_meta (it, ":DECODER", decoder_id);
1877 return it;
1878 }
1879
1880 void
pl_item_ref(playItem_t * it)1881 pl_item_ref (playItem_t *it) {
1882 LOCK;
1883 it->_refc++;
1884 //fprintf (stderr, "\033[0;34m+it %p: refc=%d: %s\033[37;0m\n", it, it->_refc, pl_find_meta_raw (it, ":URI"));
1885 UNLOCK;
1886 }
1887
1888 static void
pl_item_free(playItem_t * it)1889 pl_item_free (playItem_t *it) {
1890 LOCK;
1891 if (it) {
1892 while (it->meta) {
1893 DB_metaInfo_t *m = it->meta;
1894 it->meta = m->next;
1895 metacache_remove_string (m->key);
1896 metacache_remove_string (m->value);
1897 free (m);
1898 }
1899 free (it);
1900 }
1901 UNLOCK;
1902 }
1903
1904 void
pl_item_unref(playItem_t * it)1905 pl_item_unref (playItem_t *it) {
1906 LOCK;
1907 it->_refc--;
1908 //trace ("\033[0;31m-it %p: refc=%d: %s\033[37;0m\n", it, it->_refc, pl_find_meta_raw (it, ":URI"));
1909 if (it->_refc < 0) {
1910 trace ("\033[0;31mplaylist: bad refcount on item %p\033[37;0m\n", it);
1911 }
1912 if (it->_refc <= 0) {
1913 //printf ("\033[0;31mdeleted %s\033[37;0m\n", pl_find_meta_raw (it, ":URI"));
1914 pl_item_free (it);
1915 }
1916 UNLOCK;
1917 }
1918
1919 int
plt_delete_selected(playlist_t * playlist)1920 plt_delete_selected (playlist_t *playlist) {
1921 LOCK;
1922 int i = 0;
1923 int ret = -1;
1924 playItem_t *next = NULL;
1925 for (playItem_t *it = playlist->head[PL_MAIN]; it; it = next, i++) {
1926 next = it->next[PL_MAIN];
1927 if (it->selected) {
1928 if (ret == -1) {
1929 ret = i;
1930 }
1931 plt_remove_item (playlist, it);
1932 }
1933 }
1934 if (playlist->current_row[PL_MAIN] >= playlist->count[PL_MAIN]) {
1935 playlist->current_row[PL_MAIN] = playlist->count[PL_MAIN] - 1;
1936 }
1937 if (playlist->current_row[PL_SEARCH] >= playlist->count[PL_SEARCH]) {
1938 playlist->current_row[PL_SEARCH] = playlist->count[PL_SEARCH] - 1;
1939 }
1940 UNLOCK;
1941 return ret;
1942 }
1943
1944 int
pl_delete_selected(void)1945 pl_delete_selected (void) {
1946 LOCK;
1947 int ret = plt_delete_selected (playlist);
1948 UNLOCK;
1949 return ret;
1950 }
1951
1952 void
plt_crop_selected(playlist_t * playlist)1953 plt_crop_selected (playlist_t *playlist) {
1954 LOCK;
1955 playItem_t *next = NULL;
1956 for (playItem_t *it = playlist->head[PL_MAIN]; it; it = next) {
1957 next = it->next[PL_MAIN];
1958 if (!it->selected) {
1959 plt_remove_item (playlist, it);
1960 }
1961 }
1962 UNLOCK;
1963 }
1964
1965 void
pl_crop_selected(void)1966 pl_crop_selected (void) {
1967 LOCK;
1968 plt_crop_selected (playlist);
1969 UNLOCK;
1970 }
1971
1972 int
plt_save(playlist_t * plt,playItem_t * first,playItem_t * last,const char * fname,int * pabort,int (* cb)(playItem_t * it,void * data),void * user_data)1973 plt_save (playlist_t *plt, playItem_t *first, playItem_t *last, const char *fname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) {
1974 LOCK;
1975 plt->last_save_modification_idx = plt->last_save_modification_idx;
1976 const char *ext = strrchr (fname, '.');
1977 if (ext) {
1978 DB_playlist_t **plug = deadbeef->plug_get_playlist_list ();
1979 for (int i = 0; plug[i]; i++) {
1980 if (plug[i]->extensions && plug[i]->load) {
1981 const char **exts = plug[i]->extensions;
1982 if (exts && plug[i]->save) {
1983 for (int e = 0; exts[e]; e++) {
1984 if (!strcasecmp (exts[e], ext+1)) {
1985 int res = plug[i]->save ((ddb_playlist_t *)plt, fname, (DB_playItem_t *)playlist->head[PL_MAIN], NULL);
1986 UNLOCK;
1987 return res;
1988 }
1989 }
1990 }
1991 }
1992 }
1993 }
1994
1995 char tempfile[PATH_MAX];
1996 snprintf (tempfile, sizeof (tempfile), "%s.tmp", fname);
1997 const char magic[] = "DBPL";
1998 uint8_t majorver = PLAYLIST_MAJOR_VER;
1999 uint8_t minorver = PLAYLIST_MINOR_VER;
2000 FILE *fp = fopen (tempfile, "w+b");
2001 if (!fp) {
2002 UNLOCK;
2003 return -1;
2004 }
2005 if (fwrite (magic, 1, 4, fp) != 4) {
2006 goto save_fail;
2007 }
2008 if (fwrite (&majorver, 1, 1, fp) != 1) {
2009 goto save_fail;
2010 }
2011 if (fwrite (&minorver, 1, 1, fp) != 1) {
2012 goto save_fail;
2013 }
2014 uint32_t cnt = plt->count[PL_MAIN];
2015 if (fwrite (&cnt, 1, 4, fp) != 4) {
2016 goto save_fail;
2017 }
2018 for (playItem_t *it = plt->head[PL_MAIN]; it; it = it->next[PL_MAIN]) {
2019 uint16_t l;
2020 uint8_t ll;
2021 if (cb) {
2022 cb(it, user_data);
2023 }
2024 #if (PLAYLIST_MINOR_VER==2)
2025 const char *fname = pl_find_meta_raw (it, ":URI");
2026 l = strlen (fname);
2027 if (fwrite (&l, 1, 2, fp) != 2) {
2028 goto save_fail;
2029 }
2030 if (fwrite (fname, 1, l, fp) != l) {
2031 goto save_fail;
2032 }
2033 const char *decoder_id = pl_find_meta_raw (it, ":DECODER");
2034 if (decoder_id) {
2035 ll = strlen (decoder_id);
2036 if (fwrite (&ll, 1, 1, fp) != 1) {
2037 goto save_fail;
2038 }
2039 if (fwrite (decoder_id, 1, ll, fp) != ll) {
2040 goto save_fail;
2041 }
2042 }
2043 else
2044 {
2045 ll = 0;
2046 if (fwrite (&ll, 1, 1, fp) != 1) {
2047 goto save_fail;
2048 }
2049 }
2050 l = pl_find_meta_int (it, ":TRACKNUM", 0);
2051 if (fwrite (&l, 1, 2, fp) != 2) {
2052 goto save_fail;
2053 }
2054 #endif
2055 if (fwrite (&it->startsample, 1, 4, fp) != 4) {
2056 goto save_fail;
2057 }
2058 if (fwrite (&it->endsample, 1, 4, fp) != 4) {
2059 goto save_fail;
2060 }
2061 if (fwrite (&it->_duration, 1, 4, fp) != 4) {
2062 goto save_fail;
2063 }
2064 #if (PLAYLIST_MINOR_VER==2)
2065 const char *filetype = pl_find_meta_raw (it, ":FILETYPE");
2066 if (!filetype) {
2067 filetype = "";
2068 }
2069 uint8_t ft = strlen (filetype);
2070 if (fwrite (&ft, 1, 1, fp) != 1) {
2071 goto save_fail;
2072 }
2073 if (ft) {
2074 if (fwrite (filetype, 1, ft, fp) != ft) {
2075 goto save_fail;
2076 }
2077 }
2078 float rg_albumgain = pl_get_item_replaygain (it, DDB_REPLAYGAIN_ALBUMGAIN);
2079 float rg_albumpeak = pl_get_item_replaygain (it, DDB_REPLAYGAIN_ALBUMPEAK);
2080 float rg_trackgain = pl_get_item_replaygain (it, DDB_REPLAYGAIN_TRACKGAIN);
2081 float rg_trackpeak = pl_get_item_replaygain (it, DDB_REPLAYGAIN_TRACKPEAK);
2082 if (fwrite (&rg_albumgain, 1, 4, fp) != 4) {
2083 goto save_fail;
2084 }
2085 if (fwrite (&rg_albumpeak, 1, 4, fp) != 4) {
2086 goto save_fail;
2087 }
2088 if (fwrite (&rg_trackgain, 1, 4, fp) != 4) {
2089 goto save_fail;
2090 }
2091 if (fwrite (&rg_trackpeak, 1, 4, fp) != 4) {
2092 goto save_fail;
2093 }
2094 #endif
2095 if (fwrite (&it->_flags, 1, 4, fp) != 4) {
2096 goto save_fail;
2097 }
2098
2099 int16_t nm = 0;
2100 DB_metaInfo_t *m;
2101 for (m = it->meta; m; m = m->next) {
2102 if (m->key[0] == '_' || m->key[0] == '!') {
2103 continue; // skip reserved names
2104 }
2105 nm++;
2106 }
2107 if (fwrite (&nm, 1, 2, fp) != 2) {
2108 goto save_fail;
2109 }
2110 for (m = it->meta; m; m = m->next) {
2111 if (m->key[0] == '_' || m->key[0] == '!') {
2112 continue; // skip reserved names
2113 }
2114
2115 l = strlen (m->key);
2116 if (fwrite (&l, 1, 2, fp) != 2) {
2117 goto save_fail;
2118 }
2119 if (l) {
2120 if (fwrite (m->key, 1, l, fp) != l) {
2121 goto save_fail;
2122 }
2123 }
2124 l = strlen (m->value);
2125 if (fwrite (&l, 1, 2, fp) != 2) {
2126 goto save_fail;
2127 }
2128 if (l) {
2129 if (fwrite (m->value, 1, l, fp) != l) {
2130 goto save_fail;
2131 }
2132 }
2133 }
2134 }
2135
2136 // write playlist metadata
2137 int16_t nm = 0;
2138 DB_metaInfo_t *m;
2139 for (m = plt->meta; m; m = m->next) {
2140 nm++;
2141 }
2142 if (fwrite (&nm, 1, 2, fp) != 2) {
2143 goto save_fail;
2144 }
2145
2146 for (m = plt->meta; m; m = m->next) {
2147 uint16_t l;
2148 l = strlen (m->key);
2149 if (fwrite (&l, 1, 2, fp) != 2) {
2150 goto save_fail;
2151 }
2152 if (l) {
2153 if (fwrite (m->key, 1, l, fp) != l) {
2154 goto save_fail;
2155 }
2156 }
2157 l = strlen (m->value);
2158 if (fwrite (&l, 1, 2, fp) != 2) {
2159 goto save_fail;
2160 }
2161 if (l) {
2162 if (fwrite (m->value, 1, l, fp) != l) {
2163 goto save_fail;
2164 }
2165 }
2166 }
2167
2168 UNLOCK;
2169 fclose (fp);
2170 if (rename (tempfile, fname) != 0) {
2171 fprintf (stderr, "playlist rename %s -> %s failed: %s\n", tempfile, fname, strerror (errno));
2172 return -1;
2173 }
2174 return 0;
2175 save_fail:
2176 UNLOCK;
2177 fclose (fp);
2178 unlink (tempfile);
2179 return -1;
2180 }
2181
2182 int
plt_save_n(int n)2183 plt_save_n (int n) {
2184 char path[PATH_MAX];
2185 if (snprintf (path, sizeof (path), "%s/playlists", dbconfdir) > sizeof (path)) {
2186 fprintf (stderr, "error: failed to make path string for playlists folder\n");
2187 return -1;
2188 }
2189 // make folder
2190 mkdir (path, 0755);
2191
2192 LOCK;
2193 int err = 0;
2194
2195 plt_loading = 1;
2196 if (snprintf (path, sizeof (path), "%s/playlists/%d.dbpl", dbconfdir, n) > sizeof (path)) {
2197 fprintf (stderr, "error: failed to make path string for playlist file\n");
2198 UNLOCK;
2199 return -1;
2200 }
2201
2202 int i;
2203 playlist_t *plt;
2204 for (i = 0, plt = playlists_head; plt && i < n; i++, plt = plt->next);
2205 err = plt_save (plt, NULL, NULL, path, NULL, NULL, NULL);
2206 plt_loading = 0;
2207 UNLOCK;
2208 return err;
2209 }
2210
2211 int
pl_save_current(void)2212 pl_save_current (void) {
2213 return plt_save_n (plt_get_curr_idx ());
2214 }
2215
2216 int
pl_save_all(void)2217 pl_save_all (void) {
2218 trace ("pl_save_all\n");
2219 char path[PATH_MAX];
2220 if (snprintf (path, sizeof (path), "%s/playlists", dbconfdir) > sizeof (path)) {
2221 fprintf (stderr, "error: failed to make path string for playlists folder\n");
2222 return -1;
2223 }
2224 // make folder
2225 mkdir (path, 0755);
2226
2227 LOCK;
2228 playlist_t *p = playlists_head;
2229 int i;
2230 int cnt = plt_get_count ();
2231 int curr = plt_get_curr_idx ();
2232 int err = 0;
2233
2234 plt_gen_conf ();
2235 plt_loading = 1;
2236 for (i = 0; i < cnt; i++, p = p->next) {
2237 if (snprintf (path, sizeof (path), "%s/playlists/%d.dbpl", dbconfdir, i) > sizeof (path)) {
2238 fprintf (stderr, "error: failed to make path string for playlist file\n");
2239 err = -1;
2240 break;
2241 }
2242 if (p->last_save_modification_idx == p->modification_idx) {
2243 continue;
2244 }
2245 err = plt_save (p, NULL, NULL, path, NULL, NULL, NULL);
2246 if (err < 0) {
2247 break;
2248 }
2249 }
2250 plt_loading = 0;
2251 UNLOCK;
2252 return err;
2253 }
2254
2255 static playItem_t *
plt_load_int(int visibility,playlist_t * plt,playItem_t * after,const char * fname,int * pabort,int (* cb)(playItem_t * it,void * data),void * user_data)2256 plt_load_int (int visibility, playlist_t *plt, playItem_t *after, const char *fname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) {
2257 // try plugins 1st
2258 const char *ext = strrchr (fname, '.');
2259 if (ext) {
2260 trace ("finding playlist plugin for %s\n", ext);
2261 ext++;
2262 DB_playlist_t **plug = plug_get_playlist_list ();
2263 int p, e;
2264 for (p = 0; plug[p]; p++) {
2265 for (e = 0; plug[p]->extensions[e]; e++) {
2266 if (plug[p]->load && !strcasecmp (ext, plug[p]->extensions[e])) {
2267 DB_playItem_t *it = NULL;
2268 if (cb || (plug[p]->load && !plug[p]->load2)) {
2269 it = plug[p]->load ((ddb_playlist_t *)plt, (DB_playItem_t *)after, fname, pabort, (int (*)(DB_playItem_t *, void *))cb, user_data);
2270 }
2271 else if (plug[p]->load2) {
2272 plug[p]->load2 (visibility, (ddb_playlist_t *)plt, (DB_playItem_t *)after, fname, pabort);
2273 }
2274 return (playItem_t *)it;
2275 }
2276 }
2277 }
2278 }
2279 trace ("plt_load: loading dbpl\n");
2280 FILE *fp = fopen (fname, "rb");
2281 if (!fp) {
2282 trace ("plt_load: failed to open %s\n", fname);
2283 return NULL;
2284 }
2285
2286 playItem_t *last_added = NULL;
2287
2288 uint8_t majorver;
2289 uint8_t minorver;
2290 playItem_t *it = NULL;
2291 char magic[4];
2292 if (fread (magic, 1, 4, fp) != 4) {
2293 trace ("failed to read magic\n");
2294 goto load_fail;
2295 }
2296 if (strncmp (magic, "DBPL", 4)) {
2297 trace ("bad signature\n");
2298 goto load_fail;
2299 }
2300 if (fread (&majorver, 1, 1, fp) != 1) {
2301 goto load_fail;
2302 }
2303 if (majorver != PLAYLIST_MAJOR_VER) {
2304 trace ("bad majorver=%d\n", majorver);
2305 goto load_fail;
2306 }
2307 if (fread (&minorver, 1, 1, fp) != 1) {
2308 goto load_fail;
2309 }
2310 if (minorver < 1) {
2311 trace ("bad minorver=%d\n", minorver);
2312 goto load_fail;
2313 }
2314 trace ("playlist version=%d.%d\n", majorver, minorver);
2315 uint32_t cnt;
2316 if (fread (&cnt, 1, 4, fp) != 4) {
2317 goto load_fail;
2318 }
2319
2320 for (uint32_t i = 0; i < cnt; i++) {
2321 it = pl_item_alloc ();
2322 if (!it) {
2323 goto load_fail;
2324 }
2325 uint16_t l;
2326 int16_t tracknum = 0;
2327 if (minorver <= 2) {
2328 // fname
2329 if (fread (&l, 1, 2, fp) != 2) {
2330 goto load_fail;
2331 }
2332 char fname[l+1];
2333 if (fread (fname, 1, l, fp) != l) {
2334 goto load_fail;
2335 }
2336 fname[l] = 0;
2337 pl_add_meta (it, ":URI", fname);
2338 // decoder
2339 uint8_t ll;
2340 if (fread (&ll, 1, 1, fp) != 1) {
2341 goto load_fail;
2342 }
2343 if (ll >= 20) {
2344 goto load_fail;
2345 }
2346 char decoder_id[20] = "";
2347 if (ll) {
2348 if (fread (decoder_id, 1, ll, fp) != ll) {
2349 goto load_fail;
2350 }
2351 decoder_id[ll] = 0;
2352 pl_add_meta (it, ":DECODER", decoder_id);
2353 }
2354 // tracknum
2355 if (fread (&tracknum, 1, 2, fp) != 2) {
2356 goto load_fail;
2357 }
2358 pl_set_meta_int (it, ":TRACKNUM", tracknum);
2359 }
2360 // startsample
2361 if (fread (&it->startsample, 1, 4, fp) != 4) {
2362 goto load_fail;
2363 }
2364 // endsample
2365 if (fread (&it->endsample, 1, 4, fp) != 4) {
2366 goto load_fail;
2367 }
2368 // duration
2369 if (fread (&it->_duration, 1, 4, fp) != 4) {
2370 goto load_fail;
2371 }
2372 char s[100];
2373 pl_format_time (it->_duration, s, sizeof(s));
2374 pl_replace_meta (it, ":DURATION", s);
2375
2376 if (minorver <= 2) {
2377 // legacy filetype support
2378 uint8_t ft;
2379 if (fread (&ft, 1, 1, fp) != 1) {
2380 goto load_fail;
2381 }
2382 if (ft) {
2383 char ftype[ft+1];
2384 if (fread (ftype, 1, ft, fp) != ft) {
2385 goto load_fail;
2386 }
2387 ftype[ft] = 0;
2388 pl_replace_meta (it, ":FILETYPE", ftype);
2389 }
2390
2391 float f;
2392
2393 if (fread (&f, 1, 4, fp) != 4) {
2394 goto load_fail;
2395 }
2396 if (f != 0) {
2397 pl_set_item_replaygain (it, DDB_REPLAYGAIN_ALBUMGAIN, f);
2398 }
2399
2400 if (fread (&f, 1, 4, fp) != 4) {
2401 goto load_fail;
2402 }
2403 if (f == 0) {
2404 f = 1;
2405 }
2406 if (f != 1) {
2407 pl_set_item_replaygain (it, DDB_REPLAYGAIN_ALBUMPEAK, f);
2408 }
2409
2410 if (fread (&f, 1, 4, fp) != 4) {
2411 goto load_fail;
2412 }
2413 if (f != 0) {
2414 pl_set_item_replaygain (it, DDB_REPLAYGAIN_TRACKGAIN, f);
2415 }
2416
2417 if (fread (&f, 1, 4, fp) != 4) {
2418 goto load_fail;
2419 }
2420 if (f == 0) {
2421 f = 1;
2422 }
2423 if (f != 1) {
2424 pl_set_item_replaygain (it, DDB_REPLAYGAIN_TRACKPEAK, f);
2425 }
2426 }
2427
2428 uint32_t flg = 0;
2429 if (minorver >= 2) {
2430 if (fread (&flg, 1, 4, fp) != 4) {
2431 goto load_fail;
2432 }
2433 }
2434 else {
2435 if (it->startsample > 0 || it->endsample > 0 || tracknum > 0) {
2436 flg |= DDB_IS_SUBTRACK;
2437 }
2438 }
2439 pl_set_item_flags (it, flg);
2440
2441 int16_t nm = 0;
2442 if (fread (&nm, 1, 2, fp) != 2) {
2443 goto load_fail;
2444 }
2445 for (int i = 0; i < nm; i++) {
2446 if (fread (&l, 1, 2, fp) != 2) {
2447 goto load_fail;
2448 }
2449 if (l >= 20000) {
2450 goto load_fail;
2451 }
2452 char key[l+1];
2453 if (fread (key, 1, l, fp) != l) {
2454 goto load_fail;
2455 }
2456 key[l] = 0;
2457 if (fread (&l, 1, 2, fp) != 2) {
2458 goto load_fail;
2459 }
2460 if (l >= 20000) {
2461 // skip
2462 fseek (fp, l, SEEK_CUR);
2463 }
2464 else {
2465 char value[l+1];
2466 int res = fread (value, 1, l, fp);
2467 if (res != l) {
2468 trace ("read error: requested %d, got %d\n", l, res);
2469 goto load_fail;
2470 }
2471 value[l] = 0;
2472 if (key[0] == ':') {
2473 // to avoid storage conflicts -- give more priority to metadata
2474 pl_replace_meta (it, key, value);
2475 }
2476 else {
2477 pl_add_meta (it, key, value);
2478 }
2479 }
2480 }
2481 plt_insert_item (plt, plt->tail[PL_MAIN], it);
2482 if (last_added) {
2483 pl_item_unref (last_added);
2484 }
2485 last_added = it;
2486 }
2487
2488 // load playlist metadata
2489 int16_t nm = 0;
2490 // for backwards format compatibility, don't fail if metadata is not found
2491 if (fread (&nm, 1, 2, fp) == 2) {
2492 for (int i = 0; i < nm; i++) {
2493 int16_t l;
2494 if (fread (&l, 1, 2, fp) != 2) {
2495 goto load_fail;
2496 }
2497 if (l < 0 || l >= 20000) {
2498 goto load_fail;
2499 }
2500 char key[l+1];
2501 if (fread (key, 1, l, fp) != l) {
2502 goto load_fail;
2503 }
2504 key[l] = 0;
2505 if (fread (&l, 1, 2, fp) != 2) {
2506 goto load_fail;
2507 }
2508 if (l<0 || l >= 20000) {
2509 // skip
2510 fseek (fp, l, SEEK_CUR);
2511 }
2512 else {
2513 char value[l+1];
2514 int res = fread (value, 1, l, fp);
2515 if (res != l) {
2516 trace ("read error: requested %d, got %d\n", l, res);
2517 goto load_fail;
2518 }
2519 value[l] = 0;
2520 plt_add_meta (plt, key, value);
2521 }
2522 }
2523 }
2524
2525 if (fp) {
2526 fclose (fp);
2527 }
2528 trace ("plt_load: success\n");
2529 if (last_added) {
2530 pl_item_unref (last_added);
2531 }
2532 return last_added;
2533 load_fail:
2534 plt_clear (plt);
2535 fprintf (stderr, "playlist load fail (%s)!\n", fname);
2536 if (fp) {
2537 fclose (fp);
2538 }
2539 if (last_added) {
2540 pl_item_unref (last_added);
2541 }
2542 return last_added;
2543 }
2544
2545
2546 playItem_t *
plt_load(playlist_t * plt,playItem_t * after,const char * fname,int * pabort,int (* cb)(playItem_t * it,void * data),void * user_data)2547 plt_load (playlist_t *plt, playItem_t *after, const char *fname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) {
2548 return plt_load_int (0, plt, after, fname, pabort, cb, user_data);
2549 }
2550
2551 int
pl_load_all(void)2552 pl_load_all (void) {
2553 int i = 0;
2554 int err = 0;
2555 char path[1024];
2556 DB_conf_item_t *it = conf_find ("playlist.tab.", NULL);
2557 if (!it) {
2558 // legacy (0.3.3 and earlier)
2559 char defpl[1024]; // $HOME/.config/deadbeef/default.dbpl
2560 if (snprintf (defpl, sizeof (defpl), "%s/default.dbpl", dbconfdir) > sizeof (defpl)) {
2561 fprintf (stderr, "error: cannot make string with default playlist path\n");
2562 return -1;
2563 }
2564 if (plt_add (plt_get_count (), _("Default")) < 0) {
2565 return -1;
2566 }
2567
2568 playlist_t *plt = plt_get_for_idx (0);
2569 playItem_t *it = plt_load (plt, NULL, defpl, NULL, NULL, NULL);
2570 plt_unref (plt);
2571 return 0;
2572 }
2573 trace ("pl_load_all started\n");
2574 LOCK;
2575 trace ("locked\n");
2576 plt_loading = 1;
2577 while (it) {
2578 fprintf (stderr, "INFO: loading playlist %s\n", it->value);
2579 if (!err) {
2580 if (plt_add (plt_get_count (), it->value) < 0) {
2581 return -1;
2582 }
2583 plt_set_curr_idx (plt_get_count () - 1);
2584 }
2585 err = 0;
2586 if (snprintf (path, sizeof (path), "%s/playlists/%d.dbpl", dbconfdir, i) > sizeof (path)) {
2587 fprintf (stderr, "error: failed to make path string for playlist filename\n");
2588 err = -1;
2589 }
2590 else {
2591 fprintf (stderr, "INFO: from file %s\n", path);
2592
2593 playlist_t *plt = plt_get_curr ();
2594 playItem_t *trk = plt_load (plt, NULL, path, NULL, NULL, NULL);
2595 char conf[100];
2596 snprintf (conf, sizeof (conf), "playlist.cursor.%d", i);
2597 plt->current_row[PL_MAIN] = deadbeef->conf_get_int (conf, -1);
2598 snprintf (conf, sizeof (conf), "playlist.scroll.%d", i);
2599 plt->scroll = deadbeef->conf_get_int (conf, 0);
2600 plt->last_save_modification_idx = plt->modification_idx = 0;
2601 plt_unref (plt);
2602
2603 if (!it) {
2604 fprintf (stderr, "WARNING: there were errors while loading playlist '%s' (%s)\n", it->value, path);
2605 }
2606 }
2607 it = conf_find ("playlist.tab.", it);
2608 trace ("conf_find returned %p (%s)\n", it, it ? it->value : "null");
2609 i++;
2610 }
2611 plt_set_curr (0);
2612 plt_loading = 0;
2613 plt_gen_conf ();
2614 messagepump_push (DB_EV_PLAYLISTSWITCHED, 0, 0, 0);
2615 UNLOCK;
2616 trace ("pl_load_all finished\n");
2617 return err;
2618 }
2619
2620 void
plt_select_all(playlist_t * playlist)2621 plt_select_all (playlist_t *playlist) {
2622 LOCK;
2623 for (playItem_t *it = playlist->head[PL_MAIN]; it; it = it->next[PL_MAIN]) {
2624 it->selected = 1;
2625 }
2626 UNLOCK;
2627 }
2628
2629 void
pl_select_all(void)2630 pl_select_all (void) {
2631 LOCK;
2632 plt_select_all (playlist);
2633 UNLOCK;
2634 }
2635
2636 void
plt_reshuffle(playlist_t * playlist,playItem_t ** ppmin,playItem_t ** ppmax)2637 plt_reshuffle (playlist_t *playlist, playItem_t **ppmin, playItem_t **ppmax) {
2638 LOCK;
2639 playItem_t *pmin = NULL;
2640 playItem_t *pmax = NULL;
2641 playItem_t *prev = NULL;
2642 const char *alb = NULL;
2643 const char *art = NULL;
2644 const char *aa = NULL;
2645 for (playItem_t *it = playlist->head[PL_MAIN]; it; it = it->next[PL_MAIN]) {
2646 const char *new_aa = NULL;
2647 new_aa = pl_find_meta_raw (it, "band");
2648 if (!new_aa) {
2649 new_aa = pl_find_meta_raw (it, "album artist");
2650 }
2651 if (!new_aa) {
2652 new_aa = pl_find_meta_raw (it, "albumartist");
2653 }
2654 if (pl_order == PLAYBACK_ORDER_SHUFFLE_ALBUMS && prev && alb == pl_find_meta_raw (it, "album") && ((aa && new_aa && aa == new_aa) || art == pl_find_meta_raw (it, "artist"))) {
2655 it->shufflerating = prev->shufflerating;
2656 }
2657 else {
2658 prev = it;
2659 it->shufflerating = rand ();
2660 alb = pl_find_meta_raw (it, "album");
2661 art = pl_find_meta_raw (it, "artist");
2662 aa = new_aa;
2663 }
2664 if (!pmin || it->shufflerating < pmin->shufflerating) {
2665 pmin = it;
2666 }
2667 if (!pmax || it->shufflerating > pmax->shufflerating) {
2668 pmax = it;
2669 }
2670 it->played = 0;
2671 }
2672 if (ppmin) {
2673 *ppmin = pmin;
2674 }
2675 if (ppmax) {
2676 *ppmax = pmax;
2677 }
2678 UNLOCK;
2679 }
2680
2681 void
plt_set_item_duration(playlist_t * playlist,playItem_t * it,float duration)2682 plt_set_item_duration (playlist_t *playlist, playItem_t *it, float duration) {
2683 LOCK;
2684 if (it->in_playlist && playlist) {
2685 if (it->_duration > 0) {
2686 playlist->totaltime -= it->_duration;
2687 }
2688 if (duration > 0) {
2689 playlist->totaltime += duration;
2690 }
2691 if (playlist->totaltime < 0) {
2692 playlist->totaltime = 0;
2693 }
2694 }
2695 it->_duration = duration;
2696 char s[100];
2697 pl_format_time (it->_duration, s, sizeof(s));
2698 pl_replace_meta (it, ":DURATION", s);
2699 UNLOCK;
2700 }
2701
2702 float
pl_get_item_duration(playItem_t * it)2703 pl_get_item_duration (playItem_t *it) {
2704 return it->_duration;
2705 }
2706
2707 void
pl_set_item_replaygain(playItem_t * it,int idx,float value)2708 pl_set_item_replaygain (playItem_t *it, int idx, float value) {
2709 char s[100];
2710 switch (idx) {
2711 case DDB_REPLAYGAIN_ALBUMGAIN:
2712 case DDB_REPLAYGAIN_TRACKGAIN:
2713 snprintf (s, sizeof (s), "%0.2f dB", value);
2714 break;
2715 case DDB_REPLAYGAIN_ALBUMPEAK:
2716 case DDB_REPLAYGAIN_TRACKPEAK:
2717 snprintf (s, sizeof (s), "%0.6f", value);
2718 break;
2719 default:
2720 return;
2721 }
2722 pl_replace_meta (it, ddb_internal_rg_keys[idx], s);
2723 }
2724
2725 float
pl_get_item_replaygain(playItem_t * it,int idx)2726 pl_get_item_replaygain (playItem_t *it, int idx) {
2727 if (idx < 0 || idx > DDB_REPLAYGAIN_TRACKPEAK) {
2728 return 0;
2729 }
2730
2731 switch (idx) {
2732 case DDB_REPLAYGAIN_ALBUMGAIN:
2733 case DDB_REPLAYGAIN_TRACKGAIN:
2734 return pl_find_meta_float (it, ddb_internal_rg_keys[idx], 0);
2735 case DDB_REPLAYGAIN_ALBUMPEAK:
2736 case DDB_REPLAYGAIN_TRACKPEAK:
2737 return pl_find_meta_float (it, ddb_internal_rg_keys[idx], 1);
2738 }
2739 return 0;
2740 }
2741
2742 int
pl_format_item_queue(playItem_t * it,char * s,int size)2743 pl_format_item_queue (playItem_t *it, char *s, int size) {
2744 LOCK;
2745 *s = 0;
2746 int initsize = size;
2747 const char *val = pl_find_meta_raw (it, "_playing");
2748 while (val && *val) {
2749 while (*val && *val != '=') {
2750 val++;
2751 }
2752 if (*val == '=') {
2753 // found value
2754 val++;
2755 if (!(*val)) {
2756 break;
2757 }
2758 const char *e = NULL;
2759 if (*val == '"') {
2760 val++;
2761 e = val;
2762 while (*e && *e != '"') {
2763 e++;
2764 }
2765 }
2766 else {
2767 e = val;
2768 while (*e && *e != ' ') {
2769 e++;
2770 }
2771 }
2772 int n = (int)(e - val);
2773 if (n > size-1) {
2774 n = size-1;
2775 }
2776 strncpy (s, val, n);
2777 s += n;
2778 *s++ = ' ';
2779 *s = 0;
2780 size -= n+1;
2781 val = e;
2782 if (*val) {
2783 val++;
2784 }
2785 while (*val && *val == ' ') {
2786 val++;
2787 }
2788 }
2789 }
2790
2791 int pq_cnt = playqueue_getcount ();
2792
2793 if (!pq_cnt) {
2794 UNLOCK;
2795 return 0;
2796 }
2797
2798 int qinitsize = size;
2799 int init = 1;
2800 int len;
2801 for (int i = 0; i < pq_cnt; i++) {
2802 if (size <= 0) {
2803 break;
2804 }
2805
2806 playItem_t *pqitem = playqueue_get_item (i);
2807 if (pqitem == it) {
2808 if (init) {
2809 init = 0;
2810 s[0] = '(';
2811 s++;
2812 size--;
2813 len = snprintf (s, size, "%d", i+1);
2814 }
2815 else {
2816 len = snprintf (s, size, ",%d", i+1);
2817 }
2818 s += len;
2819 size -= len;
2820 }
2821 pl_item_unref (pqitem);
2822 }
2823 if (size != qinitsize && size > 0) {
2824 len = snprintf (s, size, ")");
2825 s += len;
2826 size -= len;
2827 }
2828 UNLOCK;
2829 return initsize-size;
2830 }
2831
2832 void
pl_format_time(float t,char * dur,int size)2833 pl_format_time (float t, char *dur, int size) {
2834 if (t >= 0) {
2835 t = roundf (t);
2836 int hourdur = t / (60 * 60);
2837 int mindur = (t - hourdur * 60 * 60) / 60;
2838 int secdur = t - hourdur*60*60 - mindur * 60;
2839
2840 if (hourdur) {
2841 snprintf (dur, size, "%d:%02d:%02d", hourdur, mindur, secdur);
2842 }
2843 else {
2844 snprintf (dur, size, "%d:%02d", mindur, secdur);
2845 }
2846 }
2847 else {
2848 strcpy (dur, "∞");
2849 }
2850 }
2851
2852 const char *
pl_format_duration(playItem_t * it,const char * ret,char * dur,int size)2853 pl_format_duration (playItem_t *it, const char *ret, char *dur, int size) {
2854 if (ret) {
2855 return ret;
2856 }
2857 pl_format_time (pl_get_item_duration (it), dur, size);
2858 return dur;
2859 }
2860
2861 static const char *
pl_format_elapsed(const char * ret,char * elapsed,int size)2862 pl_format_elapsed (const char *ret, char *elapsed, int size) {
2863 if (ret) {
2864 return ret;
2865 }
2866 float playpos = streamer_get_playpos ();
2867 pl_format_time (playpos, elapsed, size);
2868 return elapsed;
2869 }
2870
2871 // this function allows to escape special chars substituted for conversions
2872 // @escape_chars: list of escapable characters terminated with 0, or NULL if none
2873 static int
pl_format_title_int(const char * escape_chars,playItem_t * it,int idx,char * s,int size,int id,const char * fmt)2874 pl_format_title_int (const char *escape_chars, playItem_t *it, int idx, char *s, int size, int id, const char *fmt) {
2875 char tmp[50];
2876 char tags[200];
2877 char dirname[PATH_MAX];
2878 const char *duration = NULL;
2879 const char *elapsed = NULL;
2880 int escape_slash = 0;
2881
2882 char *ss = s;
2883
2884 LOCK;
2885 if (id != -1 && it) {
2886 const char *text = NULL;
2887 switch (id) {
2888 case DB_COLUMN_FILENUMBER:
2889 if (idx == -1) {
2890 idx = pl_get_idx_of (it);
2891 }
2892 snprintf (tmp, sizeof (tmp), "%d", idx+1);
2893 text = tmp;
2894 break;
2895 case DB_COLUMN_PLAYING:
2896 UNLOCK;
2897 return pl_format_item_queue (it, s, size);
2898 }
2899 if (text) {
2900 strncpy (s, text, size);
2901 UNLOCK;
2902 for (ss = s; *ss; ss++) {
2903 if (*ss == '\n') {
2904 *ss = ';';
2905 }
2906 }
2907 return strlen (s);
2908 }
2909 else {
2910 s[0] = 0;
2911 }
2912 UNLOCK;
2913 return 0;
2914 }
2915 int n = size-1;
2916 while (*fmt && n > 0) {
2917 if (*fmt != '%') {
2918 *s++ = *fmt;
2919 n--;
2920 }
2921 else {
2922 fmt++;
2923 const char *meta = NULL;
2924 if (*fmt == 0) {
2925 break;
2926 }
2927 else if (!it && *fmt != 'V') {
2928 // only %V (version) works without track pointer
2929 }
2930 else if (*fmt == '@') {
2931 const char *e = fmt;
2932 e++;
2933 while (*e && *e != '@') {
2934 e++;
2935 }
2936 if (*e == '@') {
2937 char nm[100];
2938 int l = e-fmt-1;
2939 l = min (l, sizeof (nm)-1);
2940 strncpy (nm, fmt+1, l);
2941 nm[l] = 0;
2942 meta = pl_find_meta_raw (it, nm);
2943 if (!meta) {
2944 meta = "";
2945 }
2946 fmt = e;
2947 }
2948 }
2949 else if (*fmt == '/') {
2950 // this means all '/' in the ongoing fields must be replaced with '\'
2951 escape_slash = 1;
2952 }
2953 else if (*fmt == 'a') {
2954 meta = pl_find_meta_raw (it, "artist");
2955 #ifndef DISABLE_CUSTOM_TITLE
2956 const char *custom = pl_find_meta_raw (it, ":CUSTOM_TITLE");
2957 #endif
2958 if (!meta
2959 #ifndef DISABLE_CUSTOM_TITLE
2960 && !custom
2961 #endif
2962 ) {
2963 meta = "Unknown artist";
2964 }
2965
2966 #ifndef DISABLE_CUSTOM_TITLE
2967 if (custom) {
2968 if (!meta) {
2969 meta = custom;
2970 }
2971 else {
2972 int l = strlen (custom) + strlen (meta) + 4;
2973 char *out = alloca (l);
2974 snprintf (out, l, "[%s] %s", custom, meta);
2975 meta = out;
2976 }
2977 }
2978 #endif
2979 }
2980 else if (*fmt == 't') {
2981 meta = pl_find_meta_raw (it, "title");
2982 if (!meta) {
2983 const char *f = pl_find_meta_raw (it, ":URI");
2984 if (f) {
2985 const char *start = strrchr (f, '/');
2986 if (start) {
2987 start++;
2988 }
2989 else {
2990 start = f;
2991 }
2992 const char *end = strrchr (start, '.');
2993 if (end) {
2994 int n = end-start;
2995 n = min (end-start, sizeof (dirname)-1);
2996 strncpy (dirname, start, n);
2997 dirname[n] = 0;
2998 meta = dirname;
2999 }
3000 else {
3001 meta = "";
3002 }
3003 }
3004 else {
3005 meta = "";
3006 }
3007 }
3008 }
3009 else if (*fmt == 'b') {
3010 meta = pl_find_meta_raw (it, "album");
3011 if (!meta) {
3012 meta = "Unknown album";
3013 }
3014 }
3015 else if (*fmt == 'B') {
3016 meta = pl_find_meta_raw (it, "band");
3017 if (!meta) {
3018 meta = pl_find_meta_raw (it, "album artist");
3019 if (!meta) {
3020 meta = pl_find_meta_raw (it, "albumartist");
3021 if (!meta) {
3022 meta = pl_find_meta_raw (it, "artist");
3023 }
3024 }
3025 }
3026
3027 #ifndef DISABLE_CUSTOM_TITLE
3028 const char *custom = pl_find_meta_raw (it, ":CUSTOM_TITLE");
3029 if (custom) {
3030 if (!meta) {
3031 meta = custom;
3032 }
3033 else {
3034 int l = strlen (custom) + strlen (meta) + 4;
3035 char *out = alloca (l);
3036 snprintf (out, l, "[%s] %s", custom, meta);
3037 meta = out;
3038 }
3039 }
3040 #endif
3041 }
3042 else if (*fmt == 'C') {
3043 meta = pl_find_meta_raw (it, "composer");
3044 }
3045 else if (*fmt == 'n') {
3046 meta = pl_find_meta_raw (it, "track");
3047 if (meta) {
3048 // check if it's numbers only
3049 const char *p = meta;
3050 while (*p) {
3051 if (!isdigit (*p)) {
3052 break;
3053 }
3054 p++;
3055 }
3056 if (!(*p)) {
3057 snprintf (tmp, sizeof (tmp), "%02d", atoi (meta));
3058 meta = tmp;
3059 }
3060 }
3061 }
3062 else if (*fmt == 'N') {
3063 meta = pl_find_meta_raw (it, "numtracks");
3064 }
3065 else if (*fmt == 'y') {
3066 meta = pl_find_meta_raw (it, "year");
3067 }
3068 else if (*fmt == 'Y') {
3069 meta = pl_find_meta_raw (it, "original_release_time");
3070 if (!meta) {
3071 meta = pl_find_meta_raw (it, "original_release_year");
3072 if (!meta) {
3073 meta = pl_find_meta_raw (it, "year");
3074 }
3075 }
3076 }
3077 else if (*fmt == 'g') {
3078 meta = pl_find_meta_raw (it, "genre");
3079 }
3080 else if (*fmt == 'c') {
3081 meta = pl_find_meta_raw (it, "comment");
3082 }
3083 else if (*fmt == 'r') {
3084 meta = pl_find_meta_raw (it, "copyright");
3085 }
3086 else if (*fmt == 'l') {
3087 const char *value = (duration = pl_format_duration (it, duration, tmp, sizeof (tmp)));
3088 while (n > 0 && *value) {
3089 *s++ = *value++;
3090 n--;
3091 }
3092 }
3093 else if (*fmt == 'e') {
3094 // what a hack..
3095 const char *value = (elapsed = pl_format_elapsed (elapsed, tmp, sizeof (tmp)));
3096 while (n > 0 && *value) {
3097 *s++ = *value++;
3098 n--;
3099 }
3100 }
3101 else if (*fmt == 'f') {
3102 const char *f = pl_find_meta_raw (it, ":URI");
3103 meta = strrchr (f, '/');
3104 if (meta) {
3105 meta++;
3106 }
3107 else {
3108 meta = f;
3109 }
3110 }
3111 else if (*fmt == 'F') {
3112 meta = pl_find_meta_raw (it, ":URI");
3113 }
3114 else if (*fmt == 'T') {
3115 char *t = tags;
3116 char *e = tags + sizeof (tags);
3117 int c;
3118 *t = 0;
3119
3120 if (it->_flags & DDB_TAG_ID3V1) {
3121 c = snprintf (t, e-t, "ID3v1 | ");
3122 t += c;
3123 }
3124 if (it->_flags & DDB_TAG_ID3V22) {
3125 c = snprintf (t, e-t, "ID3v2.2 | ");
3126 t += c;
3127 }
3128 if (it->_flags & DDB_TAG_ID3V23) {
3129 c = snprintf (t, e-t, "ID3v2.3 | ");
3130 t += c;
3131 }
3132 if (it->_flags & DDB_TAG_ID3V24) {
3133 c = snprintf (t, e-t, "ID3v2.4 | ");
3134 t += c;
3135 }
3136 if (it->_flags & DDB_TAG_APEV2) {
3137 c = snprintf (t, e-t, "APEv2 | ");
3138 t += c;
3139 }
3140 if (it->_flags & DDB_TAG_VORBISCOMMENTS) {
3141 c = snprintf (t, e-t, "VorbisComments | ");
3142 t += c;
3143 }
3144 if (it->_flags & DDB_TAG_CUESHEET) {
3145 c = snprintf (t, e-t, "CueSheet | ");
3146 t += c;
3147 }
3148 if (it->_flags & DDB_TAG_ICY) {
3149 c = snprintf (t, e-t, "Icy | ");
3150 t += c;
3151 }
3152 if (it->_flags & DDB_TAG_ITUNES) {
3153 c = snprintf (t, e-t, "iTunes | ");
3154 t += c;
3155 }
3156 if (t != tags) {
3157 *(t - 3) = 0;
3158 }
3159 meta = tags;
3160 }
3161 else if (*fmt == 'd') {
3162 // directory
3163 const char *f = pl_find_meta_raw (it, ":URI");
3164 const char *end = strrchr (f, '/');
3165 if (!end) {
3166 meta = ""; // got relative path without folder (should not happen)
3167 }
3168 else {
3169 const char *start = end;
3170 start--;
3171 while (start > f && (*start != '/')) {
3172 start--;
3173 }
3174
3175 if (*start == '/') {
3176 start++;
3177 }
3178
3179 // copy
3180 int len = end-start;
3181 len = min (len, sizeof (dirname)-1);
3182 strncpy (dirname, start, len);
3183 dirname[len] = 0;
3184 meta = dirname;
3185 }
3186 }
3187 else if (*fmt == 'D') {
3188 const char *f = pl_find_meta_raw (it, ":URI");
3189 // directory with path
3190 const char *end = strrchr (f, '/');
3191 if (!end) {
3192 meta = ""; // got relative path without folder (should not happen)
3193 }
3194 else {
3195 // copy
3196 int len = end - f;
3197 len = min (len, sizeof (dirname)-1);
3198 strncpy (dirname, f, len);
3199 dirname[len] = 0;
3200 meta = dirname;
3201 }
3202 }
3203 else if (*fmt == 'L') {
3204 float l = 0;
3205 for (playItem_t *it = playlist->head[PL_MAIN]; it; it = it->next[PL_MAIN]) {
3206 if (it->selected) {
3207 l += it->_duration;
3208 }
3209 }
3210 pl_format_time (l, tmp, sizeof(tmp));
3211 meta = tmp;
3212 }
3213 else if (*fmt == 'X') {
3214 int n = 0;
3215 for (playItem_t *it = playlist->head[PL_MAIN]; it; it = it->next[PL_MAIN]) {
3216 if (it->selected) {
3217 n++;
3218 }
3219 }
3220 snprintf (tmp, sizeof (tmp), "%d", n);
3221 meta = tmp;
3222 }
3223 else if (*fmt == 'Z') {
3224 DB_fileinfo_t *c = deadbeef->streamer_get_current_fileinfo (); // FIXME: might crash streamer
3225 if (c) {
3226 if (c->fmt.channels <= 2) {
3227 meta = c->fmt.channels == 1 ? _("Mono") : _("Stereo");
3228 }
3229 else {
3230 snprintf (tmp, sizeof (tmp), "%dch Multichannel", c->fmt.channels);
3231 meta = tmp;
3232 }
3233 }
3234 }
3235 else if (*fmt == 'V') {
3236 meta = VERSION;
3237 }
3238 else {
3239 *s++ = *fmt;
3240 n--;
3241 }
3242
3243 if (meta) {
3244 const char *value = meta;
3245 if (escape_chars) {
3246 // need space for at least 2 single-quotes
3247 if (n < 2) {
3248 goto error;
3249 }
3250 *s++ = '\'';
3251 n--;
3252 while (n > 2 && *value) {
3253 const char *e = escape_chars;
3254 if (strchr (escape_chars, *value)) {
3255 *s++ = '\\';
3256 n--;
3257 *s++ = *value++;
3258 n--;
3259 }
3260 else if (escape_slash && *value == '/') {
3261 *s++ = '\\';
3262 n--;
3263 *s++ = '\\';
3264 n--;
3265 break;
3266 }
3267 else {
3268 *s++ = *value++;
3269 }
3270 }
3271 if (n < 1) {
3272 fprintf (stderr, "pl_format_title_int: got unpredicted state while formatting escaped string. please report a bug.\n");
3273 *ss = 0; // should never happen
3274 return -1;
3275 }
3276 *s++ = '\'';
3277 n--;
3278 }
3279 else {
3280 while (n > 0 && *value) {
3281 if (escape_slash && *value == '/') {
3282 *s++ = '\\';
3283 n--;
3284 value++;
3285 }
3286 else {
3287 *s++ = *value++;
3288 n--;
3289 }
3290 }
3291 }
3292 }
3293 }
3294 fmt++;
3295 }
3296 error:
3297 *s = 0;
3298 UNLOCK;
3299
3300 // replace all \n with ;
3301 while (*ss) {
3302 if (*ss == '\n') {
3303 *ss = ';';
3304 }
3305 ss++;
3306 }
3307
3308 return size - n - 1;
3309 }
3310
3311 int
pl_format_title(playItem_t * it,int idx,char * s,int size,int id,const char * fmt)3312 pl_format_title (playItem_t *it, int idx, char *s, int size, int id, const char *fmt) {
3313 return pl_format_title_int (NULL, it, idx, s, size, id, fmt);
3314 }
3315
3316 int
pl_format_title_escaped(playItem_t * it,int idx,char * s,int size,int id,const char * fmt)3317 pl_format_title_escaped (playItem_t *it, int idx, char *s, int size, int id, const char *fmt) {
3318 return pl_format_title_int ("'", it, idx, s, size, id, fmt);
3319 }
3320
3321 void
plt_reset_cursor(playlist_t * playlist)3322 plt_reset_cursor (playlist_t *playlist) {
3323 int i;
3324 LOCK;
3325 for (i = 0; i < PL_MAX_ITERATORS; i++) {
3326 playlist->current_row[i] = -1;
3327 }
3328 UNLOCK;
3329 }
3330
3331 float
plt_get_totaltime(playlist_t * playlist)3332 plt_get_totaltime (playlist_t *playlist) {
3333 if (!playlist) {
3334 return 0;
3335 }
3336 return playlist->totaltime;
3337 }
3338
3339 float
pl_get_totaltime(void)3340 pl_get_totaltime (void) {
3341 LOCK;
3342 float t = plt_get_totaltime (playlist);
3343 UNLOCK;
3344 return t;
3345 }
3346
3347 void
pl_set_selected(playItem_t * it,int sel)3348 pl_set_selected (playItem_t *it, int sel) {
3349 LOCK;
3350 it->selected = sel;
3351 UNLOCK;
3352 }
3353
3354 int
pl_is_selected(playItem_t * it)3355 pl_is_selected (playItem_t *it) {
3356 return it->selected;
3357 }
3358
3359 playItem_t *
plt_get_first(playlist_t * playlist,int iter)3360 plt_get_first (playlist_t *playlist, int iter) {
3361 if (!playlist) {
3362 return NULL;
3363 }
3364 playItem_t *p = playlist->head[iter];
3365 if (p) {
3366 pl_item_ref (p);
3367 }
3368 return p;
3369 }
3370
3371 playItem_t *
pl_get_first(int iter)3372 pl_get_first (int iter) {
3373 LOCK;
3374 playItem_t *it = plt_get_first (playlist, iter);
3375 UNLOCK;
3376 return it;
3377 }
3378
3379 playItem_t *
plt_get_last(playlist_t * playlist,int iter)3380 plt_get_last (playlist_t *playlist, int iter) {
3381 playItem_t *p = playlist->tail[iter];
3382 if (p) {
3383 pl_item_ref (p);
3384 }
3385 return p;
3386 }
3387
3388 playItem_t *
pl_get_last(int iter)3389 pl_get_last (int iter) {
3390 LOCK;
3391 playItem_t *it = plt_get_last (playlist, iter);
3392 UNLOCK;
3393 return it;
3394 }
3395
3396 playItem_t *
pl_get_next(playItem_t * it,int iter)3397 pl_get_next (playItem_t *it, int iter) {
3398 playItem_t *next = it ? it->next[iter] : NULL;
3399 if (next) {
3400 pl_item_ref (next);
3401 }
3402 return next;
3403 }
3404
3405 playItem_t *
pl_get_prev(playItem_t * it,int iter)3406 pl_get_prev (playItem_t *it, int iter) {
3407 playItem_t *prev = it ? it->prev[iter] : NULL;
3408 if (prev) {
3409 pl_item_ref (prev);
3410 }
3411 return prev;
3412 }
3413
3414 int
plt_get_cursor(playlist_t * playlist,int iter)3415 plt_get_cursor (playlist_t *playlist, int iter) {
3416 if (!playlist) {
3417 return -1;
3418 }
3419 return playlist->current_row[iter];
3420 }
3421
3422 int
pl_get_cursor(int iter)3423 pl_get_cursor (int iter) {
3424 LOCK;
3425 int c = plt_get_cursor (playlist, iter);
3426 UNLOCK;
3427 return c;
3428 }
3429
3430 void
plt_set_cursor(playlist_t * playlist,int iter,int cursor)3431 plt_set_cursor (playlist_t *playlist, int iter, int cursor) {
3432 playlist->current_row[iter] = cursor;
3433 }
3434
3435 void
pl_set_cursor(int iter,int cursor)3436 pl_set_cursor (int iter, int cursor) {
3437 LOCK;
3438 plt_set_cursor (playlist, iter, cursor);
3439 UNLOCK;
3440 }
3441
3442 // this function must move items in playlist
3443 // list of items is indexes[count]
3444 // drop_before is insertion point
3445 void
plt_move_items(playlist_t * to,int iter,playlist_t * from,playItem_t * drop_before,uint32_t * indexes,int count)3446 plt_move_items (playlist_t *to, int iter, playlist_t *from, playItem_t *drop_before, uint32_t *indexes, int count) {
3447 LOCK;
3448
3449 if (!from || !to) {
3450 UNLOCK;
3451 return;
3452 }
3453
3454 // don't let streamer think that current song was removed
3455 no_remove_notify = 1;
3456
3457 // unlink items from from, and link together
3458 playItem_t *head = NULL;
3459 playItem_t *tail = NULL;
3460 int processed = 0;
3461 int idx = 0;
3462 playItem_t *next = NULL;
3463
3464 // find insertion point
3465 playItem_t *drop_after = NULL;
3466 if (drop_before) {
3467 drop_after = drop_before->prev[iter];
3468 }
3469 else {
3470 drop_after = to->tail[iter];
3471 }
3472
3473 playItem_t *playing = streamer_get_playing_track ();
3474
3475 for (playItem_t *it = from->head[iter]; it && processed < count; it = next, idx++) {
3476 next = it->next[iter];
3477 if (idx == indexes[processed]) {
3478 if (it == playing && to != from) {
3479 streamer_set_streamer_playlist (to);
3480 }
3481 pl_item_ref (it);
3482 if (drop_after == it) {
3483 drop_after = it->prev[PL_MAIN];
3484 }
3485 plt_remove_item (from, it);
3486 plt_insert_item (to, drop_after, it);
3487 pl_item_unref (it);
3488 drop_after = it;
3489 processed++;
3490 }
3491 }
3492
3493 if (playing) {
3494 pl_item_unref (playing);
3495 }
3496
3497
3498 no_remove_notify = 0;
3499 UNLOCK;
3500 }
3501
3502 void
plt_copy_items(playlist_t * to,int iter,playlist_t * from,playItem_t * before,uint32_t * indices,int cnt)3503 plt_copy_items (playlist_t *to, int iter, playlist_t *from, playItem_t *before, uint32_t *indices, int cnt) {
3504 pl_lock ();
3505
3506 if (!from || !to || cnt == 0) {
3507 pl_unlock ();
3508 return;
3509 }
3510
3511 playItem_t **items = malloc (cnt * sizeof(playItem_t *));
3512 for (int i = 0; i < cnt; i++) {
3513 playItem_t *it = from->head[iter];
3514 for (int idx = 0; it && idx < indices[i]; idx++) {
3515 it = it->next[iter];
3516 }
3517 items[i] = it;
3518 if (!it) {
3519 trace ("plt_copy_items: warning: item %d not found in source plt_to\n", indices[i]);
3520 }
3521 }
3522 playItem_t *after = before ? before->prev[iter] : to->tail[iter];
3523 for (int i = 0; i < cnt; i++) {
3524 if (items[i]) {
3525 playItem_t *new_it = pl_item_alloc();
3526 pl_item_copy (new_it, items[i]);
3527 pl_insert_item (after, new_it);
3528 pl_item_unref (new_it);
3529 after = new_it;
3530 }
3531 }
3532 free(items);
3533
3534 pl_unlock ();
3535 }
3536
3537 void
plt_search_reset(playlist_t * playlist)3538 plt_search_reset (playlist_t *playlist) {
3539 LOCK;
3540 while (playlist->head[PL_SEARCH]) {
3541 playItem_t *next = playlist->head[PL_SEARCH]->next[PL_SEARCH];
3542 playlist->head[PL_SEARCH]->selected = 0;
3543 playlist->head[PL_SEARCH]->next[PL_SEARCH] = NULL;
3544 playlist->head[PL_SEARCH]->prev[PL_SEARCH] = NULL;
3545 playlist->head[PL_SEARCH] = next;
3546 }
3547 playlist->tail[PL_SEARCH] = NULL;
3548 playlist->count[PL_SEARCH] = 0;
3549 UNLOCK;
3550 }
3551
3552 void
plt_search_process(playlist_t * playlist,const char * text)3553 plt_search_process (playlist_t *playlist, const char *text) {
3554 LOCK;
3555 plt_search_reset (playlist);
3556
3557 // convert text to lowercase, to save some cycles
3558 char lc[1000];
3559 int n = sizeof (lc)-1;
3560 const char *p = text;
3561 char *out = lc;
3562 while (*p) {
3563 int32_t i = 0;
3564 char s[10];
3565 const char *next;
3566 u8_nextchar (p, &i);
3567 int l = u8_tolower (p, i, s);
3568 n -= l;
3569 if (n < 0) {
3570 break;
3571 }
3572 memcpy (out, s, l);
3573 p += i;
3574 out += l;
3575 }
3576 *out = 0;
3577
3578 static int cmpidx = 0;
3579 cmpidx++;
3580 if (cmpidx > 127) {
3581 cmpidx = 1;
3582 }
3583
3584 for (playItem_t *it = playlist->head[PL_MAIN]; it; it = it->next[PL_MAIN]) {
3585 it->selected = 0;
3586 if (*text) {
3587 DB_metaInfo_t *m = NULL;
3588 for (m = it->meta; m; m = m->next) {
3589 int is_uri = !strcmp (m->key, ":URI");
3590 if ((m->key[0] == ':' && !is_uri) || m->key[0] == '_' || m->key[0] == '!') {
3591 break;
3592 }
3593 const char *value = m->value;
3594 if (is_uri) {
3595 value = strrchr (value, '/');
3596 if (value) {
3597 value++;
3598 }
3599 else {
3600 value = m->value;
3601 }
3602 }
3603 if (strcasecmp(m->key, "cuesheet") && strcasecmp (m->key, "log")) {
3604 char cmp = *(m->value-1);
3605
3606 if (abs (cmp) == cmpidx) {
3607 if (cmp > 0) {
3608 it->next[PL_SEARCH] = NULL;
3609 it->prev[PL_SEARCH] = playlist->tail[PL_SEARCH];
3610 if (playlist->tail[PL_SEARCH]) {
3611 playlist->tail[PL_SEARCH]->next[PL_SEARCH] = it;
3612 playlist->tail[PL_SEARCH] = it;
3613 }
3614 else {
3615 playlist->head[PL_SEARCH] = playlist->tail[PL_SEARCH] = it;
3616 }
3617 it->selected = 1;
3618 playlist->count[PL_SEARCH]++;
3619 break;
3620 }
3621 }
3622 else if (u8_valid(value, strlen(value), NULL) && u8_valid(lc, strlen(lc), NULL) && utfcasestr_fast (value, lc)) {
3623 //fprintf (stderr, "%s -> %s match (%s.%s)\n", text, value, pl_find_meta_raw (it, ":URI"), m->key);
3624 // add to list
3625 it->next[PL_SEARCH] = NULL;
3626 it->prev[PL_SEARCH] = playlist->tail[PL_SEARCH];
3627 if (playlist->tail[PL_SEARCH]) {
3628 playlist->tail[PL_SEARCH]->next[PL_SEARCH] = it;
3629 playlist->tail[PL_SEARCH] = it;
3630 }
3631 else {
3632 playlist->head[PL_SEARCH] = playlist->tail[PL_SEARCH] = it;
3633 }
3634 it->selected = 1;
3635 playlist->count[PL_SEARCH]++;
3636 *((char *)m->value-1) = cmpidx;
3637 break;
3638 }
3639 else {
3640 *((char *)m->value-1) = -cmpidx;
3641 }
3642 }
3643 }
3644 }
3645 }
3646 UNLOCK;
3647 }
3648
3649 void
send_trackinfochanged(playItem_t * track)3650 send_trackinfochanged (playItem_t *track) {
3651 ddb_event_track_t *ev = (ddb_event_track_t *)messagepump_event_alloc (DB_EV_TRACKINFOCHANGED);
3652 ev->track = DB_PLAYITEM (track);
3653 if (track) {
3654 pl_item_ref (track);
3655 }
3656 messagepump_push_event ((ddb_event_t*)ev, 0, 0);
3657 }
3658
3659 void
pl_items_copy_junk(playItem_t * from,playItem_t * first,playItem_t * last)3660 pl_items_copy_junk (playItem_t *from, playItem_t *first, playItem_t *last) {
3661 LOCK;
3662 DB_metaInfo_t *meta = from->meta;
3663 while (meta) {
3664 playItem_t *i;
3665 for (i = first; i; i = i->next[PL_MAIN]) {
3666 i->_flags = from->_flags;
3667 pl_add_meta (i, meta->key, meta->value);
3668 if (i == last) {
3669 break;
3670 }
3671 }
3672 meta = meta->next;
3673 }
3674 UNLOCK;
3675 }
3676
3677 uint32_t
pl_get_item_flags(playItem_t * it)3678 pl_get_item_flags (playItem_t *it) {
3679 LOCK;
3680 uint32_t flags = it->_flags;
3681 UNLOCK;
3682 return flags;
3683 }
3684
3685 void
pl_set_item_flags(playItem_t * it,uint32_t flags)3686 pl_set_item_flags (playItem_t *it, uint32_t flags) {
3687 LOCK;
3688 it->_flags = flags;
3689
3690 char s[200];
3691 pl_format_title (it, -1, s, sizeof (s), -1, "%T");
3692 pl_replace_meta (it, ":TAGS", s);
3693 pl_replace_meta (it, ":HAS_EMBEDDED_CUESHEET", (flags & DDB_HAS_EMBEDDED_CUESHEET) ? _("Yes") : _("No"));
3694 UNLOCK;
3695 }
3696
3697 playlist_t *
pl_get_playlist(playItem_t * it)3698 pl_get_playlist (playItem_t *it) {
3699 LOCK;
3700 playlist_t *p = playlists_head;
3701 while (p) {
3702 int idx = plt_get_item_idx (p, it, PL_MAIN);
3703 if (idx != -1) {
3704 plt_ref (p);
3705 UNLOCK;
3706 return p;
3707 }
3708 p = p->next;
3709 }
3710 UNLOCK;
3711 return NULL;
3712 }
3713
3714 // this function must be called when the user starts track manually in shuffle albums mode
3715 // r is an index of current track
3716 // mark previous songs in the album as played
3717 void
plt_init_shuffle_albums(playlist_t * plt,int r)3718 plt_init_shuffle_albums (playlist_t *plt, int r) {
3719 pl_lock ();
3720 playItem_t *first = plt_get_item_for_idx (plt, r, PL_MAIN);
3721 if (!first) {
3722 pl_unlock ();
3723 return;
3724 }
3725 if (first->played) {
3726 plt_reshuffle (plt, NULL, NULL);
3727 }
3728 if (first) {
3729 int rating = first->shufflerating;
3730 playItem_t *it = first->prev[PL_MAIN];
3731 pl_item_unref (first);
3732 while (it && rating == it->shufflerating) {
3733 it->played = 1;
3734 it = it->prev[PL_MAIN];
3735 }
3736 }
3737 pl_unlock ();
3738 }
3739
3740 void
plt_set_fast_mode(playlist_t * plt,int fast)3741 plt_set_fast_mode (playlist_t *plt, int fast) {
3742 plt->fast_mode = (unsigned)fast;
3743 }
3744
3745 int
plt_is_fast_mode(playlist_t * plt)3746 plt_is_fast_mode (playlist_t *plt) {
3747 return plt->fast_mode;
3748 }
3749
3750 void
pl_ensure_lock(void)3751 pl_ensure_lock (void) {
3752 #if DETECT_PL_LOCK_RC
3753 pthread_t tid = pthread_self ();
3754 for (int i = 0; i < ntids; i++) {
3755 if (tids[i] == tid) {
3756 return;
3757 }
3758 }
3759 fprintf (stderr, "\033[0;31mnon-thread-safe playlist access function was called outside of pl_lock. please make a backtrace and post a bug. thank you.\033[37;0m\n");
3760 assert(0);
3761 #endif
3762 }
3763
3764 int
plt_get_idx(playlist_t * plt)3765 plt_get_idx (playlist_t *plt) {
3766 int i;
3767 playlist_t *p;
3768 for (i = 0, p = playlists_head; p && p != plt; i++, p = p->next);
3769 if (p == 0) {
3770 return -1;
3771 }
3772 return i;
3773 }
3774
3775 int
plt_save_config(playlist_t * plt)3776 plt_save_config (playlist_t *plt) {
3777 int i = plt_get_idx (plt);
3778 if (i == -1) {
3779 return -1;
3780 }
3781 return plt_save_n (i);
3782 }
3783
3784 int
listen_file_added(int (* callback)(ddb_fileadd_data_t * data,void * user_data),void * user_data)3785 listen_file_added (int (*callback)(ddb_fileadd_data_t *data, void *user_data), void *user_data) {
3786 ddb_fileadd_listener_t *l;
3787
3788 int id = 1;
3789 for (l = file_add_listeners; l; l = l->next) {
3790 if (l->id > id) {
3791 id = l->id+1;
3792 }
3793 }
3794
3795 l = malloc (sizeof (ddb_fileadd_listener_t));
3796 memset (l, 0, sizeof (ddb_fileadd_listener_t));
3797 l->id = id;
3798 l->callback = callback;
3799 l->user_data = user_data;
3800 l->next = file_add_listeners;
3801 file_add_listeners = l;
3802 return id;
3803 }
3804
3805 void
unlisten_file_added(int id)3806 unlisten_file_added (int id) {
3807 ddb_fileadd_listener_t *prev = NULL;
3808 for (ddb_fileadd_listener_t *l = file_add_listeners; l; prev = l, l = l->next) {
3809 if (l->id == id) {
3810 if (prev) {
3811 prev->next = l->next;
3812 }
3813 else {
3814 file_add_listeners = l->next;
3815 }
3816 free (l);
3817 break;
3818 }
3819 }
3820 }
3821
3822 int
listen_file_add_beginend(void (* callback_begin)(ddb_fileadd_data_t * data,void * user_data),void (* callback_end)(ddb_fileadd_data_t * data,void * user_data),void * user_data)3823 listen_file_add_beginend (void (*callback_begin) (ddb_fileadd_data_t *data, void *user_data), void (*callback_end)(ddb_fileadd_data_t *data, void *user_data), void *user_data) {
3824 ddb_fileadd_beginend_listener_t *l;
3825
3826 int id = 1;
3827 for (l = file_add_beginend_listeners; l; l = l->next) {
3828 if (l->id > id) {
3829 id = l->id+1;
3830 }
3831 }
3832
3833 l = malloc (sizeof (ddb_fileadd_beginend_listener_t));
3834 memset (l, 0, sizeof (ddb_fileadd_beginend_listener_t));
3835 l->id = id;
3836 l->callback_begin = callback_begin;
3837 l->callback_end = callback_end;
3838 l->user_data = user_data;
3839 l->next = file_add_beginend_listeners;
3840 file_add_beginend_listeners = l;
3841 return id;
3842 }
3843
3844 void
unlisten_file_add_beginend(int id)3845 unlisten_file_add_beginend (int id) {
3846 ddb_fileadd_beginend_listener_t *prev = NULL;
3847 for (ddb_fileadd_beginend_listener_t *l = file_add_beginend_listeners; l; prev = l, l = l->next) {
3848 if (l->id == id) {
3849 if (prev) {
3850 prev->next = l->next;
3851 }
3852 else {
3853 file_add_beginend_listeners = l->next;
3854 }
3855 free (l);
3856 break;
3857 }
3858 }
3859 }
3860
3861 playItem_t *
plt_load2(int visibility,playlist_t * plt,playItem_t * after,const char * fname,int * pabort,int (* callback)(playItem_t * it,void * user_data),void * user_data)3862 plt_load2 (int visibility, playlist_t *plt, playItem_t *after, const char *fname, int *pabort, int (*callback)(playItem_t *it, void *user_data), void *user_data) {
3863 return plt_load_int (visibility, plt, after, fname, pabort, callback, user_data);
3864 }
3865
3866 int
plt_add_file2(int visibility,playlist_t * plt,const char * fname,int (* callback)(playItem_t * it,void * user_data),void * user_data)3867 plt_add_file2 (int visibility, playlist_t *plt, const char *fname, int (*callback)(playItem_t *it, void *user_data), void *user_data) {
3868 return plt_add_file_int (visibility, plt, fname, callback, user_data);
3869 }
3870
3871 int
plt_add_dir2(int visibility,playlist_t * plt,const char * dirname,int (* callback)(playItem_t * it,void * user_data),void * user_data)3872 plt_add_dir2 (int visibility, playlist_t *plt, const char *dirname, int (*callback)(playItem_t *it, void *user_data), void *user_data) {
3873 follow_symlinks = conf_get_int ("add_folders_follow_symlinks", 0);
3874 ignore_archives = conf_get_int ("ignore_archives", 1);
3875 int abort = 0;
3876 playItem_t *it = plt_insert_dir_int (visibility, plt, NULL, plt->tail[PL_MAIN], dirname, &abort, callback, user_data);
3877 if (it) {
3878 // pl_insert_file doesn't hold reference, don't unref here
3879 return 0;
3880 }
3881 return -1;
3882 }
3883
3884 playItem_t *
plt_insert_file2(int visibility,playlist_t * playlist,playItem_t * after,const char * fname,int * pabort,int (* callback)(playItem_t * it,void * user_data),void * user_data)3885 plt_insert_file2 (int visibility, playlist_t *playlist, playItem_t *after, const char *fname, int *pabort, int (*callback)(playItem_t *it, void *user_data), void *user_data) {
3886 return plt_insert_file_int (visibility, playlist, after, fname, pabort, callback, user_data);
3887 }
3888
3889 playItem_t *
plt_insert_dir2(int visibility,playlist_t * plt,playItem_t * after,const char * dirname,int * pabort,int (* callback)(playItem_t * it,void * user_data),void * user_data)3890 plt_insert_dir2 (int visibility, playlist_t *plt, playItem_t *after, const char *dirname, int *pabort, int (*callback)(playItem_t *it, void *user_data), void *user_data) {
3891 follow_symlinks = conf_get_int ("add_folders_follow_symlinks", 0);
3892 ignore_archives = conf_get_int ("ignore_archives", 1);
3893
3894 playItem_t *ret = plt_insert_dir_int (visibility, plt, NULL, after, dirname, pabort, callback, user_data);
3895 ignore_archives = 0;
3896 return ret;
3897 }
3898
3899 int
plt_add_files_begin(playlist_t * plt,int visibility)3900 plt_add_files_begin (playlist_t *plt, int visibility) {
3901 pl_lock ();
3902 if (addfiles_playlist) {
3903 pl_unlock ();
3904 return -1;
3905 }
3906 if (plt->files_adding) {
3907 pl_unlock ();
3908 return -1;
3909 }
3910 addfiles_playlist = plt;
3911 plt_ref (addfiles_playlist);
3912 plt->files_adding = 1;
3913 plt->files_add_visibility = visibility;
3914 pl_unlock ();
3915 ddb_fileadd_data_t d;
3916 memset (&d, 0, sizeof (d));
3917 d.visibility = visibility;
3918 d.plt = (ddb_playlist_t *)plt;
3919 for (ddb_fileadd_beginend_listener_t *l = file_add_beginend_listeners; l; l = l->next) {
3920 l->callback_begin (&d, l->user_data);
3921 }
3922 background_job_increment ();
3923 return 0;
3924 }
3925
3926 void
plt_add_files_end(playlist_t * plt,int visibility)3927 plt_add_files_end (playlist_t *plt, int visibility) {
3928 pl_lock ();
3929 if (addfiles_playlist) {
3930 plt_unref (addfiles_playlist);
3931 }
3932 addfiles_playlist = NULL;
3933 messagepump_push (DB_EV_PLAYLISTCHANGED, 0, DDB_PLAYLIST_CHANGE_CONTENT, 0);
3934 plt->files_adding = 0;
3935 pl_unlock ();
3936 ddb_fileadd_data_t d;
3937 memset (&d, 0, sizeof (d));
3938 d.visibility = visibility;
3939 d.plt = (ddb_playlist_t *)plt;
3940 for (ddb_fileadd_beginend_listener_t *l = file_add_beginend_listeners; l; l = l->next) {
3941 l->callback_end (&d, l->user_data);
3942 }
3943 background_job_decrement ();
3944 }
3945
3946 void
plt_deselect_all(playlist_t * playlist)3947 plt_deselect_all (playlist_t *playlist) {
3948 LOCK;
3949 for (playItem_t *it = playlist->head[PL_MAIN]; it; it = it->next[PL_MAIN]) {
3950 it->selected = 0;
3951 }
3952 UNLOCK;
3953 }
3954
3955 void
plt_set_scroll(playlist_t * plt,int scroll)3956 plt_set_scroll (playlist_t *plt, int scroll) {
3957 plt->scroll = scroll;
3958 }
3959
3960 int
plt_get_scroll(playlist_t * plt)3961 plt_get_scroll (playlist_t *plt) {
3962 return plt->scroll;
3963 }
3964
3965