JAL-4298 Fixed initial log level to see .jalview_properties setting. Moved Java Conso...
[jalview.git] / src / jalview / bin / Cache.java
index 216b498..a44d474 100755 (executable)
@@ -24,7 +24,9 @@ import java.awt.Color;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
@@ -34,27 +36,28 @@ import java.net.PasswordAuthentication;
 import java.net.URL;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 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 java.util.StringTokenizer;
 import java.util.TreeSet;
-import java.util.regex.Pattern;
 
 import javax.swing.LookAndFeel;
 import javax.swing.UIManager;
 
-import org.apache.log4j.ConsoleAppender;
-import org.apache.log4j.Level;
-import org.apache.log4j.Logger;
-import org.apache.log4j.SimpleLayout;
-
+import jalview.analytics.Plausible;
 import jalview.datamodel.PDBEntry;
 import jalview.gui.Preferences;
 import jalview.gui.UserDefinedColours;
+import jalview.log.JLoggerLog4j;
 import jalview.schemes.ColourSchemeLoader;
 import jalview.schemes.ColourSchemes;
 import jalview.schemes.UserColourScheme;
@@ -126,7 +129,7 @@ import jalview.ws.sifts.SiftsSettings;
  * 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>
@@ -138,6 +141,10 @@ import jalview.ws.sifts.SiftsSettings;
  * <li>WRAP_ALIGNMENT</li>
  * <li>EPS_RENDERING (Prompt each time|Lineart|Text) default for EPS rendering
  * style check</li>
+ * <li>BITMAP_SCALE - scale factor for PNG export - default 0.0 - native
+ * resolution</li>
+ * <li>BITMAP_HEIGHT - height bound for PNG export or 0 for unbound</li>
+ * <li>BITMAP_WIDTH - width bound for PNG export or 0 for unbound</li>
  * <li>SORT_ALIGNMENT (No sort|Id|Pairwise Identity)</li>
  * <li>SEQUENCE_LINKS list of name|URL pairs for opening a url with
  * $SEQUENCE_ID$</li>
@@ -175,6 +182,8 @@ import jalview.ws.sifts.SiftsSettings;
  * <li>FOLLOW_SELECTIONS (true) Controls whether a new alignment view should
  * respond to selections made in other alignments containing the same sequences.
  * </li>
+ * <li>SHOW_JWS2_SERVICES (true) when set to false, jalview will not
+ * auto-discover JABAWS services</li>
  * <li>JWS2HOSTURLS comma-separated list of URLs to try for JABAWS services</li>
  * <li>SHOW_WSDISCOVERY_ERRORS (true) Controls if the web service URL discovery
  * warning dialog box is displayed.</li>
@@ -182,8 +191,8 @@ import jalview.ws.sifts.SiftsSettings;
  * when shading by annotation</li>
  * <li>ANNOTATIONCOLOUR_MAX (red) Shade used for maximum value of annotation
  * when shading by annotation</li>
