From: James Procter Date: Wed, 21 Jun 2023 11:24:27 +0000 (+0200) Subject: Merge branch 'patch/JAL-4196_structure_viewer_synchronising' into develop X-Git-Tag: Release_2_11_3_0~13^2~7 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=refs%2Fheads%2Fpatch%2FJAL-4196_structure_viewer_synchronising;hp=b15a4dfe51ded457b2850f14c19b5245aadb918e;p=jalview.git Merge branch 'patch/JAL-4196_structure_viewer_synchronising' into develop --- diff --git a/build.gradle b/build.gradle index ca599a8..a47a7fd 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,10 @@ import org.gradle.plugins.ide.eclipse.model.Output 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 @@ -568,11 +572,16 @@ ext { 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 } @@ -4201,9 +4210,156 @@ task eclipseAutoBuildTask { } +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 } - diff --git a/gradle.properties b/gradle.properties index 7b3a031..75231f3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -260,5 +260,13 @@ jalviewjs_j2s_to_console = true 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 diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index 4200a46..7f5746e 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -1452,6 +1452,8 @@ label.choose_tempfac_type = Choose Temperature Factor type label.interpret_tempfac_as = Interpret Temperature Factor as label.add_pae_matrix_file = Add PAE matrix file label.nothing_selected = Nothing selected +prompt.google_analytics_title = Jalview Usage Statistics +prompt.google_analytics = Do you want to help make Jalview better by enabling the collection of usage statistics with Google 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 diff --git a/resources/lang/Messages_es.properties b/resources/lang/Messages_es.properties index b3c6988..a4594dc 100644 --- a/resources/lang/Messages_es.properties +++ b/resources/lang/Messages_es.properties @@ -1434,3 +1434,5 @@ label.tftype_default = Default label.tftype_plddt = pLDDT label.add_pae_matrix_file = Añadir un fichero de matriz PAE label.nothing_selected = Nada seleccionado +prompt.google_analytics_title = Jalview Estadísticas de Uso +prompt.google_analytics = ¿Quiere ayudar a mejorar Jalview habilitando la recopilación de estadísticas de uso con Google Analytics?\nPuede habilitar o deshabilitar el seguimiento de uso en las preferencias. diff --git a/src/jalview/analytics/GoogleAnalytics4.java b/src/jalview/analytics/GoogleAnalytics4.java new file mode 100644 index 0000000..ba8a920 --- /dev/null +++ b/src/jalview/analytics/GoogleAnalytics4.java @@ -0,0 +1,531 @@ +package jalview.analytics; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +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 java.util.UUID; + +import jalview.bin.Cache; +import jalview.bin.Console; +import jalview.util.ChannelProperties; + +public class GoogleAnalytics4 +{ + private static final String JALVIEW_ID = "Jalview Desktop"; + + private static final String SESSION_ID = new Random().toString(); + + private static final String MEASUREMENT_ID = "G-6TMPHMXEQ0"; + + private static final String API_SECRET = "Qb9NSbqkRDqizG6j2BBJ2g"; + + // This will generate a different CLIENT_ID each time the application is + // launched. Do we want to store it in .jalview_properties? + private static final String CLIENT_ID = UUID.randomUUID().toString(); + + private static final String BASE_URL = "https://www.google-analytics.com/mp/collect"; + + private static final String DESKTOP_EVENT = "desktop_event"; + + private List> queryStringValues; + + private List> jsonObject; + + private List events; + + private List> cookieValues; + + private static boolean ENABLED = false; + + private static GoogleAnalytics4 instance = null; + + private static final Map defaultParams; + + static + { + defaultParams = new HashMap<>(); + defaultParams.put("app_name", + ChannelProperties.getProperty("app_name") + " Desktop"); + defaultParams.put("version", Cache.getProperty("VERSION")); + defaultParams.put("build_date", + Cache.getDefault("BUILD_DATE", "unknown")); + defaultParams.put("java_version", System.getProperty("java.version")); + String val = System.getProperty("sys.install4jVersion"); + if (val != null) + { + defaultParams.put("install4j_version", val); + } + val = System.getProperty("installer_template_version"); + if (val != null) + { + defaultParams.put("install4j_template_version", val); + } + val = System.getProperty("launcher_version"); + if (val != null) + { + defaultParams.put("launcher_version", val); + } + defaultParams.put("java_arch", + System.getProperty("os.arch") + " " + + System.getProperty("os.name") + " " + + System.getProperty("os.version")); + } + + private GoogleAnalytics4() + { + this.resetLists(); + } + + public static void setEnabled(boolean b) + { + ENABLED = b; + } + + public void sendAnalytics(String eventName, String... paramsStrings) + { + sendAnalytics(eventName, false, paramsStrings); + } + + /** + * The simplest way to send an analytic event. + * + * @param eventName + * The event name. To emulate a webpage view use "page_view" and set + * a "page_location" parameter. See + * https://developers.google.com/analytics/devguides/collection/ga4/events?client_type=gtag + * @param sendDefaultParams + * Flag whether to add the default params about the application. + * @param paramsStrings + * Optional multiple Strings in key, value pairs (there should be an + * even number of paramsStrings) to be set as parameters of the + * event. To emulate a webpage view use "page_location" as the URL in + * a "page_view" event. + */ + public void sendAnalytics(String eventName, boolean sendDefaultParams, + String... paramsStrings) + { + // clear out old lists + this.resetLists(); + + if (!ENABLED) + { + Console.debug("GoogleAnalytics4 not enabled."); + return; + } + Map params = new HashMap<>(); + params.put("event_category", DESKTOP_EVENT); + params.put("event_label", eventName); + + // add these to all events from this application instance + if (sendDefaultParams) + { + params.putAll(defaultParams); + } + + // add (and overwrite with) the passed in params + if (paramsStrings != null && paramsStrings.length > 0) + { + if (paramsStrings.length % 2 != 0) + { + Console.warn( + "Cannot addEvent with odd number of paramsStrings. Ignoring the last one."); + } + for (int i = 0; i < paramsStrings.length - 1; i += 2) + { + String key = paramsStrings[i]; + String value = paramsStrings[i + 1]; + params.put(key, value); + } + } + + addEvent(eventName, params); + addQueryStringValue("measurement_id", MEASUREMENT_ID); + addQueryStringValue("api_secret", API_SECRET); + addJsonValue("client_id", CLIENT_ID); + addJsonValues("events", Event.toObjectList(events)); + StringBuilder urlSb = new StringBuilder(); + urlSb.append(BASE_URL); + urlSb.append('?'); + urlSb.append(buildQueryString()); + 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("GA4: HTTP Request is: '" + urlSb.toString() + "'"); + Console.debug("GA4: 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; charset=UTF-8"); + 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("GoogleAnalytics4 connection failed: '" + responseCode + + " " + responseMessage + "'"); + } + else + { + Console.debug("GoogleAnalytics4 connection succeeded: '" + + responseCode + " " + responseMessage + "'"); + } + } catch (MalformedURLException e) + { + Console.debug( + "Somehow the GoogleAnalytics4 BASE_URL and queryString is malformed.", + e); + return; + } catch (IOException e) + { + Console.debug("Connection to GoogleAnalytics4 BASE_URL '" + BASE_URL + + "' failed.", e); + } catch (ClassCastException e) + { + Console.debug( + "Couldn't cast URLConnection to HttpURLConnection in GoogleAnalytics4.", + e); + } + } + + public void addEvent(String name, Map params) + { + Event event = new Event(name); + if (params != null && params.size() > 0) + { + for (String key : params.keySet()) + { + String value = params.get(key); + event.addParam(key, value); + } + } + events.add(event); + } + + private void addJsonObject(String key, + List> object) + { + jsonObject.add(objectEntry(key, object)); + } + + private void addJsonValues(String key, List 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<>(); + events = new ArrayList(); + queryStringValues = new ArrayList<>(); + cookieValues = new ArrayList<>(); + } + + public static GoogleAnalytics4 getInstance() + { + if (instance == null) + { + instance = new GoogleAnalytics4(); + } + return instance; + } + + public static void reset() + { + getInstance().resetLists(); + } + + private String buildQueryString() + { + StringBuilder sb = new StringBuilder(); + for (Map.Entry 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> entries) + { + indent(sb, indent); + sb.append('{'); + newline(sb); + Iterator> entriesI = entries.iterator(); + while (entriesI.hasNext()) + { + Map.Entry 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 entry = (Map.Entry) value; + List> object = new ArrayList<>(); + object.add(entry); + addJsonObject(sb, indent, object); + } + else if (value instanceof List) + { + // list of Map.Entries or list of values? + List valueList = (List) value; + if (valueList.size() > 0 && valueList.get(0) instanceof Map.Entry) + { + // entries + // indent(sb, indent); + List> entryList = (List>) value; + addJsonObject(sb, indent, entryList); + } + else + { + // values + indent(sb, indent); + sb.append('['); + newline(sb); + Iterator 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("((?= 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 objectEntry(String s, Object o) + { + return new AbstractMap.SimpleEntry(s, o); + } + + protected static Map.Entry stringEntry(String s, String v) + { + return new AbstractMap.SimpleEntry(s, v); + } +} + +class Event +{ + private String name; + + private List> params; + + @SafeVarargs + public Event(String name, Map.Entry... paramEntries) + { + this.name = name; + this.params = new ArrayList>(); + for (Map.Entry paramEntry : paramEntries) + { + if (paramEntry == null) + { + continue; + } + params.add(paramEntry); + } + } + + public void addParam(String param, String value) + { + params.add(GoogleAnalytics4.stringEntry(param, value)); + } + + protected List> toObject() + { + List> object = new ArrayList<>(); + object.add(GoogleAnalytics4.objectEntry("name", (Object) name)); + if (params.size() > 0) + { + object.add(GoogleAnalytics4.objectEntry("params", (Object) params)); + } + return object; + } + + protected static List toObjectList(List events) + { + List eventObjectList = new ArrayList<>(); + for (Event event : events) + { + eventObjectList.add((Object) event.toObject()); + } + return eventObjectList; + } +} diff --git a/src/jalview/bin/Cache.java b/src/jalview/bin/Cache.java index 5aab0ec..fc9ddda 100755 --- a/src/jalview/bin/Cache.java +++ b/src/jalview/bin/Cache.java @@ -52,6 +52,7 @@ import java.util.TreeSet; import javax.swing.LookAndFeel; import javax.swing.UIManager; +import jalview.analytics.GoogleAnalytics4; import jalview.datamodel.PDBEntry; import jalview.gui.Preferences; import jalview.gui.UserDefinedColours; @@ -962,93 +963,138 @@ public class Cache protected static Class jgoogleanalyticstracker = null; + private static boolean useGA4 = true; + /** * Initialise the google tracker if it is not done already. */ public static void initGoogleTracker() { - if (tracker == null) + if (useGA4) { - 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"; + GoogleAnalytics4.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 { - // 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) + path = "/" + String.join("/", URLEncoder.encode(appName, "UTF-8"), + URLEncoder.encode(version, "UTF-8"), + URLEncoder.encode(APPLICATION_STARTED, "UTF-8")); + } catch (UnsupportedEncodingException e) { - re = e; - } catch (Exception e) - { - ex = e; - } catch (Error e) - { - err = e; + */ + path = ("/" + String.join("/", appName, version, APPLICATION_STARTED)) + .replace(' ', '+'); + /* } - if (re != null || ex != null || err != null) + */ + GoogleAnalytics4 ga4 = GoogleAnalytics4.getInstance(); + + // This will add a page_view similar to the old UA analytics. + // We probably want to get rid of this once the application_launch event + // is being processed properly. + ga4.sendAnalytics("page_view", "page_location", path, "page_title", + APPLICATION_STARTED); + + // This will send a new "application_launch" event with parameters + // including the old-style "path", the channel name and version + ga4.sendAnalytics("application_launch", true, "page_location", path); + } + else + { + if (tracker == null) { - if (re != null) + if (jgoogleanalyticstracker == null) { - Console.debug("Caught runtime exception in googletracker init:", - re); + // 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; + } } - if (ex != null) + // 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) { - Console.warn( - "Failed to initialise GoogleTracker for Jalview Desktop with version " - + vrs, - ex); + 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); + } } - if (err != null) + else { - Console.error( - "Whilst initing GoogleTracker for Jalview Desktop version " - + vrs, - err); + Console.debug("Successfully initialised tracker."); } } - else - { - Console.debug("Successfully initialised tracker."); - } } } + private static final String APPLICATION_STARTED = "Application Started."; + /** * get the user's default colour if available * @@ -1478,10 +1524,11 @@ public class Cache 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 diff --git a/src/jalview/bin/Console.java b/src/jalview/bin/Console.java index 4b18484..30fd530 100644 --- a/src/jalview/bin/Console.java +++ b/src/jalview/bin/Console.java @@ -220,6 +220,11 @@ public class Console return JLogger.toLevel(level); } + public static JLogger getLogger() + { + return log; + } + public static boolean initLogger() { return initLogger(null); @@ -236,9 +241,13 @@ public class Console JLogger.LogLevel logLevel = JLogger.LogLevel.INFO; if (JLogger.isLevel(providedLogLevel)) + { logLevel = Console.getLogLevel(providedLogLevel); + } else + { logLevel = getCachedLogLevel(); + } if (!Platform.isJS()) { diff --git a/src/jalview/bin/Jalview.java b/src/jalview/bin/Jalview.java index 6d2866a..3a733f3 100755 --- a/src/jalview/bin/Jalview.java +++ b/src/jalview/bin/Jalview.java @@ -1595,10 +1595,9 @@ public class Jalview * 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.google_analytics_title"), + MessageManager.getString("prompt.google_analytics"), new Runnable() { @Override diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java index 33ba97c..b901ae4 100644 --- a/src/jalview/gui/Desktop.java +++ b/src/jalview/gui/Desktop.java @@ -536,6 +536,9 @@ public class Desktop extends jalview.jbgui.GDesktop setBounds(xPos, yPos, 900, 650); } + // start dialogue queue for single dialogues + startDialogQueue(); + if (!Platform.isJS()) /** * Java only @@ -623,6 +626,12 @@ public class Desktop extends jalview.jbgui.GDesktop } }); desktop.addMouseListener(ma); + + if (Platform.isJS()) + { + // used for jalviewjsTest + jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP"); + } } /** @@ -3051,7 +3060,7 @@ public class Desktop extends jalview.jbgui.GDesktop /** * pause the queue */ - private java.util.concurrent.Semaphore block = new Semaphore(0); + private Semaphore block = new Semaphore(0); private static groovy.ui.Console groovyConsole; @@ -3069,12 +3078,7 @@ public class Desktop extends jalview.jbgui.GDesktop { if (dialogPause) { - try - { - block.acquire(); - } catch (InterruptedException x) - { - } + acquireDialogQueue(); } if (instance == null) { @@ -3092,12 +3096,41 @@ public class Desktop extends jalview.jbgui.GDesktop }); } + 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; } /** @@ -3132,10 +3165,14 @@ public class Desktop extends jalview.jbgui.GDesktop 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); } } @@ -3582,7 +3619,8 @@ public class Desktop extends jalview.jbgui.GDesktop */ public static void closeDesktop() { - if (Desktop.instance != null) { + if (Desktop.instance != null) + { Desktop.instance.closeAll_actionPerformed(null); Desktop.instance.setVisible(false); Desktop.instance.dispose(); diff --git a/src/jalview/gui/SplashScreen.java b/src/jalview/gui/SplashScreen.java index 61273c7..0b7af0d 100755 --- a/src/jalview/gui/SplashScreen.java +++ b/src/jalview/gui/SplashScreen.java @@ -113,6 +113,7 @@ public class SplashScreen extends JPanel */ public SplashScreen(boolean isTransient) { + Desktop.instance.acquireDialogQueue(); this.transientDialog = isTransient; if (Platform.isJS()) // BH 2019 @@ -323,7 +324,7 @@ public class SplashScreen extends JPanel } closeSplash(); - Desktop.instance.startDialogQueue(); + Desktop.instance.releaseDialogQueue(); } /** diff --git a/utils/jalviewjs/chromium_test/jalview_bin_Jalview-stderr.html b/utils/jalviewjs/chromium_test/jalview_bin_Jalview-stderr.html new file mode 100644 index 0000000..bf7a678 --- /dev/null +++ b/utils/jalviewjs/chromium_test/jalview_bin_Jalview-stderr.html @@ -0,0 +1,36 @@ + + + +SwingJS test Jalview + + + + + +
+
+This is System.out. clear it
Add ?j2snocore to URL to see full class list; ?j2sdebug to use uncompressed j2s/core files
get _j2sClassList.txt +
+ +