1 package jalview.ext.pymol;
3 import jalview.bin.Cache;
4 import jalview.gui.Preferences;
5 import jalview.structure.StructureCommand;
6 import jalview.structure.StructureCommandI;
8 import java.io.BufferedReader;
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.io.InputStreamReader;
13 import java.io.PrintWriter;
15 import java.net.URLConnection;
16 import java.nio.file.Paths;
17 import java.util.ArrayList;
18 import java.util.List;
20 public class PymolManager
22 private static final int RPC_REPLY_TIMEOUT_MS = 15000;
24 private static final int CONNECTION_TIMEOUT_MS = 100;
26 private static final String POST1 = "<methodCall><methodName>";
28 private static final String POST2 = "</methodName><params>";
30 private static final String POST3 = "</params></methodCall>";
32 private Process pymolProcess;
34 private int pymolXmlRpcPort;
37 * Returns a list of paths to try for the PyMOL executable. Any user
38 * preference is placed first, otherwise 'standard' paths depending on the
43 public static List<String> getPymolPaths()
45 List<String> pathList = new ArrayList<>();
47 String userPath = Cache
48 .getDefault(Preferences.PYMOL_PATH, null);
51 pathList.add(0, userPath);
55 * add default installation paths
57 String pymol = "PyMOL";
58 String os = System.getProperty("os.name");
59 if (os.startsWith("Linux"))
61 pathList.add("/usr/local/pymol/bin/" + pymol);
62 pathList.add("/usr/local/bin/" + pymol);
63 pathList.add("/usr/bin/" + pymol);
64 pathList.add(System.getProperty("user.home") + "/opt/bin/" + pymol);
66 else if (os.startsWith("Windows"))
68 // todo Windows installation path(s)
70 else if (os.startsWith("Mac"))
72 pathList.add("/Applications/PyMOL.app/Contents/MacOS/" + pymol);
77 public boolean isPymolLaunched()
79 // TODO pull up generic methods for external viewer processes
80 boolean launched = false;
81 if (pymolProcess != null)
85 pymolProcess.exitValue();
86 // if we get here, process has ended
87 } catch (IllegalThreadStateException e)
89 // ok - not yet terminated
96 public void exitPymol()
98 if (isPymolLaunched() && pymolProcess != null)
100 sendCommand(new StructureCommand("quit"), false);
103 // currentModelsMap.clear();
104 this.pymolXmlRpcPort = 0;
108 * Sends the command to Pymol; if requested, tries to get and return any
109 * replies, else returns null
115 public List<String> sendCommand(StructureCommandI command,
118 String postBody = getPostRequest(command);
119 // System.out.println(postBody);// debug
120 String rpcUrl = "http://127.0.0.1:" + this.pymolXmlRpcPort;
121 PrintWriter out = null;
122 BufferedReader in = null;
123 List<String> result = new ArrayList<>();
126 URL realUrl = new URL(rpcUrl);
127 URLConnection conn = realUrl.openConnection();
128 conn.setRequestProperty("accept", "*/*");
129 conn.setRequestProperty("content-type", "text/xml");
130 conn.setDoOutput(true);
131 conn.setDoInput(true);
132 out = new PrintWriter(conn.getOutputStream());
135 in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
137 while ((line = in.readLine()) != null)
141 } catch (Exception e)
156 } catch (IOException ex)
158 ex.printStackTrace();
165 * Builds the body of the XML-RPC format POST request to execute the command
170 static String getPostRequest(StructureCommandI command)
172 StringBuilder sb = new StringBuilder(64);
173 sb.append(POST1).append(command.getCommand()).append(POST2);
174 if (command.hasParameters())
176 for (String p : command.getParameters())
179 * for now assuming all are string - <string> element is optional
180 * refactor in future if other data types needed
181 * https://www.tutorialspoint.com/xml-rpc/xml_rpc_data_model.htm
183 sb.append("<parameter><value>").append(p)
184 .append("</value></parameter>");
188 return sb.toString();
191 public boolean launchPymol()
193 // todo pull up much of this
194 // Do nothing if already launched
195 if (isPymolLaunched())
200 String error = "Error message: ";
201 for (String pymolPath : getPymolPaths())
205 // ensure symbolic links are resolved
206 pymolPath = Paths.get(pymolPath).toRealPath().toString();
207 File path = new File(pymolPath);
208 // uncomment the next line to simulate Pymol not installed
209 // path = new File(pymolPath + "x");
210 if (!path.canExecute())
212 error += "File '" + path + "' does not exist.\n";
215 List<String> args = new ArrayList<>();
217 args.add("-R"); // https://pymolwiki.org/index.php/RPC
218 ProcessBuilder pb = new ProcessBuilder(args);
219 pymolProcess = pb.start();
222 } catch (Exception e)
224 // pPymol could not be started using this path
225 error += e.getMessage();
228 if (error.length() == 0)
230 this.pymolXmlRpcPort = getPortNumber();
232 "PyMOL XMLRPC started on port " + pymolXmlRpcPort);
233 return (pymolXmlRpcPort > 0);
236 // logger.warn(error);
240 private int getPortNumber()
242 // TODO pull up most of this!
244 InputStream readChan = pymolProcess.getInputStream();
245 BufferedReader lineReader = new BufferedReader(
246 new InputStreamReader(readChan));
247 StringBuilder responses = new StringBuilder();
250 String response = lineReader.readLine();
251 while (response != null)
253 responses.append("\n" + response);
254 // expect: xml-rpc server running on host localhost, port 9123
255 if (response.contains("xml-rpc"))
257 String[] tokens = response.split(" ");
258 for (int i = 0; i < tokens.length - 1; i++)
260 if ("port".equals(tokens[i]))
262 port = Integer.parseInt(tokens[i + 1]);
269 break; // hack for hanging readLine()
271 response = lineReader.readLine();
273 } catch (Exception e)
276 "Failed to get REST port number from " + responses + ": "
278 // logger.error("Failed to get REST port number from " + responses + ": "
279 // + e.getMessage());
285 } catch (IOException e2)
291 System.err.println("Failed to start PyMOL with XMLRPC, response was: "
294 System.err.println("PyMOL started with XMLRPC on port " + port);