- * <li>www.jalview.org (http://www.jalview.org) a property enabling all HTTP
- * requests to be redirected to a mirror of http://www.jalview.org</li>
+ * <li>www.jalview.org (https://www.jalview.org) a property enabling all HTTP
+ * requests to be redirected to a mirror of https://www.jalview.org</li>
  * <li>FIGURE_AUTOIDWIDTH (false) Expand the left hand column of an exported
  * alignment figure to accommodate even the longest sequence ID or annotation
  * label.</li>
@@ -202,7 +211,13 @@ import jalview.ws.sifts.SiftsSettings;
  * <li>CHIMERA_PATH specify full path to Chimera program (if non-standard)</li>
  * <li>ID_ORG_HOSTURL location of jalview service providing identifiers.org urls
  * </li>
- * 
+ * <li>NONEWS - when set disables Jalview News from automatically appearing</li>
+ * <li>NOHTMLTEMPLATES - when set, the
+ * https://github.com/jalview/exporter-templates/tree/master/biojs repository is
+ * not downloaded automatically</li>
+ * <li>NOIDENTIFIERSSERVICE - when set, jalview won't automatically download
+ * available URL linkouts via www.jalview.org/services/identifiers</li>
+ * <li>
  * </ul>
  * Deprecated settings:
  * <ul>
@@ -237,6 +252,9 @@ public class Cache
    */
   public static final String JALVIEWLOGLEVEL = "logs.Jalview.level";
 
+  // for tests
+  public static final String BOOTSTRAP_TEST = "BOOTSTRAP_TEST";
+
   /**
    * Sifts settings
    */
@@ -275,7 +293,8 @@ public class Cache
   /**
    * Initialises the Jalview Application Log
    */
-  public static Logger log;
+
+  public final static String JALVIEW_LOGGER_NAME = "JalviewLogger";
 
   // save the proxy properties set at startup
   public final static String[] startupProxyProperties = {
@@ -299,6 +318,24 @@ public class Cache
   // in-memory only storage of proxy password, safer to use char array
   public static char[] proxyAuthPassword = null;
 
+  /**
+   * Session properties, set by command line, try not to affect stored
+   * properties!
+   */
+  private static Map<String, String> sessionProperties = new HashMap<>();
+
+  private static boolean bypassSessionProperties = false;
+
+  public static void enableSessionProperties()
+  {
+    bypassSessionProperties = false;
+  }
+
+  public static void disableSessionProperties()
+  {
+    bypassSessionProperties = true;
+  }
+
   /** Jalview Properties */
   public static Properties applicationProperties = new Properties()
   {
@@ -316,62 +353,25 @@ public class Cache
   /** Default file is ~/.jalview_properties */
   static String propertiesFile;
 
+  private static final String fallbackPropertiesFile = ".jalview_properties";
+
   private static boolean propsAreReadOnly = Platform.isJS();
 
-  private final static String JS_PROPERTY_PREFIX = "jalview_";
+  public static boolean isPropsAreReadOnly()
+  {
+    return propsAreReadOnly;
+  }
 
-  public static void initLogger()
+  public static void setPropsAreReadOnly(boolean propsAreReadOnly)
   {
-    if (log != null)
-    {
-      return;
-    }
-    try
-    {
-      // TODO: redirect stdout and stderr here in order to grab the output of
-      // the log
-
-      ConsoleAppender ap = new ConsoleAppender(new SimpleLayout(),
-              "System.err");
-      ap.setName("JalviewLogger");
-      org.apache.log4j.Logger.getRootLogger().addAppender(ap); // catch all for
-      // log output
-      Logger laxis = Logger.getLogger("org.apache.axis");
-      Logger lcastor = Logger.getLogger("org.exolab.castor");
-      jalview.bin.Cache.log = Logger.getLogger("jalview.bin.Jalview");
-
-      laxis.setLevel(Level.toLevel(
-              Cache.getDefault("logs.Axis.Level", Level.INFO.toString())));
-      lcastor.setLevel(Level.toLevel(Cache.getDefault("logs.Castor.Level",
-              Level.INFO.toString())));
-      lcastor = Logger.getLogger("org.exolab.castor.xml");
-      lcastor.setLevel(Level.toLevel(Cache.getDefault("logs.Castor.Level",
-              Level.INFO.toString())));
-      // lcastor = Logger.getLogger("org.exolab.castor.xml.Marshaller");
-      // lcastor.setLevel(Level.toLevel(Cache.getDefault("logs.Castor.Level",
-      // Level.INFO.toString())));
-      // we shouldn't need to do this
-      org.apache.log4j.Logger.getRootLogger()
-              .setLevel(org.apache.log4j.Level.INFO);
-
-      jalview.bin.Cache.log.setLevel(Level.toLevel(Cache
-              .getDefault("logs.Jalview.level", Level.INFO.toString())));
-      // laxis.addAppender(ap);
-      // lcastor.addAppender(ap);
-      // jalview.bin.Cache.log.addAppender(ap);
-      // Tell the user that debug is enabled
-      jalview.bin.Cache.log.debug(ChannelProperties.getProperty("app_name")
-              + " Debugging Output Follows.");
-    } catch (Exception ex)
-    {
-      System.err.println("Problems initializing the log4j system\n");
-      ex.printStackTrace(System.err);
-    }
+    Cache.propsAreReadOnly = propsAreReadOnly;
   }
 
+  private final static String JS_PROPERTY_PREFIX = "jalview_";
+
   /**
-   * Loads properties from the given properties file. Any existing properties are
-   * first cleared.
+   * Loads properties from the given properties file. Any existing properties
+   * are first cleared.
    */
   public static void loadProperties(String propsFile)
   {
@@ -382,7 +382,7 @@ public class Cache
     {
       String channelPrefsFilename = ChannelProperties
               .getProperty("preferences.filename");
-      String releasePrefsFilename = ".jalview_properties";
+      String releasePrefsFilename = fallbackPropertiesFile;
       propertiesFile = System.getProperty("user.home") + File.separatorChar
               + channelPrefsFilename;
       releasePropertiesFile = System.getProperty("user.home")
@@ -409,10 +409,13 @@ public class Cache
         {
           // props file provided as URL
           fis = new URL(propertiesFile).openStream();
-          System.out.println(
-                  "Loading jalview properties from : " + propertiesFile);
-          System.out.println(
-                  "Disabling Jalview writing to user's local properties file.");
+          if (!Jalview.quiet())
+          {
+            jalview.bin.Console.outPrintln(
+                    "Loading jalview properties from : " + propertiesFile);
+            jalview.bin.Console.outPrintln(
+                    "Disabling Jalview writing to user's local properties file.");
+          }
           propsAreReadOnly = true;
         } catch (Exception ex)
         {
@@ -440,7 +443,9 @@ public class Cache
         fis.close();
       } catch (Exception ex)
       {
-        System.out.println("Error reading properties file: " + ex);
+        if (!Jalview.quiet())
+          jalview.bin.Console
+                  .outPrintln("Error reading properties file: " + ex);
       }
     }
 
@@ -479,7 +484,7 @@ public class Cache
     default:
       String message = "Incorrect PROXY_TYPE - should be 'none' (clear proxy properties), 'false' (system settings), 'true' (custom settings): "
               + proxyType;
-      Cache.warn(message);
+      Console.warn(message);
     }
 
     // LOAD THE AUTHORS FROM THE authors.props file
@@ -496,7 +501,9 @@ public class Cache
       }
     } catch (Exception ex)
     {
-      System.out.println("Error reading author details: " + ex);
+      if (!Jalview.quiet())
+        jalview.bin.Console
+                .outPrintln("Error reading author details: " + ex);
       authorDetails = null;
     }
     if (authorDetails == null)
@@ -511,27 +518,27 @@ public class Cache
     SiftsSettings
             .setMapWithSifts(Cache.getDefault("MAP_WITH_SIFTS", false));
 
-    SiftsSettings.setSiftDownloadDirectory(jalview.bin.Cache
+    SiftsSettings.setSiftDownloadDirectory(Cache
             .getDefault("sifts_download_dir", DEFAULT_SIFTS_DOWNLOAD_DIR));
 
     SiftsSettings.setFailSafePIDThreshold(
-            jalview.bin.Cache.getDefault("sifts_fail_safe_pid_threshold",
+            Cache.getDefault("sifts_fail_safe_pid_threshold",
                     DEFAULT_FAIL_SAFE_PID_THRESHOLD));
 
     SiftsSettings.setCacheThresholdInDays(
-            jalview.bin.Cache.getDefault("sifts_cache_threshold_in_days",
+            Cache.getDefault("sifts_cache_threshold_in_days",
                     DEFAULT_CACHE_THRESHOLD_IN_DAYS));
 
     IdOrgSettings.setUrl(getDefault("ID_ORG_HOSTURL",
-            "http://www.jalview.org/services/identifiers"));
+            "https://www.jalview.org/services/identifiers"));
     IdOrgSettings.setDownloadLocation(ID_ORG_FILE);
 
-    StructureImportSettings.setDefaultStructureFileFormat(jalview.bin.Cache
-            .getDefault("PDB_DOWNLOAD_FORMAT", PDB_DOWNLOAD_FORMAT));
+    StructureImportSettings.setDefaultStructureFileFormat(
+            Cache.getDefault("PDB_DOWNLOAD_FORMAT", PDB_DOWNLOAD_FORMAT));
     StructureImportSettings
             .setDefaultPDBFileParser(DEFAULT_PDB_FILE_PARSER);
     // StructureImportSettings
-    // .setDefaultPDBFileParser(jalview.bin.Cache.getDefault(
+    // .setDefaultPDBFileParser(Cache.getDefault(
     // "DEFAULT_PDB_FILE_PARSER", DEFAULT_PDB_FILE_PARSER));
 
     String jnlpVersion = System.getProperty("jalview.version");
@@ -559,29 +566,36 @@ public class Cache
           if (orgtimeout == null)
           {
             orgtimeout = "30";
-            System.out.println("# INFO: Setting default net timeout to "
-                    + orgtimeout + " seconds.");
+            Console.debug("Setting default net timeout to " + orgtimeout
+                    + " seconds.");
           }
           String remoteVersion = null;
-          try
+          if (remoteBuildPropertiesUrl.startsWith("http"))
           {
-            System.setProperty("sun.net.client.defaultConnectTimeout",
-                    "5000");
-            java.net.URL url = new java.net.URL(remoteBuildPropertiesUrl);
+            try
+            {
+              System.setProperty("sun.net.client.defaultConnectTimeout",
+                      "5000");
 
-            BufferedReader in = new BufferedReader(
-                    new InputStreamReader(url.openStream()));
+              URL url = new URL(remoteBuildPropertiesUrl);
 
-            Properties remoteBuildProperties = new Properties();
-            remoteBuildProperties.load(in);
-            remoteVersion = remoteBuildProperties.getProperty("VERSION");
-          } catch (Exception ex)
-          {
-            System.out
-                    .println("Non-fatal exception when checking version at "
-                            + remoteBuildPropertiesUrl + ":");
-            System.out.println(ex);
-            remoteVersion = getProperty("VERSION");
+              BufferedReader in = new BufferedReader(
+                      new InputStreamReader(url.openStream()));
+
+              Properties remoteBuildProperties = new Properties();
+              remoteBuildProperties.load(in);
+              remoteVersion = remoteBuildProperties.getProperty("VERSION");
+            } catch (Exception ex)
+            {
+              if (!Jalview.quiet())
+              {
+                jalview.bin.Console.errPrintln(
+                        "Non-fatal exception when checking version at "
+                                + remoteBuildPropertiesUrl + ":");
+                jalview.bin.Console.printStackTrace(ex);
+              }
+              remoteVersion = getProperty("VERSION");
+            }
           }
           System.setProperty("sun.net.client.defaultConnectTimeout",
                   orgtimeout);
@@ -628,8 +642,8 @@ public class Cache
         url = Cache.class.getResource(resourcePath).toString();
       } catch (Exception ex)
       {
-        System.err.println("Failed to resolve resource " + resourcePath
-                + ": " + ex.getMessage());
+        jalview.bin.Console.errPrintln("Failed to resolve resource "
+                + resourcePath + ": " + ex.getMessage());
       }
     }
     else
@@ -671,9 +685,16 @@ public class Cache
         applicationProperties.put("VERSION",
                 buildProperties.getProperty("VERSION"));
       }
