Clover icon

Coverage Report

  1. Project Clover database Mon Nov 11 2024 17:27:16 GMT
  2. Package jalview.ext.pymol

File PymolManager.java

 

Coverage histogram

../../../img/srcFileCovDistChart2.png
54% of files have more coverage

Code metrics

48
122
7
1
376
275
38
0.31
17.43
7
5.43

Classes

Class Line # Actions
PymolManager 44 122 38
0.1977401119.8%
 

Contributing tests

This file is covered by 2 tests. .

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
7    * Jalview is free software: you can redistribute it and/or
8    * modify it under the terms of the GNU General Public License
9    * as published by the Free Software Foundation, either version 3
10    * of the License, or (at your option) any later version.
11    *
12    * Jalview is distributed in the hope that it will be useful, but
13    * WITHOUT ANY WARRANTY; without even the implied warranty
14    * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15    * PURPOSE. See the GNU General Public License for more details.
16    *
17    * You should have received a copy of the GNU General Public License
18    * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19    * The Jalview Authors are detailed in the 'AUTHORS' file.
20    */
21    package jalview.ext.pymol;
22   
23    import java.io.BufferedReader;
24    import java.io.File;
25    import java.io.IOException;
26    import java.io.InputStream;
27    import java.io.InputStreamReader;
28    import java.io.PrintWriter;
29    import java.net.HttpURLConnection;
30    import java.net.SocketException;
31    import java.net.URL;
32    import java.nio.file.Paths;
33    import java.util.ArrayList;
34    import java.util.List;
35    import java.util.Locale;
36   
37    import jalview.bin.Cache;
38    import jalview.bin.Console;
39    import jalview.gui.Preferences;
40    import jalview.structure.StructureCommandI;
41    import jalview.util.HttpUtils;
42    import jalview.util.Platform;
43   
 
