The Big Fat RoboDJ revamp. Rewrote the server using Presents; rewrote the

chooser to interface using distributed objects; implemented 90% of the
music importing process (just need to upload the data to the server and
stuff the new music records in the database).


git-svn-id: https://samskivert.googlecode.com/svn/trunk@1489 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
mdb
2004-08-18 04:23:00 +00:00
parent 32ce5c7048
commit 7a2a5bed25
26 changed files with 1902 additions and 261 deletions
+37 -8
View File
@@ -9,6 +9,7 @@
<property name="build.compiler" value="jikes"/>
<property name="web.home" value="/export/samskivert/pages/code"/>
<property name="install.dir" value="${web.home}/${app.name}"/>
<property name="narya.home" value="../../work/narya"/>
<!-- we want to access the environment -->
<property environment="env"/>
@@ -21,22 +22,51 @@
<property name="javadoc.dir" value="${deploy.dir}/docs"/>
<property name="doc.packages" value="robodj.*"/>
<property name="doc.overview" value="robodj/overview.html"/>
<property name="copyright.holder" value="Michael Bayne"/>
<property name="nartool.path" value="${narya.home}/bin"/>
<property name="classes.dir" value="${deploy.dir}/classes"/>
<!-- declare our classpath -->
<path id="classpath">
<fileset dir="${java.libraries}" includesfile="lib/CLIENT_LIBS"/>
<fileset dir="lib" includes="**/*.jar"/>
<pathelement location="${deploy.dir}/classes"/>
<pathelement location="${classes.dir}"/>
</path>
<!-- generates .java files for all .dobj files -->
<target name="gendobj">
<apply executable="${nartool.path}/gendobj"
failonerror="true" parallel="true">
<srcfile/>
<fileset dir="src/java" includes="**/*.dobj"/>
</apply>
</target>
<!-- generates marshaller and dispatcher classes for all invocation -->
<!-- service declarations -->
<target name="genservice">
<!-- make sure the service class files are all compiled -->
<javac srcdir="src/java" destdir="${classes.dir}"
debug="on" optimize="${build.optimize}" deprecation="on">
<classpath refid="classpath"/>
<include name="**/*Service.java"/>
</javac>
<apply executable="${nartool.path}/genservice"
failonerror="true" parallel="true">
<arg value="--classpath"/>
<arg value="${classes.dir}:${java.libraries}/narya-distrib.jar:${java.libraries}/narya-base.jar"/>
<arg value="--sourcedir"/>
<arg value="src/java"/>
<fileset dir="src/java" includes="**/*Service.java"/>
</apply>
</target>
<!-- prepares the application directories -->
<target name="prepare">
<target name="prepare" depends="gendobj">
<mkdir dir="${deploy.dir}"/>
<mkdir dir="${deploy.dir}/classes"/>
<mkdir dir="${classes.dir}"/>
<mkdir dir="${javadoc.dir}"/>
<!-- copy media and properties into the target directory -->
<copy todir="${deploy.dir}/classes">
<copy todir="${classes.dir}">
<fileset dir="${src.dir}" includes="**/*.jpg"/>
<fileset dir="${src.dir}" includes="**/*.png"/>
<fileset dir="${src.dir}" includes="**/*.properties"/>
@@ -55,7 +85,7 @@
<!-- build the java class files -->
<target name="compile" depends="prepare">
<javac srcdir="${src.dir}" destdir="${deploy.dir}/classes"
<javac srcdir="${src.dir}" destdir="${classes.dir}"
debug="on" optimize="off" deprecation="off">
<classpath refid="classpath"/>
</javac>
@@ -68,7 +98,6 @@
windowtitle="${app.name} API"
doctitle="${app.name} API"
overview="${src.dir}/${doc.overview}"
bottom="Copyright &#169; 2002 ${copyright.holder}. All Rights Reserved."
destdir="${javadoc.dir}">
<classpath refid="classpath"/>
<link href="http://www.waywardgeeks.org/code/samskivert/javadoc"/>
@@ -82,7 +111,7 @@
<!-- builds our distribution files (war and jar) -->
<target name="dist" depends="prepare,compile">
<jar destfile="${deploy.dir}/${dist.jar}"
basedir="${deploy.dir}/classes"/>
basedir="${classes.dir}"/>
</target>
<!-- installs the JWS app -->
@@ -7,6 +7,7 @@ import java.io.IOException;
import java.util.Properties;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import com.samskivert.io.PersistenceException;
@@ -18,7 +19,16 @@ import com.samskivert.util.ConfigUtil;
import com.samskivert.util.PropertiesUtil;
import com.samskivert.util.RunAnywhere;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.ClientObserver;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.ObjectAccessException;
import com.threerings.presents.dobj.Subscriber;
import com.threerings.presents.net.UsernamePasswordCreds;
import robodj.Log;
import robodj.client.DJService;
import robodj.data.DJObject;
import robodj.repository.Model;
import robodj.repository.Repository;
import robodj.util.ErrorUtil;
@@ -31,16 +41,19 @@ import robodj.util.ServerControl;
* collection and managing the playlist.
*/
public class Chooser
implements Client.Invoker, ClientObserver, Subscriber
{
public static Repository repository;
public static Model model;
public static ServerControl scontrol;
public static Client client;
public static DJObject djobj;
public static ChooserFrame frame;
public static void main (String[] args)
public void init ()
{
boolean error = false;
@@ -85,8 +98,11 @@ public class Chooser
"No music server host specified in configuration.");
}
// establish a connection with the music server
scontrol = new ServerControl(
host, RDJPrefs.getMusicDaemonPort());
client = new Client(new UsernamePasswordCreds(), this);
client.addClientObserver(this);
client.setServer(host, Client.DEFAULT_SERVER_PORT);
client.logon();
} catch (IOException ioe) {
String errmsg = "Unable to communicate with music " +
"server on host '" + RDJPrefs.getMusicDaemonHost() + "' ";
@@ -98,12 +114,110 @@ public class Chooser
break;
}
}
// create our primary user interface frame, center the frame in
// the screen and show it
frame = new ChooserFrame();
frame.setSize(660, frame.getToolkit().getScreenSize().height - 60);
SwingUtil.centerWindow(frame);
frame.setVisible(true);
// documentation inherited from interface
public void clientDidLogon (Client lclient)
{
// subscribe to our DJObject
DJService djsvc = (DJService)client.requireService(DJService.class);
djsvc.getDJOid(client, new DJService.ResultListener() {
public void requestProcessed (Object result) {
int djoid = ((Integer)result).intValue();
client.getDObjectManager().subscribeToObject(
djoid, Chooser.this);
}
public void requestFailed (String cause) {
ErrorUtil.reportError("Failed to fetch DJ oid: " + cause, null);
System.exit(-1);
}
});
}
// documentation inherited from interface
public void clientObjectDidChange (Client client)
{
}
// documentation inherited from interface
public void clientDidLogoff (Client client)
{
System.exit(-1);
}
// documentation inherited from interface
public void clientFailedToLogon (Client lclient, Exception cause)
{
if (ErrorUtil.reportError("Failed to connect to server", cause)) {
System.exit(-1);
} else {
Thread t = new Thread() {
public void run () {
RDJPrefsPanel.display(true);
client.setServer(RDJPrefs.getMusicDaemonHost(),
Client.DEFAULT_SERVER_PORT);
client.logon();
}
};
t.start();
}
}
// documentation inherited from interface
public void clientConnectionFailed (Client client, Exception cause)
{
ErrorUtil.reportError("Lost connection to server", cause);
System.exit(-1);
}
// documentation inherited from interface
public boolean clientWillLogoff (Client client)
{
return true;
}
// documentation inherited from interface
public void invokeLater (Runnable run)
{
SwingUtilities.invokeLater(run);
}
// documentation inherited from interface
public void objectAvailable (DObject object)
{
djobj = (DJObject)object;
displayInterface();
}
// documentation inherited from interface
public void requestFailed (int oid, ObjectAccessException cause)
{
ErrorUtil.reportError("Failed to fetch DJ object", cause);
System.exit(-1);
}
protected void displayInterface ()
{
if (frame == null) {
// create our primary user interface frame, center the frame
// in the screen and show it
frame = new ChooserFrame();
frame.setSize(660, frame.getToolkit().getScreenSize().height - 60);
SwingUtil.centerWindow(frame);
frame.setVisible(true);
} else {
frame.toFront();
}
}
public static void exit ()
{
client.logoff(false);
}
public static void main (String[] args)
{
Chooser chooser = new Chooser();
chooser.init();
}
}
@@ -4,11 +4,8 @@
package robodj.chooser;
import java.awt.event.ActionEvent;
import javax.swing.JOptionPane;
import com.samskivert.swing.Controller;
import com.samskivert.swing.util.TaskMaster;
import com.samskivert.swing.util.TaskObserver;
import robodj.Log;
@@ -16,12 +13,9 @@ import robodj.Log;
* Handles top-level chooser UI commands.
*/
public class ChooserController extends Controller
implements TaskObserver
{
public ChooserController ()
{
// read our playing state
TaskMaster.invokeMethodTask("refreshPlaying", Chooser.scontrol, this);
}
// documentation inherited
@@ -29,48 +23,28 @@ public class ChooserController extends Controller
{
String cmd = event.getActionCommand();
if (cmd.equals("exit")) {
System.exit(0);
Chooser.exit();
} else if (cmd.equals("skip")) {
TaskMaster.invokeMethodTask("skip", Chooser.scontrol, this);
Chooser.djobj.skip();
} else if (cmd.equals("back")) {
TaskMaster.invokeMethodTask("back", Chooser.scontrol, this);
Chooser.djobj.back();
} else if (cmd.equals("pause")) {
TaskMaster.invokeMethodTask("pause", Chooser.scontrol, this);
Chooser.djobj.pause();
} else if (cmd.equals("play")) {
TaskMaster.invokeMethodTask("play", Chooser.scontrol, this);
Chooser.djobj.play();
} else if (cmd.equals("stop")) {
TaskMaster.invokeMethodTask("stop", Chooser.scontrol, this);
Chooser.djobj.stop();
} else {
System.out.println("Unknown action event: " + cmd);
Log.warning("Unknown action event: " + cmd);
return false;
}
return true;
}
// documentation inherited from interface
public void taskCompleted (String name, Object result)
{
// nothing to do here
}
// documentation inherited from interface
public void taskFailed (String name, Throwable exception)
{
String msg;
if (Exception.class.equals(exception.getClass())) {
msg = exception.getMessage();
} else {
msg = exception.toString();
}
JOptionPane.showMessageDialog(Chooser.frame, msg, "Error",
JOptionPane.ERROR_MESSAGE);
Log.logStackTrace(exception);
}
}
@@ -6,6 +6,8 @@ package robodj.chooser;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.*;
@@ -19,17 +21,21 @@ import robodj.repository.*;
import robodj.util.ButtonUtil;
import robodj.util.FancyPanel;
import robodj.util.RDJPrefs;
import robodj.util.ServerControl.PlayingListener;
public class ChooserFrame extends JFrame
implements PlayingListener, ControllerProvider
implements ControllerProvider
{
public ChooserFrame ()
{
super("RoboDJ Chooser " + Version.RELEASE_VERSION);
// quit if we're closed
setDefaultCloseOperation(EXIT_ON_CLOSE);
// quit gracefully if we're closed
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
public void windowClosing (WindowEvent e) {
Chooser.exit();
}
});
// we create a top-level panel to manage everything
GroupLayout gl = new VGroupLayout(GroupLayout.STRETCH);
@@ -109,8 +115,8 @@ public class ChooserFrame extends JFrame
}
});
// add ourselves as a playing listener
Chooser.scontrol.addPlayingListener(this);
// TODO: add ourselves as a playing listener
// Chooser.djobj.addListener(this);
// create our controller
_controller = new ChooserController();
@@ -152,6 +158,7 @@ public class ChooserFrame extends JFrame
return user;
}
// TODO: convert to dobj biz
public void playingUpdated (int songid, boolean paused)
{
if (songid == -1 || paused) {
@@ -16,8 +16,10 @@ import javax.swing.event.AncestorListener;
import com.samskivert.io.PersistenceException;
import com.samskivert.swing.util.SwingUtil;
import com.samskivert.swing.util.TaskMaster;
import com.samskivert.util.ArrayUtil;
import robodj.Log;
import robodj.data.PlaylistEntry;
import robodj.repository.Entry;
import robodj.repository.Song;
@@ -71,11 +73,7 @@ public abstract class EntryController extends ItemController
_list.populateEntries(_entries);
} else if (name.equals("readAndPlay")) {
for (int i = 0; i < _entry.songs.length; i++) {
Chooser.scontrol.append(_entry.songs[i].entryid,
_entry.songs[i].songid,
_entry.songs[i].location);
}
appendToPlaylist(_entry.songs);
} else if (name.equals("readSongs")) {
_list.populateSong(_entry);
@@ -110,7 +108,7 @@ public abstract class EntryController extends ItemController
} else if (cmd.equals(SongItem.PLAY)) {
Song song = SongItem.getSong(e.getSource());
Chooser.scontrol.append(song.entryid, song.songid, song.location);
appendToPlaylist(new Song[] { song });
} else if (cmd.equals("refresh")) {
// re-read the category
@@ -164,6 +162,20 @@ public abstract class EntryController extends ItemController
// nothing doing
}
protected void appendToPlaylist (Song[] songs)
{
PlaylistEntry[] plist = Chooser.djobj.playlist;
if (plist == null) {
plist = new PlaylistEntry[0];
}
for (int i = 0; i < songs.length; i++) {
PlaylistEntry entry = new PlaylistEntry(
songs[i].entryid, songs[i].songid, songs[i].title);
plist = (PlaylistEntry[])ArrayUtil.append(plist, entry);
}
Chooser.djobj.setPlaylist(plist);
}
protected EntryList _list;
protected Entry[] _entries;
protected Entry _entry;
@@ -13,32 +13,42 @@ import javax.swing.JOptionPane;
import javax.swing.JPanel;
import com.samskivert.io.PersistenceException;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.StringUtil;
import com.samskivert.swing.util.SwingUtil;
import com.samskivert.swing.util.TaskAdapter;
import com.samskivert.swing.util.TaskMaster;
import com.threerings.presents.dobj.AttributeChangeListener;
import com.threerings.presents.dobj.AttributeChangedEvent;
import robodj.Log;
import robodj.chooser.PlaylistPanel.PlaylistEntry;
import robodj.data.DJObject;
import robodj.data.PlaylistEntry;
import robodj.repository.Entry;
import robodj.repository.Song;
import robodj.util.ServerControl.PlayingListener;
/**
* Handles playlist UI commands.
*/
public class PlaylistController extends ItemController
implements PlayingListener
implements AttributeChangeListener
{
public PlaylistController (PlaylistPanel panel)
{
super(panel);
_panel = panel;
// add ourselves as a playing listener
Chooser.scontrol.addPlayingListener(this);
// listen to the DJ object
Chooser.djobj.addListener(this);
}
public void wasAdded ()
{
super.wasAdded();
// start up the task that reads the CD info from the database
TaskMaster.invokeMethodTask("readPlaylist", this, this);
}
// documentation inherited
@@ -46,121 +56,47 @@ public class PlaylistController extends ItemController
{
String cmd = e.getActionCommand();
if (cmd.equals(PlaylistPanel.CLEAR)) {
TaskMaster.invokeMethodTask("clear", Chooser.scontrol, this);
} else if (cmd.equals(PlaylistPanel.REFRESH)) {
_panel.prepareForRefresh();
// start up the task that reads the CD info from the database
TaskMaster.invokeMethodTask("readPlaylist", this, this);
Chooser.djobj.clear();
} else if (cmd.equals(SongItem.SKIP_TO)) {
JButton button = (JButton)e.getSource();
SongItem item = (SongItem)button.getParent();
// fire up a task to talk to the music daemon
final int songid = item.getSong().songid;
TaskMaster.invokeTask("noop", new TaskAdapter() {
public Object invoke () throws Exception {
Chooser.scontrol.skipto(songid);
return null;
}
}, this);
Chooser.djobj.play(((Integer)item.extra).intValue());
} else if (cmd.equals(SongItem.REMOVE)) {
JButton button = (JButton)e.getSource();
SongItem item = (SongItem)button.getParent();
// remove the entry UI elements
item.getParent().remove(item);
SwingUtil.refresh(_panel); // relayout
// remove the entry from the playlist
_panel.plist.remove((PlaylistEntry)item.extra);
// fire up a task to talk to the music daemon
final int songid = item.getSong().songid;
TaskMaster.invokeTask("noop", new TaskAdapter() {
public Object invoke () throws Exception {
Chooser.scontrol.remove(songid);
return null;
}
}, this);
int sidx = ((Integer)item.extra).intValue();
Chooser.djobj.remove(sidx, 1);
} else if (cmd.equals(PlaylistPanel.REMOVE_ALL)) {
JButton src = (JButton)e.getSource();
PlaylistEntry entry =
(PlaylistEntry)src.getClientProperty("entry");
PlaylistPanel.PLE entry =
(PlaylistPanel.PLE)src.getClientProperty("entry");
// remove all entries starting with this one until we get to
// one that has a different entryid
int count = 0;
Iterator iter = _panel.plist.iterator();
while (iter.hasNext()) {
PlaylistEntry pe = (PlaylistEntry)iter.next();
int start = -1, length = 1;
for (int ii = 0; ii < _panel.plist.size(); ii++) {
PlaylistPanel.PLE pe = (PlaylistPanel.PLE)_panel.plist.get(ii);
if (entry == pe) {
count++;
// remove the entry UI elements
pe.item.getParent().remove(pe.item);
// remove the entry
iter.remove();
} else if (count > 0) {
start = ii;
} else if (start != -1) {
if (pe.entry.entryid == entry.entry.entryid) {
count++;
// remove the entry UI elements
pe.item.getParent().remove(pe.item);
// remove the entry
iter.remove();
length++;
} else {
// we hit an entry that doesn't match, bail
break;
}
}
}
// remove the title and stuff
JPanel tpanel = (JPanel)src.getParent();
tpanel.getParent().remove(tpanel);
_panel.revalidate(); // relayout
// fire up a task to talk to the music daemon, telling it to
// remove those entries
final int songid = entry.song.songid;
final int fcount = count;
TaskMaster.invokeTask("noop", new TaskAdapter() {
public Object invoke () throws Exception {
Chooser.scontrol.removeGroup(songid, fcount);
return null;
}
}, this);
if (start != -1) {
Chooser.djobj.remove(start, length);
}
} else if (cmd.equals(PlaylistPanel.SHUFFLE)) {
// put all of the songs in the playlist into a list and
// shuffle them
ArrayList slist = new ArrayList();
for (Iterator iter = _panel.plist.iterator(); iter.hasNext(); ) {
slist.add(((PlaylistEntry)iter.next()).song);
}
final Song[] songs = (Song[])slist.toArray(new Song[slist.size()]);
ArrayUtil.shuffle(songs);
// now clear and reload the playlist entirely
TaskMaster.invokeTask("noop", new TaskAdapter() {
public Object invoke () throws Exception {
Chooser.scontrol.clear();
for (int ii = 0; ii < songs.length; ii++) {
Chooser.scontrol.append(
songs[ii].entryid, songs[ii].songid,
songs[ii].location);
}
// finally queue up a UI refresh
postAction(_panel, PlaylistPanel.REFRESH);
return null;
}
}, this);
// shuffle!
Chooser.djobj.shuffle();
} else {
return super.handleAction(e);
@@ -169,86 +105,70 @@ public class PlaylistController extends ItemController
return true;
}
// documentation inherited from interface
public void attributeChanged (AttributeChangedEvent event)
{
if (event.getName().equals(DJObject.PLAYLIST)) {
TaskMaster.invokeMethodTask("readPlaylist", this, this);
} else if (event.getName().equals(DJObject.PLAYING)) {
playingUpdated();
}
}
public void readPlaylist ()
throws PersistenceException
{
// clear out any previous playlist
_panel.plist.clear();
// get the playlist from the music daemon
String[] plist = Chooser.scontrol.getPlaylist();
// parse it into playlist entries
for (int i = 0; i < plist.length; i++) {
String[] toks = StringUtil.split(plist[i], "\t");
int eid, sid;
try {
eid = Integer.parseInt(toks[0]);
sid = Integer.parseInt(toks[1]);
} catch (NumberFormatException nfe) {
Log.warning("Bogus playlist record: '" + plist[i] + "'.");
continue;
}
Entry entry = Chooser.model.getEntry(eid);
int pcount = (Chooser.djobj.playlist == null) ? 0 :
Chooser.djobj.playlist.length;
for (int ii = 0; ii < pcount; ii++) {
PlaylistEntry pentry = Chooser.djobj.playlist[ii];
Entry entry = Chooser.model.getEntry(pentry.entryId);
if (entry != null) {
Song song = entry.getSong(sid);
Song song = entry.getSong(pentry.songId);
if (song != null) {
_panel.plist.add(new PlaylistEntry(entry, song));
_panel.plist.add(new PlaylistPanel.PLE(entry, song));
} else {
Log.warning("No song for entry? [sid=" + sid +
", entry=" + entry + "].");
Log.warning("No song for entry? [pentry=" + pentry + "].");
}
} else {
Log.warning("Unable to load entry [eid=" + eid + "].");
Log.warning("Unable to load entry? [pentry=" + pentry + "].");
}
}
// now find out what's currently playing
Chooser.scontrol.refreshPlaying();
}
public void playingUpdated (int songid, boolean paused)
public void playingUpdated ()
{
// unhighlight whoever is playing now
PlaylistEntry pentry = getPlayingEntry();
if (pentry != null && pentry.item != null) {
pentry.item.setIsPlaying(false);
if (_playing != null) {
_playing.item.setIsPlaying(false);
_playing = null;
}
// grab the new playing song id
_playid = songid;
int pidx = Chooser.djobj.playing;
if (pidx >= 0 && _panel.plist.size() > pidx) {
_playing = (PlaylistPanel.PLE)_panel.plist.get(pidx);
}
// highlight the playing song
for (int i = 0; i < _panel.plist.size(); i++) {
PlaylistEntry entry = (PlaylistEntry)_panel.plist.get(i);
if (entry.song.songid == _playid && entry.item != null) {
entry.item.setIsPlaying(true);
}
if (_playing != null) {
_playing.item.setIsPlaying(true);
}
_panel.repaint();
}
protected PlaylistEntry getPlayingEntry ()
{
for (int i = 0; i < _panel.plist.size(); i++) {
PlaylistEntry entry = (PlaylistEntry)_panel.plist.get(i);
if (entry.song.songid == _playid) {
return entry;
}
}
return null;
}
public void taskCompleted (String name, Object result)
{
if (name.equals("readPlaylist")) {
_panel.populatePlaylist(_panel.plist, _playid);
_panel.populatePlaylist(_panel.plist);
playingUpdated();
} else {
super.taskCompleted(name, result);
}
}
protected PlaylistPanel _panel;
protected int _playid;
protected PlaylistPanel.PLE _playing;
}
@@ -22,7 +22,7 @@ import robodj.util.ButtonUtil;
public class PlaylistPanel extends ControlledPanel
{
public static class PlaylistEntry
public static class PLE
{
public Entry entry;
@@ -30,7 +30,7 @@ public class PlaylistPanel extends ControlledPanel
public SongItem item;
public PlaylistEntry (Entry entry, Song song)
public PLE (Entry entry, Song song)
{
this.entry = entry;
this.song = song;
@@ -42,8 +42,6 @@ public class PlaylistPanel extends ControlledPanel
}
}
public static final String REFRESH = "refresh";
public static final String CLEAR = "clear";
public static final String REMOVE_ALL = "remove_all";
@@ -80,8 +78,6 @@ public class PlaylistPanel extends ControlledPanel
_clearbut = ButtonUtil.createControlButton(
CLEAR_TIP, CLEAR, CLEAR_ICON_PATH);
cbar.add(_clearbut);
cbar.add(ButtonUtil.createControlButton(
REFRESH_TIP, REFRESH, REFRESH_ICON_PATH));
cbar.add(ButtonUtil.createControlButton(
SHUFFLE_TIP, SHUFFLE, SHUFFLE_ICON_PATH));
add(cbar, GroupLayout.FIXED);
@@ -97,8 +93,6 @@ public class PlaylistPanel extends ControlledPanel
public void addNotify ()
{
super.addNotify();
// load up the playlist
_controller.postAction(this, REFRESH);
}
// documentation inherited
@@ -118,7 +112,7 @@ public class PlaylistPanel extends ControlledPanel
_bpanel.revalidate();
}
protected void populatePlaylist (ArrayList plist, int playid)
protected void populatePlaylist (ArrayList plist)
{
// clear out any existing children
_bpanel.removeAll();
@@ -128,10 +122,12 @@ public class PlaylistPanel extends ControlledPanel
gl.setOffAxisPolicy(GroupLayout.EQUALIZE);
gl.setOffAxisJustification(GroupLayout.LEFT);
int current = Chooser.djobj.playing;
// add buttons for every entry
String title = null;
for (int i = 0; i < plist.size(); i++) {
PlaylistEntry entry = (PlaylistEntry)plist.get(i);
for (int ii = 0; ii < plist.size(); ii++) {
PLE entry = (PLE)plist.get(ii);
JButton button;
// add record/artist indicators when the record and artist
@@ -155,13 +151,13 @@ public class PlaylistPanel extends ControlledPanel
// create a song item to display this playlist entry
entry.item = new SongItem(entry.song, SongItem.PLAYLIST);
entry.item.setIsPlaying(entry.song.songid == playid);
entry.item.extra = entry;
entry.item.setIsPlaying(current == ii);
entry.item.extra = new Integer(ii);
_bpanel.add(entry.item);
// let the bpanel know that we want to scroll the active track
// label into place once we're all laid out
if (entry.song.songid == playid) {
if (current == ii) {
_bpanel.setScrollTarget(entry.item);
}
}
@@ -229,7 +225,6 @@ public class PlaylistPanel extends ControlledPanel
// icon paths
protected static final String ICON_ROOT = "/robodj/chooser/images/";
protected static final String REFRESH_ICON_PATH = ICON_ROOT + "refresh.png";
protected static final String CLEAR_ICON_PATH = ICON_ROOT + "clear.png";
protected static final String SHUFFLE_ICON_PATH = ICON_ROOT + "shuffle.png";
protected static final String SKIPTO_ICON_PATH = ICON_ROOT + "skip.png";
@@ -239,7 +234,6 @@ public class PlaylistPanel extends ControlledPanel
ICON_ROOT + "remove_song.png";
// button tips
protected static final String REFRESH_TIP = "Refresh the playlist";
protected static final String CLEAR_TIP =
"Clear all songs from the playlist";
protected static final String SHUFFLE_TIP =
@@ -0,0 +1,16 @@
//
// $Id$
package robodj.client;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService;
/**
* Defines the main RoboDJ services.
*/
public interface DJService extends InvocationService
{
/** Used to obtain the oid of the DJ object. */
public void getDJOid (Client client, ResultListener listener);
}
@@ -0,0 +1,35 @@
//
// $Id$
package robodj.data;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService.ResultListener;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.dobj.InvocationResponseEvent;
import robodj.client.DJService;
/**
* Provides the implementation of the {@link DJService} interface
* that marshalls the arguments and delivers the request to the provider
* on the server. Also provides an implementation of the response listener
* interfaces that marshall the response arguments and deliver them back
* to the requesting client.
*/
public class DJMarshaller extends InvocationMarshaller
implements DJService
{
/** The method id used to dispatch {@link #getDJOid} requests. */
public static final int GET_DJOID = 1;
// documentation inherited from interface
public void getDJOid (Client arg1, ResultListener arg2)
{
ResultMarshaller listener2 = new ResultMarshaller();
listener2.listener = arg2;
sendRequest(arg1, GET_DJOID, new Object[] {
listener2
});
}
}
@@ -0,0 +1,128 @@
//
// $Id$
package robodj.data;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.ListUtil;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DSet;
import robodj.Log;
/**
* Contains the shared state of RoboDJ.
*/
public class DJObject extends DObject
{
/** The current playlist. */
public PlaylistEntry[] playlist;
/** The index of the currently playing track. */
public int playing = -1;
/**
* Returns the currently playing track.
*/
public PlaylistEntry getPlaying ()
{
return (playing < 0) ? null : getEntry(playing);
}
/**
* Returns the playlist entry for the next song in the playlist.
*/
public PlaylistEntry getNext ()
{
return getEntry(playing + 1);
}
/**
* Returns the playlist entry with the current index or null if the
* index is not valid.
*/
public PlaylistEntry getEntry (int index)
{
return havePlaylist() ? playlist[index % playlist.length] : null;
}
/**
* Plays the specified track in the current playlist.
*/
public void play (int index)
{
if (havePlaylist()) {
setPlaying(index % playlist.length);
}
}
public void play ()
{
if (havePlaylist()) {
// TODO: handle unpausing
if (playing == -1) {
setPlaying(0);
}
}
}
public void skip ()
{
if (havePlaylist()) {
play(playing + 1);
}
}
public void back ()
{
if (havePlaylist()) {
play(playing + playlist.length - 1);
}
}
public void stop ()
{
setPlaying(-1);
}
public void pause ()
{
// TODO
}
public void remove (int start, int length)
{
if (havePlaylist()) {
setPlaylist((PlaylistEntry[])ArrayUtil.splice(
playlist, start, length));
if (playing >= (start+length)) {
setPlaying(playing-length);
} else if (playing > start) {
setPlaying(start);
}
}
}
public void shuffle ()
{
if (havePlaylist()) {
PlaylistEntry entry = getPlaying();
ArrayUtil.shuffle(playlist);
setPlaylist(playlist);
setPlaying(ListUtil.indexOf(playlist, entry));
}
}
public void clear ()
{
setPlaying(-1);
setPlaylist(null);
}
/** Helper function. */
protected boolean havePlaylist ()
{
return (playlist != null && playlist.length > 0);
}
}
@@ -0,0 +1,176 @@
//
// $Id$
package robodj.data;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.ListUtil;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DSet;
import robodj.Log;
/**
* Contains the shared state of RoboDJ.
*/
public class DJObject extends DObject
{
/** The field name of the <code>playlist</code> field. */
public static final String PLAYLIST = "playlist";
/** The field name of the <code>playing</code> field. */
public static final String PLAYING = "playing";
/** The current playlist. */
public PlaylistEntry[] playlist;
/** The index of the currently playing track. */
public int playing = -1;
/**
* Returns the currently playing track.
*/
public PlaylistEntry getPlaying ()
{
return (playing < 0) ? null : getEntry(playing);
}
/**
* Returns the playlist entry for the next song in the playlist.
*/
public PlaylistEntry getNext ()
{
return getEntry(playing + 1);
}
/**
* Returns the playlist entry with the current index or null if the
* index is not valid.
*/
public PlaylistEntry getEntry (int index)
{
return havePlaylist() ? playlist[index % playlist.length] : null;
}
/**
* Plays the specified track in the current playlist.
*/
public void play (int index)
{
if (havePlaylist()) {
setPlaying(index % playlist.length);
}
}
public void play ()
{
if (havePlaylist()) {
// TODO: handle unpausing
if (playing == -1) {
setPlaying(0);
}
}
}
public void skip ()
{
if (havePlaylist()) {
play(playing + 1);
}
}
public void back ()
{
if (havePlaylist()) {
play(playing + playlist.length - 1);
}
}
public void stop ()
{
setPlaying(-1);
}
public void pause ()
{
// TODO
}
public void remove (int start, int length)
{
if (havePlaylist()) {
setPlaylist((PlaylistEntry[])ArrayUtil.splice(
playlist, start, length));
if (playing >= (start+length)) {
setPlaying(playing-length);
} else if (playing > start) {
setPlaying(start);
}
}
}
public void shuffle ()
{
if (havePlaylist()) {
PlaylistEntry entry = getPlaying();
ArrayUtil.shuffle(playlist);
setPlaylist(playlist);
setPlaying(ListUtil.indexOf(playlist, entry));
}
}
public void clear ()
{
setPlaying(-1);
setPlaylist(null);
}
/** Helper function. */
protected boolean havePlaylist ()
{
return (playlist != null && playlist.length > 0);
}
/**
* Requests that the <code>playlist</code> field be set to the specified
* value. The local value will be updated immediately and an event
* will be propagated through the system to notify all listeners that
* the attribute did change. Proxied copies of this object (on
* clients) will apply the value change when they received the
* attribute changed notification.
*/
public void setPlaylist (PlaylistEntry[] playlist)
{
requestAttributeChange(PLAYLIST, playlist);
this.playlist = playlist;
}
/**
* Requests that the <code>index</code>th element of
* <code>playlist</code> field be set to the specified value. The local
* value will be updated immediately and an event will be propagated
* through the system to notify all listeners that the attribute did
* change. Proxied copies of this object (on clients) will apply the
* value change when they received the attribute changed notification.
*/
public void setPlaylistAt (PlaylistEntry value, int index)
{
requestElementUpdate(PLAYLIST, value, index);
this.playlist[index] = value;
}
/**
* Requests that the <code>playing</code> field be set to the specified
* value. The local value will be updated immediately and an event
* will be propagated through the system to notify all listeners that
* the attribute did change. Proxied copies of this object (on
* clients) will apply the value change when they received the
* attribute changed notification.
*/
public void setPlaying (int playing)
{
requestAttributeChange(PLAYING, new Integer(playing));
this.playing = playing;
}
}
@@ -0,0 +1,71 @@
//
// $Id$
package robodj.data;
import com.threerings.io.SimpleStreamableObject;
import com.threerings.presents.dobj.DSet;
import robodj.repository.Entry;
import robodj.repository.Song;
/**
* Defines an entry currently in the playlist.
*/
public class PlaylistEntry extends SimpleStreamableObject
implements DSet.Entry
{
/** The album with which we are associated. */
public int entryId;
/** The id of the song represented by this entry. */
public int songId;
/** The title of this entry (duplicated from the Song to simplify
* playlist display. */
public String title;
/** Zero argument constructor for streaming. */
public PlaylistEntry ()
{
}
/** Creates a playlist entry from the specified entry and song. */
public PlaylistEntry (Entry entry, Song song)
{
this(entry.entryid, song.songid, song.title);
}
/** Creates a playlist entry from the specified information. */
public PlaylistEntry (int entryId, int songId, String title)
{
this.entryId = entryId;
this.songId = songId;
this.title = title;
_key = entryId + ":" + songId;
}
// documentation inherited
public Comparable getKey ()
{
return _key;
}
// documentation inherited
public boolean equals (Object other)
{
if (other instanceof PlaylistEntry) {
return _key.equals(((PlaylistEntry)other)._key);
} else {
return false;
}
}
// documentation inherited
public int hashCode ()
{
return _key.hashCode();
}
protected String _key;
}
@@ -0,0 +1,49 @@
//
// $Id$
package robodj.importer;
import java.util.ArrayList;
import java.util.Iterator;
import java.awt.BorderLayout;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import robodj.repository.Entry;
import robodj.repository.Song;
/**
* Displays an Entry and a list of similar entries and a check box.
*/
public class EntryUploader extends JPanel
{
public EntryUploader (Entry entry, ArrayList similar)
{
super(new BorderLayout(5, 5));
add(new JLabel(entry.artist + " - " + entry.title),
BorderLayout.CENTER);
add(_checked = new JCheckBox(), BorderLayout.WEST);
StringBuffer extra = new StringBuffer("<html>");
for (int ii = 0; ii < entry.songs.length; ii++) {
Song song = entry.songs[ii];
extra.append(song.position).append(". ");
extra.append(song.title).append("<br>");
}
if (similar.size() > 0) {
extra.append("<p><u>Similar entries:</u><br>");
for (Iterator iter = similar.iterator(); iter.hasNext(); ) {
Entry siment = (Entry)iter.next();
extra.append(siment.artist).append(" - ").append(siment.title);
extra.append("<br>");
}
}
extra.append("</html>");
JLabel exlab = new JLabel(extra.toString());
exlab.setFont(getFont().deriveFont(11));
add(exlab, BorderLayout.SOUTH);
}
protected JCheckBox _checked;
}
@@ -16,7 +16,7 @@ public class FinishedPanel
extends ImporterPanel
implements ActionListener
{
public FinishedPanel (Entry entry)
public FinishedPanel ()
{
GroupLayout gl = new HGroupLayout(GroupLayout.STRETCH);
gl.setOffAxisPolicy(GroupLayout.STRETCH);
@@ -35,7 +35,7 @@ public class FinishedPanel
// create our action buttons
String[] labels = new String[]
{ "Import another CD", "Edit/categorize CDs" };
{ "Import more music", "Edit/categorize CDs" };
String[] actions = new String[] { "import", "edit" };
for (int i = 0; i < labels.length; i++) {
@@ -47,9 +47,6 @@ public class FinishedPanel
// add our panel to the main group
add(ppanel);
// save this guy for later
_entry = entry;
}
public void wasAddedToFrame (ImporterFrame frame, boolean popped)
@@ -68,7 +65,7 @@ public class FinishedPanel
System.exit(0);
} else if (cmd.equals("import")) {
_frame.setPanel(new InsertCDPanel());
_frame.setPanel(new StartPanel());
} else {
System.out.println("Unknown action event: " + cmd);
@@ -76,5 +73,4 @@ public class FinishedPanel
}
protected ImporterFrame _frame;
protected Entry _entry;
}
@@ -0,0 +1,329 @@
//
// $Id: ImportMusicPanel.java,v 1.7 2003/05/04 18:16:07 mdb Exp $
package robodj.importer;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import com.samskivert.net.cddb.CDDB;
import com.samskivert.swing.*;
import com.samskivert.swing.util.*;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.StringUtil;
import org.farng.mp3.*;
import org.farng.mp3.id3.*;
import robodj.convert.*;
import robodj.repository.*;
public class ImportMusicPanel extends ImporterPanel
implements ActionListener, TaskObserver
{
public ImportMusicPanel ()
{
GroupLayout gl = new HGroupLayout(GroupLayout.STRETCH);
gl.setOffAxisPolicy(GroupLayout.STRETCH);
setLayout(gl);
// create our label for the left hand side
JLabel cdlabel = new JLabel("Import Music", JLabel.CENTER);
cdlabel.setFont(new Font("Helvetica", Font.BOLD, 24));
add(cdlabel, GroupLayout.FIXED);
// on the right hand side we want a status display to let the user
// know how the ID3 reading process is going
GroupLayout vgl = new VGroupLayout(GroupLayout.STRETCH);
vgl.setOffAxisPolicy(GroupLayout.STRETCH);
_spanel = new JPanel(vgl);
JLabel sl = new JLabel("Status", JLabel.LEFT);
_spanel.add(sl, GroupLayout.FIXED);
// set up our status display
_status.setText("Ready to import music...\n");
_spanel.add(new JScrollPane(_status));
// add our panel to the main group
add(_spanel);
}
public void wasAddedToFrame (ImporterFrame frame, boolean popped)
{
// keep this for later
_frame = frame;
frame.addControlButton("Cancel", "cancel", this);
frame.addControlButton("Back", "back", this);
_next = frame.addControlButton("Next...", "next", this);
_next.setEnabled(popped);
if (popped) {
// clear out our song arrays
for (Iterator iter = _byid.values().iterator(); iter.hasNext(); ) {
((Entry)iter.next()).songs = null;
}
} else {
// create a file chooser dialog
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
chooser.setMultiSelectionEnabled(true);
chooser.setDialogTitle("Select files and directories to import...");
int rv = chooser.showDialog(this, "Import music");
if (rv == JFileChooser.APPROVE_OPTION) {
// start scanning those directories for MP3 files
final File[] files = chooser.getSelectedFiles();
TaskMaster.invokeTask("scan", new TaskAdapter() {
public Object invoke () throws Exception {
return scanForFiles(files);
}
}, this);
} else {
// pop back to the last panel
_frame.popPanel();
}
}
}
public void taskCompleted (String name, Object result)
{
if (name.equals("scan")) {
final File[] files = (File[])result;
// now fire up a task to read the ID3 info from these files
TaskMaster.invokeTask("readid3", new TaskAdapter() {
public Object invoke () throws Exception {
readID3(files);
return null;
}
}, this);
} else if (name.equals("readid3")) {
// replace the info text with the track info table
remove(_spanel);
JPanel bits = new JPanel(new BorderLayout(5, 5));
bits.add(new JLabel("Edit metadata"), BorderLayout.NORTH);
JTable table = new JTable(
new SongTableModel(_byid, _byname, _songs));
bits.add(new JScrollPane(table), BorderLayout.CENTER);
bits.add(new JLabel("You will select which albums to upload " +
"on the next page..."), BorderLayout.SOUTH);
add(bits);
SwingUtil.sizeToContents(table);
// enable the next button
_next.setEnabled(true);
// relay everything out
validate();
}
}
public void taskFailed (String name, Throwable exception)
{
if (name.equals("scan")) {
_status.append("\nUnable to scan for MP3 files:\n\n" +
exception.getMessage());
} else if (name.equals("readid3")) {
_status.append("\nFailed while reading ID3 tags:\n\n" +
exception.getMessage());
}
}
public void actionPerformed (ActionEvent e)
{
String cmd = e.getActionCommand();
if (cmd.equals("cancel")) {
System.exit(0);
} else if (cmd.equals("back")) {
// pop back to the last panel
_frame.popPanel();
} else if (cmd.equals("next")) {
entrifyAndContinue();
} else {
System.out.println("Unknown action event: " + cmd);
}
}
/**
* Scans the supplied set of files and directories for all files that
* appear to be MP3s.
*
* @return an array of all matched files.
*/
protected File[] scanForFiles (File[] seeds)
{
ArrayList matches = new ArrayList();
scanForFiles(seeds, matches);
return (File[])matches.toArray(new File[matches.size()]);
}
protected void scanForFiles (File[] seeds, ArrayList matches)
{
for (int ii = 0; ii < seeds.length; ii++) {
if (seeds[ii].isDirectory()) {
postAsyncStatus("Scanning " + seeds[ii] + "...");
scanForFiles(seeds[ii].listFiles(), matches);
} else if (seeds[ii].getName().toLowerCase().endsWith(".mp3")) {
matches.add(seeds[ii]);
}
}
}
protected void readID3 (File[] files)
{
for (int ii = 0; ii < files.length; ii++) {
try {
MP3File mpf = new MP3File(files[ii]);
String title = getID3Info(mpf, "TIT2", "title");
String artist = getID3Info(mpf, "TPE1", "artist");
String album = getID3Info(mpf, "TALB", "album");
String track = getID3Info(mpf, "TRCK", "");
// locate the Entry associated with this album/artist,
// creating a new one if necessary
String key = SongTableModel.getKey(album, artist);
Entry entry = (Entry)_byname.get(key);
if (entry == null) {
entry = new Entry();
entry.entryid = _byname.size(); // assign a temporary id
entry.artist = nullToUnknown(artist);
entry.title = nullToUnknown(album);
entry.source = "import";
_byname.put(key, entry);
_byid.put(entry.entryid, entry);
postAsyncStatus("Located album '" + album + "' by '" +
artist + "'.");
}
Song song = new Song();
song.entryid = entry.entryid;
song.title = StringUtil.blank(title) ?
stripSuffix(files[ii].getName()) : title;
song.location = files[ii].toString();
// try to figure out what the track number is
if (StringUtil.blank(track)) {
Matcher m = _trackNo.matcher(files[ii].getName());
if (m.matches()) {
track = m.group(1);
}
}
try {
song.position = Integer.parseInt(track);
} catch (Throwable t) {
song.position = 1;
}
// finally slap that song on the list
_songs.add(song);
postAsyncStatus("Located song '" + title + "' " +
"(" + song.position + ").");
} catch (Exception e) {
postAsyncStatus("Failure processing " + files[ii] + ": " + e);
e.printStackTrace(System.err);
}
}
}
protected String nullToUnknown (String value)
{
return StringUtil.blank(value) ? "Unknown" : value;
}
protected String stripSuffix (String value)
{
int didx = value.lastIndexOf(".");
return (didx == -1) ? value : value.substring(0, didx);
}
protected String getID3Info (MP3File file, String v2tag, String v1tag)
{
// first look for the v2 tag
AbstractID3v2 tag2 = file.getID3v2Tag();
String value = null;
if (tag2 != null) {
AbstractMP3Fragment frame = tag2.getFrame(v2tag);
if (frame != null) {
value = ((AbstractFrameBodyTextInformation)
frame.getBody()).getText();
}
if (!StringUtil.blank(value)) {
return value;
}
}
// if that fails, try the v1 tag
ID3v1 tag1 = file.getID3v1Tag();
if (tag1 != null) {
if (v1tag.equals("album")) {
return tag1.getAlbum();
} else if (v1tag.equals("artist")) {
return tag1.getArtist();
} else if (v1tag.equals("title")) {
return tag1.getTitle();
}
}
return null;
}
protected void entrifyAndContinue ()
{
// go through our songs and collect them into their Entry objects
ArrayList elist = new ArrayList();
for (Iterator iter = _songs.iterator(); iter.hasNext(); ) {
Song song = (Song)iter.next();
Entry entry = (Entry)_byid.get(song.entryid);
if (entry.songs == null) {
entry.songs = new Song[] { song };
} else {
entry.songs = (Song[])ArrayUtil.append(entry.songs, song);
}
if (!elist.contains(entry)) {
elist.add(entry);
}
}
// create the upload panel and pass the entries along
Entry[] entries = (Entry[])elist.toArray(new Entry[elist.size()]);
_frame.pushPanel(new UploadMusicPanel(entries));
}
protected ImporterFrame _frame;
protected JButton _next;
protected JPanel _spanel;
protected HashMap _byname = new HashMap();
protected HashIntMap _byid = new HashIntMap();
protected ArrayList _songs = new ArrayList();
protected static Pattern _trackNo;
static {
try {
_trackNo = Pattern.compile("\\s(\\d+)\\s");
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
}
@@ -67,11 +67,10 @@ public class Importer
// create our frame and first panel
ImporterFrame frame = new ImporterFrame();
InsertCDPanel panel = new InsertCDPanel();
frame.pushPanel(panel);
frame.pushPanel(new StartPanel());
// center the frame in the screen and show it
frame.setSize(640, 480);
frame.setSize(1024, 600);
SwingUtil.centerWindow(frame);
frame.setVisible(true);
}
@@ -3,18 +3,28 @@
package robodj.importer;
import java.awt.BorderLayout;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
public abstract class ImporterPanel extends JPanel
{
public ImporterPanel ()
{
this(new BorderLayout());
}
public ImporterPanel (LayoutManager layout)
{
super(layout);
_status = new JTextArea();
_status.setLineWrap(true);
_status.setEditable(false);
}
/**
@@ -26,4 +36,24 @@ public abstract class ImporterPanel extends JPanel
* than added to the frame for the first time.
*/
public abstract void wasAddedToFrame (ImporterFrame frame, boolean popped);
/**
* Displays the supplied string in the status field and scrolls the
* status field to the bottom.
*/
protected void postAsyncStatus (final String status)
{
SwingUtilities.invokeLater(new Runnable() {
public void run ()
{
_status.append(status + "\n");
Rectangle bot = _status.getBounds();
bot.y = bot.height-1;
bot.height = 1;
_status.scrollRectToVisible(bot);
}
});
}
protected JTextArea _status;
}
@@ -60,11 +60,8 @@ public class RipPanel
// create the status log
JLabel sl = new JLabel("Status", JLabel.LEFT);
ppanel.add(sl, GroupLayout.FIXED);
_statusLog = new JTextArea();
_statusLog.setLineWrap(true);
_statusLog.setEditable(false);
// make something for it to scroll around in
ppanel.add(new JScrollPane(_statusLog));
ppanel.add(new JScrollPane(_status));
// add our panel to the main group
add(ppanel);
@@ -74,16 +71,6 @@ public class RipPanel
_entry = entry;
}
protected void postAsyncStatus (final String status)
{
SwingUtilities.invokeLater(new Runnable() {
public void run ()
{
_statusLog.append(status + "\n");
}
});
}
protected void postAsyncProgress (final int overallComplete,
final int taskComplete)
{
@@ -390,7 +377,7 @@ public class RipPanel
} else {
msg = exception.toString();
}
_statusLog.append("Error: " + msg + "\n");
_status.append("Error: " + msg + "\n");
Log.logStackTrace(exception);
}
@@ -401,7 +388,7 @@ public class RipPanel
System.exit(0);
} else if (cmd.equals("next")) {
_frame.pushPanel(new FinishedPanel(_entry));
_frame.pushPanel(new FinishedPanel());
} else {
System.out.println("Unknown action event: " + cmd);
@@ -424,7 +411,6 @@ public class RipPanel
protected JProgressBar _oprogress;
protected JProgressBar _progress;
protected JTextArea _statusLog;
protected Ripper.TrackInfo[] _info;
protected Entry _entry;
@@ -0,0 +1,191 @@
//
// $Id$
package robodj.importer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import javax.swing.table.AbstractTableModel;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.QuickSort;
import robodj.repository.Entry;
import robodj.repository.Song;
/**
* Allows the display and editing of a big list of songs.
*/
public class SongTableModel extends AbstractTableModel
{
/**
* Creates a song table model with the supplied set of entries and
* songs.
*/
public SongTableModel (HashIntMap byid, HashMap byname, ArrayList songs)
{
_byid = byid;
_byname = byname;
_songs = songs;
QuickSort.sort(_songs, SONG_COMP);
}
// documentation inherited
public Class getColumnClass (int column)
{
return CLASSES[column];
}
// documentation inherited
public String getColumnName (int column)
{
return COLUMNS[column];
}
// documentation inherited
public int getRowCount()
{
return _songs.size();
}
// documentation inherited
public int getColumnCount()
{
return COLUMNS.length;
}
// documentation inherited
public Object getValueAt (int row, int column) {
Song song = (Song)_songs.get(row);
Entry entry = (Entry)_byid.get(song.entryid);
switch (column) {
case 0: return entry.artist;
case 1: return entry.title;
case 2: return song.title;
case 3: return new Integer(song.position);
default: return "<error:" + column + ">";
}
}
// documentation inherited
public boolean isCellEditable (int row, int column) {
return true;
}
// documentation inherited
public void setValueAt (Object value, int row, int column) {
Song song = (Song)_songs.get(row);
switch (column) {
case 0: remapSong(song, null, (String)value); break;
case 1: remapSong(song, (String)value, null); break;
case 2: song.title = (String)value; return; // avoid fire..Changed
case 3: song.position = ((Integer)value).intValue(); break;
default:
System.err.println("Refusing to update bogus column [row=" + row +
", column=" + column + ", val=" + value + "].");
break;
}
QuickSort.sort(_songs, SONG_COMP);
fireTableDataChanged();
}
/** Updates the album title or artist for the supplied song. */
protected void remapSong (Song song, String album, String artist)
{
Entry entry = (Entry)_byid.get(song.entryid);
// fill in any unknown data
if (album == null) {
album = entry.title;
}
if (artist == null) {
artist = entry.artist;
}
String key = getKey(album, artist);
System.err.println("Considering " + key);
// if neither album or artist are "Unknown", and the new name does
// not already exist as an entry, then just update this entry
if (!entry.title.equals("Unknown") && !entry.artist.equals("Unknown") &&
!_byname.containsKey(key)) {
_byname.remove(getKey(entry)); // remove the old key
entry.title = (album == null) ? entry.title : album;
entry.artist = (artist == null) ? entry.artist : artist;
_byname.put(key, entry); // map with the new key
return;
}
// look up (or create) a new entry
Entry nentry = (Entry)_byname.get(key);
if (nentry == null) {
nentry = clone(entry);
nentry.title = album;
nentry.artist = artist;
nentry.entryid = _byname.size();
_byname.put(getKey(nentry), nentry);
_byid.put(nentry.entryid, nentry);
}
song.entryid = nentry.entryid;
System.err.println("Remapped " + song);
}
/** Duplicates the supplied entry. */
protected Entry clone (Entry source)
{
Entry nentry = new Entry();
nentry.title = source.title;
nentry.artist = source.artist;
nentry.source = source.source;
return nentry;
}
/** Creates a key from the entry title and artist. */
protected static String getKey (Entry entry)
{
return getKey(entry.title, entry.artist);
}
/** Creates a key from the entry title and artist. */
protected static String getKey (String title, String artist)
{
return title + ":" + artist;
}
protected HashMap _byname = new HashMap();
protected HashIntMap _byid = new HashIntMap();
protected ArrayList _songs;
protected Comparator SONG_COMP = new Comparator() {
public int compare (Object a, Object b) {
Song sa = (Song)a, sb = (Song)b;
Entry ea = (Entry)_byid.get(sa.entryid);
Entry eb = (Entry)_byid.get(sb.entryid);
int comp;
if ((comp = ea.artist.compareTo(eb.artist)) != 0) {
return comp;
}
if ((comp = ea.title.compareTo(eb.title)) != 0) {
return comp;
}
if (sa.position != sb.position) {
return sa.position - sb.position;
}
return sa.title.compareTo(sb.title);
}
};
protected static final String[] COLUMNS = {
"Artist", "Album", "Title", "#"
};
protected static final Class[] CLASSES = {
String.class, String.class, String.class, Integer.class
};
}
@@ -1,19 +1,22 @@
//
// $Id: InsertCDPanel.java,v 1.3 2002/03/03 21:17:03 mdb Exp $
// $Id: StartPanel.java,v 1.3 2002/03/03 21:17:03 mdb Exp $
package robodj.importer;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.net.URL;
import javax.swing.*;
public class InsertCDPanel
extends ImporterPanel
import com.samskivert.swing.VGroupLayout;
public class StartPanel extends ImporterPanel
implements ActionListener
{
public InsertCDPanel ()
public StartPanel ()
{
super(new GridLayout(1, 2));
@@ -24,10 +27,21 @@ public class InsertCDPanel
JLabel cdlabel = new JLabel(cdicon);
add(cdlabel);
// create the "insert cd" text label for the right hand side
JLabel ilabel = new JLabel("Please insert the CD...",
JLabel.CENTER);
add(ilabel);
// allow the user to select whether they want to rip a CD or
// import music directly
VGroupLayout vgl = new VGroupLayout();
vgl.setOffAxisJustification(VGroupLayout.LEFT);
JPanel bpanel = new JPanel(vgl);
bpanel.add(_rip = new JRadioButton("Rip a CD (insert CD now)"));
_rip.setMnemonic(KeyEvent.VK_R);
_rip.setSelected(true);
bpanel.add(_imp = new JRadioButton("Import music"));
_imp.setMnemonic(KeyEvent.VK_M);
add(bpanel);
ButtonGroup group = new ButtonGroup();
group.add(_imp);
group.add(_rip);
// give ourselves a wee bit of a border
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
@@ -49,12 +63,17 @@ public class InsertCDPanel
System.exit(0);
} else if (cmd.equals("next")) {
_frame.pushPanel(new CDDBLookupPanel());
if (_rip.isSelected()) {
_frame.pushPanel(new CDDBLookupPanel());
} else {
_frame.pushPanel(new ImportMusicPanel());
}
} else {
System.out.println("Unknown action event: " + cmd);
}
}
protected JRadioButton _imp, _rip;
protected ImporterFrame _frame;
}
@@ -0,0 +1,191 @@
//
// $Id: UploadMusicPanel.java,v 1.7 2003/05/04 18:16:07 mdb Exp $
package robodj.importer;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import com.samskivert.net.cddb.CDDB;
import com.samskivert.swing.*;
import com.samskivert.swing.util.*;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.StringUtil;
import org.farng.mp3.*;
import org.farng.mp3.id3.*;
import robodj.convert.*;
import robodj.repository.*;
import com.samskivert.util.CollectionUtil;
public class UploadMusicPanel extends ImporterPanel
implements ActionListener, TaskObserver
{
public UploadMusicPanel (Entry[] entries)
{
GroupLayout gl = new HGroupLayout(GroupLayout.STRETCH);
gl.setOffAxisPolicy(GroupLayout.STRETCH);
setLayout(gl);
// save these for later
_entries = entries;
// create our label for the left hand side
JLabel cdlabel = new JLabel("Upload Music", JLabel.CENTER);
cdlabel.setFont(new Font("Helvetica", Font.BOLD, 24));
add(cdlabel, GroupLayout.FIXED);
// display status on looking up similar albums
GroupLayout vgl = new VGroupLayout(GroupLayout.STRETCH);
vgl.setOffAxisPolicy(GroupLayout.STRETCH);
_spanel = new JPanel(vgl);
JLabel sl = new JLabel("Status", JLabel.LEFT);
_spanel.add(sl, GroupLayout.FIXED);
_status.setText("Checking for similar music...\n");
_spanel.add(new JScrollPane(_status));
// add our panel to the main group
add(_spanel);
}
public void wasAddedToFrame (ImporterFrame frame, boolean popped)
{
// keep this for later
_frame = frame;
frame.addControlButton("Cancel", "cancel", this);
_back = frame.addControlButton("Back", "back", this);
_next = frame.addControlButton("Upload", "upload", this);
// fire up the similarity checker task
TaskMaster.invokeTask("checkrepo", new TaskAdapter() {
public Object invoke () throws Exception {
checkForSimilar();
return null;
}
}, this);
}
public void taskCompleted (String name, Object result)
{
if (name.equals("checkrepo")) {
// display the entries and their similarity matches
remove(_spanel);
JPanel panel = GroupLayout.makeVBox(
GroupLayout.NONE, GroupLayout.TOP, GroupLayout.EQUALIZE);
((GroupLayout)panel.getLayout()).setOffAxisJustification(
GroupLayout.LEFT);
panel.add(new JLabel("Select the albums to upload..."));
panel.add(new JSeparator());
for (int ii = 0; ii < _entries.length; ii++) {
panel.add(new EntryUploader(
_entries[ii], (ArrayList)
_similar.get(_entries[ii].entryid)));
}
add(new JScrollPane(panel));
// enable the next button
_next.setEnabled(true);
// relay everything out
validate();
} else if (name.equals("upload")) {
_next.setEnabled(true);
_next.setActionCommand("next");
_next.setText("Next...");
}
}
public void taskFailed (String name, Throwable exception)
{
if (name.equals("checkrepo")) {
_status.append("\nUnable to check repository for " +
"similar music:\n\n" + exception.getMessage());
} else if (name.equals("readid3")) {
_status.append("\nFailed while reading ID3 tags:\n\n" +
exception.getMessage());
}
}
public void actionPerformed (ActionEvent e)
{
String cmd = e.getActionCommand();
if (cmd.equals("cancel")) {
System.exit(0);
} else if (cmd.equals("back")) {
// pop back to the last panel
_frame.popPanel();
} else if (cmd.equals("upload")) {
_next.setEnabled(false);
_back.setEnabled(false);
TaskMaster.invokeTask("upload", new TaskAdapter() {
public Object invoke () throws Exception {
performUpload();
return null;
}
}, this);
} else if (cmd.equals("next")) {
_frame.pushPanel(new FinishedPanel());
} else {
System.out.println("Unknown action event: " + cmd);
}
}
protected void checkForSimilar ()
{
for (int ii = 0; ii < _entries.length; ii++) {
Entry entry = _entries[ii];
postAsyncStatus("Checking for music like '" + entry.title +
"' - '" +entry.artist + "'.");
ArrayList similar = new ArrayList();
try {
String eartist = StringUtil.replace(entry.artist, "'", "\\'");
Entry[] artist =
Importer.repository.getEntries(
"where soundex(artist) = soundex('" + eartist + "')");
CollectionUtil.addAll(similar, artist);
String etitle = StringUtil.replace(entry.title, "'", "\\'");
Entry[] title =
Importer.repository.getEntries(
"where soundex(title) = soundex('" + etitle + "')");
CollectionUtil.addAll(similar, title);
_similar.put(entry.entryid, similar);
} catch (Exception e) {
postAsyncStatus("Choked checking repository: " + e);
}
}
}
protected void performUpload ()
{
}
protected ImporterFrame _frame;
protected JButton _next, _back;
protected JPanel _spanel;
protected Entry[] _entries;
protected HashIntMap _similar = new HashIntMap();
}
@@ -0,0 +1,52 @@
//
// $Id$
package robodj.server;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService.ResultListener;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.server.InvocationDispatcher;
import com.threerings.presents.server.InvocationException;
import robodj.client.DJService;
import robodj.data.DJMarshaller;
/**
* Dispatches requests to the {@link DJProvider}.
*/
public class DJDispatcher extends InvocationDispatcher
{
/**
* Creates a dispatcher that may be registered to dispatch invocation
* service requests for the specified provider.
*/
public DJDispatcher (DJProvider provider)
{
this.provider = provider;
}
// documentation inherited
public InvocationMarshaller createMarshaller ()
{
return new DJMarshaller();
}
// documentation inherited
public void dispatchRequest (
ClientObject source, int methodId, Object[] args)
throws InvocationException
{
switch (methodId) {
case DJMarshaller.GET_DJOID:
((DJProvider)provider).getDJOid(
source,
(ResultListener)args[0]
);
return;
default:
super.dispatchRequest(source, methodId, args);
}
}
}
@@ -0,0 +1,24 @@
//
// $Id$
package robodj.server;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationProvider;
import robodj.client.DJService;
/**
* Implements the main RoboDJ services.
*/
public class DJProvider implements InvocationProvider
{
/** Used to obtain the oid of the DJ object. */
public void getDJOid (ClientObject caller,
DJService.ResultListener listener)
throws InvocationException
{
listener.requestProcessed(new Integer(MusicDaemon.djobj.getOid()));
}
}
@@ -0,0 +1,105 @@
//
// $Id$
package robodj.server;
import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.StaticConnectionProvider;
import com.samskivert.util.StringUtil;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.ObjectAccessException;
import com.threerings.presents.dobj.Subscriber;
import com.threerings.presents.server.PresentsServer;
import robodj.Log;
import robodj.data.DJObject;
import robodj.repository.Repository;
import robodj.util.ErrorUtil;
import robodj.util.RDJPrefs;
import robodj.util.RDJPrefsPanel;
/**
* The main entry point for the music playing daemon.
*/
public class MusicDaemon extends PresentsServer
{
/** Provides access to the main RoboDJ services. */
public static DJProvider djprov = new DJProvider();
/** The entity that actually plays the music in the playlist. */
public static MusicPlayer player = new MusicPlayer();
/** Our shared server state. */
public static DJObject djobj;
/** Our music repository. */
public static Repository repo;
// documentation inherited
public void init ()
throws Exception
{
super.init();
// loop until the user provides us with a configuration that works
// or requests to exit
for (int ii = 0; ii < 100; ii++) {
String repodir = RDJPrefs.getRepositoryDirectory();
if (StringUtil.blank(repodir) || ii > 0) {
// display the initial configuration wizard if we are not
// yet properly configured
RDJPrefsPanel.display(true);
}
// create our repository
try {
repo = new Repository(
new StaticConnectionProvider(RDJPrefs.getJDBCConfig()));
} catch (PersistenceException pe) {
String errmsg = "Unable to communicate with database:";
if (ErrorUtil.reportError(errmsg, pe)) {
System.exit(-1);
}
continue;
}
break;
}
// create and register our providers
invmgr.registerDispatcher(new DJDispatcher(djprov), true);
// create our DJ object
omgr.createObject(DJObject.class, new Subscriber() {
public void objectAvailable (DObject object) {
djobj = (DJObject)object;
player.init(djobj);
Log.info("Music daemon ready [oid=" + djobj.getOid() + "].");
}
public void requestFailed (int oid, ObjectAccessException cause) {
Log.warning("Failed to create DJ object: " + cause);
}
});
}
public static void main (String[] args)
{
Log.info("RoboDJ music server starting...");
MusicDaemon server = new MusicDaemon();
try {
// initialize the server
server.init();
// start the server to running (this method call won't return
// until the server is shut down)
server.run();
} catch (Exception e) {
Log.warning("Unable to initialize server.");
Log.logStackTrace(e);
System.exit(-1);
}
}
}
@@ -0,0 +1,189 @@
//
// $Id$
package robodj.server;
import java.io.FileInputStream;
import javazoom.jl.player.Player;
import com.threerings.presents.dobj.AttributeChangeListener;
import com.threerings.presents.dobj.AttributeChangedEvent;
import robodj.Log;
import robodj.data.DJObject;
import robodj.data.PlaylistEntry;
import robodj.repository.Entry;
import robodj.repository.Song;
/**
* Handles the actual playing of music in the playlist.
*/
public class MusicPlayer extends Thread
implements AttributeChangeListener
{
public MusicPlayer ()
{
// start up the music playing thread
setDaemon(true);
start();
}
public void init (DJObject djobj)
{
_djobj = djobj;
_djobj.addListener(this);
}
// documentation inherited from interface
public void attributeChanged (AttributeChangedEvent event)
{
if (event.getName().equals(DJObject.PLAYING)) {
playEntry(_djobj.getPlaying());
recomputeNext();
} else if (event.getName().equals(DJObject.PLAYLIST)) {
recomputeNext();
}
}
public void run ()
{
while (true) {
try {
// this will block if there's nothing to play
Player player = getPlaying();
Log.info("Playing " + _pentry + "...");
while (player.play(PLAY_FRAMES)) {
// loop!
}
} catch (Throwable t) {
Log.warning("Music player choked.");
Log.logStackTrace(t);
}
}
}
/**
* Configures the specified playlist entry as the currently playing
* entry. Even if we are currently playing the same entry, we will
* restart play from the beginning.
*/
protected synchronized void playEntry (PlaylistEntry entry)
{
// if we're already playing what we want to be playing, stop
if (equals(_pentry, entry)) {
return;
}
Log.info("Forcing play [entry=" + entry + "].");
// queue up the next track
_fnentry = entry;
_fnext = getPlayer(_fnentry);
// clear out any current track
if (_playing != null) {
Log.info("Stopping current track.");
_playing.close();
}
// finally wake up the player in case it is sleeping
notify();
}
protected synchronized void recomputeNext ()
{
// if we're properly wired up, NOOP
PlaylistEntry nentry = _djobj.getNext();
if (equals(_nentry, nentry)) {
return;
}
// clear things out in preparation
if (_next != null) {
_next.close();
_next = null;
}
// compute and load up info on the next playing song
_nentry = nentry;
Log.info("Queuing up " + _nentry + "...");
_next = getPlayer(_nentry);
// wake up the music player if necessary
notify();
}
protected Player getPlayer (PlaylistEntry entry)
{
if (entry == null) {
return null;
}
Song song = null;
try {
Entry album = MusicDaemon.repo.getEntry(entry.entryId);
if (album != null) {
song = album.getSong(entry.songId);
}
if (album == null || song == null) {
Log.warning("Bogus playlist entry! [entry=" + entry +
", album=" + album + ", song=" + song + "].");
return null;
}
return new Player(new FileInputStream(song.location));
} catch (Exception e) {
Log.warning("Failed to load song [pentry=" + entry +
", song=" + song + "].");
Log.logStackTrace(e);
return null;
}
}
protected synchronized Player getPlaying ()
{
while (_next == null && _fnext == null) {
try {
Log.info("Waiting for track...");
wait();
} catch (InterruptedException ie) {
Log.warning("Interrupted waiting for track to play.");
}
}
if (_fnext != null) {
_playing = _fnext;
_pentry = _fnentry;
_fnext = null;
_fnentry = null;
} else {
_playing = _next;
_pentry = _nentry;
_nentry = null;
_next = null;
_djobj.skip(); // move forward in the playlist
}
return _playing;
}
/** Helper function. */
protected boolean equals (PlaylistEntry e1, PlaylistEntry e2)
{
return (e1 == e2 || (e1 != null && e1.equals(e2)));
}
protected DJObject _djobj;
protected Player _playing;
protected PlaylistEntry _pentry;
protected Player _next, _fnext;
protected int _nidx;
protected PlaylistEntry _nentry, _fnentry;
/** The number of frames to play before checking up on things. */
protected static final int PLAY_FRAMES = 10;
}
@@ -168,6 +168,11 @@ public class RDJPrefsPanel extends JPanel
protected String _prefKey;
}
public static void main (String[] args)
{
display(true);
}
protected JFrame _frame;
/** Defines our configurable preferences. */