1 package jalview.ext.pymol;
3 import java.io.BufferedReader;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.io.InputStreamReader;
8 import java.io.PrintWriter;
9 import java.net.HttpURLConnection;
10 import java.net.SocketException;
12 import java.nio.file.Paths;
13 import java.util.ArrayList;
14 import java.util.List;
16 import jalview.bin.Cache;
17 import jalview.bin.Console;
18 import jalview.gui.Preferences;
19 import jalview.structure.StructureCommand;
20 import jalview.structure.StructureCommandI;
22 public class PymolManager
24 private static final int RPC_REPLY_TIMEOUT_MS = 15000;
26 private static final int CONNECTION_TIMEOUT_MS = 100;
28 private static final String POST1 = "<methodCall><methodName>";
30 private static final String POST2 = "</methodName><params>";
32 private static final String POST3 = "</params></methodCall>";
34 private Process pymolProcess;
36 private int pymolXmlRpcPort;
39 * Returns a list of paths to try for the PyMOL executable. Any user
40 * preference is placed first, otherwise 'standard' paths depending on the
45 public static List<String> getPymolPaths()
47 return getPymolPaths(System.getProperty("os.name"));
51 * Returns a list of paths to try for the PyMOL executable. Any user
52 * preference is placed first, otherwise 'standard' paths depending on the
56 * operating system as reported by environment variable
60 protected static List<String> getPymolPaths(String os)
62 List<String> pathList = new ArrayList<>();
64 String userPath = Cache
65 .getDefault(Preferences.PYMOL_PATH, null);
68 pathList.add(userPath);
72 * add default installation paths
74 String pymol = "PyMOL";
75 if (os.startsWith("Linux"))
77 pathList.add("/usr/local/pymol/bin/" + pymol);
78 pathList.add("/usr/local/bin/" + pymol);
79 pathList.add("/usr/bin/" + pymol);
80 pathList.add(System.getProperty("user.home") + "/opt/bin/" + pymol);
82 else if (os.startsWith("Windows"))
84 // todo Windows installation path(s)
86 else if (os.startsWith("Mac"))
88 pathList.add("/Applications/PyMOL.app/Contents/MacOS/" + pymol);
93 public boolean isPymolLaunched()
95 // TODO pull up generic methods for external viewer processes
96 boolean launched = false;
97 if (pymolProcess != null)
101 pymolProcess.exitValue();
102 // if we get here, process has ended
103 } catch (IllegalThreadStateException e)
105 // ok - not yet terminated
113 * Sends the command to Pymol; if requested, tries to get and return any
114 * replies, else returns null
120 public List<String> sendCommand(StructureCommandI command,
123 String postBody = getPostRequest(command);
124 // System.out.println(postBody);// debug
125 String rpcUrl = "http://127.0.0.1:" + this.pymolXmlRpcPort;
126 PrintWriter out = null;
127 BufferedReader in = null;
128 List<String> result = getReply ? new ArrayList<>() : null;
132 URL realUrl = new URL(rpcUrl);
133 HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();
134 conn.setRequestProperty("accept", "*/*");
135 conn.setRequestProperty("content-type", "text/xml");
136 conn.setDoOutput(true);
137 conn.setDoInput(true);
138 out = new PrintWriter(conn.getOutputStream());
141 int rc = conn.getResponseCode();
142 if (rc != HttpURLConnection.HTTP_OK)
145 String.format("Error status from %s: %d", rpcUrl, rc));
149 InputStream inputStream = conn.getInputStream();
152 in = new BufferedReader(new InputStreamReader(inputStream));
154 while ((line = in.readLine()) != null)
159 } catch (SocketException e)
161 // thrown when 'quit' command is sent to PyMol
162 Console.warn(String.format("Request to %s returned %s", rpcUrl,
164 } catch (Exception e)
173 if (Console.isTraceEnabled())
175 Console.trace("Sent: " + command.toString());
178 Console.trace("Received: " + result);
186 * Builds the body of the XML-RPC format POST request to execute the command
191 static String getPostRequest(StructureCommandI command)
193 StringBuilder sb = new StringBuilder(64);
194 sb.append(POST1).append(command.getCommand()).append(POST2);
195 if (command.hasParameters())
197 for (String p : command.getParameters())
200 * for now assuming all are string - <string> element is optional
201 * refactor in future if other data types needed
202 * https://www.tutorialspoint.com/xml-rpc/xml_rpc_data_model.htm
204 sb.append("<parameter><value>").append(p)
205 .append("</value></parameter>");
209 return sb.toString();
212 public Process launchPymol()
214 // todo pull up much of this
215 // Do nothing if already launched
216 if (isPymolLaunched())
221 String error = "Error message: ";
222 for (String pymolPath : getPymolPaths())
226 // ensure symbolic links are resolved
227 pymolPath = Paths.get(pymolPath).toRealPath().toString();
228 File path = new File(pymolPath);
229 // uncomment the next line to simulate Pymol not installed
230 // path = new File(pymolPath + "x");
231 if (!path.canExecute())
233 error += "File '" + path + "' does not exist.\n";
236 List<String> args = new ArrayList<>();
238 args.add("-R"); // https://pymolwiki.org/index.php/RPC
239 ProcessBuilder pb = new ProcessBuilder(args);
240 pymolProcess = pb.start();
243 } catch (Exception e)
245 // Pymol could not be started using this path
246 error += e.getMessage();
250 if (pymolProcess != null)
252 this.pymolXmlRpcPort = getPortNumber();
253 if (pymolXmlRpcPort > 0)
255 Console.info("PyMOL XMLRPC started on port " + pymolXmlRpcPort);
259 error += "Failed to read PyMOL XMLRPC port number";
260 Console.error(error);
261 pymolProcess.destroy();
269 private int getPortNumber()
271 // TODO pull up most of this!
273 InputStream readChan = pymolProcess.getInputStream();
274 BufferedReader lineReader = new BufferedReader(
275 new InputStreamReader(readChan));
276 StringBuilder responses = new StringBuilder();
279 String response = lineReader.readLine();
280 while (response != null)
282 responses.append("\n" + response);
283 // expect: xml-rpc server running on host localhost, port 9123
284 if (response.contains("xml-rpc"))
286 String[] tokens = response.split(" ");
287 for (int i = 0; i < tokens.length - 1; i++)
289 if ("port".equals(tokens[i]))
291 port = Integer.parseInt(tokens[i + 1]);
298 break; // hack for hanging readLine()
300 response = lineReader.readLine();
302 } catch (Exception e)
304 Console.error("Failed to get REST port number from " + responses
305 + ": " + e.getMessage());
306 // logger.error("Failed to get REST port number from " + responses + ": "
307 // + e.getMessage());
313 } catch (IOException e2)
319 Console.error("Failed to start PyMOL with XMLRPC, response was: "
322 Console.error("PyMOL started with XMLRPC on port " + port);