44    public class PymolManager
45    {
46    private static final int RPC_REPLY_TIMEOUT_MS = 15000;
47   
48    private static final int CONNECTION_TIMEOUT_MS = 100;
49   
50    private static final String POST1 = "<methodCall><methodName>";
51   
52    private static final String POST2 = "</methodName><params>";
53   
54    private static final String POST3 = "</params></methodCall>";
55   
56    private Process pymolProcess;
57   
58    private int pymolXmlRpcPort;
59   
60    /**
61    * Returns a list of paths to try for the PyMOL executable. Any user
62    * preference is placed first, otherwise 'standard' paths depending on the
63    * operating system.
64    *
65    * @return
66    */
 
67  0 toggle public static List<String> getPymolPaths()
68    {
69  0 return getPymolPaths(System.getProperty("os.name"));
70    }
71   
72    /**
73    * Returns a list of paths to try for the PyMOL executable. Any user
74    * preference is placed first, otherwise 'standard' paths depending on the
75    * operating system.
76    *
77    * @param os
78    * operating system as reported by environment variable
79    * {@code os.name}
80    * @return
81    */
 
82  4 toggle protected static List<String> getPymolPaths(String os)
83    {
84  4 List<String> pathList = new ArrayList<>();
85   
86  4 String userPath = Cache.getDefault(Preferences.PYMOL_PATH, null);
87  4 if (userPath != null)
88    {
89  0 pathList.add(userPath);
90    }
91   
92    /*
93    * add default installation paths
94    */
95  4 String pymol = "PyMOL";
96  4 if (os.startsWith("Linux"))
97    {
98  1 pathList.add("/usr/local/pymol/bin/" + pymol);
99  1 pathList.add("/usr/local/bin/" + pymol);
100  1 pathList.add("/usr/bin/" + pymol);
101  1 pathList.add(System.getProperty("user.home") + "/opt/bin/" + pymol);
102    }
103  3 else if (os.startsWith("Windows"))
104    {
105  1 for (String root : new String[] {
106    String.format("%s\\AppData\\Local",
107    System.getProperty("user.home")), // default user path
108    "\\ProgramData", "C:\\ProgramData", // this is the default install
109    // path "for everyone"
110    System.getProperty("user.home"), "\\Program Files",
111    "C:\\Program Files", "\\Program Files (x86)",
112    "C:\\Program Files (x86)" })
113    {
114  8 for (String path : new String[] { "Schrodinger\\PyMOL2", "PyMOL" })
115    {
116  16 for (String binary : new String[] { "PyMOLWinWithConsole.bat",
117    "Scripts\\pymol.exe", "PyMOLWin.exe" })
118    {
119  48 pathList.add(String.format("%s\\%s\\%s", root, path, binary));
120    }
121    }
122    }
123    }
124  2 else if (os.startsWith("Mac"))
125    {
126  1 pathList.add("/Applications/PyMOL.app/Contents/MacOS/" + pymol);
127    }
128  4 return pathList;
129    }
130   
 
131  0 toggle public boolean isPymolLaunched()
132    {
133    // TODO pull up generic methods for external viewer processes
134  0 boolean launched = false;
135  0 if (pymolProcess != null)
136    {
137  0 try
138    {
139  0 pymolProcess.exitValue();
140    // if we get here, process has ended
141    } catch (IllegalThreadStateException e)
142    {
143    // ok - not yet terminated
144  0 launched = true;
145    }
146    }
147  0 return launched;
148    }
149   
150    /**
151    * Sends the command to Pymol; if requested, tries to get and return any
152    * replies, else returns null
153    *
154    * @param command
155    * @param getReply
156    * @return
157    */
 
158  0 toggle public List<String> sendCommand(StructureCommandI command,
159    boolean getReply)
160    {
161  0 String postBody = getPostRequest(command);
162    // jalview.bin.Console.outPrintln(postBody);// debug
163  0 String rpcUrl = "http://127.0.0.1:" + this.pymolXmlRpcPort;
164  0 PrintWriter out = null;
165  0 BufferedReader in = null;
166  0 List<String> result = getReply ? new ArrayList<>() : null;
167   
168  0 try
169    {
170  0 URL realUrl = new URL(rpcUrl);
171  0 HttpURLConnection conn = (HttpURLConnection) HttpUtils
172    .openConnection(realUrl);
173  0 conn.setRequestProperty("accept", "*/*");
174  0 conn.setRequestProperty("content-type", "text/xml");
175  0 conn.setDoOutput(true);
176  0 conn.setDoInput(true);
177  0 out = new PrintWriter(conn.getOutputStream());
178  0 out.print(postBody);
179  0 out.flush();
180  0 int rc = conn.getResponseCode();
181  0 if (rc != HttpURLConnection.HTTP_OK)
182    {
183  0 Console.error(
184    String.format("Error status from %s: %d", rpcUrl, rc));
185  0 return result;
186    }
187   
188  0 InputStream inputStream = conn.getInputStream();
189  0 if (getReply)
190    {
191  0 in = new BufferedReader(new InputStreamReader(inputStream));
192  0 String line;
193  0 while ((line = in.readLine()) != null)
194    {
195  0 result.add(line);
196    }
197    }
198    } catch (SocketException e)
199    {
200    // thrown when 'quit' command is sent to PyMol
201  0 Console.warn(String.format("Request to %s returned %s", rpcUrl,
202    e.toString()));
203    } catch (Exception e)
204    {
205  0 e.printStackTrace();
206    } finally
207    {
208  0 if (out != null)
209    {
210  0 out.close();
211    }
212  0 if (Console.isTraceEnabled())
213    {
214  0 Console.trace("Sent: " + command.toString());
215  0 if (result != null)
216    {
217  0 Console.trace("Received: " + result);
218    }
219    }
220    }
221  0 return result;
222    }
223   
224    /**
225    * Builds the body of the XML-RPC format POST request to execute the command
226    *
227    * @param command
228    * @return
229    */
 
230  2 toggle static String getPostRequest(StructureCommandI command)
231    {
232  2 StringBuilder sb = new StringBuilder(64);
233  2 sb.append(POST1).append(command.getCommand()).append(POST2);
234  2 if (command.hasParameters())
235    {
236  1 for (String p : command.getParameters())
237    {
238    /*
239    * for now assuming all are string - <string> element is optional
240    * refactor in future if other data types needed
241    * https://www.tutorialspoint.com/xml-rpc/xml_rpc_data_model.htm
242    */
243  2 sb.append("<parameter><value>").append(p)
244    .append("</value></parameter>");
245    }
246    }
247  2 sb.append(POST3);
248  2 return sb.toString();
249    }
250   
 
251  0 toggle public Process launchPymol()
252    {
253    // todo pull up much of this
254    // Do nothing if already launched
255  0 if (isPymolLaunched())
256    {
257  0 return pymolProcess;
258    }
259   
260  0 String error = "Error message: ";
261  0 for (String pymolPath : getPymolPaths())
262    {
263  0 Console.debug("Using '" + pymolPath + "' to look for Pymol");
264  0 try
265    {
266    // ensure symbolic links are resolved
267  0 pymolPath = Paths.get(pymolPath).toRealPath().toString();
268  0 File path = new File(pymolPath);
269    // uncomment the next line to simulate Pymol not installed
270    // path = new File(pymolPath + "x");
271  0 if (!path.canExecute())
272    {
273  0 Console.debug("Cannot execute " + path.toString());
274  0 error += "File '" + path + "' does not exist.\n";
275  0 continue;
276    }
277  0 List<String> args = new ArrayList<>();
278  0 args.add(pymolPath);
279   
280    // Windows PyMOLWin.exe needs an extra argument
281  0 if (Platform.isWin() && pymolPath.toLowerCase(Locale.ROOT)
282    .endsWith("\\pymolwin.exe"))
283    {
284  0 args.add("+2");
285    }
286  0 args.add("-R"); // https://pymolwiki.org/index.php/RPC
287  0 ProcessBuilder pb = new ProcessBuilder(args);
288  0 Console.debug("Running PyMOL with '"
289    + String.join(" ", pb.command()) + "'");
290  0 pymolProcess = pb.start();
291  0 error = "";
292  0 break;
293    } catch (Exception e)
294    {
295    // Pymol could not be started using this path
296  0 error += e.getMessage();
297    }
298    }
299   
300  0 if (pymolProcess != null)
301    {
302  0 this.pymolXmlRpcPort = getPortNumber();
303  0 if (pymolXmlRpcPort > 0)
304    {
305  0 Console.info("PyMOL XMLRPC started on port " + pymolXmlRpcPort);
306    }
307    else
308    {
309  0 error += "Failed to read PyMOL XMLRPC port number";
310  0 Console.error(error);
311  0 pymolProcess.destroy();
312  0 pymolProcess = null;
313    }
314    }
315   
316  0 return pymolProcess;
317    }
318   
 
319  0 toggle private int getPortNumber()
320    {
321    // TODO pull up most of this!
322  0 int port = 0;
323  0 InputStream readChan = pymolProcess.getInputStream();
324  0 BufferedReader lineReader = new BufferedReader(
325    new InputStreamReader(readChan));
326  0 StringBuilder responses = new StringBuilder();
327  0 try
328    {
329  0 String response = lineReader.readLine();
330  0 while (response != null)
331    {
332  0 responses.append("\n" + response);
333    // expect: xml-rpc server running on host localhost, port 9123
334  0 if (response.contains("xml-rpc"))
335    {
336  0 String[] tokens = response.split(" ");
337  0 for (int i = 0; i < tokens.length - 1; i++)
338    {
339  0 if ("port".equals(tokens[i]))
340    {
341  0 port = Integer.parseInt(tokens[i + 1]);
342  0 break;
343    }
344    }
345    }
346  0 if (port > 0)
347    {
348  0 break; // hack for hanging readLine()
349    }
350  0 response = lineReader.readLine();
351    }
352    } catch (Exception e)
353    {
354  0 Console.error("Failed to get REST port number from " + responses
355    + ": " + e.getMessage());
356    // logger.error("Failed to get REST port number from " + responses + ": "
357    // + e.getMessage());
358    } finally
359    {
360  0 try
361    {
362  0 lineReader.close();
363    } catch (IOException e2)
364    {
365    }
366    }
367  0 if (port == 0)
368    {
369  0 Console.error("Failed to start PyMOL with XMLRPC, response was: "
370    + responses);
371    }
372  0 Console.info("PyMOL started with XMLRPC on port " + port);
373  0 return port;
374    }
375   
376    }