+      if (buildProperties.getProperty("JAVA_COMPILE_VERSION", null) != null)
+      {
+        applicationProperties.put("JAVA_COMPILE_VERSION",
+                buildProperties.getProperty("JAVA_COMPILE_VERSION"));
+      }
     } catch (Exception ex)
     {
-      System.out.println("Error reading build details: " + ex);
+      if (!Jalview.quiet())
+        jalview.bin.Console
+                .outPrintln("Error reading build details: " + ex);
       applicationProperties.remove("VERSION");
     }
     String codeVersion = getProperty("VERSION");
@@ -693,8 +714,9 @@ public class Cache
     new BuildDetails(codeVersion, null, codeInstallation);
     if (printVersion && reportVersion)
     {
-      System.out.println(ChannelProperties.getProperty("app_name")
-              + " Version: " + codeVersion + codeInstallation);
+      jalview.bin.Console
+              .outPrintln(ChannelProperties.getProperty("app_name")
+                      + " version: " + codeVersion + codeInstallation);
     }
   }
 
@@ -710,16 +732,31 @@ public class Cache
   }
 
   /**
-   * Gets Jalview application property of given key. Returns null if key not found
+   * Gets Jalview application property of given key. Returns null if key not
+   * found
    * 
    * @param key
-   *              Name of property
+   *          Name of property
    * 
    * @return Property value
    */
   public static String getProperty(String key)
   {
-    String prop = applicationProperties.getProperty(key);
+    return getProperty(key, false);
+  }
+
+  public static String getProperty(String key,
+          boolean skipSessionProperties)
+  {
+    String prop = null;
+    if (!(skipSessionProperties || bypassSessionProperties))
+    {
+      prop = getSessionProperty(key);
+    }
+    if (prop == null)
+    {
+      prop = applicationProperties.getProperty(key);
+    }
     if (prop == null && Platform.isJS())
     {
       prop = applicationProperties.getProperty(Platform.getUniqueAppletID()
@@ -729,8 +766,8 @@ public class Cache
   }
 
   /**
-   * These methods are used when checking if the saved preference is different to
-   * the default setting
+   * These methods are used when checking if the saved preference is different
+   * to the default setting
    */
 
   public static boolean getDefault(String property, boolean def)
@@ -754,8 +791,28 @@ public class Cache
         def = Integer.parseInt(string);
       } catch (NumberFormatException e)
       {
-        System.out.println("Error parsing int property '" + property
-                + "' with value '" + string + "'");
+        if (!Jalview.quiet())
+          jalview.bin.Console.errPrintln("Error parsing int property '"
+                  + property + "' with value '" + string + "'");
+      }
+    }
+
+    return def;
+  }
+
+  public static float getDefault(String property, float def)
+  {
+    String string = getProperty(property);
+    if (string != null)
+    {
+      try
+      {
+        def = Float.parseFloat(string);
+      } catch (NumberFormatException e)
+      {
+        if (!Jalview.quiet())
+          jalview.bin.Console.errPrintln("Error parsing float property '"
+                  + property + "' with value '" + string + "'");
       }
     }
 
@@ -763,8 +820,8 @@ public class Cache
   }
 
   /**
-   * Answers the value of the given property, or the supplied default value if the
-   * property is not set
+   * Answers the value of the given property, or the supplied default value if
+   * the property is not set
    */
   public static String getDefault(String property, String def)
   {
@@ -776,9 +833,9 @@ public class Cache
    * Stores property in the file "HOME_DIR/.jalview_properties"
    * 
    * @param key
-   *              Name of object
+   *          Name of object
    * @param obj
-   *              String value of property
+   *          String value of property
    * 
    * @return previous value of property (or null)
    */
@@ -788,16 +845,25 @@ public class Cache
     try
     {
       oldValue = applicationProperties.setProperty(key, obj);
-      if (propertiesFile != null && !propsAreReadOnly)
+      if (propertiesFile != null && !propsAreReadOnly
+      // don't rewrite if new value is same as old value
+              && !((obj == null && oldValue == null)
+                      || (obj != null && obj.equals(oldValue))))
       {
+        // reset the session property too
+        if (sessionProperties.containsKey(key))
+        {
+          sessionProperties.remove(key);
+        }
         FileOutputStream out = new FileOutputStream(propertiesFile);
         applicationProperties.store(out, "---JalviewX Properties File---");
         out.close();
       }
     } catch (Exception ex)
     {
-      System.out.println(
-              "Error setting property: " + key + " " + obj + "\n" + ex);
+      if (!Jalview.quiet())
+        jalview.bin.Console.errPrintln(
+                "Error setting property: " + key + " " + obj + "\n" + ex);
     }
     return oldValue;
   }
