package ini.trakem2.utils; import ini.trakem2.Project; import ini.trakem2.persistence.FSLoader; import ini.trakem2.persistence.Loader; import ini.trakem2.persistence.XMLOptions; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Writer; import java.net.ServerSocket; import java.net.Socket; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** Send any input to port 29391 and TrakEM2 will * try to save all {@link FSLoader} projects to XML files. * * For example: * <pre> * $ telnet localhost 29391 * > save * Saving... * Saved project[1] to: /path/to/file.xml * > * </pre> * * Commands: * <ul> * <li>save : saves the project to its own XML file, or to an automatic location if it was never saved before; prints the file name.</li> * <li>saveas : saves to a new XML file (never overwriting the existing XML file); prints that file name.</li> * <li>stream : outputs the XML file into the remote terminal.</li> * <li>quit : disconnect.</li> * </ul> */ public class RedPhone { /** May change up to 31000, searching for a port not in use; * the port that will be used is printed. */ public int port = 29391; ServerSocket server = null; final Set<Socket> openConnections = Collections.synchronizedSet(new HashSet<Socket>()); private ThreadGroup group = new ThreadGroup("RedPhone"); final private Object WRITE = new Object(); synchronized public void quit() { if (null != server) { try { group.interrupt(); // Wait until the last round of writing commands completes synchronized (WRITE) { server.close(); for (Socket s : openConnections.toArray(new Socket[0])) { s.close(); } } } catch (IOException e) { System.out.println("Error closing RedPhone socket:"); e.printStackTrace(); } } } synchronized public void start() { while (true) { try { server = new ServerSocket(port); server.setReuseAddress(true); break; } catch (IOException e) { port++; if (port > 31000) { System.out.println("Red phone not running: no port available"); return; } } } System.out.println("Red phone at port " + port); new Thread(group, "RedPhone-server") { { setPriority(Thread.NORM_PRIORITY); } @Override public void run() { final HashMap<String,Action> cmds = new HashMap<String,Action>(); cmds.put("save", new Save()); cmds.put("saveas", new SaveAs()); cmds.put("stream", new Stream()); // while (!isInterrupted()) { Socket s = null; try { s = server.accept(); // blocks until there is input System.out.println("Connected."); new Connection(s, cmds).start(); } catch (IOException e) { if (!isInterrupted()) e.printStackTrace(); System.out.println("RedPhone hang up!"); }/* finally { // Don't close the Socket s, would close the server. }*/ } } }.start(); } private class Connection extends Thread { private final Socket s; private final Map<String,Action> cmds; Connection(final Socket s, final Map<String,Action> cmds) { super(group, "RedPhone-connection"); this.s = s; this.cmds = cmds; openConnections.add(s); setPriority(Thread.NORM_PRIORITY); } @Override public void run() { try { final IO io = new IO(s); while (!isInterrupted()) { String command = io.readOneLine(); System.out.println("RedPhone read command: " + command); synchronized (WRITE) { if (null == command || "quit".equals(command.trim().toLowerCase())) { io.writeLine("OK bye!"); s.close(); openConnections.remove(s); return; } command = command.trim().toLowerCase(); // Validate if (0 == command.length()) { continue; } final Action action = cmds.get(command); if (null == action) { io.writeLine("Do not know command: " + command); continue; } // try { int count = 0; for (final Project p : Project.getProjects()) { final Loader l = p.getLoader(); if (l instanceof FSLoader) { action.execute(p, (FSLoader)l, io, count); } else { final String answer = "Could not save project[" + count + "]: not an XML-based project."; System.out.println(answer); io.writeLine(answer); } count += 1; } } catch (IOException ioe) { ioe.printStackTrace(); if (!s.isClosed()) ioe.printStackTrace(new PrintWriter(s.getOutputStream())); } } } } catch (IOException ioe) { System.out.println("Red phone hang up!"); if (!isInterrupted()) { ioe.printStackTrace(); if (!s.isClosed()) try { ioe.printStackTrace(new PrintWriter(s.getOutputStream())); } catch (IOException e) { e.printStackTrace(); } } } } } private abstract class Action { protected abstract void exec(Project p, FSLoader fl, IO io, int count) throws IOException; public void execute(Project p, FSLoader fl, IO io, int count) { try { exec(p, fl, io, count); } catch (IOException ioe) { ioe.printStackTrace(); try { ioe.printStackTrace(new PrintWriter(io.out)); } catch (Throwable t) { t.printStackTrace(); } } } } private class Save extends Action { @Override protected void exec(Project p, FSLoader fl, IO io, int count) throws IOException { io.writeLine("Saving..."); String path = fl.getProjectXMLPath(); if (null == path) { new SaveAs().exec(p, fl, io, count); return; } io.writeLine("Saved: " + p.save()); } } private class SaveAs extends Action { @Override protected void exec(Project p, FSLoader fl, IO io, int count) throws IOException { final String path = handlePath(fl.getProjectXMLPath()); io.writeLine("Saving..."); p.saveAs(path, false); final String answer = "Saved project[" + count + "] to: " + path; System.out.println(answer); io.writeLine(answer); } } private class Stream extends Action { @Override protected void exec(Project p, FSLoader fl, IO io, int count) throws IOException { io.writeLine("Streaming: " + fl.getProjectXMLPath()); try { XMLOptions options = new XMLOptions(); options.export_images = false; options.patches_dir = null; options.include_coordinate_transform = true; fl.writeXMLTo(p, io.out, options); } catch (Exception e) { e.printStackTrace(); io.writeLine("Could not stream project " + fl.getProjectXMLPath()); } } } private String handlePath(String path) { if (null == path) { String dir = System.getProperty("user.dir"); int count = 1; do { path = dir + "/trakem2-recovered-" + count + ".xml"; count += 1; } while (new File(path).exists()); return path; } int count = 1; String p = path; while (new File(p).exists()) { p = path + "-" + count + ".xml"; count += 1; } return p; } /** Note: closing the Socket also closes the underlying streams. */ private final class IO { private final BufferedReader in; private final Writer out; IO(final Socket s) throws IOException { this.in = new BufferedReader(new InputStreamReader(s.getInputStream())); this.out = new OutputStreamWriter(new BufferedOutputStream(s.getOutputStream())); } final String readOneLine() throws IOException { return in.readLine(); } final void writeLine(final String answer) throws IOException { out.append(answer); out.append('\n'); out.flush(); } } }