/*
* OSCRoot.java
* Eisenkraut
*
* Copyright (c) 2004-2016 Hanns Holger Rutz. All rights reserved.
*
* This software is published under the GNU General Public License v3+
*
*
* For further information, please contact Hanns Holger Rutz at
* contact@sciss.de
*
*
* Changelog:
* 19-Jan-06 created
*/
package de.sciss.eisenkraut.net;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.List;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;
import java.util.regex.Pattern;
import de.sciss.app.AbstractApplication;
import de.sciss.app.BasicEvent;
import de.sciss.app.EventManager;
import de.sciss.net.*;
import de.sciss.util.Param;
import de.sciss.util.ParamSpace;
public class OSCRoot
implements OSCRouter, OSCListener, EventManager.Processor, PreferenceChangeListener {
public static final int DEFAULT_PORT = 0x4549; // 'E', 'I'
/**
* Convenient name for preferences node
*/
public static final String DEFAULT_NODE = "oscserver";
public static final String KEY_ACTIVE = "active"; // boolean
public static final String KEY_PROTOCOL = "protocol"; // String
public static final String KEY_PORT = "port"; // Param
// public static final Param DEFAULT_PORT = new Param( 0x4549, ParamSpace.NONE | ParamSpace.ABS );
private final Preferences prefs;
private OSCServer serv = null;
// private DatagramChannel dch = null;
// private OSCTransmitter trns = null;
// private OSCReceiver rcv = null;
private boolean running = false;
private OSCGUI gui = null;
private final Pattern oscPathPtrn = Pattern.compile( "/" );
// elements = RoutedOSCMessage instances
// private final List collMessages = Collections.synchronizedList( new ArrayList() );
private final OSCRouterWrapper osc;
private final EventManager elm;
private static final String OSC_DUMP = "dumpOSC";
public static final String OSC_QUERY = "query";
public static final String OSC_GET = "get";
public static final String OSC_QUERYREPLY = "/query.reply";
public static final String OSC_GETREPLY = "/get.reply";
public static final String OSC_FAILEDREPLY = "/failed";
public static final String OSC_DONEREPLY = "/done";
private final Param defaultPortParam;
// private int uniqueID = 0;
private static OSCRoot instance;
public OSCRoot(Preferences prefs)
{
super();
if (instance != null) throw new IllegalStateException("Only one instance allowed");
instance = this;
this.prefs = prefs;
defaultPortParam = new Param(DEFAULT_PORT, ParamSpace.ABS);
if (prefs.get(KEY_PORT, null) == null) { // create defaults
prefs.putBoolean(KEY_ACTIVE, false);
prefs.put(KEY_PORT, defaultPortParam.toString());
}
elm = new EventManager( this );
osc = new OSCRouterWrapper( null, this );
osc.oscAddRouter( new OSCRouter() {
public String oscGetPathComponent()
{
return OSC_DUMP;
}
public void oscRoute( RoutedOSCMessage rom )
{
oscCmdDump( rom );
}
public void oscAddRouter( OSCRouter subRouter )
{
throw new IllegalStateException( "Path endpoint" );
}
public void oscRemoveRouter( OSCRouter subRouter )
{
throw new IllegalStateException( "Path endpoint" );
}
});
// // not really a path but
// // the general responder for query replies
// osc.oscAddRouter( new OSCRouter() {
// public String oscGetPathComponent()
// {
// return OSC_QUERYREPLY;
// }
//
// public void oscRoute( RoutedOSCMessage rom )
// {
// oscCmdDump( rom );
// }
//
// public void oscAddRouter( OSCRouter subRouter )
// {
// throw new IllegalStateException( "Path endpoint" );
// }
// });
}
public static OSCRoot getInstance()
{
return instance;
}
/** If the OSC server preferences says the server is active, this will boot the server. */
public void init() {
if (prefs.getBoolean(KEY_ACTIVE, false)) boot();
prefs.addPreferenceChangeListener(this);
}
public Preferences getPreferences()
{
return prefs;
}
public OSCGUI getGUI()
{
return gui;
}
public void boot()
{
try {
boot(getOSCProtocol(), getOSCPort(), true);
}
catch( IOException e1 ) {
System.err.println( e1.getClass().getName() + " : " + e1.getLocalizedMessage() );
}
}
private String getOSCProtocol() {
return prefs.get(KEY_PROTOCOL, OSCChannel.TCP);
}
private int getOSCPort() {
return (int) Param.fromPrefs(prefs, KEY_PORT, defaultPortParam).val;
}
/**
* Checks if an instance of Eisenkraut is already running. This only works
* for an active TCP server! In that case, it will submit document-open
* OSC commands to that other application instance and exit the JVM afterwards,
* ensuring that only one application instance exists.
*/
public void checkExisting(List<String> paths) {
final String protocol = getOSCProtocol();
if (protocol.equals(OSCChannel.TCP)) {
final int port = getOSCPort();
try {
final OSCTransmitter t = OSCTransmitter.newUsing(protocol, 0, true);
try {
t.setTarget(new InetSocketAddress("127.0.0.1", port));
t.connect();
if (paths != null) for (String path : paths) {
t.send(new OSCMessage("/doc", new Object[]{"open", path}));
}
System.exit(0);
} finally {
t.dispose();
}
} catch (IOException ex) {
// ignore
}
}
}
public void boot( String protocol, int port, boolean loopBack )
throws IOException
{
synchronized( this ) {
if( running ) {
throw new IllegalStateException( "Already booted" );
}
if( gui == null ) gui = new OSCGUI();
// final InetSocketAddress addr = loopBack ?
// new InetSocketAddress( "127.0.0.1", port ) :
// new InetSocketAddress( InetAddress.getLocalHost(), port );
try {
// dch = DatagramChannel.open();
// dch.socket().bind( addr );
serv = OSCServer.newUsing( protocol, port, loopBack );
// rcv = new OSCReceiver( dch );
// trns = new OSCTransmitter( dch );
// rcv.addOSCListener( this );
// rcv.startListening();
serv.addOSCListener( this );
serv.start();
System.out.println( AbstractApplication.getApplication().getName() + " " +
getResourceString( "oscRcvAt" ) + " " + protocol.toUpperCase() + " " + getResourceString( "oscPort" ) + " " + port );
}
catch( IOException e1 ) {
if( serv != null ) {
serv.dispose();
serv = null;
// try {
// rcv.stopListening();
// }
// catch( IOException e2 ) {}
}
// if( dch != null ) {
// try {
// dch.close();
// }
// catch( IOException e2 ) {}
// }
// rcv = null;
// trns = null;
// dch = null;
throw e1;
}
running = true;
}
}
public static String getResourceString( String key )
{
return AbstractApplication.getApplication().getResourceString( key );
}
public void send( OSCPacket p, SocketAddress addr )
throws IOException
{
if( running ) {
//System.err.println( "sending to "+addr );
// trns.send( p, addr );
serv.send( p, addr );
} else {
throw new IllegalStateException( "Not running" );
}
}
// public OSCMessage sendSync( OSCPacket p, InetSocketAddress addr, String doneCmd, String failCmd, int argIdx, Object argMatch )
// throws IOException
// {
// return sendSync( p, addr, doneCmd, failCmd, argIdx, argMatch, 4f );
// }
//
// public OSCMessage sendSync( OSCPacket p, InetSocketAddress addr, String doneCmd, String failCmd, int argIdx, Object argMatch, float timeout )
// throws IOException
// {
// final SyncResponder resp = new SyncResponder( addr, doneCmd, failCmd, argIdx, argMatch );
//
// try {
// synchronized( resp ) {
// resp.add();
// send( p, addr );
// resp.wait( (long) (timeout * 1000) );
// }
// }
// catch( InterruptedException e1 ) {}
// finally {
// resp.remove();
// }
// return resp.doneMsg;
// }
public boolean isRunning()
{
synchronized( this ) {
return running;
}
}
// public Object[] query( InetSocketAddress addr, String path, String[] properties )
// throws IOException
// {
// final Object queryID = new Integer( ++uniqueID );
// final Object[] args = new Object[ properties.length + 2 ];
// final OSCMessage reply;
//
// args[ 0 ] = "query";
// args[ 1 ] = queryID;
// System.arraycopy( properties, 0, args, 2, properties.length );
//
// reply = sendSync( new OSCMessage( path, args ), addr, "/query.reply", "/failed", 0, queryID );
// if( (reply != null) && reply.getName().equals( "/query.reply" )) {
// final Object[] result = new Object[ Math.min( properties.length, reply.getArgCount() - 1 )];
// for( int i = 1, j = 0; j < result.length; i++, j++ ) {
// result[ j ] = reply.getArg( i );
// }
// return result;
// } else {
// return null;
// }
// }
public void quit()
{
synchronized( this ) {
if( running ) {
serv.dispose();
serv = null;
// try {
// rcv.stopListening();
// System.out.println( AbstractApplication.getApplication().getName() + " " +
// getResourceString( "oscStoppedRcv" ));
// }
// catch( IOException e1 ) {
// System.err.println( e1.getLocalizedMessage() );
// }
// try {
// dch.close();
// }
// catch( IOException e1 ) {
// System.err.println( e1.getLocalizedMessage() );
// }
if( gui != null ) {
gui.dispose();
gui = null;
}
running = false;
// rcv = null;
// trns = null;
// dch = null;
}
}
}
// /**
// * @synchronization call only in event thread
// */
// public void oscRemoveRouter( OSCRouter r )
// {
// final String cmd = r.oscGetPathComponent();
//
//// synchronized( mapRouters ) {
// if( mapRouters.remove( cmd ) == null ) {
// throw new IllegalStateException( "Trying to remove unregistered command '" + cmd + "'" );
// }
//// }
// }
public static void failedUnknownPath( OSCMessage msg )
{
failed( msg, "Path not found" );
}
public static void failedUnknownPath( RoutedOSCMessage rom )
{
failedUnknownPath( rom, rom.getPathIndex() );
}
public static void failedUnknownCmd(RoutedOSCMessage rom) {
System.err.println("FAILURE " + rom.msg.getName() + " " + rom.msg.getArg(0).toString() + " Command not found");
}
public static void failedUnknownPath(RoutedOSCMessage rom, int pathIdx) {
final int endIdx;
int startIdx = 0;
final String fullCmd = rom.msg.getName();
if (rom.getPathCount() > 0) {
startIdx += rom.getPathComponent(0).length();
}
for (int i = 1; i < pathIdx; i++) {
startIdx += rom.getPathComponent(i).length() + 1;
}
endIdx = Math.min(fullCmd.length(),
startIdx + (pathIdx < rom.getPathCount() ? rom.getPathComponent(pathIdx).length() + 1 : 0));
System.err.println("FAILURE " + fullCmd.substring(0, startIdx) + "!" +
fullCmd.substring(startIdx, endIdx) + "!" +
fullCmd.substring(endIdx) + " Path not found");
}
public static void failedArgCount(RoutedOSCMessage rom) {
failed(rom.msg, "Illegal argument count (" + rom.msg.getArgCount() + ")");
}
public static void failedArgType(RoutedOSCMessage rom, int argIdx) {
failed(rom.msg, "Illegal argument type (" + rom.msg.getArg(argIdx) + ")");
}
public static void failedQuery(RoutedOSCMessage rom, String property) {
failed(rom.msg, "Illegal query property (" + property + ")");
}
public static void failedGet(RoutedOSCMessage rom, String param) {
failed(rom.msg, "Illegal get command (" + param + ")");
}
public static void failedArgValue(RoutedOSCMessage rom, int argIdx) {
failed(rom.msg, "Illegal argument value (" + rom.msg.getArg(argIdx) + ")");
}
public static void failed(RoutedOSCMessage rom, Throwable t) {
failed(rom.msg, t.getClass().getName() + " : " + t.getLocalizedMessage());
}
public static void failed(OSCMessage msg, String why) {
System.err.println("FAILURE " + msg.getName() + " " + why);
}
// ------------ Runnable interface ------------
// called from the event thread
// when new messages have been queued
public void processEvent(BasicEvent e) {
osc.oscRoute((RoutedOSCMessage) e);
}
// ------------ OSCRouter interface ------------
public String oscGetPathComponent() {
return null;
}
public void oscRoute(RoutedOSCMessage rom) {
osc.oscRoute(rom);
}
public void oscAddRouter(OSCRouter subRouter) {
osc.oscAddRouter(subRouter);
}
public void oscRemoveRouter(OSCRouter subRouter) {
osc.oscRemoveRouter(subRouter);
}
/*
* Command: /dumpOSC, int <incomingMode> [, int <outgoingMode> ]
*/
protected void oscCmdDump( RoutedOSCMessage rom )
{
final int numArgs = rom.msg.getArgCount();
int argIdx = 0;
try {
// rcv.dumpOSC( ((Number) rom.msg.getArg( argIdx )).intValue(), System.out );
serv.dumpIncomingOSC( ((Number) rom.msg.getArg( argIdx )).intValue(), System.out );
argIdx++;
if( numArgs == 2 ) {
// trns.dumpOSC( ((Number) rom.msg.getArg( argIdx )).intValue(), System.out );
serv.dumpOutgoingOSC( ((Number) rom.msg.getArg( argIdx )).intValue(), System.out );
}
}
catch( ClassCastException e1 ) {
failedArgType( rom, argIdx );
}
catch( IndexOutOfBoundsException e1 ) {
failedArgCount( rom );
}
}
// ------------ OSCListener interface ------------
public void messageReceived(OSCMessage msg, SocketAddress addr, long when) {
final String[] path = oscPathPtrn.split(msg.getName());
if (path.length < 2) {
failedUnknownPath(msg);
return;
}
elm.dispatchEvent(new RoutedOSCMessage(msg, addr, when, this, path, 0));
}
// ------- PreferenceChangeListener interface -------
public void preferenceChange(PreferenceChangeEvent e) {
final String key = e.getKey();
if (key.equals(KEY_ACTIVE)) {
if (Boolean.valueOf(e.getNewValue())) {
if (!isRunning()) {
boot();
}
} else {
if (isRunning()) {
quit();
}
}
}
}
}