@@ -827,7 +893,8 @@ public class Cache
         out.close();
       } catch (Exception ex)
       {
-        System.out.println("Error saving properties: " + ex);
+        if (!Jalview.quiet())
+          jalview.bin.Console.errPrintln("Error saving properties: " + ex);
       }
     }
   }
@@ -851,14 +918,13 @@ public class Cache
         if (jalview.jbgui.GDesktop.class.getClassLoader()
                 .loadClass("uk.ac.vamsas.client.VorbaId") != null)
         {
-          jalview.bin.Cache.log.debug(
+          Console.debug(
                   "Found Vamsas Classes (uk.ac..vamsas.client.VorbaId can be loaded)");
           vamsasJarsArePresent = 1;
-          Logger lvclient = Logger.getLogger("uk.ac.vamsas");
-          lvclient.setLevel(Level.toLevel(Cache
-                  .getDefault("logs.Vamsas.Level", Level.INFO.toString())));
-
-          lvclient.addAppender(log.getAppender("JalviewLogger"));
+          JLoggerLog4j lvclient = JLoggerLog4j.getLogger("uk.ac.vamsas",
+                  Console.getCachedLogLevel("logs.Vamsas.Level"));
+          JLoggerLog4j.addAppender(lvclient, Console.log,
+                  JALVIEW_LOGGER_NAME);
           // Tell the user that debug is enabled
           lvclient.debug(ChannelProperties.getProperty("app_name")
                   + " Vamsas Client Debugging Output Follows.");
@@ -866,7 +932,7 @@ public class Cache
       } catch (Exception e)
       {
         vamsasJarsArePresent = 0;
-        jalview.bin.Cache.log.debug("Vamsas Classes are not present");
+        Console.debug("Vamsas Classes are not present");
       }
     }
     return (vamsasJarsArePresent > 0);
