import org.gradle.plugins.ide.eclipse.model.Library
import java.security.MessageDigest
import java.util.regex.Matcher
+import java.util.concurrent.Executors
+import java.util.concurrent.Future
+import java.util.concurrent.ScheduledExecutorService
+import java.util.concurrent.TimeUnit
import groovy.transform.ExternalizeMethods
import groovy.util.XmlParser
import groovy.xml.XmlUtil
jalviewjsJ2sAltSettingsFileName = string("${jalviewDir}/${jalviewjs_j2s_alt_settings}")
jalviewjsJ2sProps = null
jalviewjsJ2sPlugin = jalviewjs_j2s_plugin
+ jalviewjsStderrLaunchFilename = "${jalviewjsSiteDir}/"+(file(jalviewjs_stderr_launch).getName())
eclipseWorkspace = null
eclipseBinary = string("")
eclipseVersion = string("")
eclipseDebug = false
+
+ jalviewjsChromiumUserDir = "${jalviewjsBuildDir}/${jalviewjs_chromium_user_dir}"
+ jalviewjsChromiumProfileDir = "${ext.jalviewjsChromiumUserDir}/${jalviewjs_chromium_profile_name}"
+
// ENDEXT
}
}
+task jalviewjsCopyStderrLaunchFile(type: Copy) {
+ from file(jalviewjs_stderr_launch)
+ into jalviewjsSiteDir
+
+ inputs.file jalviewjs_stderr_launch
+ outputs.file jalviewjsStderrLaunchFilename
+}
+
+task cleanJalviewjsChromiumUserDir {
+ doFirst {
+ delete jalviewjsChromiumUserDir
+ }
+ outputs.dir jalviewjsChromiumUserDir
+ // always run when depended on
+ outputs.upToDateWhen { !file(jalviewjsChromiumUserDir).exists() }
+}
+
+task jalviewjsChromiumProfile {
+ dependsOn cleanJalviewjsChromiumUserDir
+ mustRunAfter cleanJalviewjsChromiumUserDir
+
+ def firstRun = file("${jalviewjsChromiumUserDir}/First Run")
+
+ doFirst {
+ mkdir jalviewjsChromiumProfileDir
+ firstRun.text = ""
+ }
+ outputs.file firstRun
+}
+
+task jalviewjsLaunchTest {
+ group "Test"
+ description "Check JalviewJS opens in a browser"
+ dependsOn jalviewjsBuildSite
+ dependsOn jalviewjsCopyStderrLaunchFile
+ dependsOn jalviewjsChromiumProfile
+
+ def macOS = OperatingSystem.current().isMacOsX()
+ def chromiumBinary = macOS ? jalviewjs_macos_chromium_binary : jalviewjs_chromium_binary
+ if (chromiumBinary.startsWith("~/")) {
+ chromiumBinary = System.getProperty("user.home") + chromiumBinary.substring(1)
+ }
+
+ def stdout
+ def stderr
+ doFirst {
+ def timeoutms = Integer.valueOf(jalviewjs_chromium_timeout) * 1000
+
+ def binary = file(chromiumBinary)
+ if (!binary.exists()) {
+ throw new StopExecutionException("Could not find chromium binary '${chromiumBinary}'. Cannot run task ${name}.")
+ }
+ stdout = new ByteArrayOutputStream()
+ stderr = new ByteArrayOutputStream()
+ def execStdout
+ def execStderr
+ if (jalviewjs_j2s_to_console.equals("true")) {
+ execStdout = new org.apache.tools.ant.util.TeeOutputStream(
+ stdout,
+ System.out)
+ execStderr = new org.apache.tools.ant.util.TeeOutputStream(
+ stderr,
+ System.err)
+ } else {
+ execStdout = stdout
+ execStderr = stderr
+ }
+ def execArgs = [
+ "--no-sandbox", // --no-sandbox IS USED BY THE THORIUM APPIMAGE ON THE BUILDSERVER
+ "--headless=new",
+ "--disable-gpu",
+ "--timeout=${timeoutms}",
+ "--virtual-time-budget=${timeoutms}",
+ "--user-data-dir=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_chromium_user_dir}",
+ "--profile-directory=${jalviewjs_chromium_profile_name}",
+ "--allow-file-access-from-files",
+ "--enable-logging=stderr",
+ "file://${jalviewDirAbsolutePath}/${jalviewjsStderrLaunchFilename}"
+ ]
+
+ if (true || macOS) {
+ ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
+ Future f1 = executor.submit(
+ () -> {
+ exec {
+ standardOutput = execStdout
+ errorOutput = execStderr
+ executable(chromiumBinary)
+ args(execArgs)
+ }
+ executor.shutdownNow()
+ }
+ )
+
+ def noChangeBytes = 0
+ def noChangeIterations = 0
+ executor.scheduleAtFixedRate(
+ () -> {
+ String stderrString = stderr.toString()
+ // shutdown the task if we have a success string
+ if (stderrString.contains(jalviewjs_desktop_init_string)) {
+ f1.cancel()
+ Thread.sleep(1000)
+ executor.shutdownNow()
+ }
+ // if no change in stderr for 10s then also end
+ if (noChangeIterations >= 10) {
+ executor.shutdownNow()
+ }
+ if (stderrString.length() == noChangeBytes) {
+ noChangeIterations++
+ } else {
+ noChangeBytes = stderrString.length()
+ noChangeIterations = 0
+ }
+ },
+ 1, 1, TimeUnit.SECONDS)
+
+ executor.schedule(new Runnable(){
+ public void run(){
+ f1.cancel()
+ executor.shutdownNow()
+ }
+ }, timeoutms, TimeUnit.MILLISECONDS)
+
+ executor.awaitTermination(timeoutms+10000, TimeUnit.MILLISECONDS)
+ executor.shutdownNow()
+ }
+
+ }
+
+ doLast {
+ def found = false
+ stderr.toString().eachLine { line ->
+ if (line.contains(jalviewjs_desktop_init_string)) {
+ println("Found line '"+line+"'")
+ found = true
+ return
+ }
+ }
+ if (!found) {
+ throw new GradleException("Could not find evidence of Desktop launch in JalviewJS.")
+ }
+ }
+}
+
+
task jalviewjs {
group "JalviewJS"
- description "Build the site"
+ description "Build the JalviewJS site and run the launch test"
dependsOn jalviewjsBuildSite
+ dependsOn jalviewjsLaunchTest
}
-
jalviewjs_closure_compiler = tools/closure_compiler.jar
jalviewjs_j2s_closure_stdout = j2s-closure.out
+# for checking jalviewjs launches okay
+jalviewjs_chromium_binary = ~/buildtools/chromium/chrome
+jalviewjs_macos_chromium_binary = /Applications/Chromium.app/Contents/MacOS/Chromium
+jalviewjs_chromium_user_dir = chromium
+jalviewjs_chromium_timeout = 30
+jalviewjs_chromium_profile_name = BUILD
+jalviewjs_stderr_launch = utils/jalviewjs/chromium_test/jalview_bin_Jalview-stderr.html
+jalviewjs_desktop_init_string = JALVIEWJS: CREATED DESKTOP
testp=gradle.properties
<p>Usage data is collected from the logs of various web services
that the Jalview Desktop contacts through its normal operation.
These are described below:</p>
- <ul>
- <li><em>HTTP logs on the Jalview website</em><br> We
- record IP addresses of machines which access the web site, either
- via the browser when downloading the application, or when the
- Jalview Desktop user interface is launched.<br> <br>
- <ul>
- <li><i>The Jalview Getdown Launcher</i> (Since 2.11.0) examines release
- channels every time Jalview launches to determine if a new
- release is available.</li>
- <li><i>The questionnaire web service at
- www.jalview.org/cgi-bin/questionnaire.pl is checked and a
- unique cookie for the current questionnaire is stored in the
- Jalview properties file.</i></li>
- <li><i>The Jalview web services stack is contacted to
- retrieve the currently available web services. All
- interactions with the public Jalview web services are
- logged, but we delete all job data (input data and results)
- after about two weeks.</i></li>
- </ul> <br></li>
- <li><em>Google Analytics</em><br> Since Jalview 2.4.0b2,
- the Jalview Desktop records usage data with Google Analytics via
- the <a href="http://code.google.com/p/jgoogleanalytics/">JGoogleAnalytics</a>
- class.<br> The Google Analytics logs for Jalview version 2.4
- only record the fact that the application was started, but in the
- future, we will use this mechanism to improve the Desktop user
- interface, by tracking which parts of the user interface are being
- used most often.</li>
- </ul>
- </p>
+ <ul>
+ <li><em>HTTP logs on the Jalview website</em><br> We record
+ IP addresses of machines which access the web site, either via the
+ browser when downloading the application, or when the Jalview Desktop
+ user interface is launched.<br> <br>
+ <ul>
+ <li><i>The Jalview Getdown Launcher</i> (Since 2.11.0) examines
+ release channels every time Jalview launches to determine if a new
+ release is available.</li>
+ <li><i>The questionnaire web service at
+ www.jalview.org/cgi-bin/questionnaire.pl is checked and a unique
+ cookie for the current questionnaire is stored in the Jalview
+ properties file.</i></li>
+ <li><i>The Jalview web services stack is contacted to
+ retrieve the currently available web services. All interactions
+ with the public Jalview web services are logged, but we delete all
+ job data (input data and results) after about two weeks.</i></li>
+ </ul> <br></li>
+ <li><em>Usage Analytics</em><br> Since Jalview 2.11.2.7, the
+ Jalview Desktop records usage data with a self-hosted instance of the
+ analytics stack <a href="https://plausible.io">Plausible.io</a> via a
+ custom GPLv3 client developed by Ben Soares. Prior to this, Jalview
+ versions as far back as 2.4 recorded application launches via <a
+ href="http://code.google.com/p/jgoogleanalytics/">JGoogleAnalytics</a>
+ .<br> Usage logs for Jalview record the fact that the
+ application was started, and details about the OS, installed Jalview
+ launcher (if any) and java version used. In the future, we will use
+ this mechanism to improve the Desktop user interface, by tracking
+ which parts of the user interface are being used most often.</li>
+ </ul>
<p>
<strong>Stopping Jalview from calling home</strong><br> If you
run Jalview in 'headless mode' via the command line, then the
--- /dev/null
+---
+version: 2.11.2.7
+date: 2023-06-30
+channel: "release"
+---
+
+## New Features
+- <!-- JAL-4001 --> Jalview now reports usage statistics via Plausible.io
+
+## Issues Resolved
+- <!-- JAL-4116 --> PDB structures slow to view when Jalview Java console is open
+- <!-- JAL-4216 --> chains in PDB or mmCIF files with negative RESNUMs not correctly parsed
--- /dev/null
+Jalview 2.11.2.7 is a minor patch release - it includes patches affecting efficiency when importing structures and a small revision to the import processing of structures with negative residue numbering.
+
+With this release, Jalview usage statistics are now collected by a jalview.org hosted instance of the open source privacy-preserving analytics stack, Plausible.io.
+
+
label.interpret_tempfac_as = Interpret Temperature Factor as
label.add_pae_matrix_file = Add PAE matrix file
label.nothing_selected = Nothing selected
+prompt.analytics_title = Jalview Usage Statistics
+prompt.analytics = Do you want to help make Jalview better by enabling the collection of usage statistics with Plausible analytics?\nYou can enable or disable usage tracking in the preferences.
label.working_ellipsis = Working ...
action.show_groups_on_matrix = Show groups on matrix
action.show_groups_on_matrix_tooltip = When enabled, clusters defined on the matrix's associated tree or below the assigned threshold are shown as different colours on the matrix annotation row
label.tftype_plddt = pLDDT
label.add_pae_matrix_file = Añadir un fichero de matriz PAE
label.nothing_selected = Nada seleccionado
+prompt.analytics_title = Jalview EstadÃsticas de Uso
+prompt.analytics = ¿Quiere ayudar a mejorar Jalview habilitando la recopilación de estadÃsticas de uso con análisis Plausible?\nPuede habilitar o deshabilitar el seguimiento de uso en las preferencias.
pdbpos++;
}
- if (allowmismatch || c1 == c2)
+ // ignore case differences
+ if (allowmismatch || (c1 == c2) || (Math.abs(c2-c1)==('a'-'A')))
{
// extend mapping interval
if (lp1 + 1 != alignpos || lp2 + 1 != pdbpos)
* @param alignment
* the alignment to add them to
* @param selectionGroup
- * current selection group (or null if none)
+ * current selection group - may be null, if provided then any added annotation will be trimmed to just those columns in the selection group
*/
public static void addReferenceAnnotations(
Map<SequenceI, List<AlignmentAnnotation>> annotations,
* @param seq
* @param ann
* @param selectionGroup
- * - may be null
+ * current selection group - may be null, if provided then any added annotation will be trimmed to just those columns in the selection group
* @return annotation added to {@code seq and {@code alignment}
*/
public static AlignmentAnnotation addReferenceAnnotationTo(
--- /dev/null
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ *
+ * This file is part of Jalview.
+ *
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * Jalview is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.analytics;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.lang.invoke.MethodHandles;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import jalview.bin.Cache;
+import jalview.bin.Console;
+import jalview.util.ChannelProperties;
+import jalview.util.HttpUtils;
+
+public class Plausible
+{
+ private static final String USER_AGENT;
+
+ private static final String JALVIEW_ID = "Jalview Desktop";
+
+ private static final String DOMAIN = "jalview.org";
+
+ private static final String CONFIG_API_BASE_URL = "https://www.jalview.org/services/config/analytics/url";
+
+ private static final String DEFAULT_API_BASE_URL = "https://analytics.jalview.org/api/event";
+
+ private static final String API_BASE_URL;
+
+ private static final String clientId;
+
+ public static final String APPLICATION_BASE_URL = "desktop://localhost";
+
+ private List<Map.Entry<String, String>> queryStringValues;
+
+ private List<Map.Entry<String, Object>> jsonObject;
+
+ private List<Map.Entry<String, String>> cookieValues;
+
+ private static boolean ENABLED = false;
+
+ private static boolean DEBUG = true;
+
+ private static Plausible instance = null;
+
+ private static final Map<String, String> defaultProps;
+
+ static
+ {
+ defaultProps = new HashMap<>();
+ defaultProps.put("app_name",
+ ChannelProperties.getProperty("app_name") + " Desktop");
+ defaultProps.put("version", Cache.getProperty("VERSION"));
+ defaultProps.put("build_date",
+ Cache.getDefault("BUILD_DATE", "unknown"));
+ defaultProps.put("java_version", System.getProperty("java.version"));
+ String val = System.getProperty("sys.install4jVersion");
+ if (val != null)
+ {
+ defaultProps.put("install4j_version", val);
+ }
+ val = System.getProperty("installer_template_version");
+ if (val != null)
+ {
+ defaultProps.put("install4j_template_version", val);
+ }
+ val = System.getProperty("launcher_version");
+ if (val != null)
+ {
+ defaultProps.put("launcher_version", val);
+ }
+ defaultProps.put("java_arch",
+ System.getProperty("os.arch") + " "
+ + System.getProperty("os.name") + " "
+ + System.getProperty("os.version"));
+ defaultProps.put("os", System.getProperty("os.name"));
+ defaultProps.put("os_version", System.getProperty("os.version"));
+ defaultProps.put("os_arch", System.getProperty("os.arch"));
+ String installation = Cache.applicationProperties
+ .getProperty("INSTALLATION");
+ if (installation != null)
+ {
+ defaultProps.put("installation", installation);
+ }
+
+ // ascertain the API_BASE_URL
+ API_BASE_URL = getAPIBaseURL();
+
+ // random clientId to make User-Agent unique (to register analytic)
+ clientId = String.format("%08x", new Random().nextInt());
+
+ USER_AGENT = HttpUtils.getUserAgent(
+ MethodHandles.lookup().lookupClass().getCanonicalName() + " "
+ + clientId);
+ }
+
+ private Plausible()
+ {
+ this.resetLists();
+ }
+
+ public static void setEnabled(boolean b)
+ {
+ ENABLED = b;
+ }
+
+ public void sendEvent(String eventName, String urlString,
+ String... propsStrings)
+ {
+ sendEvent(eventName, urlString, false, propsStrings);
+ }
+
+ /**
+ * The simplest way to send an analytic event.
+ *
+ * @param eventName
+ * The event name. To emulate a webpage view use "pageview" and set a
+ * "url" key/value. See https://plausible.io/docs/events-api
+ * @param sendDefaultProps
+ * Flag whether to add the default props about the application.
+ * @param propsStrings
+ * Optional multiple Strings in key, value pairs (there should be an
+ * even number of propsStrings) to be set as property of the event.
+ * To emulate a webpage view set "url" as the URL in a "pageview"
+ * event.
+ */
+ public void sendEvent(String eventName, String urlString,
+ boolean sendDefaultProps, String... propsStrings)
+ {
+ // clear out old lists
+ this.resetLists();
+
+ if (!ENABLED)
+ {
+ Console.debug("Plausible not enabled.");
+ return;
+ }
+ Map<String, String> props = new HashMap<>();
+
+ // add these to all events from this application instance
+ if (sendDefaultProps)
+ {
+ props.putAll(defaultProps);
+ }
+
+ // add (and overwrite with) the passed in props
+ if (propsStrings != null && propsStrings.length > 0)
+ {
+ if (propsStrings.length % 2 != 0)
+ {
+ Console.warn(
+ "Cannot addEvent with odd number of propsStrings. Ignoring the last one.");
+ }
+ for (int i = 0; i < propsStrings.length - 1; i += 2)
+ {
+ String key = propsStrings[i];
+ String value = propsStrings[i + 1];
+ props.put(key, value);
+ }
+ }
+
+ addJsonValue("domain", DOMAIN);
+ addJsonValue("name", eventName);
+ StringBuilder eventUrlSb = new StringBuilder(APPLICATION_BASE_URL);
+ if (!APPLICATION_BASE_URL.endsWith("/") && !urlString.startsWith("/"))
+ {
+ eventUrlSb.append("/");
+ }
+ eventUrlSb.append(urlString);
+ addJsonValue("url", eventUrlSb.toString());
+ addJsonObject("props", props);
+ StringBuilder urlSb = new StringBuilder();
+ urlSb.append(API_BASE_URL);
+ String qs = buildQueryString();
+ if (qs != null && qs.length() > 0)
+ {
+ urlSb.append('?');
+ urlSb.append(qs);
+ }
+ try
+ {
+ URL url = new URL(urlSb.toString());
+ URLConnection urlConnection = url.openConnection();
+ HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;
+ httpURLConnection.setRequestMethod("POST");
+ httpURLConnection.setDoOutput(true);
+
+ String jsonString = buildJson();
+
+ Console.debug(
+ "Plausible: HTTP Request is: '" + urlSb.toString() + "'");
+ if (DEBUG)
+ {
+ Console.debug("Plausible: User-Agent is: '" + USER_AGENT + "'");
+ }
+ Console.debug("Plausible: POSTed JSON is:\n" + jsonString);
+
+ byte[] jsonBytes = jsonString.getBytes(StandardCharsets.UTF_8);
+ int jsonLength = jsonBytes.length;
+
+ httpURLConnection.setFixedLengthStreamingMode(jsonLength);
+ httpURLConnection.setRequestProperty("Content-Type",
+ "application/json");
+ httpURLConnection.setRequestProperty("User-Agent", USER_AGENT);
+ httpURLConnection.connect();
+ try (OutputStream os = httpURLConnection.getOutputStream())
+ {
+ os.write(jsonBytes);
+ }
+ int responseCode = httpURLConnection.getResponseCode();
+ String responseMessage = httpURLConnection.getResponseMessage();
+
+ if (responseCode < 200 || responseCode > 299)
+ {
+ Console.warn("Plausible connection failed: '" + responseCode + " "
+ + responseMessage + "'");
+ }
+ else
+ {
+ Console.debug("Plausible connection succeeded: '" + responseCode
+ + " " + responseMessage + "'");
+ }
+
+ if (DEBUG)
+ {
+ BufferedReader br = new BufferedReader(new InputStreamReader(
+ (httpURLConnection.getInputStream())));
+ StringBuilder sb = new StringBuilder();
+ String response;
+ while ((response = br.readLine()) != null)
+ {
+ sb.append(response);
+ }
+ String body = sb.toString();
+ Console.debug("Plausible response content:\n" + body);
+ }
+ } catch (MalformedURLException e)
+ {
+ Console.debug(
+ "Somehow the Plausible BASE_URL and queryString is malformed: '"
+ + urlSb.toString() + "'",
+ e);
+ return;
+ } catch (IOException e)
+ {
+ Console.debug("Connection to Plausible BASE_URL '" + API_BASE_URL
+ + "' failed.", e);
+ } catch (ClassCastException e)
+ {
+ Console.debug(
+ "Couldn't cast URLConnection to HttpURLConnection in Plausible.",
+ e);
+ }
+ }
+
+ private void addJsonObject(String key, Map<String, String> map)
+ {
+ List<Map.Entry<String, ? extends Object>> list = new ArrayList<>();
+ for (String k : map.keySet())
+ {
+ list.add(stringEntry(k, map.get(k)));
+ }
+ addJsonObject(key, list);
+
+ }
+
+ private void addJsonObject(String key,
+ List<Map.Entry<String, ? extends Object>> object)
+ {
+ jsonObject.add(objectEntry(key, object));
+ }
+
+ private void addJsonValues(String key, List<Object> values)
+ {
+ jsonObject.add(objectEntry(key, values));
+ }
+
+ private void addJsonValue(String key, String value)
+ {
+ jsonObject.add(objectEntry(key, value));
+ }
+
+ private void addJsonValue(String key, int value)
+ {
+ jsonObject.add(objectEntry(key, Integer.valueOf(value)));
+ }
+
+ private void addJsonValue(String key, boolean value)
+ {
+ jsonObject.add(objectEntry(key, Boolean.valueOf(value)));
+ }
+
+ private void addQueryStringValue(String key, String value)
+ {
+ queryStringValues.add(stringEntry(key, value));
+ }
+
+ private void addCookieValue(String key, String value)
+ {
+ cookieValues.add(stringEntry(key, value));
+ }
+
+ private void resetLists()
+ {
+ jsonObject = new ArrayList<>();
+ queryStringValues = new ArrayList<>();
+ cookieValues = new ArrayList<>();
+ }
+
+ public static Plausible getInstance()
+ {
+ if (instance == null)
+ {
+ instance = new Plausible();
+ }
+ return instance;
+ }
+
+ public static void reset()
+ {
+ getInstance().resetLists();
+ }
+
+ private String buildQueryString()
+ {
+ StringBuilder sb = new StringBuilder();
+ for (Map.Entry<String, String> entry : queryStringValues)
+ {
+ if (sb.length() > 0)
+ {
+ sb.append('&');
+ }
+ try
+ {
+ sb.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
+ } catch (UnsupportedEncodingException e)
+ {
+ sb.append(entry.getKey());
+ }
+ sb.append('=');
+ try
+ {
+ sb.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
+ } catch (UnsupportedEncodingException e)
+ {
+ sb.append(entry.getValue());
+ }
+ }
+ return sb.toString();
+ }
+
+ private void buildCookieHeaders()
+ {
+ // TODO not needed yet
+ }
+
+ private String buildJson()
+ {
+ StringBuilder sb = new StringBuilder();
+ addJsonObject(sb, 0, jsonObject);
+ return sb.toString();
+ }
+
+ private void addJsonObject(StringBuilder sb, int indent,
+ List<Map.Entry<String, Object>> entries)
+ {
+ indent(sb, indent);
+ sb.append('{');
+ newline(sb);
+ Iterator<Map.Entry<String, Object>> entriesI = entries.iterator();
+ while (entriesI.hasNext())
+ {
+ Map.Entry<String, Object> entry = entriesI.next();
+ String key = entry.getKey();
+ // TODO sensibly escape " characters in key
+ Object value = entry.getValue();
+ indent(sb, indent + 1);
+ sb.append('"').append(quoteEscape(key)).append('"').append(':');
+ space(sb);
+ if (value != null && value instanceof List)
+ {
+ newline(sb);
+ }
+ addJsonValue(sb, indent + 2, value);
+ if (entriesI.hasNext())
+ {
+ sb.append(',');
+ }
+ newline(sb);
+ }
+ indent(sb, indent);
+ sb.append('}');
+ }
+
+ private void addJsonValue(StringBuilder sb, int indent, Object value)
+ {
+ if (value == null)
+ {
+ return;
+ }
+ try
+ {
+ if (value instanceof Map.Entry)
+ {
+ Map.Entry<String, Object> entry = (Map.Entry<String, Object>) value;
+ List<Map.Entry<String, Object>> object = new ArrayList<>();
+ object.add(entry);
+ addJsonObject(sb, indent, object);
+ }
+ else if (value instanceof List)
+ {
+ // list of Map.Entries or list of values?
+ List<Object> valueList = (List<Object>) value;
+ if (valueList.size() > 0 && valueList.get(0) instanceof Map.Entry)
+ {
+ // entries
+ // indent(sb, indent);
+ List<Map.Entry<String, Object>> entryList = (List<Map.Entry<String, Object>>) value;
+ addJsonObject(sb, indent, entryList);
+ }
+ else
+ {
+ // values
+ indent(sb, indent);
+ sb.append('[');
+ newline(sb);
+ Iterator<Object> valueListI = valueList.iterator();
+ while (valueListI.hasNext())
+ {
+ Object v = valueListI.next();
+ addJsonValue(sb, indent + 1, v);
+ if (valueListI.hasNext())
+ {
+ sb.append(',');
+ }
+ newline(sb);
+ }
+ indent(sb, indent);
+ sb.append("]");
+ }
+ }
+ else if (value instanceof String)
+ {
+ sb.append('"').append(quoteEscape((String) value)).append('"');
+ }
+ else if (value instanceof Integer)
+ {
+ sb.append(((Integer) value).toString());
+ }
+ else if (value instanceof Boolean)
+ {
+ sb.append('"').append(((Boolean) value).toString()).append('"');
+ }
+ } catch (ClassCastException e)
+ {
+ Console.debug(
+ "Could not deal with type of json Object " + value.toString(),
+ e);
+ }
+ }
+
+ private static String quoteEscape(String s)
+ {
+ if (s == null)
+ {
+ return null;
+ }
+ // this escapes quotation marks (") that aren't already escaped (in the
+ // string) ready to go into a quoted JSON string value
+ return s.replaceAll("((?<!\\\\)(?:\\\\{2})*)\"", "$1\\\\\"");
+ }
+
+ private static void prettyWhitespace(StringBuilder sb, String whitespace,
+ int repeat)
+ {
+ // only add whitespace if we're in DEBUG mode
+ if (!Console.getLogger().isDebugEnabled())
+ {
+ return;
+ }
+ if (repeat >= 0 && whitespace != null)
+ {
+ // sb.append(whitespace.repeat(repeat));
+ sb.append(String.join("", Collections.nCopies(repeat, whitespace)));
+
+ }
+ else
+ {
+ sb.append(whitespace);
+ }
+ }
+
+ private static void indent(StringBuilder sb, int indent)
+ {
+ prettyWhitespace(sb, " ", indent);
+ }
+
+ private static void newline(StringBuilder sb)
+ {
+ prettyWhitespace(sb, "\n", -1);
+ }
+
+ private static void space(StringBuilder sb)
+ {
+ prettyWhitespace(sb, " ", -1);
+ }
+
+ protected static Map.Entry<String, Object> objectEntry(String s, Object o)
+ {
+ return new AbstractMap.SimpleEntry<String, Object>(s, o);
+ }
+
+ protected static Map.Entry<String, String> stringEntry(String s, String v)
+ {
+ return new AbstractMap.SimpleEntry<String, String>(s, v);
+ }
+
+ private static String getAPIBaseURL()
+ {
+ try
+ {
+ URL url = new URL(CONFIG_API_BASE_URL);
+ URLConnection urlConnection = url.openConnection();
+ HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;
+ httpURLConnection.setRequestMethod("GET");
+ httpURLConnection.setRequestProperty("User-Agent", USER_AGENT);
+ httpURLConnection.setConnectTimeout(5000);
+ httpURLConnection.setReadTimeout(3000);
+ httpURLConnection.connect();
+ int responseCode = httpURLConnection.getResponseCode();
+ String responseMessage = httpURLConnection.getResponseMessage();
+
+ if (responseCode < 200 || responseCode > 299)
+ {
+ Console.warn("Config URL connection to '" + CONFIG_API_BASE_URL
+ + "' failed: '" + responseCode + " " + responseMessage
+ + "'");
+ }
+
+ BufferedReader br = new BufferedReader(
+ new InputStreamReader((httpURLConnection.getInputStream())));
+ StringBuilder sb = new StringBuilder();
+ String response;
+ while ((response = br.readLine()) != null)
+ {
+ sb.append(response);
+ }
+ if (sb.length() > 7 && sb.substring(0, 5).equals("https"))
+ {
+ return sb.toString();
+ }
+
+ } catch (MalformedURLException e)
+ {
+ Console.debug("Somehow the config URL is malformed: '"
+ + CONFIG_API_BASE_URL + "'", e);
+ } catch (IOException e)
+ {
+ Console.debug("Connection to Plausible BASE_URL '" + API_BASE_URL
+ + "' failed.", e);
+ } catch (ClassCastException e)
+ {
+ Console.debug(
+ "Couldn't cast URLConnection to HttpURLConnection in Plausible.",
+ e);
+ }
+ return DEFAULT_API_BASE_URL;
+ }
+}
/*
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2b1)
- * Copyright (C) 2014 The Jalview Authors
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
*
* This file is part of Jalview.
*
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
+import jalview.analytics.Plausible;
import jalview.datamodel.PDBEntry;
import jalview.gui.Preferences;
import jalview.gui.UserDefinedColours;
* service</li>
* <li>QUESTIONNAIRE last questionnaire:responder id string from questionnaire
* service</li>
- * <li>USAGESTATS (false - user prompted) Enable google analytics tracker for
+ * <li>USAGESTATS (false - user prompted) Enable analytics tracker for
* collecting usage statistics</li>
* <li>SHOW_OVERVIEW boolean for overview window display</li>
* <li>ANTI_ALIAS boolean for smooth fonts</li>
}
/**
- * GA tracker object - actually JGoogleAnalyticsTracker null if tracking not
- * enabled.
+ * Initialise the tracker if it is not done already.
*/
- protected static Object tracker = null;
-
- protected static Class trackerfocus = null;
-
- protected static Class jgoogleanalyticstracker = null;
-
- /**
- * Initialise the google tracker if it is not done already.
- */
- public static void initGoogleTracker()
+ public static void initAnalytics()
{
- if (tracker == null)
+ Plausible.setEnabled(true);
+
+ String appName = ChannelProperties.getProperty("app_name") + " Desktop";
+ String version = Cache.getProperty("VERSION") + "_"
+ + Cache.getDefault("BUILD_DATE", "unknown");
+ String path;
+ /* we don't want to encode ':' as "%3A" for backward compatibility with the UA setup
+ try
{
- if (jgoogleanalyticstracker == null)
- {
- // try to get the tracker class
- try
- {
- jgoogleanalyticstracker = Cache.class.getClassLoader().loadClass(
- "com.boxysystems.jgoogleanalytics.JGoogleAnalyticsTracker");
- trackerfocus = Cache.class.getClassLoader()
- .loadClass("com.boxysystems.jgoogleanalytics.FocusPoint");
- } catch (Exception e)
- {
- Console.debug(
- "com.boxysystems.jgoogleanalytics package is not present - tracking not enabled.");
- tracker = null;
- jgoogleanalyticstracker = null;
- trackerfocus = null;
- return;
- }
- }
- // now initialise tracker
- Exception re = null, ex = null;
- Error err = null;
- String vrs = "No Version Accessible";
- try
- {
- // Google analytics tracking code for Library Finder
- tracker = jgoogleanalyticstracker
- .getConstructor(new Class[]
- { String.class, String.class, String.class })
- .newInstance(new Object[]
- { ChannelProperties.getProperty("app_name") + " Desktop",
- (vrs = Cache.getProperty("VERSION") + "_"
- + Cache.getDefault("BUILD_DATE", "unknown")),
- "UA-9060947-1" });
- jgoogleanalyticstracker
- .getMethod("trackAsynchronously", new Class[]
- { trackerfocus })
- .invoke(tracker, new Object[]
- { trackerfocus.getConstructor(new Class[] { String.class })
- .newInstance(new Object[]
- { "Application Started." }) });
- } catch (RuntimeException e)
- {
- re = e;
- } catch (Exception e)
- {
- ex = e;
- } catch (Error e)
- {
- err = e;
- }
- if (re != null || ex != null || err != null)
- {
- if (re != null)
- {
- Console.debug("Caught runtime exception in googletracker init:",
- re);
- }
- if (ex != null)
- {
- Console.warn(
- "Failed to initialise GoogleTracker for Jalview Desktop with version "
- + vrs,
- ex);
- }
- if (err != null)
- {
- Console.error(
- "Whilst initing GoogleTracker for Jalview Desktop version "
- + vrs,
- err);
- }
- }
- else
- {
- Console.debug("Successfully initialised tracker.");
- }
+ path = "/" + String.join("/", URLEncoder.encode(appName, "UTF-8"),
+ URLEncoder.encode(version, "UTF-8"),
+ URLEncoder.encode(APPLICATION_STARTED, "UTF-8"));
+ } catch (UnsupportedEncodingException e)
+ {
+ */
+ List<String> pathParts = new ArrayList<>();
+ pathParts.add(appName);
+ pathParts.add(version);
+ pathParts.add(APPLICATION_STARTED);
+ path = ("/" + String.join("/", pathParts)).replace(' ', '+');
+ /*
}
+ */
+ Plausible plausible = Plausible.getInstance();
+
+ // This will send a new "application_launch" event with parameters
+ // including the old-style "path", the channel name and version
+ plausible.sendEvent("application_launch", path, true);
}
+ private static final String APPLICATION_STARTED = "Application Started";
+
/**
* get the user's default colour if available
*
if (customProxySet &&
// we have a username but no password for the scheme being
// requested
- (protocol.equalsIgnoreCase("http")
- && (httpUser != null && httpUser.length() > 0
- && (httpPassword == null
- || httpPassword.length == 0)))
+ (protocol.equalsIgnoreCase("http")
+ && (httpUser != null
+ && httpUser.length() > 0
+ && (httpPassword == null
+ || httpPassword.length == 0)))
|| (protocol.equalsIgnoreCase("https")
&& (httpsUser != null
&& httpsUser.length() > 0
structureFilepath, tft, paeFilepath, false,
ssFromStructure, false, viewerType);
- if (headless)
+ if (sv==null)
{
- sv.setAsync(false);
+ Console.error("Failed to import and open structure view.");
+ continue;
}
-
+ try
+ {
+ while (sv.isBusy())
+ {
+ Thread.sleep(25);
+ if (sv.isBusy())
+ {
+ Console.debug(
+ "Waiting for viewer for " + structureFilepath);
+ }
+ }
+ } catch (Exception x)
+ {
+ Console.warn("Exception whilst waiting for structure viewer "+structureFilepath,x);
+ }
+ Console.debug("Successfully opened viewer for "+structureFilepath);
String structureImageFilename = ArgParser.getValueFromSubValOrArg(
avm, av, Arg.STRUCTUREIMAGE, subVals);
if (sv != null && structureImageFilename != null)
}
BitmapImageSizing userBis = ImageMaker
.parseScaleWidthHeightStrings(scale, width, height);
+ // TODO MAKE THIS VIEWER INDEPENDENT!!
switch (StructureViewer.getViewerType())
{
case JMOL:
try
{
- Thread.sleep(1000);
+ Thread.sleep(1000); // WHY ???
} catch (InterruptedException e)
{
// TODO Auto-generated catch block
{
AppJmol jmol = (AppJmol) sview;
try {
+ Console.debug("Rendering image to "+structureImageFile);
jmol.makePDBImage(structureImageFile, imageType, renderer,
userBis);
+ Console.debug("Finished Rendering image to "+structureImageFile);
+
}
catch (ImageOutputException ioexc)
{
return JLogger.toLevel(level);
}
+ public static JLogger getLogger()
+ {
+ return log;
+ }
+
public static boolean initLogger()
{
return initLogger(null);
JLogger.LogLevel logLevel = JLogger.LogLevel.INFO;
if (JLogger.isLevel(providedLogLevel))
+ {
logLevel = Console.getLogLevel(providedLogLevel);
+ }
else
+ {
logLevel = getCachedLogLevel();
+ }
if (!Platform.isJS())
{
{
headless = true;
}
- System.setProperty("http.agent",
- "Jalview Desktop/" + Cache.getDefault("VERSION", "Unknown"));
+ System.setProperty("http.agent", HttpUtils.getUserAgent());
try
{
// Run Commands from cli
cmds = new Commands(argparser, headlessArg);
boolean commandsSuccess = cmds.argsWereParsed();
+
if (commandsSuccess)
{
if (headlessArg)
ex.printStackTrace(System.err);
}
}
-
+
if (groovyscript != null)
{
// Execute the groovy script after we've done all the rendering stuff
private static void setLookAndFeel()
{
- // property laf = "crossplatform", "system", "gtk", "metal", "nimbus",
- // "mac" or "flat"
- // If not set (or chosen laf fails), use the normal SystemLaF and if on Mac,
- // try Quaqua/Vaqua.
- String lafProp = System.getProperty("laf");
- String lafSetting = Cache.getDefault("PREFERRED_LAF", null);
- String laf = "none";
- if (lafProp != null)
- {
- laf = lafProp;
- }
- else if (lafSetting != null)
- {
- laf = lafSetting;
- }
- boolean lafSet = false;
- switch (laf)
+ if (!Platform.isJS())
+ /**
+ * Java only
+ *
+ * @j2sIgnore
+ */
{
- case "crossplatform":
- lafSet = setCrossPlatformLookAndFeel();
- if (!lafSet)
- {
- Console.error("Could not set requested laf=" + laf);
+ // property laf = "crossplatform", "system", "gtk", "metal", "nimbus",
+ // "mac" or "flat"
+ // If not set (or chosen laf fails), use the normal SystemLaF and if on
+ // Mac,
+ // try Quaqua/Vaqua.
+ String lafProp = System.getProperty("laf");
+ String lafSetting = Cache.getDefault("PREFERRED_LAF", null);
+ String laf = "none";
+ if (lafProp != null)
+ {
+ laf = lafProp;
}
- break;
- case "system":
- lafSet = setSystemLookAndFeel();
- if (!lafSet)
+ else if (lafSetting != null)
{
- Console.error("Could not set requested laf=" + laf);
+ laf = lafSetting;
}
- break;
- case "gtk":
- lafSet = setGtkLookAndFeel();
- if (!lafSet)
- {
- Console.error("Could not set requested laf=" + laf);
- }
- break;
- case "metal":
- lafSet = setMetalLookAndFeel();
- if (!lafSet)
- {
- Console.error("Could not set requested laf=" + laf);
- }
- break;
- case "nimbus":
- lafSet = setNimbusLookAndFeel();
- if (!lafSet)
- {
- Console.error("Could not set requested laf=" + laf);
- }
- break;
- case "flat":
- lafSet = setFlatLookAndFeel();
- if (!lafSet)
- {
- Console.error("Could not set requested laf=" + laf);
- }
- break;
- case "mac":
- lafSet = setMacLookAndFeel();
- if (!lafSet)
+ boolean lafSet = false;
+ switch (laf)
{
- Console.error("Could not set requested laf=" + laf);
+ case "crossplatform":
+ lafSet = setCrossPlatformLookAndFeel();
+ if (!lafSet)
+ {
+ Console.error("Could not set requested laf=" + laf);
+ }
+ break;
+ case "system":
+ lafSet = setSystemLookAndFeel();
+ if (!lafSet)
+ {
+ Console.error("Could not set requested laf=" + laf);
+ }
+ break;
+ case "gtk":
+ lafSet = setGtkLookAndFeel();
+ if (!lafSet)
+ {
+ Console.error("Could not set requested laf=" + laf);
+ }
+ break;
+ case "metal":
+ lafSet = setMetalLookAndFeel();
+ if (!lafSet)
+ {
+ Console.error("Could not set requested laf=" + laf);
+ }
+ break;
+ case "nimbus":
+ lafSet = setNimbusLookAndFeel();
+ if (!lafSet)
+ {
+ Console.error("Could not set requested laf=" + laf);
+ }
+ break;
+ case "flat":
+ lafSet = setFlatLookAndFeel();
+ if (!lafSet)
+ {
+ Console.error("Could not set requested laf=" + laf);
+ }
+ break;
+ case "mac":
+ lafSet = setMacLookAndFeel();
+ if (!lafSet)
+ {
+ Console.error("Could not set requested laf=" + laf);
+ }
+ break;
+ case "none":
+ break;
+ default:
+ Console.error("Requested laf=" + laf + " not implemented");
}
- break;
- case "none":
- break;
- default:
- Console.error("Requested laf=" + laf + " not implemented");
- }
- if (!lafSet)
- {
- // Flatlaf default for everyone!
- lafSet = setFlatLookAndFeel();
if (!lafSet)
{
- setSystemLookAndFeel();
- }
- if (Platform.isLinux())
- {
- setLinuxLookAndFeel();
- }
- if (Platform.isMac())
- {
- setMacLookAndFeel();
+ // Flatlaf default for everyone!
+ lafSet = setFlatLookAndFeel();
+ if (!lafSet)
+ {
+ setSystemLookAndFeel();
+ }
+ if (Platform.isLinux())
+ {
+ setLinuxLookAndFeel();
+ }
+ if (Platform.isMac())
+ {
+ setMacLookAndFeel();
+ }
}
}
}
+ "-questionnaire URL\tQueries the given URL for information about any Jalview user questionnaires.\n"
+ "-noquestionnaire\tTurn off questionnaire check.\n"
+ "-nonews\tTurn off check for Jalview news.\n"
- + "-nousagestats\tTurn off google analytics tracking for this session.\n"
+ + "-nousagestats\tTurn off analytics tracking for this session.\n"
+ "-sortbytree OR -nosortbytree\tEnable or disable sorting of the given alignment by the given tree\n"
// +
// "-setprop PROPERTY=VALUE\tSet the given Jalview property,
* start a User Config prompt asking if we can log usage statistics.
*/
PromptUserConfig prompter = new PromptUserConfig(Desktop.desktop,
- "USAGESTATS", "Jalview Usage Statistics",
- "Do you want to help make Jalview better by enabling "
- + "the collection of usage statistics with Google Analytics ?"
- + "\n\n(you can enable or disable usage tracking in the preferences)",
+ "USAGESTATS",
+ MessageManager.getString("prompt.plausible_analytics_title"),
+ MessageManager.getString("prompt.plausible_analytics"),
new Runnable()
{
@Override
public void run()
{
- Console.debug(
- "Initialising googletracker for usage stats.");
- Cache.initGoogleTracker();
+ Console.debug("Initialising analytics for usage stats.");
+ Cache.initAnalytics();
Console.debug("Tracking enabled.");
}
}, new Runnable()
@Override
public void run()
{
- Console.debug("Not enabling Google Tracking.");
+ Console.debug("Not enabling analytics.");
}
}, null, true);
desktop.addDialogThread(prompter);
transferAnnotation(entry.getDatasetSequence(), mp);
return;
}
+ // transfer from entry to sequence
+ // if entry has a description and sequence doesn't, then transfer
+ if (entry.getDescription()!=null && (description==null || description.trim().length()==0))
+ {
+ description = entry.getDescription();
+ }
+
// transfer any new features from entry onto sequence
if (entry.getSequenceFeatures() != null)
{
{
int length = sq.getLength();
boolean ssFound = false;
- Annotation asecstr[] = new Annotation[length + firstResNum - 1];
+ Annotation asecstr[] = new Annotation[length + (firstResNum-sq.getStart())];
for (int p = 0; p < length; p++)
{
if (secstr[p] >= 'A' && secstr[p] <= 'z')
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.io.File;
+import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
};
String view = MessageManager.getString("action.view")
.toLowerCase(Locale.ROOT);
- ImageExporter exporter = new ImageExporter(writer,
+ final ImageExporter exporter = new ImageExporter(writer,
getProgressIndicator(), type, getTitle());
- exporter.doExport(file, this, width, height, view, renderer, userBis);
-
+ final Throwable[] exceptions = new Throwable[1];
+ exceptions[0] = null;
+ final AppJmol us = this;
+ try
+ {
+ Thread runner = Executors.defaultThreadFactory().newThread(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ exporter.doExport(file, us, width, height, view, renderer,
+ userBis);
+ } catch (Throwable t)
+ {
+ exceptions[0] = t;
+ }
+ }
+ });
+ runner.start();
+ do { Thread.sleep(25); } while (runner.isAlive());
+ } catch (Throwable e)
+ {
+ throw new ImageOutputException(
+ "Unexpected error when generating image", e);
+ }
+ if (exceptions[0] != null)
+ {
+ if (exceptions[0] instanceof ImageOutputException)
+ {
+ throw ((ImageOutputException) exceptions[0]);
+ }
+ else
+ {
+ throw new ImageOutputException(
+ "Unexpected error when generating image", exceptions[0]);
+ }
+ }
}
@Override
setBounds(xPos, yPos, 900, 650);
}
+ // start dialogue queue for single dialogues
+ startDialogQueue();
+
if (!Platform.isJS())
/**
* Java only
}
});
desktop.addMouseListener(ma);
+
+ if (Platform.isJS())
+ {
+ // used for jalviewjsTest
+ jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
+ }
}
/**
setKeyBindings(frame);
- desktop.add(frame);
+ // Since the latest FlatLaf patch, we occasionally have problems showing structureViewer frames...
+ int tries=3;
+ boolean shown=false;
+ Exception last=null;
+ do
+ {
+ try
+ {
+ desktop.add(frame);
+ shown=true;
+ } catch (IllegalArgumentException iaex)
+ {
+ last=iaex;
+ tries--;
+ jalview.bin.Console.info(
+ "Squashed IllegalArgument Exception (" + tries + " left) for "+frame.getTitle(),
+ iaex);
+ try
+ {
+ Thread.sleep(5);
+ } catch (InterruptedException iex)
+ {
+ }
+ ;
+ }
+ } while (!shown && tries > 0);
+ if (!shown)
+ {
+ jalview.bin.Console.error("Serious Problem whilst showing window "+frame.getTitle(),last);
+ }
windowMenu.add(menuItem);
/**
* pause the queue
*/
- private java.util.concurrent.Semaphore block = new Semaphore(0);
+ private Semaphore block = new Semaphore(0);
private static groovy.ui.Console groovyConsole;
{
if (dialogPause)
{
- try
- {
- block.acquire();
- } catch (InterruptedException x)
- {
- }
+ acquireDialogQueue();
}
if (instance == null)
{
});
}
+ private boolean dialogQueueStarted = false;
+
public void startDialogQueue()
{
+ if (dialogQueueStarted)
+ {
+ return;
+ }
// set the flag so we don't pause waiting for another permit and semaphore
// the current task to begin
- dialogPause = false;
+ releaseDialogQueue();
+ dialogQueueStarted = true;
+ }
+
+ public void acquireDialogQueue()
+ {
+ try
+ {
+ block.acquire();
+ dialogPause = true;
+ } catch (InterruptedException e)
+ {
+ jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
+ e);
+ }
+ }
+
+ public void releaseDialogQueue()
+ {
+ if (!dialogPause)
+ {
+ return;
+ }
block.release();
+ dialogPause = false;
}
/**
String title = "View of desktop";
ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
title);
- try {
+ try
+ {
exporter.doExport(of, this, width, height, title);
- } catch (ImageOutputException ioex) {
- jalview.bin.Console.error("Unexpected error whilst writing Jalview desktop snapshot as EPS",ioex);
+ } catch (ImageOutputException ioex)
+ {
+ jalview.bin.Console.error(
+ "Unexpected error whilst writing Jalview desktop snapshot as EPS",
+ ioex);
}
}
*/
public static void closeDesktop()
{
- if (Desktop.instance != null) {
+ if (Desktop.instance != null)
+ {
Desktop.instance.closeAll_actionPerformed(null);
Desktop.instance.setVisible(false);
- Desktop.instance.dispose();
+ Desktop us = Desktop.instance;
Desktop.instance = null;
+ // call dispose in a separate thread - try to avoid indirect deadlocks
+ new Thread(new Runnable() {
+ @Override
+ public void run()
+ {
+ ExecutorService dex = us.dialogExecutor;
+ if (dex!=null) {
+ dex.shutdownNow();
+ us.dialogExecutor=null;
+ us.block.drainPermits();
+ }
+ us.dispose();
+ }
+ }).start();
+ }
+ }
+
+ /**
+ * checks if any progress bars are being displayed in any of the windows managed by the desktop
+ * @return
+ */
+ public boolean operationsAreInProgress()
+ {
+ JInternalFrame[] frames = getAllFrames();
+ for (JInternalFrame frame:frames)
+ {
+ if (frame instanceof IProgressIndicator)
+ {
+ if (((IProgressIndicator)frame).operationInProgress())
+ {
+ return true;
+ }
+ }
}
+ return operationInProgress();
}
}
*/
if (file == null && !Jalview.isHeadlessMode())
{
+ if (Desktop.instance.isInBatchMode())
+ {
+ // defensive error report - we could wait for user input.. I guess ?
+ throw(new ImageOutputException("Need an output file to render to when exporting images in batch mode!"));
+ }
JalviewFileChooser chooser = imageType.getFileChooser();
chooser.setFileView(new JalviewFileView());
MessageManager.formatMessage("label.create_image_of",
protected void addReferenceAnnotations_actionPerformed(
Map<SequenceI, List<AlignmentAnnotation>> candidates)
{
- final SequenceGroup selectionGroup = this.ap.av.getSelectionGroup();
final AlignmentI alignment = this.ap.getAlignment();
AlignmentUtils.addReferenceAnnotations(candidates, alignment,
- selectionGroup);
+ null);
refresh();
}
*/
public SplashScreen(boolean isTransient)
{
+ Desktop.instance.acquireDialogQueue();
this.transientDialog = isTransient;
if (Platform.isJS()) // BH 2019
}
closeSplash();
- Desktop.instance.startDialogQueue();
+ Desktop.instance.releaseDialogQueue();
}
/**
// a long sequence.
// check for at least 55% nucleotide, and nucleotide and ambiguity codes
// (including N) must make up 95%
- return ntCount * 100 > NUCLEOTIDE_COUNT_PERCENT * allCount
+ return ntCount * 100 >= NUCLEOTIDE_COUNT_PERCENT * allCount
&& 100 * (ntCount + nCount
- + ntaCount) > NUCLEOTIDE_COUNT_LONG_SEQUENCE_AMBIGUITY_PERCENT
+ + ntaCount) >= NUCLEOTIDE_COUNT_LONG_SEQUENCE_AMBIGUITY_PERCENT
* allCount;
}
else if (allCount > NUCLEOTIDE_COUNT_VERY_SHORT_SEQUENCE)
// a short sequence.
// check if a short sequence is at least 55% nucleotide and the rest of
// the symbols are all X or all N
- if (ntCount * 100 > NUCLEOTIDE_COUNT_PERCENT * allCount
+ if (ntCount * 100 >= NUCLEOTIDE_COUNT_PERCENT * allCount
&& (nCount == aaCount || xCount == aaCount))
{
return true;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
-import java.util.List;
import javax.ws.rs.HttpMethod;
+import jalview.bin.Cache;
+
public class HttpUtils
{
return connection.getResponseCode() == 200;
}
+ public static String getUserAgent()
+ {
+ return getUserAgent(null);
+ }
+
+ public static String getUserAgent(String className)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Jalview");
+ sb.append('/');
+ sb.append(Cache.getDefault("VERSION", "Unknown"));
+ sb.append(" (");
+ sb.append(System.getProperty("os.name"));
+ sb.append("; ");
+ sb.append(System.getProperty("os.arch"));
+ sb.append(' ');
+ sb.append(System.getProperty("os.name"));
+ sb.append(' ');
+ sb.append(System.getProperty("os.version"));
+ sb.append("; ");
+ sb.append("java/");
+ sb.append(System.getProperty("java.version"));
+ sb.append("; ");
+ sb.append("jalview/");
+ sb.append(ChannelProperties.getProperty("channel"));
+ if (className != null)
+ {
+ sb.append("; ");
+ sb.append(className);
+ }
+ String installation = Cache.applicationProperties
+ .getProperty("INSTALLATION");
+ if (installation != null)
+ {
+ sb.append("; ");
+ sb.append(installation);
+ }
+ sb.append(')');
+ sb.append(" help@jalview.org");
+ return sb.toString();
+ }
+
}
import jalview.datamodel.AlignmentAnnotation;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
}
synchronized (updating)
{
- for (List<AlignCalcWorkerI> workers : updating.values())
+ Collection<List<AlignCalcWorkerI>> workersLists = updating.values();
+ synchronized (workersLists)
{
- working |= workers.size() > 0;
+ for (List<AlignCalcWorkerI> workers : workersLists)
+ {
+ if (workers!=null)
+ {
+ synchronized (workers) {
+ working |= workers.size() > 0;
+ }
+ }
+ }
}
}
return working;
import jalview.gui.JvOptionPane;
import java.io.PrintStream;
+import java.nio.charset.Charset;
+import java.util.Locale;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import com.google.common.base.Charsets;
+
/**
* Test the alignment -> Mapping routines
*
assertEquals(as.getAStr1(), as.getAStr2());
Mapping s1tos2 = as.getMappingFromS1(false);
+ checkMapping(s1tos2,s1,s2);
+ }
+
+ public void checkMapping(Mapping s1tos2,SequenceI _s1,SequenceI _s2)
+ {
System.out.println(s1tos2.getMap().toString());
- for (int i = s2.getStart(); i < s2.getEnd(); i++)
+ for (int i = _s2.getStart(); i < _s2.getEnd(); i++)
{
- System.out.println("Position in s2: " + i
- + " maps to position in s1: " + s1tos2.getPosition(i));
- // TODO fails: getCharAt doesn't allow for the start position??
- // assertEquals(String.valueOf(s2.getCharAt(i)),
- // String.valueOf(s1.getCharAt(s1tos2.getPosition(i))));
+ int p=s1tos2.getPosition(i);
+ char s2c=_s2.getCharAt(i-_s2.getStart());
+ char s1c=_s1.getCharAt(p-_s1.getStart());
+ System.out.println("Position in s2: " + i +s2c
+ + " maps to position in s1: " +p+s1c);
+ assertEquals(s1c,s2c);
}
}
+ @Test(groups = { "Functional" })
+ /**
+ * simple test that mapping from alignment corresponds identical positions.
+ */
+ public void testGetMappingForS1_withLowerCase()
+ {
+ // make one of the sequences lower case
+ SequenceI ns2 = new Sequence(s2);
+ ns2.replace('D', 'd');
+ AlignSeq as = AlignSeq.doGlobalNWAlignment(s1, ns2, AlignSeq.PEP);
+ System.out.println("s1: " + as.getAStr1());
+ System.out.println("s2: " + as.getAStr2());
+
+ // aligned results match
+ assertEquals("ASDFA", as.getAStr1());
+ assertEquals(as.getAStr1(), as.getAStr2().toUpperCase(Locale.ROOT));
+
+ Mapping s1tos2 = as.getMappingFromS1(false);
+ assertEquals("ASdFA",as.getAStr2());
+ // verify mapping is consistent between original all-caps sequences
+ checkMapping(s1tos2,s1,s2);
+ }
@Test(groups = { "Functional" })
public void testExtractGaps()
String[] args = cmdLine.split("\\s+");
CommandsTest.callJalviewMain(args);
- try
+ while (Desktop.instance!=null && Desktop.instance.operationsAreInProgress())
{
- // sleep for slow build server to open annotations and viewer windows
- Thread.sleep(seqNum * 50 + annNum * 50 + viewerNum * 500);
- } catch (InterruptedException e)
- {
- e.printStackTrace();
+ try
+ {
+ // sleep for slow build server to open annotations and viewer windows
+ Thread.sleep(viewerNum * 50);
+ } catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
}
+ ;
AlignFrame[] afs = Desktop.getAlignFrames();
Assert.assertNotNull(afs);
{
Sequence origSeq = new Sequence("MYSEQ", "THISISASEQ");
Sequence toSeq = new Sequence("MYSEQ", "THISISASEQ");
+ origSeq.setDescription("DESCRIPTION");
origSeq.addDBRef(new DBRefEntry("UNIPROT", "0", "Q12345", null, true));
+
+ toSeq.transferAnnotation(origSeq, null);
+ assertEquals("DESCRIPTION",toSeq.getDescription());
+ toSeq = new Sequence("MYSEQ", "THISISASEQ");
+ toSeq.setDescription("unchanged");
toSeq.transferAnnotation(origSeq, null);
+ assertEquals("unchanged",toSeq.getDescription());
+
assertTrue(toSeq.getDBRefs().size() == 1);
assertTrue(toSeq.getDBRefs().get(0).isCanonical());
--- /dev/null
+<!DOCTYPE html>
+<html>
+<head>
+<title>SwingJS test Jalview</title><meta charset="utf-8" />
+<script src="swingjs/swingjs2.js"></script>
+<script>
+if (!self.SwingJS)alert('swingjs2.js was not found. It needs to be in swingjs folder in the same directory as ' + document.location.href)
+Info = {
+ code: null,
+ main: "jalview.bin.Jalview",
+ core: "NONE",
+ width: 850,
+ height: 550,
+ readyFunction: null,
+ serverURL: 'https://chemapps.stolaf.edu/jmol/jsmol/php/jsmol.php',
+ j2sPath: 'swingjs/j2s',
+ console: window.console,
+ allowjavascript: true
+}
+</script>
+</head>
+<body>
+<script>
+// we define console.err because swingjs2.js calls it instead of console.error
+window.console.err = function() {
+ this.error.apply(this,arguments);
+}
+SwingJS.getApplet('testApplet', Info)
+getClassList = function(){J2S._saveFile('_j2sclasslist.txt', Clazz.ClassFilesLoaded.sort().join('\n'))}
+</script>
+<div style="position:absolute;left:900px;top:30px;width:600px;height:300px;">
+<div id="sysoutdiv" contentEditable="true" style="border:1px solid green;width:100%;height:95%;overflow:auto"></div>
+This is System.out. <a href="javascript:testApplet._clearConsole()">clear it</a> <br>Add ?j2snocore to URL to see full class list; ?j2sdebug to use uncompressed j2s/core files <br><a href="javascript:getClassList()">get _j2sClassList.txt</a>
+</div>
+</body>
+</html>