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