@@ -891,14 +957,13 @@ public class Cache
         if (Cache.class.getClassLoader()
                 .loadClass("groovy.lang.GroovyObject") != null)
         {
-          jalview.bin.Cache.log.debug(
+          Console.debug(
                   "Found Groovy (groovy.lang.GroovyObject can be loaded)");
           groovyJarsArePresent = 1;
-          Logger lgclient = Logger.getLogger("groovy");
-          lgclient.setLevel(Level.toLevel(Cache
-                  .getDefault("logs.Groovy.Level", Level.INFO.toString())));
-
-          lgclient.addAppender(log.getAppender("JalviewLogger"));
+          JLoggerLog4j lgclient = JLoggerLog4j.getLogger("groovy",
+                  Console.getCachedLogLevel("logs.Groovy.Level"));
+          JLoggerLog4j.addAppender(lgclient, Console.log,
+                  JALVIEW_LOGGER_NAME);
           // Tell the user that debug is enabled
           lgclient.debug(ChannelProperties.getProperty("app_name")
                   + " Groovy Client Debugging Output Follows.");
@@ -906,142 +971,53 @@ public class Cache
       } catch (Error e)
       {
         groovyJarsArePresent = 0;
-        jalview.bin.Cache.log.debug("Groovy Classes are not present", e);
+        Console.debug("Groovy Classes are not present", e);
       } catch (Exception e)
       {
         groovyJarsArePresent = 0;
-        jalview.bin.Cache.log.debug("Groovy Classes are not present");
+        Console.debug("Groovy Classes are not present");
       }
     }
     return (groovyJarsArePresent > 0);
   }
 
   /**
-   * GA tracker object - actually JGoogleAnalyticsTracker null if tracking not
-   * enabled.
-   */
-  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.
+   * Initialise the tracker if it is not done already.
    */
