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.gui.Preferences;
18 import jalview.structure.StructureCommand;
19 import jalview.structure.StructureCommandI;
21 public class PymolManager
23 private static final int RPC_REPLY_TIMEOUT_MS = 15000;
25 private static final int CONNECTION_TIMEOUT_MS = 100;
27 private static final String POST1 = "<methodCall><methodName>";
29 private static final String POST2 = "</methodName><params>";
31 private static final String POST3 = "</params></methodCall>";
33 private Process pymolProcess;
35 private int pymolXmlRpcPort;
38 * Returns a list of paths to try for the PyMOL executable. Any user
39 * preference is placed first, otherwise 'standard' paths depending on the
44 public static List<String> getPymolPaths()
46 List<String> pathList = new ArrayList<>();
48 String userPath = Cache
49 .getDefault(Preferences.PYMOL_PATH, null);
52 pathList.add(0, userPath);
56 * add default installation paths
58 String pymol = "PyMOL";
59 String os = System.getProperty("os.name");
60 if (os.startsWith("Linux"))
62 pathList.add("/usr/local/pymol/bin/" + pymol);
63 pathList.add("/usr/local/bin/" + pymol);
64 pathList.add("/usr/bin/" + pymol);
65 pathList.add(System.getProperty("user.home") + "/opt/bin/" + pymol);
67 else if (os.startsWith("Windows"))
69 // todo Windows installation path(s)
71 else if (os.startsWith("Mac"))
73 pathList.add("/Applications/PyMOL.app/Contents/MacOS/" + pymol);
78 public boolean isPymolLaunched()
80 // TODO pull up generic methods for external viewer processes
81 boolean launched = false;
82 if (pymolProcess != null)
86 pymolProcess.exitValue();
87 // if we get here, process has ended
88 } catch (IllegalThreadStateException e)
90 // ok - not yet terminated
97 public void exitPymol()
99 if (isPymolLaunched() && pymolProcess != null)
101 sendCommand(new StructureCommand("quit"), false);
104 // currentModelsMap.clear();
105 this.pymolXmlRpcPort = 0;
109 * Sends the command to Pymol; if requested, tries to get and return any
110 * replies, else returns null
116 public List<String> sendCommand(StructureCommandI command,
119 String postBody = getPostRequest(command);
120 // System.out.println(postBody);// debug
121 String rpcUrl = "http://127.0.0.1:" + this.pymolXmlRpcPort;
122 PrintWriter out = null;
123 BufferedReader in = null;
124 List<String> result = getReply ? new ArrayList<>() : null;
128 URL realUrl = new URL(rpcUrl);
129 HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();
130 conn.setRequestProperty("accept", "*/*");
131 conn.setRequestProperty("content-type", "text/xml");
132 conn.setDoOutput(true);
133 conn.setDoInput(true);
134 out = new PrintWriter(conn.getOutputStream());
137 int rc = conn.getResponseCode();
138 if (rc != HttpURLConnection.HTTP_OK)
141 String.format("Error status from %s: %d", rpcUrl, rc));
145 InputStream inputStream = conn.getInputStream();
148 in = new BufferedReader(new InputStreamReader(inputStream));
150 while ((line = in.readLine()) != null)
155 } catch (SocketException e)
157 // thrown when 'quit' command is sent to PyMol
158 Cache.log.warn(String.format("Request to %s returned %s", rpcUrl,
160 } catch (Exception e)
174 * Builds the body of the XML-RPC format POST request to execute the command
179 static String getPostRequest(StructureCommandI command)
181 StringBuilder sb = new StringBuilder(64);
182 sb.append(POST1).append(command.getCommand()).append(POST2);
183 if (command.hasParameters())
185 for (String p : command.getParameters())
188 * for now assuming all are string - <string> element is optional
189 * refactor in future if other data types needed
190 * https://www.tutorialspoint.com/xml-rpc/xml_rpc_data_model.htm
192 sb.append("<parameter><value>").append(p)
193 .append("</value></parameter>");
197 return sb.toString();
200 public boolean launchPymol()
202 // todo pull up much of this
203 // Do nothing if already launched
204 if (isPymolLaunched())
209 String error = "Error message: ";
210 for (String pymolPath : getPymolPaths())
214 // ensure symbolic links are resolved
215 pymolPath = Paths.get(pymolPath).toRealPath().toString();
216 File path = new File(pymolPath);
217 // uncomment the next line to simulate Pymol not installed
218 // path = new File(pymolPath + "x");
219 if (!path.canExecute())
221 error += "File '" + path + "' does not exist.\n";
224 List<String> args = new ArrayList<>();
226 args.add("-R"); // https://pymolwiki.org/index.php/RPC
227 ProcessBuilder pb = new ProcessBuilder(args);
228 pymolProcess = pb.start();
231 } catch (Exception e)
233 // pPymol could not be started using this path
234 error += e.getMessage();
237 if (error.length() == 0)
239 this.pymolXmlRpcPort = getPortNumber();
241 "PyMOL XMLRPC started on port " + pymolXmlRpcPort);
242 return (pymolXmlRpcPort > 0);
245 // logger.warn(error);
249 private int getPortNumber()
251 // TODO pull up most of this!
253 InputStream readChan = pymolProcess.getInputStream();
254 BufferedReader lineReader = new BufferedReader(
255 new InputStreamReader(readChan));
256 StringBuilder responses = new StringBuilder();
259 String response = lineReader.readLine();
260 while (response != null)
262 responses.append("\n" + response);
263 // expect: xml-rpc server running on host localhost, port 9123
264 if (response.contains("xml-rpc"))
266 String[] tokens = response.split(" ");
267 for (int i = 0; i < tokens.length - 1; i++)
269 if ("port".equals(tokens[i]))
271 port = Integer.parseInt(tokens[i + 1]);
278 break; // hack for hanging readLine()
280 response = lineReader.readLine();
282 } catch (Exception e)
285 "Failed to get REST port number from " + responses + ": "
287 // logger.error("Failed to get REST port number from " + responses + ": "
288 // + e.getMessage());
294 } catch (IOException e2)
300 System.err.println("Failed to start PyMOL with XMLRPC, response was: "
303 System.err.println("PyMOL started with XMLRPC on port " + port);