1 /*
2  * Copyright 2008-2013 Various Authors
3  * Copyright 2006 Timo Hirvonen
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "pl.h"
20 #include "prog.h"
21 #include "editable.h"
22 #include "options.h"
23 #include "xmalloc.h"
24 #include "load_dir.h"
25 #include "list.h"
26 #include "job.h"
27 #include "misc.h"
28 #include "ui_curses.h"
29 #include "xstrjoin.h"
30 #include "worker.h"
31 #include "uchar.h"
32 #include "mergesort.h"
33 
34 #include <unistd.h>
35 #include <stdio.h>
36 #include <signal.h>
37 
38 struct playlist {
39 	struct list_head node;
40 
41 	char *name;
42 	struct editable editable;
43 	struct rb_root shuffle_root;
44 	struct simple_track *cur_track;
45 };
46 
47 static struct playlist *pl_visible; /* never NULL */
48 static struct playlist *pl_marked; /* never NULL */
49 struct window *pl_list_win;
50 
51 /* pl_playing_track shares its track_info reference with the playlist it's in.
52  * pl_playing_track and pl_playing might be null but pl_playing_track != NULL
53  * implies pl_playing != NULL and pl_playing_track is in pl_playing.
54  */
55 static struct simple_track *pl_playing_track;
56 static struct playlist *pl_playing;
57 
58 static int pl_cursor_in_track_window;
59 struct editable_shared pl_editable_shared;
60 static LIST_HEAD(pl_head); /* never empty */
61 
62 static struct searchable *pl_searchable;
63 
pl_name_to_pl_file(const char * name)64 static char *pl_name_to_pl_file(const char *name)
65 {
66 	return xstrjoin(cmus_playlist_dir, "/", name);
67 }
68 
pl_to_iter(struct playlist * pl,struct iter * iter)69 static void pl_to_iter(struct playlist *pl, struct iter *iter)
70 {
71 	*iter = (struct iter) {
72 		.data0 = &pl_head,
73 		.data1 = &pl->node
74 	};
75 }
76 
pl_from_list(const struct list_head * list)77 static struct playlist *pl_from_list(const struct list_head *list)
78 {
79 	return container_of(list, struct playlist, node);
80 }
81 
pl_from_editable(const struct editable * editable)82 static struct playlist *pl_from_editable(const struct editable *editable)
83 {
84 	return container_of(editable, struct playlist, editable);
85 }
86 
pl_search_get_generic(struct iter * iter,struct list_head * (* list_step)(struct list_head * list),int (* iter_step)(struct iter * iter))87 static int pl_search_get_generic(struct iter *iter,
88 		struct list_head *(*list_step)(struct list_head *list),
89 		int (*iter_step)(struct iter *iter))
90 {
91 	struct list_head *pl_node = iter->data2;
92 	struct playlist *pl;
93 
94 	if (!pl_node)
95 		pl_node = &pl_head;
96 
97 	if (iter_step(iter))
98 		return 1;
99 
100 	pl_node = list_step(pl_node);
101 	if (pl_node == &pl_head)
102 		return 0;
103 
104 	pl = pl_from_list(pl_node);
105 	iter->data0 = &pl->editable.head;
106 	iter->data1 = NULL;
107 	iter->data2 = pl_node;
108 	return 1;
109 }
110 
pl_search_get_prev(struct iter * iter)111 static int pl_search_get_prev(struct iter *iter)
112 {
113 	return pl_search_get_generic(iter, list_prev, simple_track_get_prev);
114 }
115 
pl_search_get_next(struct iter * iter)116 static int pl_search_get_next(struct iter *iter)
117 {
118 	return pl_search_get_generic(iter, list_next, simple_track_get_next);
119 }
120 
pl_search_get_current(void * data,struct iter * iter)121 static int pl_search_get_current(void *data, struct iter *iter)
122 {
123 	window_get_sel(pl_editable_shared.win, iter);
124 	iter->data2 = &pl_visible->node;
125 	return 1;
126 }
127 
pl_search_matches(void * data,struct iter * iter,const char * text)128 static int pl_search_matches(void *data, struct iter *iter, const char *text)
129 {
130 	struct playlist *pl = pl_from_list(iter->data2);
131 	int matched = 0;
132 
133 	char **words = get_words(text);
134 	for (size_t i = 0; words[i]; i++) {
135 
136 		/* set in the loop to deal with empty search string */
137 		matched = 1;
138 
139 		if (!u_strcasestr_base(pl->name, words[i])) {
140 			matched = 0;
141 			break;
142 		}
143 	}
144 	free_str_array(words);
145 
146 	if (!matched && iter->data1)
147 		matched = _simple_track_search_matches(iter, text);
148 
149 	if (matched) {
150 		struct iter list_iter;
151 		pl_to_iter(pl, &list_iter);
152 		window_set_sel(pl_list_win, &list_iter);
153 
154 		editable_take_ownership(&pl->editable);
155 
156 		if (iter->data1) {
157 			struct iter track_iter = *iter;
158 			track_iter.data2 = NULL;
159 			window_set_sel(pl_editable_shared.win, &track_iter);
160 		}
161 
162 		pl_cursor_in_track_window = !!iter->data1;
163 	}
164 
165 	return matched;
166 }
167 
168 static const struct searchable_ops pl_searchable_ops = {
169 	.get_prev = pl_search_get_prev,
170 	.get_next = pl_search_get_next,
171 	.get_current = pl_search_get_current,
172 	.matches = pl_search_matches,
173 };
174 
pl_free_track(struct editable * e,struct list_head * item)175 static void pl_free_track(struct editable *e, struct list_head *item)
176 {
177 	struct playlist *pl = pl_from_editable(e);
178 	struct simple_track *track = to_simple_track(item);
179 	struct shuffle_track *shuffle_track =
180 		simple_track_to_shuffle_track(track);
181 
182 	if (track == pl->cur_track)
183 		pl->cur_track = NULL;
184 
185 	rb_erase(&shuffle_track->tree_node, &pl->shuffle_root);
186 	track_info_unref(track->info);
187 	free(track);
188 }
189 
pl_new(const char * name)190 static struct playlist *pl_new(const char *name)
191 {
192 	struct playlist *pl = xnew0(struct playlist, 1);
193 	pl->name = xstrdup(name);
194 	editable_init(&pl->editable, &pl_editable_shared, 0);
195 	return pl;
196 }
197 
pl_free(struct playlist * pl)198 static void pl_free(struct playlist *pl)
199 {
200 	editable_clear(&pl->editable);
201 	free(pl->name);
202 	free(pl);
203 }
204 
pl_add_track(struct playlist * pl,struct track_info * ti)205 static void pl_add_track(struct playlist *pl, struct track_info *ti)
206 {
207 	struct shuffle_track *track = xnew(struct shuffle_track, 1);
208 
209 	track_info_ref(ti);
210 	simple_track_init(&track->simple_track, ti);
211 	shuffle_list_add(track, &pl->shuffle_root);
212 	editable_add(&pl->editable, &track->simple_track);
213 }
214 
pl_add_cb(struct track_info * ti,void * opaque)215 static void pl_add_cb(struct track_info *ti, void *opaque)
216 {
217 	pl_add_track(opaque, ti);
218 }
219 
pl_add_file_to_marked_pl(const char * file)220 int pl_add_file_to_marked_pl(const char *file)
221 {
222 	char *full = NULL;
223 	enum file_type type = cmus_detect_ft(file, &full);
224 	int not_invalid = type != FILE_TYPE_INVALID;
225 	if (not_invalid)
226 		cmus_add(pl_add_cb, full, type, JOB_TYPE_PL, 0, pl_marked);
227 	free(full);
228 	return not_invalid;
229 }
230 
pl_add_track_to_marked_pl(struct track_info * ti)231 void pl_add_track_to_marked_pl(struct track_info *ti)
232 {
233 	pl_add_track(pl_marked, ti);
234 }
235 
pl_list_compare(const struct list_head * l,const struct list_head * r)236 static int pl_list_compare(const struct list_head *l, const struct list_head *r)
237 {
238 	struct playlist *pl = pl_from_list(l);
239 	struct playlist *pr = pl_from_list(r);
240 	return strcmp(pl->name, pr->name);
241 }
242 
pl_sort_all(void)243 static void pl_sort_all(void)
244 {
245 	list_mergesort(&pl_head, pl_list_compare);
246 }
247 
pl_load_one(const char * file)248 static void pl_load_one(const char *file)
249 {
250 	char *full = pl_name_to_pl_file(file);
251 
252 	struct playlist *pl = pl_new(file);
253 	cmus_add(pl_add_cb, full, FILE_TYPE_PL, JOB_TYPE_PL, 0, pl);
254 	list_add_tail(&pl->node, &pl_head);
255 
256 	free(full);
257 }
258 
pl_load_all(void)259 static void pl_load_all(void)
260 {
261 	struct directory dir;
262 	if (dir_open(&dir, cmus_playlist_dir))
263 		die_errno("error: cannot open playlist directory %s", cmus_playlist_dir);
264 	const char *file;
265 	while ((file = dir_read(&dir))) {
266 		if (strcmp(file, ".") == 0 || strcmp(file, "..") == 0)
267 			continue;
268 		if (!S_ISREG(dir.st.st_mode)) {
269 			error_msg("error: %s in %s is not a regular file", file,
270 					cmus_playlist_dir);
271 			continue;
272 		}
273 		pl_load_one(file);
274 	}
275 	dir_close(&dir);
276 }
277 
pl_create_default(void)278 static void pl_create_default(void)
279 {
280 	struct playlist *pl = pl_new("default");
281 	list_add_tail(&pl->node, &pl_head);
282 }
283 
284 static GENERIC_ITER_PREV(pl_list_get_prev, struct playlist, node);
285 static GENERIC_ITER_NEXT(pl_list_get_next, struct playlist, node);
286 
pl_list_sel_changed(void)287 static void pl_list_sel_changed(void)
288 {
289 	struct list_head *list = pl_list_win->sel.data1;
290 	struct playlist *pl = pl_from_list(list);
291 	pl_visible = pl;
292 	editable_take_ownership(&pl_visible->editable);
293 }
294 
pl_dummy_filter(const struct simple_track * track)295 static int pl_dummy_filter(const struct simple_track *track)
296 {
297 	return 1;
298 }
299 
pl_empty(struct playlist * pl)300 static int pl_empty(struct playlist *pl)
301 {
302 	return editable_empty(&pl->editable);
303 }
304 
pl_get_selected_track(void)305 static struct simple_track *pl_get_selected_track(void)
306 {
307 	/* pl_visible is not empty */
308 
309 	struct iter sel = pl_editable_shared.win->sel;
310 	return iter_to_simple_track(&sel);
311 }
312 
pl_get_first_track(struct playlist * pl)313 static struct simple_track *pl_get_first_track(struct playlist *pl)
314 {
315 	/* pl is not empty */
316 
317 	if (shuffle) {
318 		struct shuffle_track *st = shuffle_list_get_next(&pl->shuffle_root, NULL, pl_dummy_filter);
319 		return &st->simple_track;
320 	} else {
321 		return to_simple_track(pl->editable.head.next);
322 	}
323 }
324 
pl_play_track(struct playlist * pl,struct simple_track * t,bool force_follow)325 static struct track_info *pl_play_track(struct playlist *pl, struct simple_track *t, bool force_follow)
326 {
327 	/* t is a track in pl */
328 
329 	if (pl != pl_playing)
330 		pl_list_win->changed = 1;
331 
332 	pl_playing_track = t;
333 	pl_playing = pl;
334 	pl_editable_shared.win->changed = 1;
335 
336 	if (force_follow || follow)
337 		pl_select_playing_track();
338 
339 	/* reference owned by the caller */
340 	track_info_ref(pl_playing_track->info);
341 
342 	return pl_playing_track->info;
343 }
344 
pl_play_selected_track(void)345 static struct track_info *pl_play_selected_track(void)
346 {
347 	if (pl_empty(pl_visible))
348 		return NULL;
349 
350 	return pl_play_track(pl_visible, pl_get_selected_track(), false);
351 }
352 
pl_play_first_in_pl_playing(void)353 static struct track_info *pl_play_first_in_pl_playing(void)
354 {
355 	if (!pl_playing)
356 		pl_playing = pl_visible;
357 
358 	if (pl_empty(pl_playing)) {
359 		pl_playing = NULL;
360 		return NULL;
361 	}
362 
363 	return pl_play_track(pl_playing, pl_get_first_track(pl_playing), false);
364 }
365 
pl_get_next(struct playlist * pl,struct simple_track * cur)366 static struct simple_track *pl_get_next(struct playlist *pl, struct simple_track *cur)
367 {
368 	return simple_list_get_next(&pl->editable.head, cur, pl_dummy_filter);
369 }
370 
pl_get_next_shuffled(struct playlist * pl,struct simple_track * cur)371 static struct simple_track *pl_get_next_shuffled(struct playlist *pl,
372 		struct simple_track *cur)
373 {
374 	struct shuffle_track *st = simple_track_to_shuffle_track(cur);
375 	st = shuffle_list_get_next(&pl->shuffle_root, st, pl_dummy_filter);
376 	return &st->simple_track;
377 }
378 
pl_get_prev(struct playlist * pl,struct simple_track * cur)379 static struct simple_track *pl_get_prev(struct playlist *pl,
380 		struct simple_track *cur)
381 {
382 	return simple_list_get_prev(&pl->editable.head, cur, pl_dummy_filter);
383 }
384 
pl_get_prev_shuffled(struct playlist * pl,struct simple_track * cur)385 static struct simple_track *pl_get_prev_shuffled(struct playlist *pl,
386 		struct simple_track *cur)
387 {
388 	struct shuffle_track *st = simple_track_to_shuffle_track(cur);
389 	st = shuffle_list_get_prev(&pl->shuffle_root, st, pl_dummy_filter);
390 	return &st->simple_track;
391 }
392 
pl_match_add_job(uint32_t type,void * job_data,void * opaque)393 static int pl_match_add_job(uint32_t type, void *job_data, void *opaque)
394 {
395 	uint32_t pat = JOB_TYPE_PL | JOB_TYPE_ADD;
396 	if (type != pat)
397 		return 0;
398 
399 	struct add_data *add_data= job_data;
400 	return add_data->opaque == opaque;
401 }
402 
pl_cancel_add_jobs(struct playlist * pl)403 static void pl_cancel_add_jobs(struct playlist *pl)
404 {
405 	worker_remove_jobs_by_cb(pl_match_add_job, pl);
406 }
407 
pl_save_cb(track_info_cb cb,void * data,void * opaque)408 static int pl_save_cb(track_info_cb cb, void *data, void *opaque)
409 {
410 	struct playlist *pl = opaque;
411 	return editable_for_each(&pl->editable, cb, data, 0);
412 }
413 
pl_save_one(struct playlist * pl)414 static void pl_save_one(struct playlist *pl)
415 {
416 	char *path = pl_name_to_pl_file(pl->name);
417 	cmus_save(pl_save_cb, path, pl);
418 	free(path);
419 }
420 
pl_save_all(void)421 static void pl_save_all(void)
422 {
423 	struct playlist *pl;
424 	list_for_each_entry(pl, &pl_head, node)
425 		pl_save_one(pl);
426 }
427 
pl_delete_selected_pl(void)428 static void pl_delete_selected_pl(void)
429 {
430 	if (list_len(&pl_head) == 1) {
431 		error_msg("cannot delete the last playlist");
432 		return;
433 	}
434 
435 	if (yes_no_query("Delete selected playlist? [y/N]") != UI_QUERY_ANSWER_YES)
436 		return;
437 
438 	struct playlist *pl = pl_visible;
439 
440 	struct iter iter;
441 	pl_to_iter(pl, &iter);
442 	window_row_vanishes(pl_list_win, &iter);
443 
444 	list_del(&pl->node);
445 
446 	if (pl == pl_marked)
447 		pl_marked = pl_visible;
448 	if (pl == pl_playing) {
449 		pl_playing = NULL;
450 		pl_playing_track = NULL;
451 	}
452 
453 	char *path = pl_name_to_pl_file(pl->name);
454 	unlink(path);
455 	free(path);
456 
457 	pl_cancel_add_jobs(pl);
458 
459 	/* can't free the pl now because the worker thread might hold a
460 	 * reference to it. instead free it once all running jobs are done.
461 	 */
462 	struct pl_delete_data *pdd = xnew(struct pl_delete_data, 1);
463 	pdd->cb = pl_free;
464 	pdd->pl = pl;
465 	job_schedule_pl_delete(pdd);
466 }
467 
pl_mark_selected_pl(void)468 static void pl_mark_selected_pl(void)
469 {
470 	pl_marked = pl_visible;
471 	pl_list_win->changed = 1;
472 }
473 
474 typedef struct simple_track *(*pl_shuffled_move)(struct playlist *pl,
475 		struct simple_track *cur);
476 typedef struct simple_track *(*pl_normal_move)(struct playlist *pl,
477 		struct simple_track *cur);
478 
479 static struct track_info *pl_goto_generic(pl_shuffled_move shuffled,
480 		pl_normal_move normal)
481 {
482 	if (!pl_playing_track)
483 		return pl_play_first_in_pl_playing();
484 
485 	struct simple_track *track;
486 
487 	if (shuffle)
488 		track = shuffled(pl_playing, pl_playing_track);
489 	else
490 		track = normal(pl_playing, pl_playing_track);
491 
492 	if (track)
493 		return pl_play_track(pl_playing, track, false);
494 	return NULL;
495 }
496 
497 static void pl_clear_visible_pl(void)
498 {
499 	if (pl_cursor_in_track_window)
500 		pl_win_next();
501 	if (pl_visible == pl_playing)
502 		pl_playing_track = NULL;
503 	editable_clear(&pl_visible->editable);
504 	pl_cancel_add_jobs(pl_visible);
505 }
506 
pl_name_exists(const char * name)507 static int pl_name_exists(const char *name)
508 {
509 	struct playlist *pl;
510 	list_for_each_entry(pl, &pl_head, node) {
511 		if (strcmp(pl->name, name) == 0)
512 			return 1;
513 	}
514 	return 0;
515 }
516 
pl_check_new_pl_name(const char * name)517 static int pl_check_new_pl_name(const char *name)
518 {
519 	if (strchr(name, '/')) {
520 		error_msg("playlists cannot contain the '/' character");
521 		return 0;
522 	}
523 
524 	if (pl_name_exists(name)) {
525 		error_msg("another playlist named %s already exists", name);
526 		return 0;
527 	}
528 
529 	return 1;
530 }
531 
pl_create_name(const char * file)532 static char *pl_create_name(const char *file)
533 {
534 	size_t file_len = strlen(file);
535 
536 	char *name = xnew(char, file_len + 10);
537 	strcpy(name, file);
538 
539 	for (int i = 1; pl_name_exists(name); i++) {
540 		if (i == 100) {
541 			free(name);
542 			return NULL;
543 		}
544 		sprintf(name + file_len, ".%d", i);
545 	}
546 
547 	return name;
548 }
549 
pl_delete_selected_track(void)550 static void pl_delete_selected_track(void)
551 {
552 	/* pl_cursor_in_track_window == true */
553 
554 	if (pl_get_selected_track() == pl_playing_track)
555 		pl_playing_track = NULL;
556 	editable_remove_sel(&pl_visible->editable);
557 	if (pl_empty(pl_visible))
558 		pl_win_next();
559 }
560 
pl_init(void)561 void pl_init(void)
562 {
563 	editable_shared_init(&pl_editable_shared, pl_free_track);
564 
565 	pl_load_all();
566 	if (list_empty(&pl_head))
567 		pl_create_default();
568 
569 	pl_sort_all();
570 
571 	pl_list_win = window_new(pl_list_get_prev, pl_list_get_next);
572 	pl_list_win->sel_changed = pl_list_sel_changed;
573 	window_set_contents(pl_list_win, &pl_head);
574 	window_changed(pl_list_win);
575 
576 	/* pl_visible set by window_set_contents */
577 	pl_marked = pl_visible;
578 
579 	struct iter iter = { 0 };
580 	pl_searchable = searchable_new(NULL, &iter, &pl_searchable_ops);
581 }
582 
pl_exit(void)583 void pl_exit(void)
584 {
585 	pl_save_all();
586 }
587 
pl_save(void)588 void pl_save(void)
589 {
590 	pl_save_all();
591 }
592 
pl_import(const char * path)593 void pl_import(const char *path)
594 {
595 	const char *file = get_filename(path);
596 	if (!file) {
597 		error_msg("\"%s\" is not a valid path", path);
598 		return;
599 	}
600 
601 	char *name = pl_create_name(file);
602 	if (!name) {
603 		error_msg("a playlist named \"%s\" already exists ", file);
604 		return;
605 	}
606 
607 	if (strcmp(name, file) != 0)
608 		info_msg("adding \"%s\" as \"%s\"", file, name);
609 
610 	struct playlist *pl = pl_new(name);
611 	cmus_add(pl_add_cb, path, FILE_TYPE_PL, JOB_TYPE_PL, 0, pl);
612 	list_add_tail(&pl->node, &pl_head);
613 	pl_list_win->changed = 1;
614 
615 	free(name);
616 }
617 
pl_export_selected_pl(const char * path)618 void pl_export_selected_pl(const char *path)
619 {
620 	char *tmp = expand_filename(path);
621 	if (access(tmp, F_OK) != 0 || yes_no_query("File exists. Overwrite? [y/N]") == UI_QUERY_ANSWER_YES)
622 		cmus_save(pl_save_cb, tmp, pl_visible);
623 	free(tmp);
624 }
625 
pl_get_searchable(void)626 struct searchable *pl_get_searchable(void)
627 {
628 	return pl_searchable;
629 }
630 
pl_goto_next(void)631 struct track_info *pl_goto_next(void)
632 {
633 	return pl_goto_generic(pl_get_next_shuffled, pl_get_next);
634 }
635 
pl_goto_prev(void)636 struct track_info *pl_goto_prev(void)
637 {
638 	return pl_goto_generic(pl_get_prev_shuffled, pl_get_prev);
639 }
640 
pl_play_selected_row(void)641 struct track_info *pl_play_selected_row(void)
642 {
643 	/* a bit tricky because we want to insert the selected track at the
644 	 * current position in the shuffle list. but we must be careful not to
645 	 * insert a track into a foreign shuffle list.
646 	 */
647 
648 	int was_in_track_window = pl_cursor_in_track_window;
649 
650 	struct playlist *prev_pl = pl_playing;
651 	struct simple_track *prev_track = pl_playing_track;
652 
653 	struct track_info *rv = NULL;
654 
655 	if (!pl_cursor_in_track_window) {
656 		if (shuffle && !pl_empty(pl_visible)) {
657 			struct shuffle_track *st = shuffle_list_get_next(&pl_visible->shuffle_root, NULL, pl_dummy_filter);
658 			struct simple_track *track = &st->simple_track;
659 			rv = pl_play_track(pl_visible, track, true);
660 		}
661 	}
662 
663 	if (!rv)
664 		rv = pl_play_selected_track();
665 
666 	if (shuffle && rv && (pl_playing == prev_pl) && prev_track) {
667 		struct shuffle_track *prev_st = simple_track_to_shuffle_track(prev_track);
668 		struct shuffle_track *cur_st =
669 			simple_track_to_shuffle_track(pl_playing_track);
670 		shuffle_insert(&pl_playing->shuffle_root, prev_st, cur_st);
671 	}
672 
673 	pl_cursor_in_track_window = was_in_track_window;
674 
675 	return rv;
676 }
677 
pl_select_playing_track(void)678 void pl_select_playing_track(void)
679 {
680 	if (!pl_playing_track)
681 		return;
682 
683 	struct iter iter;
684 
685 	editable_take_ownership(&pl_playing->editable);
686 
687 	editable_track_to_iter(&pl_playing->editable, pl_playing_track, &iter);
688 	window_set_sel(pl_editable_shared.win, &iter);
689 
690 	pl_to_iter(pl_playing, &iter);
691 	window_set_sel(pl_list_win, &iter);
692 
693 	if (!pl_cursor_in_track_window)
694 		pl_mark_for_redraw();
695 
696 	pl_cursor_in_track_window = 1;
697 }
698 
pl_reshuffle(void)699 void pl_reshuffle(void)
700 {
701 	if (pl_playing)
702 		shuffle_list_reshuffle(&pl_playing->shuffle_root);
703 }
704 
pl_get_sort_str(char * buf,size_t size)705 void pl_get_sort_str(char *buf, size_t size)
706 {
707 	strscpy(buf, pl_editable_shared.sort_str, size);
708 }
709 
pl_set_sort_str(const char * buf)710 void pl_set_sort_str(const char *buf)
711 {
712 	sort_key_t *keys = parse_sort_keys(buf);
713 
714 	if (!keys)
715 		return;
716 
717 	editable_shared_set_sort_keys(&pl_editable_shared, keys);
718 	sort_keys_to_str(keys, pl_editable_shared.sort_str,
719 			sizeof(pl_editable_shared.sort_str));
720 
721 	struct playlist *pl;
722 	list_for_each_entry(pl, &pl_head, node)
723 		editable_sort(&pl->editable);
724 }
725 
pl_rename_selected_pl(const char * name)726 void pl_rename_selected_pl(const char *name)
727 {
728 	if (strcmp(pl_visible->name, name) == 0)
729 		return;
730 
731 	if (!pl_check_new_pl_name(name))
732 		return;
733 
734 	char *full_cur = pl_name_to_pl_file(pl_visible->name);
735 	char *full_new = pl_name_to_pl_file(name);
736 	rename(full_cur, full_new);
737 	free(full_cur);
738 	free(full_new);
739 
740 	free(pl_visible->name);
741 	pl_visible->name = xstrdup(name);
742 
743 	pl_mark_for_redraw();
744 }
745 
pl_clear(void)746 void pl_clear(void)
747 {
748 	if (!pl_cursor_in_track_window)
749 		return;
750 
751 	pl_clear_visible_pl();
752 }
753 
pl_mark_for_redraw(void)754 void pl_mark_for_redraw(void)
755 {
756 	pl_list_win->changed = 1;
757 	pl_editable_shared.win->changed = 1;
758 }
759 
pl_needs_redraw(void)760 int pl_needs_redraw(void)
761 {
762 	return pl_list_win->changed || pl_editable_shared.win->changed;
763 }
764 
pl_cursor_win(void)765 struct window *pl_cursor_win(void)
766 {
767 	if (pl_cursor_in_track_window)
768 		return pl_editable_shared.win;
769 	else
770 		return pl_list_win;
771 }
772 
_pl_for_each_sel(track_info_cb cb,void * data,int reverse)773 int _pl_for_each_sel(track_info_cb cb, void *data, int reverse)
774 {
775 	if (pl_cursor_in_track_window)
776 		return _editable_for_each_sel(&pl_visible->editable, cb, data, reverse);
777 	else
778 		return editable_for_each(&pl_visible->editable, cb, data, reverse);
779 }
780 
pl_for_each_sel(track_info_cb cb,void * data,int reverse,int advance)781 int pl_for_each_sel(track_info_cb cb, void *data, int reverse, int advance)
782 {
783 	if (pl_cursor_in_track_window)
784 		return editable_for_each_sel(&pl_visible->editable, cb, data, reverse, advance);
785 	else
786 		return editable_for_each(&pl_visible->editable, cb, data, reverse);
787 }
788 
789 #define pl_tw_only(cmd) if (!pl_cursor_in_track_window) { \
790 	info_msg(":%s only works in the track window", cmd); \
791 } else
792 
pl_invert_marks(void)793 void pl_invert_marks(void)
794 {
795 	pl_tw_only("invert")
796 		editable_invert_marks(&pl_visible->editable);
797 }
798 
pl_mark(char * arg)799 void pl_mark(char *arg)
800 {
801 	pl_tw_only("mark")
802 		editable_invert_marks(&pl_visible->editable);
803 }
804 
pl_unmark(void)805 void pl_unmark(void)
806 {
807 	pl_tw_only("unmark")
808 		editable_unmark(&pl_visible->editable);
809 }
810 
pl_rand(void)811 void pl_rand(void)
812 {
813 	pl_tw_only("rand")
814 		editable_rand(&pl_visible->editable);
815 }
816 
pl_win_mv_after(void)817 void pl_win_mv_after(void)
818 {
819 	if (pl_cursor_in_track_window)
820 		editable_move_after(&pl_visible->editable);
821 }
822 
pl_win_mv_before(void)823 void pl_win_mv_before(void)
824 {
825 	if (pl_cursor_in_track_window)
826 		editable_move_before(&pl_visible->editable);
827 }
828 
pl_win_remove(void)829 void pl_win_remove(void)
830 {
831 	if (pl_cursor_in_track_window)
832 		pl_delete_selected_track();
833 	else
834 		pl_delete_selected_pl();
835 }
836 
pl_win_toggle(void)837 void pl_win_toggle(void)
838 {
839 	if (pl_cursor_in_track_window)
840 		editable_toggle_mark(&pl_visible->editable);
841 	else
842 		pl_mark_selected_pl();
843 }
844 
pl_win_update(void)845 void pl_win_update(void)
846 {
847 	if (yes_no_query("Reload this playlist? [y/N]") != UI_QUERY_ANSWER_YES)
848 		return;
849 
850 	pl_clear_visible_pl();
851 
852 	char *full = pl_name_to_pl_file(pl_visible->name);
853 	cmus_add(pl_add_cb, full, FILE_TYPE_PL, JOB_TYPE_PL, 0, pl_visible);
854 	free(full);
855 }
856 
pl_win_next(void)857 void pl_win_next(void)
858 {
859 	pl_cursor_in_track_window ^= 1;
860 	if (pl_empty(pl_visible))
861 		pl_cursor_in_track_window = 0;
862 	pl_mark_for_redraw();
863 }
864 
pl_set_nr_rows(int h)865 void pl_set_nr_rows(int h)
866 {
867 	window_set_nr_rows(pl_list_win, h);
868 	window_set_nr_rows(pl_editable_shared.win, h);
869 }
870 
pl_visible_total_time(void)871 unsigned int pl_visible_total_time(void)
872 {
873 	return pl_visible->editable.total_time;
874 }
875 
pl_playing_total_time(void)876 unsigned int pl_playing_total_time(void)
877 {
878 	if (pl_playing)
879 		return pl_playing->editable.total_time;
880 	return 0;
881 }
882 
pl_list_iter_to_info(struct iter * iter,struct pl_list_info * info)883 void pl_list_iter_to_info(struct iter *iter, struct pl_list_info *info)
884 {
885 	struct playlist *pl = pl_from_list(iter->data1);
886 
887 	info->name = pl->name;
888 	info->marked = pl == pl_marked;
889 	info->active = !pl_cursor_in_track_window;
890 	info->selected = pl == pl_visible;
891 	info->current = pl == pl_playing;
892 }
893 
pl_draw(void (* list)(struct window * win),void (* tracks)(struct window * win),int full)894 void pl_draw(void (*list)(struct window *win),
895 		void (*tracks)(struct window *win), int full)
896 {
897 	if (full || pl_list_win->changed)
898 		list(pl_list_win);
899 	if (full || pl_editable_shared.win->changed)
900 		tracks(pl_editable_shared.win);
901 	pl_list_win->changed = 0;
902 	pl_editable_shared.win->changed = 0;
903 }
904 
pl_get_playing_track(void)905 struct simple_track *pl_get_playing_track(void)
906 {
907 	return pl_playing_track;
908 }
909 
pl_update_track(struct track_info * old,struct track_info * new)910 void pl_update_track(struct track_info *old, struct track_info *new)
911 {
912 	struct playlist *pl;
913 	list_for_each_entry(pl, &pl_head, node)
914 		editable_update_track(&pl->editable, old, new);
915 }
916 
pl_get_cursor_in_track_window(void)917 int pl_get_cursor_in_track_window(void)
918 {
919 	return pl_cursor_in_track_window;
920 }
921 
pl_create(const char * name)922 void pl_create(const char *name)
923 {
924 	if (!pl_check_new_pl_name(name))
925 		return;
926 
927 	struct playlist *pl = pl_new(name);
928 	list_add_tail(&pl->node, &pl_head);
929 	pl_list_win->changed = 1;
930 }
931 
pl_visible_is_marked(void)932 int pl_visible_is_marked(void)
933 {
934 	return pl_visible == pl_marked;
935 }
936 
pl_marked_pl_name(void)937 const char *pl_marked_pl_name(void)
938 {
939 	return pl_marked->name;
940 }
941 
pl_set_marked_pl_by_name(const char * name)942 void pl_set_marked_pl_by_name(const char *name)
943 {
944 	struct playlist *pl;
945 	list_for_each_entry(pl, &pl_head, node) {
946 		if (strcmp(pl->name, name) == 0) {
947 			pl_marked = pl;
948 			return;
949 		}
950 	}
951 }
952