-  public static void initGoogleTracker()
+  public static void initAnalytics()
   {
-    if (tracker == null)
-    {
-      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)
-        {
-          log.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 = jalview.bin.Cache.getProperty("VERSION") + "_"
-                            + jalview.bin.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 (log != null)
-        {
-          if (re != null)
-          {
-            log.debug("Caught runtime exception in googletracker init:",
-                    re);
-          }
-          if (ex != null)
-          {
-            log.warn(
-                    "Failed to initialise GoogleTracker for Jalview Desktop with version "
-                            + vrs,
-                    ex);
-          }
-          if (err != null)
-          {
-            log.error(
-                    "Whilst initing GoogleTracker for Jalview Desktop version "
-                            + vrs,
-                    err);
-          }
-        }
-        else
-        {
-          if (re != null)
-          {
-            System.err.println(
-                    "Debug: Caught runtime exception in googletracker init:"
-                            + vrs);
-            re.printStackTrace();
-          }
-          if (ex != null)
-          {
-            System.err.println(
-                    "Warning:  Failed to initialise GoogleTracker for Jalview Desktop with version "
-                            + vrs);
-            ex.printStackTrace();
-          }
+    Plausible.setEnabled(true);
 
-          if (err != null)
-          {
-            System.err.println(
-                    "ERROR: Whilst initing GoogleTracker for Jalview Desktop version "
-                            + vrs);
-            err.printStackTrace();
-          }
-        }
-      }
-      else
-      {
-        log.debug("Successfully initialised tracker.");
-      }
+    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
+    {
+      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
    * 
@@ -1059,7 +1035,7 @@ public class Cache
     Color col = ColorUtils.parseColourString(colprop);
     if (col == null)
     {
-      log.warn("Couldn't parse '" + colprop + "' as a colour for "
+      Console.warn("Couldn't parse '" + colprop + "' as a colour for "
               + property);
     }
     return (col == null) ? defcolour : col;
@@ -1108,8 +1084,9 @@ public class Cache
         return date_format.parse(val);
       } catch (Exception ex)
       {
-        System.err.println("Invalid or corrupt date in property '"
-                + propertyName + "' : value was '" + val + "'");
+        jalview.bin.Console
+                .errPrintln("Invalid or corrupt date in property '"
+                        + propertyName + "' : value was '" + val + "'");
       }
     }
     return null;
@@ -1132,8 +1109,8 @@ public class Cache
         return Integer.valueOf(val);
       } catch (NumberFormatException x)
       {
-        System.err.println("Invalid integer in property '" + property
-                + "' (value was '" + val + "')");
+        jalview.bin.Console.errPrintln("Invalid integer in property '"
+                + property + "' (value was '" + val + "')");
       }
     }
     return null;
@@ -1166,7 +1143,7 @@ public class Cache
    * Loads in user colour schemes from files.
    * 
    * @param files
-   *                a '|'-delimited list of file paths
+   *          a '|'-delimited list of file paths
    */
   public static void initUserColourSchemes(String files)
   {
@@ -1196,7 +1173,9 @@ public class Cache
         }
       } catch (Exception ex)
       {
-        System.out.println("Error loading User ColourFile\n" + ex);
+        if (!Jalview.quiet())
+          jalview.bin.Console
+                  .outPrintln("Error loading User ColourFile\n" + ex);
       }
     }
     if (!files.equals(coloursFound.toString()))
@@ -1228,8 +1207,11 @@ public class Cache
     {
       return;
     }
