package com.netthreads.network.osc.router.service; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.collections.ObservableList; import javafx.concurrent.Service; import javafx.concurrent.Task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.netthreads.network.osc.router.AppInjector; import com.netthreads.network.osc.router.controller.ImplementsRefresh; import com.netthreads.network.osc.router.model.OSCItem; import com.netthreads.network.osc.router.model.OSCValue; import com.netthreads.network.osc.router.properties.ApplicationProperties; import com.netthreads.network.osc.server.OSCServer; import com.netthreads.network.osc.server.OSCServerImpl; import com.netthreads.network.osc.server.OSCServerListener; import com.netthreads.osc.common.domain.OSCBundle; import com.netthreads.osc.common.domain.OSCMessage; import com.netthreads.osc.common.domain.OSCPacket; /** * OSC Service. * * Present socket through which OSC messages are received and routed. * */ public class OSCService extends Service<Void> implements OSCServerListener, RunStop { private Logger logger = LoggerFactory.getLogger(OSCService.class); private final ObservableList<OSCItem> observableList; private final OSCMessageCache messageCache; private final MIDIDeviceCache midiDeviceCache; private final ImplementsRefresh refreshView; private OSCServer oscServer; private final List<Router> routers; private final ApplicationProperties applicationProperties; private int port; private long elapsedMsec; private long refreshMsec; // --------------------------------------------------------------- // Active Property // --------------------------------------------------------------- private BooleanProperty activeProperty; public BooleanProperty getActiveProperty() { return activeProperty; } /** * Return service active status. * * @return The active status. */ public boolean getActive() { return activeProperty.get(); } /** * Set service active status. * * @param activeProperty */ public void setActive(boolean activeProperty) { this.activeProperty.set(activeProperty); if (!activeProperty) { oscServer.shutdown(); } } /** * OSC Service. * * @param observableList * Data source. * @param refreshView * View to update. */ public OSCService(ObservableList<OSCItem> observableList, ImplementsRefresh refreshView) { this.observableList = observableList; this.refreshView = refreshView; routers = new ArrayList<Router>(); // Properties activeProperty = new SimpleBooleanProperty(); // Cache singleton. messageCache = AppInjector.getInjector().getInstance(OSCMessageCache.class); midiDeviceCache = AppInjector.getInjector().getInstance(MIDIDeviceCache.class); applicationProperties = AppInjector.getInjector().getInstance(ApplicationProperties.class); elapsedMsec = 0; port = ApplicationProperties.DEFAULT_PORT; } /** * Add a router to the list. * * @param oscRouter */ public void addRouter(Router oscRouter) { routers.add(oscRouter); } /** * Activate service. * * @param port * * @return True if successful. */ @Override public void run() { reset(); start(); startRouters(); } /** * Stop service. * */ @Override public void stop() { cancel(); stopRouters(); } /** * Start OSC Routers. * */ public void startRouters() { Iterator<Router> iterator = routers.iterator(); while (iterator.hasNext()) { Router oscRouter = iterator.next(); oscRouter.start(); } } /** * Stop OSC Routers. * */ public void stopRouters() { Iterator<Router> iterator = routers.iterator(); while (iterator.hasNext()) { Router oscRouter = iterator.next(); oscRouter.stop(); } } /** * Create service task. * */ @Override protected Task<Void> createTask() { final OSCService thisService = this; return new Task<Void>() { protected Void call() { try { oscServer = new OSCServerImpl(port, thisService); oscServer.listen(); } catch (Exception e) { logger.error(e.getLocalizedMessage()); } finally { setActive(false); } return null; } }; } /** * Handle shutdown. * */ @Override protected void cancelled() { super.cancelled(); } /** * Handle bundle. * */ @Override public void handleOSCBundle(OSCBundle oscBundle) { List<OSCPacket> messages = oscBundle.getMessages(); for (OSCPacket oscPacket : messages) { if (oscPacket instanceof OSCMessage) { OSCMessage oscMessage = (OSCMessage) oscPacket; handleOSCMessage(oscMessage); } else if (oscPacket instanceof OSCBundle) { handleOSCBundle((OSCBundle) oscPacket); } } } /** * Handle message. * */ @Override public void handleOSCMessage(OSCMessage oscMessage) { logger.debug("Received :" + oscMessage.getAddress()); String address = oscMessage.getAddress(); // Look in cache for item. OSCItem oscItem = messageCache.get(address); // If not found then create and entry for message and values. if (oscItem == null) { oscItem = createOSCItem(oscMessage); } // Update item values from message. updateValues(oscMessage, oscItem); // Send to router route(oscItem); // Update UI. updateView(); } /** * Route item. * * TODO: Devise more efficient way to do this. * * @param oscItem */ private void route(OSCItem oscItem) { // Pass off routing to provided routers. Iterator<Router> iterator = routers.iterator(); boolean done = false; while (iterator.hasNext() && !done) { Router oscRouter = iterator.next(); done = oscRouter.route(oscItem); } } /** * Only update the view if a set amount of milliseconds as passed. This is to stop the UI from getting swamped with * refresh calls. */ private void updateView() { elapsedMsec += System.currentTimeMillis(); if (elapsedMsec > refreshMsec) { logger.debug("Refresh"); // Update view. refreshView.refresh(); elapsedMsec = 0; } } /** * Create OSC Item from message. * * @param oscMessage * * @return A new item. */ private OSCItem createOSCItem(OSCMessage oscMessage) { String address = oscMessage.getAddress(); OSCItem oscItem = new OSCItem(); oscItem.setAddress(address); oscItem.setRoute(MIDIMessageLookupImpl.NAMES[0]); observableList.add(oscItem); messageCache.put(address, oscItem); List<Character> types = oscMessage.getTypes(); // Set up arguments holder. for (int i = 0; i < types.size(); i++) { OSCValue oscValue = new OSCValue(); oscValue.setType(types.get(i).toString()); oscValue.setLabel(null); oscItem.getValues().add(oscValue); } return oscItem; } /** * Update values. * * @param oscMessage * @param oscItem */ private void updateValues(OSCMessage oscMessage, OSCItem oscItem) { // Fill values for arguments. List<Object> arguments = oscMessage.getArguments(); for (int i = 0; i < arguments.size(); i++) { OSCValue oscValue = oscItem.getValues().get(i); // TODO: Not sure about this. oscValue.setValue(arguments.get(i).toString()); } oscItem.setWorking(OSCItem.WORKING_DONE); } /** * Call back to set state. * */ @Override public void handleStart() { setActive(true); for (OSCItem oscItem : messageCache.items()) { String deviceName = oscItem.getDevice(); oscItem.setStatus(OSCItem.STATUS_CLOSED); if (midiDeviceCache.openDevice(deviceName)) { oscItem.setStatus(OSCItem.STATUS_OPEN); } } refreshView.refresh(); elapsedMsec = 0; refreshMsec = applicationProperties.getRefreshMsec(); logger.info("Started"); } /** * Call back to set state. * */ @Override public void handleShutdown() { setActive(false); for (OSCItem oscItem : messageCache.items()) { String deviceName = oscItem.getDevice(); midiDeviceCache.closeDevice(deviceName); oscItem.setStatus(OSCItem.STATUS_CLOSED); oscItem.setWorking(OSCItem.WORKING_DONE); } refreshView.refresh(); elapsedMsec = 0; logger.info("Stopped"); } /** * Return current port. * * @return The port. */ public int getPort() { return port; } /** * Set listen port. * * @param port */ public void setPort(int port) { this.port = port; } /** * Load message definitions. * * @param filePath * * @throws Exception */ public void load(String filePath) throws Exception { try { messageCache.deserialize(filePath); // Update view. for (OSCItem oscItem : messageCache.items()) { observableList.add(oscItem); } } catch (Exception e) { throw new Exception(e.getLocalizedMessage()); } } /** * Save message definitions. * * @param filePath * * @throws Exception */ public void save(String filePath) throws Exception { messageCache.serialize(filePath); } }