-
@@ -68,7 +98,6 @@
windowtitle="${app.name} API"
doctitle="${app.name} API"
overview="${src.dir}/${doc.overview}"
- bottom="Copyright © 2002 ${copyright.holder}. All Rights Reserved."
destdir="${javadoc.dir}">
@@ -82,7 +111,7 @@
+ basedir="${classes.dir}"/>
diff --git a/projects/robodj/src/java/robodj/chooser/Chooser.java b/projects/robodj/src/java/robodj/chooser/Chooser.java
index 95300739..6e10ed5c 100644
--- a/projects/robodj/src/java/robodj/chooser/Chooser.java
+++ b/projects/robodj/src/java/robodj/chooser/Chooser.java
@@ -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();
}
}
diff --git a/projects/robodj/src/java/robodj/chooser/ChooserController.java b/projects/robodj/src/java/robodj/chooser/ChooserController.java
index bc9a9171..d92f6630 100644
--- a/projects/robodj/src/java/robodj/chooser/ChooserController.java
+++ b/projects/robodj/src/java/robodj/chooser/ChooserController.java
@@ -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);
- }
}
diff --git a/projects/robodj/src/java/robodj/chooser/ChooserFrame.java b/projects/robodj/src/java/robodj/chooser/ChooserFrame.java
index 30d9d750..9e080613 100644
--- a/projects/robodj/src/java/robodj/chooser/ChooserFrame.java
+++ b/projects/robodj/src/java/robodj/chooser/ChooserFrame.java
@@ -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) {
diff --git a/projects/robodj/src/java/robodj/chooser/EntryController.java b/projects/robodj/src/java/robodj/chooser/EntryController.java
index ad21785e..2eeccfbd 100644
--- a/projects/robodj/src/java/robodj/chooser/EntryController.java
+++ b/projects/robodj/src/java/robodj/chooser/EntryController.java
@@ -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;
diff --git a/projects/robodj/src/java/robodj/chooser/PlaylistController.java b/projects/robodj/src/java/robodj/chooser/PlaylistController.java
index 575d823f..59820b4f 100644
--- a/projects/robodj/src/java/robodj/chooser/PlaylistController.java
+++ b/projects/robodj/src/java/robodj/chooser/PlaylistController.java
@@ -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;
}
diff --git a/projects/robodj/src/java/robodj/chooser/PlaylistPanel.java b/projects/robodj/src/java/robodj/chooser/PlaylistPanel.java
index ec05a113..66cf070d 100644
--- a/projects/robodj/src/java/robodj/chooser/PlaylistPanel.java
+++ b/projects/robodj/src/java/robodj/chooser/PlaylistPanel.java
@@ -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 =
diff --git a/projects/robodj/src/java/robodj/client/DJService.java b/projects/robodj/src/java/robodj/client/DJService.java
new file mode 100644
index 00000000..76b4ddb1
--- /dev/null
+++ b/projects/robodj/src/java/robodj/client/DJService.java
@@ -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);
+}
diff --git a/projects/robodj/src/java/robodj/data/DJMarshaller.java b/projects/robodj/src/java/robodj/data/DJMarshaller.java
new file mode 100644
index 00000000..1dd4e7e1
--- /dev/null
+++ b/projects/robodj/src/java/robodj/data/DJMarshaller.java
@@ -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
+ });
+ }
+
+}
diff --git a/projects/robodj/src/java/robodj/data/DJObject.dobj b/projects/robodj/src/java/robodj/data/DJObject.dobj
new file mode 100644
index 00000000..1b1e0452
--- /dev/null
+++ b/projects/robodj/src/java/robodj/data/DJObject.dobj
@@ -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);
+ }
+}
diff --git a/projects/robodj/src/java/robodj/data/DJObject.java b/projects/robodj/src/java/robodj/data/DJObject.java
new file mode 100644
index 00000000..6f508fb2
--- /dev/null
+++ b/projects/robodj/src/java/robodj/data/DJObject.java
@@ -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 playlist field. */
+ public static final String PLAYLIST = "playlist";
+
+ /** The field name of the playing 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 playlist 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 indexth element of
+ * playlist 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 playing 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;
+ }
+}
diff --git a/projects/robodj/src/java/robodj/data/PlaylistEntry.java b/projects/robodj/src/java/robodj/data/PlaylistEntry.java
new file mode 100644
index 00000000..35c3a060
--- /dev/null
+++ b/projects/robodj/src/java/robodj/data/PlaylistEntry.java
@@ -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;
+}
diff --git a/projects/robodj/src/java/robodj/importer/EntryUploader.java b/projects/robodj/src/java/robodj/importer/EntryUploader.java
new file mode 100644
index 00000000..64cd2de8
--- /dev/null
+++ b/projects/robodj/src/java/robodj/importer/EntryUploader.java
@@ -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("");
+ for (int ii = 0; ii < entry.songs.length; ii++) {
+ Song song = entry.songs[ii];
+ extra.append(song.position).append(". ");
+ extra.append(song.title).append("
");
+ }
+ if (similar.size() > 0) {
+ extra.append("Similar entries:
");
+ for (Iterator iter = similar.iterator(); iter.hasNext(); ) {
+ Entry siment = (Entry)iter.next();
+ extra.append(siment.artist).append(" - ").append(siment.title);
+ extra.append("
");
+ }
+ }
+ extra.append("");
+ JLabel exlab = new JLabel(extra.toString());
+ exlab.setFont(getFont().deriveFont(11));
+ add(exlab, BorderLayout.SOUTH);
+ }
+
+ protected JCheckBox _checked;
+}
diff --git a/projects/robodj/src/java/robodj/importer/FinishedPanel.java b/projects/robodj/src/java/robodj/importer/FinishedPanel.java
index 7be69924..ebd68a70 100644
--- a/projects/robodj/src/java/robodj/importer/FinishedPanel.java
+++ b/projects/robodj/src/java/robodj/importer/FinishedPanel.java
@@ -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;
}
diff --git a/projects/robodj/src/java/robodj/importer/ImportMusicPanel.java b/projects/robodj/src/java/robodj/importer/ImportMusicPanel.java
new file mode 100644
index 00000000..0d31e72f
--- /dev/null
+++ b/projects/robodj/src/java/robodj/importer/ImportMusicPanel.java
@@ -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);
+ }
+ }
+}
diff --git a/projects/robodj/src/java/robodj/importer/Importer.java b/projects/robodj/src/java/robodj/importer/Importer.java
index ac040733..3cb1875f 100644
--- a/projects/robodj/src/java/robodj/importer/Importer.java
+++ b/projects/robodj/src/java/robodj/importer/Importer.java
@@ -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);
}
diff --git a/projects/robodj/src/java/robodj/importer/ImporterPanel.java b/projects/robodj/src/java/robodj/importer/ImporterPanel.java
index 01437769..6f257e81 100644
--- a/projects/robodj/src/java/robodj/importer/ImporterPanel.java
+++ b/projects/robodj/src/java/robodj/importer/ImporterPanel.java
@@ -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;
}
diff --git a/projects/robodj/src/java/robodj/importer/RipPanel.java b/projects/robodj/src/java/robodj/importer/RipPanel.java
index b8a24a34..0a16193f 100644
--- a/projects/robodj/src/java/robodj/importer/RipPanel.java
+++ b/projects/robodj/src/java/robodj/importer/RipPanel.java
@@ -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;
diff --git a/projects/robodj/src/java/robodj/importer/SongTableModel.java b/projects/robodj/src/java/robodj/importer/SongTableModel.java
new file mode 100644
index 00000000..056682c2
--- /dev/null
+++ b/projects/robodj/src/java/robodj/importer/SongTableModel.java
@@ -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 "";
+ }
+ }
+
+ // 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
+ };
+}
diff --git a/projects/robodj/src/java/robodj/importer/InsertCDPanel.java b/projects/robodj/src/java/robodj/importer/StartPanel.java
similarity index 51%
rename from projects/robodj/src/java/robodj/importer/InsertCDPanel.java
rename to projects/robodj/src/java/robodj/importer/StartPanel.java
index a2cedb5d..37286609 100644
--- a/projects/robodj/src/java/robodj/importer/InsertCDPanel.java
+++ b/projects/robodj/src/java/robodj/importer/StartPanel.java
@@ -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;
}
diff --git a/projects/robodj/src/java/robodj/importer/UploadMusicPanel.java b/projects/robodj/src/java/robodj/importer/UploadMusicPanel.java
new file mode 100644
index 00000000..6c713a47
--- /dev/null
+++ b/projects/robodj/src/java/robodj/importer/UploadMusicPanel.java
@@ -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();
+}
diff --git a/projects/robodj/src/java/robodj/server/DJDispatcher.java b/projects/robodj/src/java/robodj/server/DJDispatcher.java
new file mode 100644
index 00000000..2c6635ac
--- /dev/null
+++ b/projects/robodj/src/java/robodj/server/DJDispatcher.java
@@ -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);
+ }
+ }
+}
diff --git a/projects/robodj/src/java/robodj/server/DJProvider.java b/projects/robodj/src/java/robodj/server/DJProvider.java
new file mode 100644
index 00000000..7de0dec1
--- /dev/null
+++ b/projects/robodj/src/java/robodj/server/DJProvider.java
@@ -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()));
+ }
+}
diff --git a/projects/robodj/src/java/robodj/server/MusicDaemon.java b/projects/robodj/src/java/robodj/server/MusicDaemon.java
new file mode 100644
index 00000000..2e194508
--- /dev/null
+++ b/projects/robodj/src/java/robodj/server/MusicDaemon.java
@@ -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);
+ }
+ }
+}
diff --git a/projects/robodj/src/java/robodj/server/MusicPlayer.java b/projects/robodj/src/java/robodj/server/MusicPlayer.java
new file mode 100644
index 00000000..506a46a1
--- /dev/null
+++ b/projects/robodj/src/java/robodj/server/MusicPlayer.java
@@ -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;
+}
diff --git a/projects/robodj/src/java/robodj/util/RDJPrefsPanel.java b/projects/robodj/src/java/robodj/util/RDJPrefsPanel.java
index 46bde5cc..cb3dab7b 100644
--- a/projects/robodj/src/java/robodj/util/RDJPrefsPanel.java
+++ b/projects/robodj/src/java/robodj/util/RDJPrefsPanel.java
@@ -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. */