-    String line = prefix + (value != null ? value : defaultValue) + suffix;
-    sb.append(line);
+    if (prefix != null)
+      sb.append(prefix);
+    sb.append(value == null ? defaultValue : value);
+    if (suffix != null)
+      sb.append(suffix);
   }
 
   /**
@@ -1241,18 +1223,19 @@ public class Cache
     StringBuilder sb = new StringBuilder();
     sb.append(ChannelProperties.getProperty("app_name"))
             .append(" Version: ");
-    sb.append(jalview.bin.Cache.getDefault("VERSION", "TEST"));
+    sb.append(Cache.getDefault("VERSION", "TEST"));
     sb.append("\n");
     sb.append(ChannelProperties.getProperty("app_name"))
             .append(" Installation: ");
-    sb.append(jalview.bin.Cache.getDefault("INSTALLATION", "unknown"));
+    sb.append(Cache.getDefault("INSTALLATION", "unknown"));
     sb.append("\n");
     sb.append("Build Date: ");
-    sb.append(jalview.bin.Cache.getDefault("BUILD_DATE", "unknown"));
+    sb.append(Cache.getDefault("BUILD_DATE", "unknown"));
     sb.append("\n");
     sb.append("Java version: ");
     sb.append(System.getProperty("java.version"));
     sb.append("\n");
+    sb.append("Java platform: ");
     sb.append(System.getProperty("os.arch"));
     sb.append(" ");
     sb.append(System.getProperty("os.name"));
@@ -1273,17 +1256,19 @@ public class Cache
     sb.append(" (");
     sb.append(lafClass);
     sb.append(")\n");
-    // Not displayed in release version ( determined by possible version number
-    // regex 9[9.]*9[.-_a9]* )
-    if (Pattern.matches("^\\d[\\d\\.]*\\d[\\.\\-\\w]*$",
-            jalview.bin.Cache.getDefault("VERSION", "TEST")))
+    appendIfNotNull(sb, "Channel: ",
+            ChannelProperties.getProperty("channel"), "\n", null);
+    if (Console.isDebugEnabled()
+            || !"release".equals(ChannelProperties.getProperty("channel")))
     {
       appendIfNotNull(sb, "Getdown appdir: ",
-              System.getProperty("getdownappdir"), "\n", null);
+              System.getProperty("getdowninstanceappdir"), "\n", null);
       appendIfNotNull(sb, "Getdown appbase: ",
-              System.getProperty("getdownappbase"), "\n", null);
+              System.getProperty("getdowninstanceappbase"), "\n", null);
       appendIfNotNull(sb, "Java home: ", System.getProperty("java.home"),
               "\n", "unknown");
+      appendIfNotNull(sb, "Preferences file: ", propertiesFile, "\n",
+              "unknown");
     }
     return sb.toString();
   }
@@ -1296,7 +1281,7 @@ public class Cache
   {
     // consider returning more human friendly info
     // eg 'built from Source' or update channel
-    return jalview.bin.Cache.getDefault("INSTALLATION", "unknown");
+    return Cache.getDefault("INSTALLATION", "unknown");
   }
 
   public static String getStackTraceString(Throwable t)
@@ -1340,7 +1325,7 @@ public class Cache
                             ? " [" + startupProxyProperties[6] + "]"
                             : "");
 
-    Cache.debug(sb.toString());
+    Console.debug(sb.toString());
   }
 
   public static void setProxyPropertiesFromPreferences()
@@ -1366,7 +1351,7 @@ public class Cache
     case Cache.PROXYTYPE_NONE:
       if (!previousProxyType.equals(proxyType))
       {
-        Cache.log.info("Setting no proxy settings");
+        Console.info("Setting no proxy settings");
         Cache.setProxyProperties(null, null, null, null, null, null, null,
                 null, null);
       }
@@ -1374,7 +1359,7 @@ public class Cache
     case Cache.PROXYTYPE_CUSTOM:
       // always re-set a custom proxy -- it might have changed, particularly
       // password
-      Cache.log.info("Setting custom proxy settings");
+      Console.info("Setting custom proxy settings");
       boolean proxyAuthSet = Cache.getDefault("PROXY_AUTH", false);
       Cache.setProxyProperties(Cache.getDefault("PROXY_SERVER", null),
               Cache.getDefault("PROXY_PORT", null),
@@ -1388,7 +1373,7 @@ public class Cache
               proxyAuthSet ? Cache.proxyAuthPassword : null, "localhost");
       break;
     default: // system proxy settings by default
-      Cache.log.info("Setting system proxy settings");
+      Console.info("Setting system proxy settings");
       Cache.resetProxyProperties();
     }
   }
@@ -1440,8 +1425,10 @@ public class Cache
         char[] displayHttpPw = new char[httpPassword == null ? 0
                 : httpPassword.length];
         Arrays.fill(displayHttpPw, '*');
-        Cache.debug("CACHE Proxy: setting new Authenticator with httpUser='"
-                + httpUser + "' httpPassword='" + displayHttpPw + "'");
+        Console.debug(
+                "CACHE Proxy: setting new Authenticator with httpUser='"
+                        + httpUser + "' httpPassword='" + displayHttpPw
+                        + "'");
         if (!Platform.isJS())
         /* *
          * java.net.Authenticator not implemented in SwingJS yet
@@ -1462,10 +1449,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
@@ -1490,7 +1478,7 @@ public class Cache
                             && getRequestingPort() == Integer
                                     .valueOf(httpPort))
                     {
-                      Cache.debug(
+                      Console.debug(
                               "AUTHENTICATOR returning PasswordAuthentication(\""
                                       + httpUser + "\", '"
                                       + new String(displayHttpPw) + "')");
@@ -1505,7 +1493,7 @@ public class Cache
                     {
                       char[] displayHttpsPw = new char[httpPassword.length];
                       Arrays.fill(displayHttpsPw, '*');
-                      Cache.debug(
+                      Console.debug(
                               "AUTHENTICATOR returning PasswordAuthentication(\""
                                       + httpsUser + "\", '" + displayHttpsPw
                                       + "'");
@@ -1514,15 +1502,15 @@ public class Cache
                     }
                   } catch (NumberFormatException e)
                   {
-                    Cache.error("Problem with proxy port values [http:"
+                    Console.error("Problem with proxy port values [http:"
                             + httpPort + ", https:" + httpsPort + "]");
                   }
-                  Cache.debug(
+                  Console.debug(
                           "AUTHENTICATOR after trying to get PasswordAuthentication");
                 }
               }
               // non proxy request
-              Cache.debug("AUTHENTICATOR returning null");
+              Console.debug("AUTHENTICATOR returning null");
               return null;
             }
           });
@@ -1530,13 +1518,13 @@ public class Cache
 
         // required to re-enable basic authentication (should be okay for a
         // local proxy)
-        Cache.debug(
+        Console.debug(
                 "AUTHENTICATOR setting property 'jdk.http.auth.tunneling.disabledSchemes' to \"\"");
         System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
       } catch (SecurityException e)
       {
-        Cache.error("Could not set default Authenticator");
-        Cache.debug(getStackTraceString(e));
+        Console.error("Could not set default Authenticator");
+        Console.debug(getStackTraceString(e));
       }
     }
     else
@@ -1554,14 +1542,16 @@ public class Cache
        * 
        */
       {
-        Cache.debug("AUTHENTICATOR setting default Authenticator to null");
+        Console.debug(
+                "AUTHENTICATOR setting default Authenticator to null");
         Authenticator.setDefault(null);
       }
     }
 
     // nonProxyHosts not currently configurable in Preferences
