--- jlscp/trunk/src/org/linuxsampler/lscp/Client.java 2007/09/16 23:15:57 1351 +++ jlscp/trunk/src/org/linuxsampler/lscp/Client.java 2008/09/08 00:16:17 1766 @@ -1,7 +1,7 @@ /* * jlscp - a java LinuxSampler control protocol API * - * Copyright (C) 2005-2007 Grigor Iliev + * Copyright (C) 2005-2008 Grigor Iliev * * This file is part of jlscp. * @@ -41,9 +41,9 @@ /** * This class is the abstraction representing a client endpoint for communication with LinuxSampler - * instance. Since it implements all commands specified in the LSCP protocol v1.1, for more + * instance. Since it implements all commands specified in the LSCP protocol v1.2, for more * information look at the - * LSCP specification. + * LSCP specification. * *

The following code establishes connection to LinuxSampler instance and gets the * LinuxSampler version: @@ -164,6 +164,51 @@ if(printOnlyMode) setPrintOnlyMode(true); } + private boolean extendedCharacterEscaping = true; + + /** + * Sets whether strings sent to LinuxSampler should be more aggressively escaped. + */ + public synchronized void + setExtendedCharacterEscaping(boolean b) { extendedCharacterEscaping = b; } + + /** + * Determines whether strings sent to LinuxSampler should be more aggressively escaped. + */ + public synchronized boolean + getExtendedCharacterEscaping() { return extendedCharacterEscaping; } + + /** + * @see java.net.Socket#setSoTimeout + */ + public synchronized void + setSoTimeout(int timeout) { + soTimeout = timeout; + + try { if(sock != null) sock.setSoTimeout(timeout); } + catch(Exception x) { this.getLogger().log(Level.INFO, "Unable to set timeout", x); } + } + + private String + toEscapedText(String s) { + s = toEscapedString(s); + return conv(s); + } + + private String + toEscapedFsEntry(String s) { + s = toEscapedFileName(s); + return conv(s); + } + + /** + * Applies an extended character escaping to the specified string if needed. + */ + private String + conv(String s) { + return getExtendedCharacterEscaping() ? toExtendedEscapeSequence(s) : s; + } + /** * Determines whether the client is in print-only mode. * Print-only mode means that the client will just print all @@ -344,16 +389,20 @@ if(!llFSI.isEmpty()) subscribe("FX_SEND_INFO"); if(!llSC.isEmpty()) subscribe("STREAM_COUNT"); if(!llVC.isEmpty()) subscribe("VOICE_COUNT"); + if(!llTSC.isEmpty()) subscribe("TOTAL_STREAM_COUNT"); if(!llTVC.isEmpty()) subscribe("TOTAL_VOICE_COUNT"); if(!llMIMC.isEmpty()) subscribe("MIDI_INSTRUMENT_MAP_COUNT"); if(!llMIMI.isEmpty()) subscribe("MIDI_INSTRUMENT_MAP_INFO"); if(!llMIC.isEmpty()) subscribe("MIDI_INSTRUMENT_COUNT"); if(!llMII.isEmpty()) subscribe("MIDI_INSTRUMENT_INFO"); + if(!llDMD.isEmpty()) subscribe("DEVICE_MIDI"); + if(!llCMD.isEmpty()) subscribe("CHANNEL_MIDI"); if(!llID.isEmpty()) { subscribe("DB_INSTRUMENT_DIRECTORY_COUNT"); subscribe("DB_INSTRUMENT_DIRECTORY_INFO"); subscribe("DB_INSTRUMENT_COUNT"); subscribe("DB_INSTRUMENT_INFO"); + subscribe("DB_INSTRUMENTS_JOB_INFO"); } if(!llGI.isEmpty()) subscribe("GLOBAL_INFO"); } @@ -485,6 +534,7 @@ private final Vector llMIDI = new Vector(); private final Vector llSC = new Vector(); private final Vector llVC = new Vector(); + private final Vector llTSC = new Vector(); private final Vector llTVC = new Vector(); /** MIDI instrument map count listeners */ @@ -497,6 +547,8 @@ /** MIDI instrument info listeners */ private final Vector llMII = new Vector(); + private final Vector llDMD = new Vector(); + private final Vector llCMD = new Vector(); private final Vector llID = new Vector(); private final Vector llGI = new Vector(); @@ -522,18 +574,94 @@ !llMIDI.isEmpty() || !llSC.isEmpty() || !llVC.isEmpty() || + !llTSC.isEmpty() || !llTVC.isEmpty() || !llMIMC.isEmpty() || !llMIMI.isEmpty() || !llMIC.isEmpty() || !llMII.isEmpty() || + !llDMD.isEmpty() || + !llCMD.isEmpty() || !llID.isEmpty() || !llGI.isEmpty(); } private synchronized void + fireDeviceMidiDataEvent(String s) { + try { + String[] list = parseStringList(s, ' '); + if(list.length != 5) { + getLogger().warning("Unknown DEVICE_MIDI format"); + return; + } + + int dev = parseInt(list[0]); + int port = parseInt(list[1]); + + MidiDataEvent.Type type = parseMidiDataType(list[2]); + if(type == null) return; + + int note = parseInt(list[3]); + int velocity = parseInt(list[4]); + + DeviceMidiDataEvent e = new DeviceMidiDataEvent(this, type, note, velocity); + e.setDeviceId(dev); + e.setPortId(port); + for(DeviceMidiDataListener l : llDMD) l.midiDataArrived(e); + } catch(LscpException x) { + getLogger().log ( + Level.WARNING, LscpI18n.getLogMsg("CommandFailed!"), x + ); + } + } + + private synchronized void + fireChannelMidiDataEvent(String s) { + try { + String[] list = parseStringList(s, ' '); + if(list.length != 4) { + getLogger().warning("Unknown CHANNEL_MIDI format"); + return; + } + + int channel = parseInt(list[0]); + + MidiDataEvent.Type type = parseMidiDataType(list[1]); + if(type == null) return; + + int note = parseInt(list[2]); + int velocity = parseInt(list[3]); + + ChannelMidiDataEvent e = new ChannelMidiDataEvent(this, type, note, velocity); + e.setChannelId(channel); + for(ChannelMidiDataListener l : llCMD) l.midiDataArrived(e); + } catch(LscpException x) { + getLogger().log ( + Level.WARNING, LscpI18n.getLogMsg("CommandFailed!"), x + ); + } + } + + private MidiDataEvent.Type + parseMidiDataType(String s) { + if("NOTE_ON".equals(s)) return MidiDataEvent.Type.NOTE_ON; + if("NOTE_OFF".equals(s)) return MidiDataEvent.Type.NOTE_OFF; + + getLogger().warning("Unknown MIDI data type: " + s); + return null; + } + + private synchronized void fireEvent(String s) { - if(s.startsWith("DB_INSTRUMENT_DIRECTORY_COUNT:")) { + // Sort by priority + + if(s.startsWith("CHANNEL_MIDI:")) { + s = s.substring("CHANNEL_MIDI:".length()); + fireChannelMidiDataEvent(s); + } else if(s.startsWith("DEVICE_MIDI:")) { + s = s.substring("DEVICE_MIDI:".length()); + fireDeviceMidiDataEvent(s); + } else if(s.startsWith("DB_INSTRUMENT_DIRECTORY_COUNT:")) { s = s.substring("DB_INSTRUMENT_DIRECTORY_COUNT:".length()); InstrumentsDbEvent e = new InstrumentsDbEvent(this, s); for(InstrumentsDbListener l : llID) l.directoryCountChanged(e); @@ -662,6 +790,17 @@ } catch(NumberFormatException x) { getLogger().log(Level.WARNING, "Unknown CHANNEL_INFO format", x); } + } else if(s.startsWith("TOTAL_STREAM_COUNT:")) { + try { + s = s.substring("TOTAL_STREAM_COUNT:".length()); + int i = Integer.parseInt(s); + TotalStreamCountEvent e = new TotalStreamCountEvent(this, i); + for(TotalStreamCountListener l : llTSC) l.totalStreamCountChanged(e); + } catch(NumberFormatException x) { + getLogger().log ( + Level.WARNING, "Unknown TOTAL_STREAM_COUNT format", x + ); + } } else if(s.startsWith("TOTAL_VOICE_COUNT:")) { try { s = s.substring("TOTAL_VOICE_COUNT:".length()); @@ -1134,6 +1273,28 @@ /** * Registers the specified listener for receiving event messages. * Listeners can be registered regardless of the connection state. + * @param l The TotalStreamCountListener to register. + */ + public synchronized void + addTotalStreamCountListener(TotalStreamCountListener l) { + if(llTSC.isEmpty()) subscribe("TOTAL_STREAM_COUNT"); + llTSC.add(l); + } + + /** + * Removes the specified listener. + * Listeners can be removed regardless of the connection state. + * @param l The TotalStreamCountListener to remove. + */ + public synchronized void + removeTotalStreamCountListener(TotalStreamCountListener l) { + boolean b = llTSC.remove(l); + if(b && llTSC.isEmpty()) unsubscribe("TOTAL_STREAM_COUNT"); + } + + /** + * Registers the specified listener for receiving event messages. + * Listeners can be registered regardless of the connection state. * @param l The TotalVoiceCountListener to register. */ public synchronized void @@ -1244,6 +1405,50 @@ /** * Registers the specified listener for receiving event messages. * Listeners can be registered regardless of the connection state. + * @param l The DeviceMidiDataListener to register. + */ + public synchronized void + addDeviceMidiDataListener(DeviceMidiDataListener l) { + if(llDMD.isEmpty()) subscribe("DEVICE_MIDI"); + llDMD.add(l); + } + + /** + * Removes the specified listener. + * Listeners can be removed regardless of the connection state. + * @param l The DeviceMidiDataListener to remove. + */ + public synchronized void + removeDeviceMidiDataListener(DeviceMidiDataListener l) { + boolean b = llDMD.remove(l); + if(b && llDMD.isEmpty()) unsubscribe("DEVICE_MIDI"); + } + + /** + * Registers the specified listener for receiving event messages. + * Listeners can be registered regardless of the connection state. + * @param l The ChannelMidiDataListener to register. + */ + public synchronized void + addChannelMidiDataListener(ChannelMidiDataListener l) { + if(llCMD.isEmpty()) subscribe("CHANNEL_MIDI"); + llCMD.add(l); + } + + /** + * Removes the specified listener. + * Listeners can be removed regardless of the connection state. + * @param l The ChannelMidiDataListener to remove. + */ + public synchronized void + removeChannelMidiDataListener(ChannelMidiDataListener l) { + boolean b = llCMD.remove(l); + if(b && llCMD.isEmpty()) unsubscribe("CHANNEL_MIDI"); + } + + /** + * Registers the specified listener for receiving event messages. + * Listeners can be registered regardless of the connection state. * @param l The InstrumentsDbListener to register. */ public synchronized void @@ -1418,7 +1623,7 @@ args.append(' ').append(param); for(Parameter p : deplist) { - if(p.getValue() == null) continue; + if(p == null || p.getName() == null || p.getValue() == null) continue; args.append(' ').append(p.getName()).append('=').append(p.getStringValue()); } @@ -1482,7 +1687,7 @@ StringBuffer args = new StringBuffer(aoDriver); for(Parameter p : paramList) { - if(p.getValue() == null) continue; + if(p == null || p.getName() == null || p.getValue() == null) continue; args.append(' ').append(p.getName()).append('=').append(p.getStringValue()); } @@ -2004,7 +2209,7 @@ args.append(' ').append(param); for(Parameter p : deplist) { - if(p.getValue() == null) continue; + if(p == null || p.getName() == null || p.getValue() == null) continue; args.append(' ').append(p.getName()).append('=').append(p.getStringValue()); } @@ -2069,7 +2274,7 @@ StringBuffer args = new StringBuffer(miDriver); for(Parameter p : paramList) { - if(p.getValue() == null) continue; + if(p == null || p.getName() == null || p.getValue() == null) continue; args.append(' ').append(p.getName()).append('=').append(p.getStringValue()); } @@ -2216,8 +2421,15 @@ mid.setActive(Boolean.parseBoolean(s)); } else if(s.startsWith("PORTS: ")) { s = s.substring("PORTS: ".length()); - int ports = Parser.parseInt(s); - MidiPort[] midiPorts = new MidiPort[ports > 0 ? ports : 0]; + + Parameter ports = (Parameter) + getMidiInputDriverParameterInfo(drv, "PORTS"); + + ports.parseValue(s); + mid.setPortsParameter(ports); + + int j = ports.getValue(); + MidiPort[] midiPorts = new MidiPort[j > 0 ? j : 0]; for(int i = 0; i < midiPorts.length; i++) midiPorts[i] = getMidiInputPortInfo(deviceId, i); @@ -2460,7 +2672,7 @@ public synchronized int addMidiInstrumentMap(String name) throws IOException, LSException, LscpException { verifyConnection(); - out.writeLine("ADD MIDI_INSTRUMENT_MAP '" + name + "'"); + out.writeLine("ADD MIDI_INSTRUMENT_MAP '" + toEscapedText(name) + "'"); if(getPrintOnlyMode()) return -1; ResultSet rs = getEmptyResultSet(); @@ -2563,7 +2775,7 @@ for(String s : lnS) { if(s.startsWith("NAME: ")) { - name = s.substring("NAME: ".length()); + name = toNonEscapedString(s.substring("NAME: ".length())); } else if(s.startsWith("DEFAULT: ")) { b = Boolean.parseBoolean(s.substring("DEFAULT: ".length())); } else { @@ -2611,6 +2823,7 @@ throws IOException, LscpException, LSException { verifyConnection(); + name = toEscapedText(name); out.writeLine("SET MIDI_INSTRUMENT_MAP NAME " + + mapId + " '" + name + "'"); if(getPrintOnlyMode()) return; @@ -2660,13 +2873,17 @@ cmd.append(entry.getMidiBank()).append(' '); cmd.append(entry.getMidiProgram()).append(' '); cmd.append(info.getEngine()).append(" '"); - cmd.append(info.getFilePath()).append("' "); + cmd.append(conv(info.getFilePath())).append("' "); cmd.append(info.getInstrumentIndex()).append(' '); cmd.append(info.getVolume()); if(!info.getLoadMode().name().equals("DEFAULT")) { cmd.append(' ').append(info.getLoadMode().name()); } - if(info.getName() != null) cmd.append(" '").append(info.getName()).append("'"); + + if(info.getName() != null) { + String s = toEscapedText(info.getName()); + cmd.append(" '").append(s).append("'"); + } out.writeLine(cmd.toString()); if(getPrintOnlyMode()) return; @@ -2755,10 +2972,42 @@ } /** - * Gets all MIDI instrument contained int the specified MIDI instrument map. + * Gets all MIDI instrument entries contained int the specified MIDI instrument map. + * @param mapId The ID of the map, which instruments should be obtained. + * @return An int array providing all MIDI instrument entries + * in the specified MIDI instrument map. + * @throws IOException If some I/O error occurs. + * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If some other error occurs. + */ + public synchronized int[][] + getMidiInstrumentEntries(int mapId) throws IOException, LscpException, LSException { + verifyConnection(); + out.writeLine("LIST MIDI_INSTRUMENTS " + String.valueOf(mapId)); + if(getPrintOnlyMode()) return null; + + String[] entries = parseArray(getSingleLineResultSet().getResult()); + int[][] e = new int[entries.length][3]; + + for(int i = 0; i < entries.length; i++) { + Integer[] vals = parseIntList(entries[i]); + if(vals.length != 3) { + throw new LscpException(LscpI18n.getLogMsg("CommandFailed!")); + } + + e[i][0] = vals[0]; + e[i][1] = vals[1]; + e[i][2] = vals[2]; + } + + return e; + } + + /** + * Gets all MIDI instruments contained int the specified MIDI instrument map. * @param mapId The ID of the map, which instruments should be obtained. * @return A MidiInstrumentInfo array providing - * all MIDI instruments from all MIDI instrument maps. + * all MIDI instruments in the specified MIDI instrument map. * @throws IOException If some I/O error occurs. * @throws LscpException If LSCP protocol corruption occurs. * @throws LSException If some other error occurs. @@ -2806,14 +3055,25 @@ throws IOException, LscpException, LSException { verifyConnection(); + requestMidiInstrumentInfo(mapId, bank, program); + return getMidiInstrumentInfoResponse(mapId, bank, program); + } + + private void + requestMidiInstrumentInfo(int mapId, int bank, int program) throws IOException { StringBuffer cmd = new StringBuffer("GET MIDI_INSTRUMENT INFO "); cmd.append(mapId).append(' '); cmd.append(bank).append(' '); cmd.append(program); out.writeLine(cmd.toString()); - if(getPrintOnlyMode()) return null; + } + + private MidiInstrumentInfo + getMidiInstrumentInfoResponse(int mapId, int bank, int program) + throws IOException, LscpException, LSException { + if(getPrintOnlyMode()) return null; ResultSet rs = getMultiLineResultSet(); MidiInstrumentEntry entry = new MidiInstrumentEntry(bank, program); return new MidiInstrumentInfo(mapId, entry, rs.getMultiLineResult()); @@ -2863,7 +3123,7 @@ throws IOException, LscpException, LSException { String cmd = nonModal ? "LOAD INSTRUMENT NON_MODAL " : "LOAD INSTRUMENT "; - String args = '\'' + filename + "' " + instrIdx + ' ' + samplerChn; + String args = '\'' + conv(filename) + "' " + instrIdx + ' ' + samplerChn; out.writeLine(cmd + args); if(getPrintOnlyMode()) return; @@ -3044,7 +3304,7 @@ out.writeLine("LIST AVAILABLE_ENGINES"); if(getPrintOnlyMode()) return null; - return parseQuotedStringList(getSingleLineResultSet().getResult()); + return parseStringList(getSingleLineResultSet().getResult()); } /** @@ -3515,7 +3775,7 @@ verifyConnection(); String s = String.valueOf(channel) + " " + String.valueOf(midiCtrl); - if(name != null) s += " '" + name + "'"; + if(name != null) s += " '" + toEscapedText(name) + "'"; out.writeLine("CREATE FX_SEND " + s); if(getPrintOnlyMode()) return -1; @@ -3646,7 +3906,7 @@ throws IOException, LscpException, LSException { verifyConnection(); - String args = " " + channel + " " + fxSend + " '" + name + "'"; + String args = " " + channel + " " + fxSend + " '" + toEscapedText(name) + "'"; out.writeLine("SET FX_SEND NAME" + args); if(getPrintOnlyMode()) return; @@ -3745,9 +4005,9 @@ * @see #getSamplerChannels */ public synchronized void - editInstrument(int samplerChn) throws IOException, LscpException, LSException { + editChannelInstrument(int samplerChn) throws IOException, LscpException, LSException { verifyConnection(); - out.writeLine("EDIT INSTRUMENT " + samplerChn); + out.writeLine("EDIT CHANNEL INSTRUMENT " + samplerChn); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -3765,7 +4025,7 @@ public synchronized void addDbDirectory(String dir) throws IOException, LSException, LscpException { verifyConnection(); - out.writeLine("ADD DB_INSTRUMENT_DIRECTORY '" + dir + "'"); + out.writeLine("ADD DB_INSTRUMENT_DIRECTORY '" + conv(dir) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -3800,7 +4060,7 @@ verifyConnection(); String s = "REMOVE DB_INSTRUMENT_DIRECTORY "; if(force) s += "FORCE "; - out.writeLine(s + "'" + dir + "'"); + out.writeLine(s + "'" + conv(dir) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -3823,7 +4083,7 @@ String cmd = "REMOVE DB_INSTRUMENT_DIRECTORY "; if(force) cmd += "FORCE "; - for(String s : dirs) out.writeLine(cmd + "'" + s + "'"); + for(String s : dirs) out.writeLine(cmd + "'" + conv(s) + "'"); if(getPrintOnlyMode()) return; @@ -3861,7 +4121,7 @@ String s; if(recursive) s = "GET DB_INSTRUMENT_DIRECTORIES RECURSIVE '"; else s = "GET DB_INSTRUMENT_DIRECTORIES '"; - out.writeLine(s + dir + "'"); + out.writeLine(s + conv(dir) + "'"); if(getPrintOnlyMode()) return -1; s = getSingleLineResultSet().getResult(); @@ -3880,7 +4140,7 @@ public synchronized String[] getDbDirectoryNames(String dir) throws IOException, LscpException, LSException { verifyConnection(); - out.writeLine("LIST DB_INSTRUMENT_DIRECTORIES '" + dir + "'"); + out.writeLine("LIST DB_INSTRUMENT_DIRECTORIES '" + conv(dir) + "'"); if(getPrintOnlyMode()) return null; String[] names = parseEscapedStringList(getSingleLineResultSet().getResult()); @@ -3902,7 +4162,7 @@ public synchronized DbDirectoryInfo getDbDirectoryInfo(String dir) throws IOException, LscpException, LSException { verifyConnection(); - out.writeLine("GET DB_INSTRUMENT_DIRECTORY INFO '" + dir + "'"); + out.writeLine("GET DB_INSTRUMENT_DIRECTORY INFO '" + conv(dir) + "'"); if(getPrintOnlyMode()) return null; ResultSet rs = getMultiLineResultSet(); @@ -3935,7 +4195,7 @@ if(!hasEndingFileSeparator(dir)) dir += "/"; DbDirectoryInfo[] infoS = new DbDirectoryInfo[dirS.length]; for(int i = 0; i < dirS.length; i++) { - infoS[i] = getDbDirectoryInfo(dir + toEscapedFileName(dirS[i])); + infoS[i] = getDbDirectoryInfo(conv(dir) + toEscapedFsEntry(dirS[i])); } return infoS; } @@ -3951,13 +4211,13 @@ * public synchronized DbDirectoryInfo[] getDbDirectories(String dir) throws IOException, LscpException, LSException { - String[] dirS = getDbDirectoryNames(dir); + String[] dirS = getDbDirectoryNames(conv(dir)); if(dirS.length == 0) return new DbDirectoryInfo[0]; - if(dir.charAt(dir.length() - 1) != '/') dir += "/"; + if(dir.charAt(dir.length() - 1) != '/') dir += "/"; // FIXME: for(int i = 0; i < dirS.length; i++) { - out.writeLine("GET DB_INSTRUMENT_DIRECTORY INFO '" + dir + dirS[i] + "'"); + out.writeLine("GET DB_INSTRUMENT_DIRECTORY INFO '" + conv(dir + dirS[i]) + "'"); } if(getPrintOnlyMode()) return null; @@ -4001,8 +4261,8 @@ public synchronized void renameDbDirectory(String dir, String name) throws IOException, LSException, LscpException { verifyConnection(); - name = toEscapedString(name); - out.writeLine("SET DB_INSTRUMENT_DIRECTORY NAME '" + dir + "' '" + name + "'"); + name = toEscapedText(name); + out.writeLine("SET DB_INSTRUMENT_DIRECTORY NAME '" + conv(dir) + "' '" + conv(name) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -4019,7 +4279,7 @@ public synchronized void moveDbDirectory(String dir, String dst) throws IOException, LSException, LscpException { verifyConnection(); - out.writeLine("MOVE DB_INSTRUMENT_DIRECTORY '" + dir + "' '" + dst + "'"); + out.writeLine("MOVE DB_INSTRUMENT_DIRECTORY '" + conv(dir) + "' '" + conv(dst) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -4037,7 +4297,7 @@ moveDbDirectories(String dirs[], String dst) throws IOException, LSException, LscpException { verifyConnection(); for(String s : dirs) { - out.writeLine("MOVE DB_INSTRUMENT_DIRECTORY '" + s + "' '" + dst + "'"); + out.writeLine("MOVE DB_INSTRUMENT_DIRECTORY '" + conv(s) + "' '" + conv(dst) + "'"); } if(getPrintOnlyMode()) return; @@ -4055,7 +4315,7 @@ public synchronized void copyDbDirectory(String dir, String dst) throws IOException, LSException, LscpException { verifyConnection(); - out.writeLine("COPY DB_INSTRUMENT_DIRECTORY '" + dir + "' '" + dst + "'"); + out.writeLine("COPY DB_INSTRUMENT_DIRECTORY '" + conv(dir) + "' '" + conv(dst) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -4073,7 +4333,7 @@ copyDbDirectories(String[] dirs, String dst) throws IOException, LSException, LscpException { verifyConnection(); for(String s : dirs) { - out.writeLine("COPY DB_INSTRUMENT_DIRECTORY '" + s + "' '" + dst + "'"); + out.writeLine("COPY DB_INSTRUMENT_DIRECTORY '" + conv(s) + "' '" + conv(dst) + "'"); } if(getPrintOnlyMode()) return; @@ -4094,7 +4354,7 @@ verifyConnection(); String s = "SET DB_INSTRUMENT_DIRECTORY DESCRIPTION '"; - out.writeLine(s + dir + "' '" + toEscapedString(desc) + "'"); + out.writeLine(s + conv(dir) + "' '" + toEscapedText(desc) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -4143,7 +4403,7 @@ verifyConnection(); String s = "ADD DB_INSTRUMENTS"; if(background) s += " NON_MODAL"; - s += " '" + dbDir + "' '" + filePath + "' "; + s += " '" + conv(dbDir) + "' '" + conv(filePath) + "' "; out.writeLine(s + String.valueOf(instrIndex)); if(getPrintOnlyMode()) return -1; @@ -4190,7 +4450,7 @@ verifyConnection(); String s = "ADD DB_INSTRUMENTS"; if(background) s += " NON_MODAL"; - out.writeLine(s + " '" + dbDir + "' '" + filePath + "'"); + out.writeLine(s + " '" + conv(dbDir) + "' '" + conv(filePath) + "'"); if(getPrintOnlyMode()) return -1; ResultSet rs = getEmptyResultSet(); @@ -4273,8 +4533,8 @@ break; } - sb.append(" '").append(dbDir).append("' '"); - sb.append(fsDir).append("'"); + sb.append(" '").append(conv(dbDir)).append("' '"); + sb.append(conv(fsDir)).append("'"); out.writeLine(sb.toString()); if(getPrintOnlyMode()) return -1; @@ -4293,7 +4553,7 @@ removeDbInstrument(String instr) throws IOException, LscpException, LSException { verifyConnection(); - out.writeLine("REMOVE DB_INSTRUMENT '" + instr + "'"); + out.writeLine("REMOVE DB_INSTRUMENT '" + conv(instr) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -4310,7 +4570,7 @@ removeDbInstruments(String[] instrs) throws IOException, LscpException, LSException { verifyConnection(); for(String s : instrs) { - out.writeLine("REMOVE DB_INSTRUMENT '" + s + "'"); + out.writeLine("REMOVE DB_INSTRUMENT '" + conv(s) + "'"); } if(getPrintOnlyMode()) return; @@ -4348,7 +4608,7 @@ String s; if(recursive) s = "GET DB_INSTRUMENTS RECURSIVE '"; else s = "GET DB_INSTRUMENTS '"; - out.writeLine(s + dir + "'"); + out.writeLine(s + conv(dir) + "'"); if(getPrintOnlyMode()) return -1; s = getSingleLineResultSet().getResult(); @@ -4367,7 +4627,7 @@ public synchronized String[] getDbInstrumentNames(String dir) throws IOException, LscpException, LSException { verifyConnection(); - out.writeLine("LIST DB_INSTRUMENTS '" + dir + "'"); + out.writeLine("LIST DB_INSTRUMENTS '" + conv(dir) + "'"); if(getPrintOnlyMode()) return null; String[] names = parseEscapedStringList(getSingleLineResultSet().getResult()); @@ -4389,7 +4649,7 @@ public synchronized DbInstrumentInfo getDbInstrumentInfo(String instr) throws IOException, LscpException, LSException { verifyConnection(); - out.writeLine("GET DB_INSTRUMENT INFO '" + instr + "'"); + out.writeLine("GET DB_INSTRUMENT INFO '" + conv(instr) + "'"); if(getPrintOnlyMode()) return null; ResultSet rs = getMultiLineResultSet(); @@ -4418,7 +4678,7 @@ DbInstrumentInfo[] infoS = new DbInstrumentInfo[instrS.length]; for(int i = 0; i < instrS.length; i++) { - infoS[i] = getDbInstrumentInfo(dir + toEscapedFileName(instrS[i])); + infoS[i] = getDbInstrumentInfo(conv(dir) + toEscapedFsEntry(instrS[i])); } return infoS; } @@ -4437,10 +4697,10 @@ String[] instrS = getDbInstrumentNames(dir); if(instrS.length == 0) return new DbInstrumentInfo[0]; - if(dir.charAt(dir.length() - 1) != '/') dir += "/"; + if(dir.charAt(dir.length() - 1) != '/') dir += "/"; FIXME: for(int i = 0; i < instrS.length; i++) { - out.writeLine("GET DB_INSTRUMENT INFO '" + dir + instrS[i] + "'"); + out.writeLine("GET DB_INSTRUMENT INFO '" + conv(dir) + instrS[i] + "'"); } if(getPrintOnlyMode()) return null; @@ -4486,8 +4746,8 @@ throws IOException, LSException, LscpException { verifyConnection(); - name = toEscapedString(name); - out.writeLine("SET DB_INSTRUMENT NAME '" + instr + "' '" + name + "'"); + name = toEscapedText(name); + out.writeLine("SET DB_INSTRUMENT NAME '" + conv(instr) + "' '" + conv(name) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -4504,7 +4764,7 @@ public synchronized void moveDbInstrument(String instr, String dst) throws IOException, LSException, LscpException { verifyConnection(); - out.writeLine("MOVE DB_INSTRUMENT '" + instr + "' '" + dst + "'"); + out.writeLine("MOVE DB_INSTRUMENT '" + conv(instr) + "' '" + conv(dst) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -4522,7 +4782,7 @@ moveDbInstruments(String[] instrs, String dst) throws IOException, LSException, LscpException { verifyConnection(); for(String s : instrs) { - out.writeLine("MOVE DB_INSTRUMENT '" + s + "' '" + dst + "'"); + out.writeLine("MOVE DB_INSTRUMENT '" + conv(s) + "' '" + conv(dst) + "'"); } if(getPrintOnlyMode()) return; @@ -4540,7 +4800,7 @@ public synchronized void copyDbInstrument(String instr, String dst) throws IOException, LSException, LscpException { verifyConnection(); - out.writeLine("COPY DB_INSTRUMENT '" + instr + "' '" + dst + "'"); + out.writeLine("COPY DB_INSTRUMENT '" + conv(instr) + "' '" + conv(dst) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -4558,7 +4818,7 @@ copyDbInstruments(String[] instrs, String dst) throws IOException, LSException, LscpException { verifyConnection(); for(String s : instrs) { - out.writeLine("COPY DB_INSTRUMENT '" + s + "' '" + dst + "'"); + out.writeLine("COPY DB_INSTRUMENT '" + conv(s) + "' '" + conv(dst) + "'"); } if(getPrintOnlyMode()) return; @@ -4578,8 +4838,28 @@ throws IOException, LSException, LscpException { verifyConnection(); - desc = toEscapedString(desc); - out.writeLine("SET DB_INSTRUMENT DESCRIPTION '" + instr + "' '" + desc + "'"); + desc = toEscapedText(desc); + out.writeLine("SET DB_INSTRUMENT DESCRIPTION '" + conv(instr) + "' '" + desc + "'"); + if(getPrintOnlyMode()) return; + + ResultSet rs = getEmptyResultSet(); + } + + /** + * Substitutes all occurrences of the instrument file + * oldPath in the database, with newPath. + * @param oldPath The absolute path name of the instrument file to substitute. + * @param newPath The new absolute path name. + * @throws IOException If some I/O error occurs. + * @throws LSException If the operation failed. + * @throws LscpException If LSCP protocol corruption occurs. + */ + public synchronized void + setDbInstrumentFilePath(String oldPath, String newPath) + throws IOException, LSException, LscpException { + + verifyConnection(); + out.writeLine("SET DB_INSTRUMENT FILE_PATH '" + conv(oldPath) + "' '" + conv(newPath) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -4623,10 +4903,10 @@ StringBuffer sb = new StringBuffer(); sb.append("FIND DB_INSTRUMENT_DIRECTORIES"); if(nonRecursive) sb.append(" NON_RECURSIVE"); - sb.append(" '").append(dir).append("'"); + sb.append(" '").append(conv(dir)).append("'"); if(query.name != null && query.name.length() > 0) { - sb.append(" NAME='").append(toEscapedString(query.name)).append("'"); + sb.append(" NAME='").append(toEscapedText(query.name)).append("'"); } String s = query.getCreatedAfter(); @@ -4651,7 +4931,7 @@ if(query.description != null && query.description.length() > 0) { sb.append(" DESCRIPTION='"); - sb.append(toEscapedString(query.description)).append("'"); + sb.append(toEscapedText(query.description)).append("'"); } out.writeLine(sb.toString()); @@ -4704,10 +4984,10 @@ StringBuffer sb = new StringBuffer(); sb.append("FIND DB_INSTRUMENTS"); if(nonRecursive) sb.append(" NON_RECURSIVE"); - sb.append(" '").append(dir).append("'"); + sb.append(" '").append(conv(dir)).append("'"); if(query.name != null && query.name.length() > 0) { - sb.append(" NAME='").append(toEscapedString(query.name)).append("'"); + sb.append(" NAME='").append(toEscapedText(query.name)).append("'"); } if(query.formatFamilies.size() > 0) { @@ -4748,7 +5028,7 @@ if(query.description != null && query.description.length() > 0) { sb.append(" DESCRIPTION='"); - sb.append(toEscapedString(query.description)).append("'"); + sb.append(toEscapedText(query.description)).append("'"); } if(query.instrumentType != DbSearchQuery.InstrumentType.BOTH) { @@ -4761,16 +5041,16 @@ } if(query.product != null && query.product.length() > 0) { - sb.append(" PRODUCT='").append(toEscapedString(query.product)).append("'"); + sb.append(" PRODUCT='").append(toEscapedText(query.product)).append("'"); } if(query.artists != null && query.artists.length() > 0) { - sb.append(" ARTISTS='").append(toEscapedString(query.artists)).append("'"); + sb.append(" ARTISTS='").append(toEscapedText(query.artists)).append("'"); } if(query.keywords != null && query.keywords.length() > 0) { sb.append(" KEYWORDS='"); - sb.append(toEscapedString(query.keywords)).append("'"); + sb.append(toEscapedText(query.keywords)).append("'"); } out.writeLine(sb.toString()); @@ -4786,6 +5066,23 @@ } /** + * Returns a list of all instrument files in the database + * that that don't exist in the filesystem. + * @throws IOException If some I/O error occurs. + * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If other error occurs. + */ + public synchronized String[] + findLostDbInstrumentFiles() throws IOException, LscpException, LSException { + + verifyConnection(); + out.writeLine("FIND LOST DB_INSTRUMENT_FILES"); + if(getPrintOnlyMode()) return null; + + return parseEscapedStringList(getSingleLineResultSet().getResult()); + } + + /** * Gets status information about the specified job. * @param jobId The ID of the job. * @return A ScanJobInfo instance providing information @@ -4807,6 +5104,22 @@ } /** + * Removes all instruments and directories and re-creates + * the instruments database structure. + * @throws IOException If some I/O error occurs. + * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If the formatting of the instruments database failed. + */ + public synchronized void + formatInstrumentsDb() throws IOException, LscpException, LSException { + verifyConnection(); + out.writeLine("FORMAT INSTRUMENTS_DB"); + if(getPrintOnlyMode()) return; + + ResultSet rs = getEmptyResultSet(); + } + + /** * Resets the specified sampler channel. * * @param samplerChn The sampler channel number. @@ -4843,6 +5156,23 @@ } /** + * Gets the current number of all active streams. + * @return The current number of all active streams. + * @throws IOException If some I/O error occurs. + * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If some other error occurs. + */ + public synchronized int + getTotalStreamCount() throws IOException, LscpException, LSException { + verifyConnection(); + out.writeLine("GET TOTAL_STREAM_COUNT"); + if(getPrintOnlyMode()) return -1; + + String s = getSingleLineResultSet().getResult(); + return parseInt(s); + } + + /** * Gets the current number of all active voices. * @return The current number of all active voices. * @throws IOException If some I/O error occurs. @@ -4931,6 +5261,86 @@ ResultSet rs = getEmptyResultSet(); } + /** + * Gets the number of instruments in the specified instrument file. + * @param filename The absolute path name of the instrument file. + * @return The number of instruments in the specified instrument file. + * @throws IOException If some I/O error occurs. + * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If the file is not found, or other error occur. + */ + public synchronized int + getFileInstrumentCount(String filename) throws IOException, LscpException, LSException { + verifyConnection(); + out.writeLine("GET FILE INSTRUMENTS '" + conv(filename) +"'"); + if(getPrintOnlyMode()) return -1; + + String s = getSingleLineResultSet().getResult(); + return parseInt(s); + } + + /** + * Gets information about the instrument with index + * instrIdx in the specified instrument file. + * @param filename The absolute path name of the instrument file. + * @param instrIdx The index of the instrument in the specified instrument file. + * @throws IOException If some I/O error occurs. + * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If failed to retrieve information. + */ + public synchronized Instrument + getFileInstrumentInfo(String filename, int instrIdx) + throws IOException, LscpException, LSException { + + verifyConnection(); + out.writeLine("GET FILE INSTRUMENT INFO '" + conv(filename) + "' " + String.valueOf(instrIdx)); + if(getPrintOnlyMode()) return null; + + ResultSet rs = getMultiLineResultSet(); + Instrument instr = new FileInstrument(rs.getMultiLineResult()) { }; + + return instr; + } + + /** + * Gets the list of instruments in the specified instrument file. + * @param filename The absolute path name of the instrument file. + * @return An Instrument array providing + * information about all instruments in the specified instrument file. + * @throws IOException If some I/O error occurs. + * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If the specified file name is invalid. + */ + public synchronized Instrument[] + getFileInstruments(String filename) throws IOException, LscpException, LSException { + int l = getFileInstrumentCount(filename); + if(l < 0) return null; + Instrument[] instrS = new FileInstrument[l]; + + for(int i = 0; i < instrS.length; i++) { + instrS[i] = getFileInstrumentInfo(filename, i); + } + return instrS; + } + + private static class FileInstrument extends AbstractInstrument { + FileInstrument(String[] resultSet) throws LscpException { + super(resultSet); + } + + public String + getEngine() { + // TODO: engine lookup? + return getFormatFamily(); + } + + public boolean + parse(String s) throws LscpException { + if(s.startsWith("PRODUCT: ") || s.startsWith("ARTISTS: ")) return true; + return super.parse(s); + } + } + private void getEmptyResultSets(int count, String err) throws LSException { StringBuffer sb = new StringBuffer();