/*
* Songs.java
*
* Copyright (C) 2006-2014 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.Log;
import com.ebixio.util.WeakPropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.openide.nodes.Children;
import org.openide.nodes.Index;
import org.openide.nodes.Node;
import org.openide.util.WeakListeners;
/**
*
* @author Gabriel Burca <gburca dash virtmus at ebixio dot com>
*/
public class Songs extends Children.Keys<Song> implements PropertyChangeListener, ChangeListener
{
private final PlayList playList;
/* When changes happen and we need to rescan the songs, 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 Songs
* @param playList The PlayList this song belongs to. */
public Songs(PlayList playList) {
tpe.allowCoreThreadTimeOut(true);
this.playList = playList;
// We want to be notified when the song order changes within a PlayList
// so that we can re-create the UI with nodes in the (new) proper order.
playList.addChangeListener(WeakListeners.change(this, playList));
}
public void init() {
playList.addPropertyChangeListener(new WeakPropertyChangeListener(this, playList));
}
@Override
protected void addNotify() {
Log.log(Level.FINEST, "Songs::addNotify");
setKeys(getKeys());
}
private ArrayList<Song> getKeys() {
ArrayList<Song> songKeys = new ArrayList<>();
synchronized (playList.songs) {
for (Song song : playList.songs) {
songKeys.add(song);
}
}
return songKeys;
}
@Override
protected Node[] createNodes(Song key) {
if (key != null) {
return new Node[] {new SongNode(playList, key, new MusicPages(key))};
} else {
return null;
}
}
@Override
public void stateChanged(ChangeEvent e) {
addNotify();
}
public Index getIndex() {
return new SongIndexer();
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (PlayList.PROP_SONG_ADDED.equals(evt.getPropertyName()) ||
PlayList.PROP_SONG_REMOVED.equals(evt.getPropertyName())) {
refreshKeys();
}
}
/**
* Handling refresh in a separate thread. This is so we don't get into a deadlock
* situation with the propertyChange() being fired/called from a synchronized(songs)
* block.
* @see {@link Tags#refreshKeys()}
*/
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();
ArrayList<Song> allKeys = getKeys();
for (Song s: allKeys) {
refreshKey(s);
}
}
});
}
public class SongIndexer extends Index.Support {
@Override
public Node[] getNodes() {
return Songs.this.getNodes();
}
@Override
public int getNodesCount() {
return getNodes().length;
}
@Override
public void reorder(int[] order) {
playList.reorder(order);
fireChangeEvent(new ChangeEvent(SongIndexer.this));
}
}
}