-    Cache.debug("AUTHENTICATOR setting property 'http.nonProxyHosts' to \""
-            + nonProxyHosts + "\"");
+    Console.debug(
+            "AUTHENTICATOR setting property 'http.nonProxyHosts' to \""
+                    + nonProxyHosts + "\"");
     setOrClearSystemProperty("http.nonProxyHosts", nonProxyHosts);
   }
 
@@ -1587,75 +1577,6 @@ public class Cache
     }
   }
 
-  public final static int TRACE = 10;
-
-  public final static int DEBUG = 20;
-
-  public final static int INFO = 30;
-
-  public final static int WARN = 40;
-
-  public final static int ERROR = 50;
-
-  public static boolean println(int level, String message)
-  {
-    if (Cache.log == null)
-    {
-      if (level >= WARN)
-        System.err.println(message);
-      else if (level >= INFO)
-        System.out.println(message);
-      // not printing debug or trace messages
-      return false;
-    }
-    if (level >= ERROR)
-    {
-      Cache.log.error(message);
-    }
-    else if (level >= WARN)
-    {
-      Cache.log.warn(message);
-    }
-    else if (level >= INFO)
-    {
-      Cache.log.info(message);
-    }
-    else if (level >= DEBUG)
-    {
-      Cache.log.debug(message);
-    }
-    else
-    {
-      Cache.log.trace(message);
-    }
-    return true;
-  }
-
-  public static void trace(String message)
-  {
-    println(TRACE, message);
-  }
-
-  public static void debug(String message)
-  {
-    println(DEBUG, message);
-  }
-
-  public static void info(String message)
-  {
-    println(INFO, message);
-  }
-
-  public static void warn(String message)
-  {
-    println(WARN, message);
-  }
-
-  public static void error(String message)
-  {
-    println(ERROR, message);
-  }
-
   /**
    * Getdown appbase methods
    */
@@ -1728,4 +1649,87 @@ public class Cache
     String appbase = getGetdownAppbase();
     return appbase + "/" + getdownDistDir + "/build_properties";
   }
+
+  private static final Collection<String> bootstrapProperties = new ArrayList<>(
+          Arrays.asList(JALVIEWLOGLEVEL, BOOTSTRAP_TEST));
+
+  public static Properties bootstrapProperties(String filename)
+  {
+    Properties bootstrapProps = new Properties();
+    File file = null;
+    if (filename != null)
+    {
+      file = new File(filename);
+    }
+    if (file == null || !file.exists())
+    {
+      if (file != null)
+      {
+        jalview.bin.Console
+                .errPrintln("Could not load bootstrap preferences file '"
+                        + file.getPath() + "'");
+      }
+      String channelPrefsFilename = ChannelProperties
+              .getProperty("preferences.filename");
+      String propertiesFilename = System.getProperty("user.home")
+              + File.separatorChar + channelPrefsFilename;
+      jalview.bin.Console.errPrintln(
+              "Using default properties file '" + propertiesFilename + "'");
+      file = new File(propertiesFilename);
+    }
+    if (file == null || !file.exists())
+
+    {
+      String releasePrefsFilename = fallbackPropertiesFile;
+      String releasePropertiesFilename = System.getProperty("user.home")
+              + File.separatorChar + releasePrefsFilename;
+      jalview.bin.Console.errPrintln("Falling back to properties file '"
+              + releasePropertiesFilename + "'");
+      file = new File(releasePropertiesFilename);
+    }
+
+    if (!file.exists())
+    {
+      jalview.bin.Console
+              .errPrintln("Could not load bootstrap preferences file '"
+                      + file.getPath() + "'");
+      return null;
+    }
+
+    try
+    {
+      FileInputStream in = new FileInputStream(file.getAbsoluteFile());
+      Properties props = new Properties();
+      props.load(in);
+      for (String prop : bootstrapProperties)
+      {
+        if (props.containsKey(prop))
+          bootstrapProps.put(prop, props.getProperty(prop));
+      }
+    } catch (FileNotFoundException e)
+    {
+      jalview.bin.Console
+              .errPrintln("Could not find bootstrap preferences file '"
+                      + file.getAbsolutePath() + "'");
+    } catch (IOException e)
+    {
+      jalview.bin.Console.errPrintln(
+              "IOException when loading bootstrap preferences file '"
+                      + file.getAbsolutePath() + "'");
+    }
+    return bootstrapProps;
+  }
+
+  public static void setSessionProperty(String key, String val)
+  {
+    if (key != null)
+    {
+      sessionProperties.put(key, val);
+    }
+  }
+
+  public static String getSessionProperty(String key)
+  {
+    return key == null ? null : sessionProperties.get(key);
+  }
 }