/*
* PlayLists.java
*
* Copyright (C) 2006-2007 Gabriel Burca (gburca dash virtmus at ebixio dot com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package com.ebixio.virtmus;
import com.ebixio.util.WeakPropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
/**
* Represents the PlayList nodes in the PlayList window.
* @author Gabriel Burca <gburca dash virtmus at ebixio dot com>
*/
public class PlayLists extends Children.Keys<PlayList> implements PropertyChangeListener {
/* When changes happen and we need to rescan the playlists, we only want at most
1 pending rescan task besides the one that's currently executing. Any more
would be superfluous.
*/
ThreadPoolExecutor tpe = new ThreadPoolExecutor(1, 1, 5L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1), new ThreadPoolExecutor.DiscardPolicy());
/**
* Creates a new instance of PlayLists.
*/
public PlayLists() {
//Log.log("PlayLists::constructor thread: " + Thread.currentThread().getName());
}
/**
* Initialize the PlayLists.
* Keeping this separate from the constructor so we don't leak "this" in
* the constructor.
*/
public void init() {
tpe.allowCoreThreadTimeOut(true);
WeakPropertyChangeListener wpcl = new WeakPropertyChangeListener(this, PlayListSet.findInstance());
PlayListSet.findInstance().addPropertyChangeListener(wpcl);
// Pick up the changes that happened before we registered for changes.
refreshKeys();
}
/**
* This method is called whenever a list of available PlayLists is about to be
* displayed. We need to create here a bunch of "keys" to represent each playlist.
* The framework will then call createNode with each "key" in turn to obtain the
* actual PlayList object to be displayed.
*
* See the "Recognizing a File Type" tutorial
*/
@Override
protected void addNotify() {
//Log.log("PlayLists::addNotify " + Thread.currentThread().getName());
setKeys(getKeys());
}
private ArrayList<PlayList> getKeys() {
synchronized(PlayListSet.findInstance().playLists) {
return new ArrayList<>(PlayListSet.findInstance().playLists);
}
}
/**
* This method will get called with one of the items passed to setKeys in
* addNotify above.
* @param key A key to create the node for
* @return A node corresponding to the key
*/
@Override
protected Node[] createNodes(PlayList key) {
Songs songs = new Songs(key);
songs.init();
PlayListNode pln = new PlayListNode(key, songs);
return new Node[] {pln};
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
String prop = evt.getPropertyName();
if (PlayListSet.PROP_NEW_PL_ADDED.equals(prop) ||
PlayListSet.PROP_ALL_PL_LOADED.equals(prop)) {
refreshKeys();
}
}
/**
* Handling re-scan in a separate thread to prevent deadlock. The property
* change event can be fired from a synchronized(playLists) block, and
* getKeys() iterates over (and locks) the same collection causing a potential
* deadlock situation.
* @see Tags.handleTagChange()
*/
private void refreshKeys() {
tpe.execute(new Runnable() {
@Override
public void run() {
/* When setKeys is called from addNotify above, the class tries to be smart
and only calls createNodes for the newly added keys. If the content of a
node has changed, but the key remained the same, we need to call refreshKey(key)
for the change to be refreshed.
*/
addNotify();
List<PlayList> allKeys = getKeys();
for (PlayList p: allKeys) {
refreshKey(p);
}
}
});
}
}