1 /*
2  * Copyright (C) 2009-2011 Carl Hetherington <carl@carlh.net>
3  * Copyright (C) 2009-2016 Paul Davis <paul@linuxaudiosystems.com>
4  * Copyright (C) 2011-2012 David Robillard <d@drobilla.net>
5  * Copyright (C) 2013-2016 John Emmas <john@creativepost.co.uk>
6  * Copyright (C) 2015-2019 Robin Gareus <robin@gareus.org>
7  * Copyright (C) 2016 Tim Mayberry <mojofunk@gmail.com>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23 #include <vector>
24 
25 #include "ardour/debug.h"
26 #include "ardour/playlist.h"
27 #include "ardour/playlist_factory.h"
28 #include "ardour/session_playlists.h"
29 #include "ardour/track.h"
30 #include "pbd/i18n.h"
31 #include "pbd/compose.h"
32 #include "pbd/xml++.h"
33 
34 using namespace std;
35 using namespace PBD;
36 using namespace ARDOUR;
37 
~SessionPlaylists()38 SessionPlaylists::~SessionPlaylists ()
39 {
40 	DEBUG_TRACE (DEBUG::Destruction, "delete playlists\n");
41 
42 	for (List::iterator i = playlists.begin(); i != playlists.end(); ) {
43 		SessionPlaylists::List::iterator tmp;
44 
45 		tmp = i;
46 		++tmp;
47 
48 		DEBUG_TRACE(DEBUG::Destruction, string_compose ("Dropping for used playlist %1 ; pre-ref = %2\n", (*i)->name(), (*i).use_count()));
49 		boost::shared_ptr<Playlist> keeper (*i);
50 		(*i)->drop_references ();
51 
52 		i = tmp;
53 	}
54 
55 	DEBUG_TRACE (DEBUG::Destruction, "delete unused playlists\n");
56 	for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ) {
57 		List::iterator tmp;
58 
59 		tmp = i;
60 		++tmp;
61 
62 		DEBUG_TRACE(DEBUG::Destruction, string_compose ("Dropping for unused playlist %1 ; pre-ref = %2\n", (*i)->name(), (*i).use_count()));
63 		boost::shared_ptr<Playlist> keeper (*i);
64 		(*i)->drop_references ();
65 
66 		i = tmp;
67 	}
68 
69 	playlists.clear ();
70 	unused_playlists.clear ();
71 }
72 
73 bool
add(boost::shared_ptr<Playlist> playlist)74 SessionPlaylists::add (boost::shared_ptr<Playlist> playlist)
75 {
76 	Glib::Threads::Mutex::Lock lm (lock);
77 
78 	bool const existing = find (playlists.begin(), playlists.end(), playlist) != playlists.end();
79 
80 	if (!existing) {
81 		playlists.insert (playlists.begin(), playlist);
82 		playlist->InUse.connect_same_thread (*this, boost::bind (&SessionPlaylists::track, this, _1, boost::weak_ptr<Playlist>(playlist)));
83 		playlist->DropReferences.connect_same_thread (
84 			*this, boost::bind (&SessionPlaylists::remove_weak, this, boost::weak_ptr<Playlist> (playlist))
85 			);
86 	}
87 
88 	return existing;
89 }
90 
91 void
remove_weak(boost::weak_ptr<Playlist> playlist)92 SessionPlaylists::remove_weak (boost::weak_ptr<Playlist> playlist)
93 {
94 	boost::shared_ptr<Playlist> p = playlist.lock ();
95 	if (p) {
96 		remove (p);
97 	}
98 }
99 
100 void
remove(boost::shared_ptr<Playlist> playlist)101 SessionPlaylists::remove (boost::shared_ptr<Playlist> playlist)
102 {
103 	Glib::Threads::Mutex::Lock lm (lock);
104 
105 	List::iterator i;
106 
107 	i = find (playlists.begin(), playlists.end(), playlist);
108 	if (i != playlists.end()) {
109 		playlists.erase (i);
110 	}
111 
112 	i = find (unused_playlists.begin(), unused_playlists.end(), playlist);
113 	if (i != unused_playlists.end()) {
114 		unused_playlists.erase (i);
115 	}
116 }
117 
118 void
update_tracking()119 SessionPlaylists::update_tracking ()
120 {
121 	/* This is intended to be called during session-load, after loading
122 	 * playlists and re-assigning them to tracks (refcnt is up to date).
123 	 * Check playlist refcnt, move unused playlist to unused_playlists
124 	 * array (which may be the case when loading old sessions)
125 	 */
126 	for (List::iterator i = playlists.begin(); i != playlists.end(); ) {
127 		if ((*i)->hidden () || (*i)->used ()) {
128 			++i;
129 			continue;
130 		}
131 
132 		warning << _("Session State: Unused playlist was listed as used.") << endmsg;
133 
134 		assert (unused_playlists.find (*i) == unused_playlists.end());
135 		unused_playlists.insert (*i);
136 
137 		List::iterator rm = i;
138 		++i;
139 		 playlists.erase (rm);
140 	}
141 }
142 
143 void
track(bool inuse,boost::weak_ptr<Playlist> wpl)144 SessionPlaylists::track (bool inuse, boost::weak_ptr<Playlist> wpl)
145 {
146 	boost::shared_ptr<Playlist> pl(wpl.lock());
147 
148 	if (!pl) {
149 		return;
150 	}
151 
152 	List::iterator x;
153 
154 	if (pl->hidden()) {
155 		/* its not supposed to be visible */
156 		return;
157 	}
158 
159 	{
160 		Glib::Threads::Mutex::Lock lm (lock);
161 
162 		if (!inuse) {
163 
164 			unused_playlists.insert (pl);
165 
166 			if ((x = playlists.find (pl)) != playlists.end()) {
167 				playlists.erase (x);
168 			}
169 
170 
171 		} else {
172 
173 			playlists.insert (pl);
174 
175 			if ((x = unused_playlists.find (pl)) != unused_playlists.end()) {
176 				unused_playlists.erase (x);
177 			}
178 		}
179 	}
180 }
181 
182 uint32_t
n_playlists() const183 SessionPlaylists::n_playlists () const
184 {
185 	Glib::Threads::Mutex::Lock lm (lock);
186 	return playlists.size();
187 }
188 
189 boost::shared_ptr<Playlist>
for_pgroup(string pgroup_id,const PBD::ID & id)190 SessionPlaylists::for_pgroup (string pgroup_id, const PBD::ID& id)
191 {
192 	Glib::Threads::Mutex::Lock lm (lock);
193 
194 	for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
195 		if ((*i)->pgroup_id() == pgroup_id) {
196 			if ((*i)->get_orig_track_id() == id) {
197 				return* i;
198 			}
199 		}
200 	}
201 
202 	for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
203 		if ((*i)->pgroup_id() == pgroup_id) {
204 			if ((*i)->get_orig_track_id() == id) {
205 				return* i;
206 			}
207 		}
208 	}
209 
210 	return boost::shared_ptr<Playlist>();
211 }
212 
213 std::vector<boost::shared_ptr<Playlist> >
playlists_for_pgroup(std::string pgroup)214 SessionPlaylists::playlists_for_pgroup (std::string pgroup)
215 {
216 	Glib::Threads::Mutex::Lock lm (lock);
217 
218 	vector<boost::shared_ptr<Playlist> > pl_tr;
219 
220 	for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
221 		if ((*i)->pgroup_id().compare(pgroup)==0) {
222 			pl_tr.push_back (*i);
223 		}
224 	}
225 
226 	for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
227 		if ((*i)->pgroup_id().compare(pgroup)==0) {
228 			pl_tr.push_back (*i);
229 		}
230 	}
231 
232 	return pl_tr;
233 }
234 
235 boost::shared_ptr<Playlist>
by_name(string name)236 SessionPlaylists::by_name (string name)
237 {
238 	Glib::Threads::Mutex::Lock lm (lock);
239 
240 	for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
241 		if ((*i)->name() == name) {
242 			return* i;
243 		}
244 	}
245 
246 	for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
247 		if ((*i)->name() == name) {
248 			return* i;
249 		}
250 	}
251 
252 	return boost::shared_ptr<Playlist>();
253 }
254 
255 boost::shared_ptr<Playlist>
by_id(const PBD::ID & id)256 SessionPlaylists::by_id (const PBD::ID& id)
257 {
258 	Glib::Threads::Mutex::Lock lm (lock);
259 
260 	for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
261 		if ((*i)->id() == id) {
262 			return* i;
263 		}
264 	}
265 
266 	for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
267 		if ((*i)->id() == id) {
268 			return* i;
269 		}
270 	}
271 
272 	return boost::shared_ptr<Playlist>();
273 }
274 
275 void
unassigned(std::list<boost::shared_ptr<Playlist>> & list)276 SessionPlaylists::unassigned (std::list<boost::shared_ptr<Playlist> > & list)
277 {
278 	Glib::Threads::Mutex::Lock lm (lock);
279 
280 	for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
281 		if (!(*i)->get_orig_track_id().to_s().compare ("0")) {
282 			list.push_back (*i);
283 		}
284 	}
285 
286 	for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
287 		if (!(*i)->get_orig_track_id().to_s().compare ("0")) {
288 			list.push_back (*i);
289 		}
290 	}
291 }
292 
293 void
update_orig_2X(PBD::ID old_orig,PBD::ID new_orig)294 SessionPlaylists::update_orig_2X (PBD::ID old_orig, PBD::ID new_orig)
295 {
296 	Glib::Threads::Mutex::Lock lm (lock);
297 
298 	for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
299 		if ((*i)->get_orig_track_id() == old_orig) {
300 			(*i)->set_orig_track_id (new_orig);
301 		}
302 	}
303 
304 	for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
305 		if ((*i)->get_orig_track_id() == old_orig) {
306 			(*i)->set_orig_track_id (new_orig);
307 		}
308 	}
309 }
310 
311 void
get(vector<boost::shared_ptr<Playlist>> & s) const312 SessionPlaylists::get (vector<boost::shared_ptr<Playlist> >& s) const
313 {
314 	Glib::Threads::Mutex::Lock lm (lock);
315 
316 	for (List::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
317 		s.push_back (*i);
318 	}
319 
320 	for (List::const_iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
321 		s.push_back (*i);
322 	}
323 }
324 
325 void
destroy_region(boost::shared_ptr<Region> r)326 SessionPlaylists::destroy_region (boost::shared_ptr<Region> r)
327 {
328 	Glib::Threads::Mutex::Lock lm (lock);
329 
330 	for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
331                 (*i)->destroy_region (r);
332 	}
333 
334 	for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
335                 (*i)->destroy_region (r);
336 	}
337 }
338 
339 /** Return the number of playlists (not regions) that contain @a src
340  *  Important: this counts usage in both used and not-used playlists.
341  */
342 uint32_t
source_use_count(boost::shared_ptr<const Source> src) const343 SessionPlaylists::source_use_count (boost::shared_ptr<const Source> src) const
344 {
345 	uint32_t count = 0;
346 
347 	/* XXXX this can go wildly wrong in the presence of circular references
348 	 * between compound regions.
349 	 */
350 
351 	for (List::const_iterator p = playlists.begin(); p != playlists.end(); ++p) {
352                 if ((*p)->uses_source (src)) {
353                         ++count;
354                         break;
355                 }
356 	}
357 
358 	for (List::const_iterator p = unused_playlists.begin(); p != unused_playlists.end(); ++p) {
359                 if ((*p)->uses_source (src)) {
360                         ++count;
361                         break;
362                 }
363 	}
364 
365 	return count;
366 }
367 
368 void
sync_all_regions_with_regions()369 SessionPlaylists::sync_all_regions_with_regions ()
370 {
371 	Glib::Threads::Mutex::Lock lm (lock);
372 
373 	for (List::const_iterator p = playlists.begin(); p != playlists.end(); ++p) {
374                 (*p)->sync_all_regions_with_regions ();
375         }
376 }
377 
378 void
update_after_tempo_map_change()379 SessionPlaylists::update_after_tempo_map_change ()
380 {
381 	for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
382 		(*i)->update_after_tempo_map_change ();
383 	}
384 
385 	for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
386 		(*i)->update_after_tempo_map_change ();
387 	}
388 }
389 
390 namespace {
391 struct id_compare
392 {
operator ()__anon91b1ae710111::id_compare393 	bool operator()(const boost::shared_ptr<Playlist>& p1, const boost::shared_ptr<Playlist>& p2) const
394 	{
395 		return p1->id () < p2->id ();
396 	}
397 };
398 
399 typedef std::set<boost::shared_ptr<Playlist> > List;
400 typedef std::set<boost::shared_ptr<Playlist>, id_compare> IDSortedList;
401 
402 static void
get_id_sorted_playlists(const List & playlists,IDSortedList & id_sorted_playlists)403 get_id_sorted_playlists (const List& playlists, IDSortedList& id_sorted_playlists)
404 {
405 	for (List::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
406 		id_sorted_playlists.insert(*i);
407 	}
408 }
409 
410 } // anonymous namespace
411 
412 void
add_state(XMLNode * node,bool save_template,bool include_unused)413 SessionPlaylists::add_state (XMLNode* node, bool save_template, bool include_unused)
414 {
415 	XMLNode* child = node->add_child ("Playlists");
416 
417 	IDSortedList id_sorted_playlists;
418 	get_id_sorted_playlists (playlists, id_sorted_playlists);
419 
420 	for (IDSortedList::iterator i = id_sorted_playlists.begin (); i != id_sorted_playlists.end (); ++i) {
421 		if (!(*i)->hidden ()) {
422 			if (save_template) {
423 				child->add_child_nocopy ((*i)->get_template ());
424 			} else {
425 				child->add_child_nocopy ((*i)->get_state ());
426 			}
427 		}
428 	}
429 
430 	if (!include_unused) {
431 		return;
432 	}
433 
434 	child = node->add_child ("UnusedPlaylists");
435 
436 	IDSortedList id_sorted_unused_playlists;
437 	get_id_sorted_playlists (unused_playlists, id_sorted_unused_playlists);
438 
439 	for (IDSortedList::iterator i = id_sorted_unused_playlists.begin ();
440 	     i != id_sorted_unused_playlists.end (); ++i) {
441 		if (!(*i)->hidden()) {
442 			if (!(*i)->empty()) {
443 				if (save_template) {
444 					child->add_child_nocopy ((*i)->get_template());
445 				} else {
446 					child->add_child_nocopy ((*i)->get_state());
447 				}
448 			}
449 		}
450 	}
451 }
452 
453 /** @return true for `stop cleanup', otherwise false */
454 bool
maybe_delete_unused(boost::function<int (boost::shared_ptr<Playlist>)> ask)455 SessionPlaylists::maybe_delete_unused (boost::function<int(boost::shared_ptr<Playlist>)> ask)
456 {
457 	vector<boost::shared_ptr<Playlist> > playlists_tbd;
458 
459 	bool delete_remaining = false;
460 	bool keep_remaining = false;
461 
462 	for (List::iterator x = unused_playlists.begin(); x != unused_playlists.end(); ++x) {
463 
464 		if (keep_remaining) {
465 			break;
466 		}
467 
468 		if (delete_remaining) {
469 			playlists_tbd.push_back (*x);
470 			continue;
471 		}
472 
473 		int status = ask (*x);
474 
475 		switch (status) {
476 		case -1:
477 			// abort
478 			return true;
479 
480 		case -2:
481 			// keep this and all later
482 			keep_remaining = true;
483 			break;
484 
485 		case 2:
486 			// delete this and all later
487 			delete_remaining = true;
488 
489 			/* fallthrough */
490 		case 1:
491 			// delete this
492 			playlists_tbd.push_back (*x);
493 			break;
494 
495 		default:
496 			/* leave it alone */
497 			break;
498 		}
499 	}
500 
501 	/* now delete any that were marked for deletion */
502 
503 	for (vector<boost::shared_ptr<Playlist> >::iterator x = playlists_tbd.begin(); x != playlists_tbd.end(); ++x) {
504 		boost::shared_ptr<Playlist> keeper (*x);
505 		(*x)->drop_references ();
506 	}
507 
508 	playlists_tbd.clear ();
509 
510 	return false;
511 }
512 
513 int
load(Session & session,const XMLNode & node)514 SessionPlaylists::load (Session& session, const XMLNode& node)
515 {
516 	XMLNodeList nlist;
517 	XMLNodeConstIterator niter;
518 	boost::shared_ptr<Playlist> playlist;
519 
520 	nlist = node.children();
521 
522 	for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
523 
524 		if ((playlist = XMLPlaylistFactory (session, **niter)) == 0) {
525 			error << _("Session: cannot create Playlist from XML description.") << endmsg;
526 			return -1;
527 		}
528 	}
529 
530 	return 0;
531 }
532 
533 int
load_unused(Session & session,const XMLNode & node)534 SessionPlaylists::load_unused (Session& session, const XMLNode& node)
535 {
536 	XMLNodeList nlist;
537 	XMLNodeConstIterator niter;
538 	boost::shared_ptr<Playlist> playlist;
539 
540 	nlist = node.children();
541 
542 	for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
543 
544 		if ((playlist = XMLPlaylistFactory (session, **niter)) == 0) {
545 			error << _("Session: cannot create Playlist from XML description.") << endmsg;
546 			continue;
547 		}
548 
549 		// now manually untrack it
550 
551 		track (false, boost::weak_ptr<Playlist> (playlist));
552 	}
553 
554 	return 0;
555 }
556 
557 boost::shared_ptr<Playlist>
XMLPlaylistFactory(Session & session,const XMLNode & node)558 SessionPlaylists::XMLPlaylistFactory (Session& session, const XMLNode& node)
559 {
560 	try {
561 		return PlaylistFactory::create (session, node);
562 	}
563 
564 	catch (failed_constructor& err) {
565 		return boost::shared_ptr<Playlist>();
566 	}
567 }
568 
569 boost::shared_ptr<Crossfade>
find_crossfade(const PBD::ID & id)570 SessionPlaylists::find_crossfade (const PBD::ID& id)
571 {
572 	Glib::Threads::Mutex::Lock lm (lock);
573 
574 	boost::shared_ptr<Crossfade> c;
575 
576 	for (List::iterator i = playlists.begin(); i != playlists.end(); ++i) {
577 		c = (*i)->find_crossfade (id);
578 		if (c) {
579 			return c;
580 		}
581 	}
582 
583 	for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
584 		c = (*i)->find_crossfade (id);
585 		if (c) {
586 			return c;
587 		}
588 	}
589 
590 	return boost::shared_ptr<Crossfade> ();
591 }
592 
593 uint32_t
region_use_count(boost::shared_ptr<Region> region) const594 SessionPlaylists::region_use_count (boost::shared_ptr<Region> region) const
595 {
596 	Glib::Threads::Mutex::Lock lm (lock);
597         uint32_t cnt = 0;
598 
599 	for (List::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
600                 cnt += (*i)->region_use_count (region);
601 	}
602 
603 	for (List::const_iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
604                 cnt += (*i)->region_use_count (region);
605 	}
606 
607 	return cnt;
608 }
609 
610 vector<boost::shared_ptr<Playlist> >
get_used() const611 SessionPlaylists::get_used () const
612 {
613 	vector<boost::shared_ptr<Playlist> > pl;
614 
615 	Glib::Threads::Mutex::Lock lm (lock);
616 
617 	for (List::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
618 		pl.push_back (*i);
619 	}
620 
621 	return pl;
622 }
623 
624 vector<boost::shared_ptr<Playlist> >
get_unused() const625 SessionPlaylists::get_unused () const
626 {
627 	vector<boost::shared_ptr<Playlist> > pl;
628 
629 	Glib::Threads::Mutex::Lock lm (lock);
630 
631 	for (List::const_iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
632 		pl.push_back (*i);
633 	}
634 
635 	return pl;
636 }
637 
638 /** @return list of Playlists that are associated with a track */
639 vector<boost::shared_ptr<Playlist> >
playlists_for_track(boost::shared_ptr<Track> tr) const640 SessionPlaylists::playlists_for_track (boost::shared_ptr<Track> tr) const
641 {
642 	vector<boost::shared_ptr<Playlist> > pl;
643 	get (pl);
644 
645 	vector<boost::shared_ptr<Playlist> > pl_tr;
646 
647 	for (vector<boost::shared_ptr<Playlist> >::iterator i = pl.begin(); i != pl.end(); ++i) {
648 		if ( ((*i)->get_orig_track_id() == tr->id()) ||
649 			(tr->playlist()->id() == (*i)->id())    ||
650 			((*i)->shared_with (tr->id())) )
651 		{
652 			pl_tr.push_back (*i);
653 		}
654 	}
655 
656 	return pl_tr;
657 }
658 
659 void
foreach(boost::function<void (boost::shared_ptr<const Playlist>)> functor,bool incl_unused)660 SessionPlaylists::foreach (boost::function<void(boost::shared_ptr<const Playlist>)> functor, bool incl_unused)
661 {
662 	Glib::Threads::Mutex::Lock lm (lock);
663 	for (List::iterator i = playlists.begin(); i != playlists.end(); i++) {
664 		if (!(*i)->hidden()) {
665 			functor (*i);
666 		}
667 	}
668 	if (!incl_unused) {
669 		return;
670 	}
671 	for (List::iterator i = unused_playlists.begin(); i != unused_playlists.end(); i++) {
672 		if (!(*i)->hidden()) {
673 			functor (*i);
674 		}
675 	}
676 }
677