Update checking now occurs when using command-line tools if no check has occurred in the last 24 hours.
- Users who don't use hosted mode should now be aware when GWT is updated.
- An update notifaction will only be emitted on a successful compile
- Update checking now uses TreeLogger's HelpInfo so that users are no longer confronted with modal dialog.
- -XdisableUpdateCheck is added to the command line tools so that, for example, build systems behind firewalls can opt-out.

Patch by: jat
Review by: me


git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/1.6@4524 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/Compiler.java b/dev/core/src/com/google/gwt/dev/Compiler.java
index 00c9f23..6778230 100644
--- a/dev/core/src/com/google/gwt/dev/Compiler.java
+++ b/dev/core/src/com/google/gwt/dev/Compiler.java
@@ -22,6 +22,9 @@
 import com.google.gwt.dev.Precompile.PrecompileOptionsImpl;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.shell.CheckForUpdates;
+import com.google.gwt.dev.shell.PlatformSpecific;
+import com.google.gwt.dev.shell.CheckForUpdates.UpdateResult;
 import com.google.gwt.dev.util.PerfLogger;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
@@ -32,6 +35,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.util.concurrent.FutureTask;
 
 /**
  * The main executable entry point for the GWT Java to JavaScript compiler.
@@ -112,7 +116,16 @@
     if (new ArgProcessor(options).processArgs(args)) {
       CompileTask task = new CompileTask() {
         public boolean run(TreeLogger logger) throws UnableToCompleteException {
-          return new Compiler(options).run(logger);
+          FutureTask<UpdateResult> updater = null;
+          if (!options.isUpdateCheckDisabled()) {
+            updater = PlatformSpecific.checkForUpdatesInBackgroundThread(logger,
+                CheckForUpdates.ONE_DAY);
+          }
+          boolean success = new Compiler(options).run(logger);
+          if (success) {
+            PlatformSpecific.logUpdateAvailable(logger, updater);
+          }
+          return success;
         }
       };
       if (CompileTaskRunner.runWithAppropriateLogger(options, task)) {
diff --git a/dev/core/src/com/google/gwt/dev/GWTCompiler.java b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
index 156f2b9..5cbe5cd 100644
--- a/dev/core/src/com/google/gwt/dev/GWTCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
@@ -21,6 +21,9 @@
 import com.google.gwt.dev.Precompile.PrecompileOptionsImpl;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.shell.CheckForUpdates;
+import com.google.gwt.dev.shell.PlatformSpecific;
+import com.google.gwt.dev.shell.CheckForUpdates.UpdateResult;
 import com.google.gwt.dev.util.PerfLogger;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.arg.ArgHandlerLocalWorkers;
@@ -30,6 +33,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.util.concurrent.FutureTask;
 
 /**
  * The main executable entry point for the GWT Java to JavaScript compiler.
@@ -107,7 +111,16 @@
     if (new ArgProcessor(options).processArgs(args)) {
       CompileTask task = new CompileTask() {
         public boolean run(TreeLogger logger) throws UnableToCompleteException {
-          return new GWTCompiler(options).run(logger);
+          FutureTask<UpdateResult> updater = null;
+          if (!options.isUpdateCheckDisabled()) {
+            updater = PlatformSpecific.checkForUpdatesInBackgroundThread(logger,
+                CheckForUpdates.ONE_DAY);
+          }
+          boolean success = new GWTCompiler(options).run(logger);
+          if (success) {
+            PlatformSpecific.logUpdateAvailable(logger, updater);
+          }
+          return success;
         }
       };
       if (CompileTaskRunner.runWithAppropriateLogger(options, task)) {
diff --git a/dev/core/src/com/google/gwt/dev/HostedModeBase.java b/dev/core/src/com/google/gwt/dev/HostedModeBase.java
index 28741a0e..3b7faa7 100644
--- a/dev/core/src/com/google/gwt/dev/HostedModeBase.java
+++ b/dev/core/src/com/google/gwt/dev/HostedModeBase.java
@@ -27,7 +27,9 @@
 import com.google.gwt.dev.shell.BrowserWidgetHost;
 import com.google.gwt.dev.shell.BrowserWidgetHostChecker;
 import com.google.gwt.dev.shell.BrowserWindowController;
+import com.google.gwt.dev.shell.CheckForUpdates;
 import com.google.gwt.dev.shell.ModuleSpaceHost;
+import com.google.gwt.dev.shell.PlatformSpecific;
 import com.google.gwt.dev.shell.ShellModuleSpaceHost;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.arg.ArgHandlerDisableAggressiveOptimization;
@@ -425,6 +427,13 @@
   }
 
   /**
+   * Derived classes can override to lengthen ping delay.
+   */
+  protected long checkForUpdatesInterval() {
+    return CheckForUpdates.ONE_MINUTE;
+  }
+
+  /**
    * Compiles all modules.
    */
   protected abstract void compile(TreeLogger logger)
@@ -461,13 +470,6 @@
             "gen"), doCreateArtifactAcceptor(moduleDef));
   }
 
-  /**
-   * Derived classes can override to prevent automatic update checking.
-   */
-  protected boolean doShouldCheckForUpdates() {
-    return true;
-  }
-
   protected abstract void doShutDownServer();
 
   protected boolean doStartup() {
@@ -479,6 +481,22 @@
     // Initialize the logger.
     //
     initializeLogger();
+    
+    // Check for updates
+    final TreeLogger logger = getTopLogger();
+    final CheckForUpdates updateChecker
+        = PlatformSpecific.createUpdateChecker(logger);
+    if (updateChecker != null) {
+      Thread checkerThread = new Thread("GWT Update Checker") {
+        @Override
+        public void run() {
+          PlatformSpecific.logUpdateAvailable(logger,
+              updateChecker.check(checkForUpdatesInterval()));
+        }
+      };
+      checkerThread.setDaemon(true);
+      checkerThread.start();
+    }
     return true;
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/Precompile.java b/dev/core/src/com/google/gwt/dev/Precompile.java
index c0ca5a2..cb86208 100644
--- a/dev/core/src/com/google/gwt/dev/Precompile.java
+++ b/dev/core/src/com/google/gwt/dev/Precompile.java
@@ -36,14 +36,19 @@
 import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
 import com.google.gwt.dev.jjs.JsOutputOption;
 import com.google.gwt.dev.jjs.UnifiedAst;
+import com.google.gwt.dev.shell.CheckForUpdates;
+import com.google.gwt.dev.shell.PlatformSpecific;
 import com.google.gwt.dev.shell.StandardRebindOracle;
+import com.google.gwt.dev.shell.CheckForUpdates.UpdateResult;
 import com.google.gwt.dev.util.PerfLogger;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.arg.ArgHandlerDisableAggressiveOptimization;
+import com.google.gwt.dev.util.arg.ArgHandlerDisableUpdateCheck;
 import com.google.gwt.dev.util.arg.ArgHandlerEnableAssertions;
 import com.google.gwt.dev.util.arg.ArgHandlerGenDir;
 import com.google.gwt.dev.util.arg.ArgHandlerScriptStyle;
 import com.google.gwt.dev.util.arg.ArgHandlerValidateOnlyFlag;
+import com.google.gwt.dev.util.arg.OptionDisableUpdateCheck;
 import com.google.gwt.dev.util.arg.OptionGenDir;
 import com.google.gwt.dev.util.arg.OptionValidateOnly;
 
@@ -53,6 +58,7 @@
 import java.util.SortedMap;
 import java.util.SortedSet;
 import java.util.TreeMap;
+import java.util.concurrent.FutureTask;
 
 /**
  * Performs the first phase of compilation, generating the set of permutations
@@ -64,7 +70,7 @@
    * The set of options for the precompiler.
    */
   public interface PrecompileOptions extends JJSOptions, CompileTaskOptions,
-      OptionGenDir, OptionValidateOnly {
+      OptionGenDir, OptionValidateOnly, OptionDisableUpdateCheck {
   }
 
   static class ArgProcessor extends CompileArgProcessor {
@@ -75,6 +81,7 @@
       registerHandler(new ArgHandlerEnableAssertions(options));
       registerHandler(new ArgHandlerDisableAggressiveOptimization(options));
       registerHandler(new ArgHandlerValidateOnlyFlag(options));
+      registerHandler(new ArgHandlerDisableUpdateCheck(options));
     }
 
     @Override
@@ -85,6 +92,7 @@
 
   static class PrecompileOptionsImpl extends CompileTaskOptionsImpl implements
       PrecompileOptions {
+    private boolean disableUpdateCheck;
     private File genDir;
     private final JJSOptionsImpl jjsOptions = new JJSOptionsImpl();
     private boolean validateOnly;
@@ -101,6 +109,7 @@
 
       jjsOptions.copyFrom(other);
 
+      setDisableUpdateCheck(other.isUpdateCheckDisabled());
       setGenDir(other.getGenDir());
       setValidateOnly(other.isValidateOnly());
     }
@@ -121,6 +130,10 @@
       return jjsOptions.isEnableAssertions();
     }
 
+    public boolean isUpdateCheckDisabled() {
+      return disableUpdateCheck;
+    }
+
     public boolean isValidateOnly() {
       return validateOnly;
     }
@@ -129,6 +142,10 @@
       jjsOptions.setAggressivelyOptimize(aggressivelyOptimize);
     }
 
+    public void setDisableUpdateCheck(boolean disabled) {
+      disableUpdateCheck = disabled;
+    }
+
     public void setEnableAssertions(boolean enableAssertions) {
       jjsOptions.setEnableAssertions(enableAssertions);
     }
@@ -227,7 +244,16 @@
     if (new ArgProcessor(options).processArgs(args)) {
       CompileTask task = new CompileTask() {
         public boolean run(TreeLogger logger) throws UnableToCompleteException {
-          return new Precompile(options).run(logger);
+          FutureTask<UpdateResult> updater = null;
+          if (!options.isUpdateCheckDisabled()) {
+            updater = PlatformSpecific.checkForUpdatesInBackgroundThread(logger,
+                CheckForUpdates.ONE_DAY);
+          }
+          boolean success = new Precompile(options).run(logger);
+          if (success) {
+            PlatformSpecific.logUpdateAvailable(logger, updater);
+          }
+          return success;
         }
       };
       if (CompileTaskRunner.runWithAppropriateLogger(options, task)) {
@@ -290,8 +316,7 @@
           merged.put(rebindResultsString, permutation);
         }
       }
-      return new Precompilation(unifiedAst, merged.values(),
-          generatedArtifacts);
+      return new Precompilation(unifiedAst, merged.values(), generatedArtifacts);
     } catch (UnableToCompleteException e) {
       // We intentionally don't pass in the exception here since the real
       // cause has been logged.
diff --git a/dev/core/src/com/google/gwt/dev/SwtHostedModeBase.java b/dev/core/src/com/google/gwt/dev/SwtHostedModeBase.java
index bda5243..c12aebb 100644
--- a/dev/core/src/com/google/gwt/dev/SwtHostedModeBase.java
+++ b/dev/core/src/com/google/gwt/dev/SwtHostedModeBase.java
@@ -221,10 +221,8 @@
 
     shell.setImages(ShellMainWindow.getIcons());
 
-    boolean checkForUpdates = doShouldCheckForUpdates();
-
     mainWnd = new ShellMainWindow(this, shell, options.isNoServer() ? 0
-        : getPort(), checkForUpdates);
+        : getPort());
 
     shell.setSize(700, 600);
     if (!isHeadless()) {
diff --git a/dev/core/src/com/google/gwt/dev/shell/CheckForUpdates.java b/dev/core/src/com/google/gwt/dev/shell/CheckForUpdates.java
index e9e7886..51bfbe4 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CheckForUpdates.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CheckForUpdates.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.dev.shell;
 
+import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.dev.About;
 
 import org.w3c.dom.Document;
@@ -27,8 +28,11 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLConnection;
@@ -43,18 +47,121 @@
  * Orchestrates a best-effort attempt to find out if a new version of GWT is
  * available.
  */
-public abstract class CheckForUpdates {
+public class CheckForUpdates {
 
   /**
-   * Abstract the action to take when an update is available.
+   * Represents a GWT version.
    */
-  public static interface UpdateAvailableCallback {
-    void onUpdateAvailable(String html);
+  public static class GwtVersion implements Comparable<GwtVersion> {
+
+    private final int[] version = new int[3];
+
+    /**
+     * Create a version that avoids any nagging -- "0.0.999".
+     */
+    public GwtVersion() {
+      version[2] = 999;
+    }
+
+    /**
+     * Parse a version number as a string. An empty or null string are
+     * explicitly allowed and are equivalent to "0.0.0".
+     * 
+     * @param versionString
+     * @throws NumberFormatException
+     */
+    public GwtVersion(String versionString) throws NumberFormatException {
+      if (versionString == null) {
+        return;
+      }
+      int part = 0;
+      int v = 0;
+      int len = versionString.length();
+      for (int i = 0; i < len; ++i) {
+        char ch = versionString.charAt(i);
+        if (ch == '.') {
+          if (part >= version.length) {
+            throw new NumberFormatException();
+          }
+          version[part++] = v;
+          v = 0;
+        } else if (Character.isDigit(ch)) {
+          int digit = Character.digit(ch, 10);
+          if (digit < 0) {
+            throw new NumberFormatException();
+          }
+          v = v * 10 + digit;
+        }
+      }
+      version[part++] = v;
+    }
+
+    public int compareTo(GwtVersion o) {
+      for (int i = 0; i <= 2; ++i) {
+        if (version[i] != o.version[i]) {
+          return version[i] - o.version[i];
+        }
+      }
+      return 0;
+    }
+
+    public int getPart(int part) {
+      // TODO: something besides IORE here?
+      return version[part];
+    }
+
+    public boolean isNoNagVersion() {
+      return version[2] == 999;
+    }
+
+    public boolean isUnspecified() {
+      return version[0] == 0 && version[1] == 0 && version[2] == 0;
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder buf = new StringBuilder();
+      buf.append(version[0]).append('.').append(version[1]).append('.');
+      buf.append(version[2]);
+      return buf.toString();
+    }
   }
 
-  protected static final String LAST_SERVER_VERSION = "lastServerVersion";
-  private static final boolean DEBUG_VERSION_CHECK;
+  /**
+   * Returns the result of an update check.
+   */
+  public interface UpdateResult {
+    /**
+     * @return the new version of GWT available.
+     */
+    GwtVersion getNewVersion();
+
+    /**
+     * @return the URL for details about the new version.
+     */
+    URL getURL();
+  }
+
+  public static final long ONE_DAY = 24 * 60 * 60 * 1000;
+  public static final long ONE_MINUTE = 60 * 1000;
+
+  // System properties used by CheckForUpdates
+  protected static final String PROPERTY_DEBUG_HTTP_GET = "gwt.debugLowLevelHttpGet";
+  protected static final String PROPERTY_FORCE_NONNATIVE = "gwt.forceVersionCheckNonNative";
+  protected static final String PROPERTY_PREFS_NAME = "gwt.prefsPathName";
+  protected static final String PROPERTY_QUERY_URL = "gwt.forceVersionCheckURL";
+
+  // Log levels -- in general we want the logging of the update process
+  // to not be visible to normal users.
+  private static final TreeLogger.Type CHECK_ERROR = TreeLogger.DEBUG;
+  private static final TreeLogger.Type CHECK_INFO = TreeLogger.SPAM;
+  private static final TreeLogger.Type CHECK_SPAM = TreeLogger.SPAM;
+  private static final TreeLogger.Type CHECK_WARN = TreeLogger.SPAM;
+
+  // Preferences keys
   private static final String FIRST_LAUNCH = "firstLaunch";
+  private static final String HIGHEST_RUN_VERSION = "highestRunVersion";
+  private static final String LAST_PING = "lastPing";
   private static final String NEXT_PING = "nextPing";
 
   // Uncomment one of constants below to try different variations of failure to
@@ -75,74 +182,6 @@
   // The real URL that should be used.
   private static final String QUERY_URL = "http://tools.google.com/webtoolkit/currentversion.xml";
 
-  private static final int VERSION_PARTS = 3;
-  private static final String VERSION_REGEXP = "\\d+\\.\\d+\\.\\d+";
-
-  static {
-    // Do this in a static initializer so we can ignore all exceptions.
-    //
-    boolean debugVersionCheck = false;
-    try {
-      if (System.getProperty("gwt.debugVersionCheck") != null) {
-        debugVersionCheck = true;
-      }
-    } catch (Throwable e) {
-      // Always silently ignore any errors.
-      //
-    } finally {
-      DEBUG_VERSION_CHECK = debugVersionCheck;
-    }
-  }
-
-  /**
-   * Determines whether the server version is definitively newer than the client
-   * version. If any errors occur in the comparison, this method returns false
-   * to avoid unwanted erroneous notifications.
-   * 
-   * @param clientVersion The current client version
-   * @param serverVersion The current server version
-   * @return true if the server is definitely newer, otherwise false
-   */
-  protected static boolean isServerVersionNewer(String clientVersion,
-      String serverVersion) {
-    if (clientVersion == null || serverVersion == null) {
-      return false;
-    }
-
-    // must match expected format
-    if (!clientVersion.matches(VERSION_REGEXP)
-        || !serverVersion.matches(VERSION_REGEXP)) {
-      return false;
-    }
-
-    // extract the relevant parts
-    String[] clientParts = clientVersion.split("\\.");
-    String[] serverParts = serverVersion.split("\\.");
-    if (clientParts.length != VERSION_PARTS
-        || serverParts.length != VERSION_PARTS) {
-      return false;
-    }
-
-    // examine piece by piece from most significant to least significant
-    for (int i = 0; i < VERSION_PARTS; ++i) {
-      try {
-        int clientPart = Integer.parseInt(clientParts[i]);
-        int serverPart = Integer.parseInt(serverParts[i]);
-        if (serverPart < clientPart) {
-          return false;
-        }
-
-        if (serverPart > clientPart) {
-          return true;
-        }
-      } catch (NumberFormatException e) {
-        return false;
-      }
-    }
-
-    return false;
-  }
-
   private static String getTextOfLastElementHavingTag(Document doc,
       String tagName) {
     NodeList nodeList = doc.getElementsByTagName(tagName);
@@ -161,197 +200,97 @@
     return null;
   }
 
-  private static void parseResponse(Preferences prefs, byte[] response,
-      UpdateAvailableCallback callback) throws IOException,
-      ParserConfigurationException, SAXException {
+  private String entryPoint;
+  private TreeLogger logger;
+  private GwtVersion myVersion;
 
-    if (DEBUG_VERSION_CHECK) {
-      System.out.println("Parsing response (length " + response.length + ")");
-    }
+  /**
+   * Create an update checker which will poll a server URL and log a message
+   * about an update if available.
+   * 
+   * @param logger TreeLogger to use
+   */
+  public CheckForUpdates(TreeLogger logger) {
+    this(logger, null);
+  }
 
-    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-    DocumentBuilder builder = factory.newDocumentBuilder();
-    ByteArrayInputStream bais = new ByteArrayInputStream(response);
-
-    // Parse the XML.
-    //
-    builder.setErrorHandler(new ErrorHandler() {
-
-      public void error(SAXParseException exception) throws SAXException {
-        // fail quietly
-      }
-
-      public void fatalError(SAXParseException exception) throws SAXException {
-        // fail quietly
-      }
-
-      public void warning(SAXParseException exception) throws SAXException {
-        // fail quietly
-      }
-    });
-    Document doc = builder.parse(bais);
-
-    // The latest version number.
-    //
-    String version = getTextOfLastElementHavingTag(doc, "latest-version");
-    if (version == null) {
-      // Not valid; quietly fail.
-      //
-      if (DEBUG_VERSION_CHECK) {
-        System.out.println("Failed to find <latest-version>");
-      }
-      return;
-    } else {
-      version = version.trim();
-    }
-
-    String[] versionParts = version.split("\\.");
-    if (versionParts.length != 3) {
-      // Not valid; quietly fail.
-      //
-      if (DEBUG_VERSION_CHECK) {
-        System.out.println("Bad version format: " + version);
-      }
-      return;
-    }
+  /**
+   * Create an update checker which will poll a server URL and log a message
+   * about an update if available.
+   * 
+   * @param logger TreeLogger to use
+   * @param entryPoint the name of the main entry point used for this execution
+   */
+  public CheckForUpdates(TreeLogger logger, String entryPoint) {
+    this.logger = logger;
+    this.entryPoint = entryPoint;
     try {
-      Integer.parseInt(versionParts[0]);
-      Integer.parseInt(versionParts[1]);
-      Integer.parseInt(versionParts[2]);
+      myVersion = new GwtVersion(About.GWT_VERSION_NUM);
     } catch (NumberFormatException e) {
-      // Not valid; quietly fail.
-      //
-      if (DEBUG_VERSION_CHECK) {
-        System.out.println("Bad version number: " + version);
-      }
-      return;
+      // if our build version number is bogus, use one that avoids nagging
+      myVersion = new GwtVersion();
     }
-
-    // Ping delay for server-controlled throttling.
-    //
-    String pingDelaySecsStr = getTextOfLastElementHavingTag(doc,
-        "min-wait-seconds");
-    int pingDelaySecs = 0;
-    if (pingDelaySecsStr == null) {
-      // Not valid; quietly fail.
-      //
-      if (DEBUG_VERSION_CHECK) {
-        System.out.println("Missing <min-wait-seconds>");
-      }
-      return;
-    } else {
-      try {
-        pingDelaySecs = Integer.parseInt(pingDelaySecsStr.trim());
-      } catch (NumberFormatException e) {
-        // Not a valid number; quietly fail.
-        //
-        if (DEBUG_VERSION_CHECK) {
-          System.out.println("Bad min-wait-seconds number: " + pingDelaySecsStr);
-        }
-        return;
-      }
-    }
-
-    // Read the HTML.
-    //
-    String html = getTextOfLastElementHavingTag(doc, "notification");
-
-    if (html == null) {
-      // Not valid; quietly fail.
-      //
-      if (DEBUG_VERSION_CHECK) {
-        System.out.println("Missing <notification>");
-      }
-      return;
-    }
-
-    // Okay -- this is a valid response.
-    //
-    processResponse(prefs, version, pingDelaySecs, html, callback);
   }
 
-  private static void processResponse(Preferences prefs, String version,
-      int pingDelaySecs, String html, UpdateAvailableCallback callback) {
-
-    // Record a ping; don't ping again until the delay is up.
-    //
-    long nextPingTime = System.currentTimeMillis() + pingDelaySecs * 1000;
-    prefs.put(NEXT_PING, String.valueOf(nextPingTime));
-
-    if (DEBUG_VERSION_CHECK) {
-      System.out.println("Ping delay is " + pingDelaySecs + "; next ping at "
-          + new Date(nextPingTime));
-    }
-
-    /*
-     * Stash the version we got last time for comparison below, and record for
-     * next time the version we just got.
-     */
-    String lastServerVersion = prefs.get(LAST_SERVER_VERSION, null);
-    prefs.put(LAST_SERVER_VERSION, version);
-
-    // Are we up to date already?
-    //
-    if (!isServerVersionNewer(About.GWT_VERSION_NUM, version)) {
-
-      // Yes, we are.
-      //
-      if (DEBUG_VERSION_CHECK) {
-        System.out.println("Server version is not newer");
-      }
-      return;
-    }
-
-    // Have we already prompted for this particular server version?
-    //
-    if (version.equals(lastServerVersion)) {
-
-      // We've already nagged the user once. Don't do it again.
-      //
-      if (DEBUG_VERSION_CHECK) {
-        System.out.println("A notification has already been shown for "
-            + version);
-      }
-      return;
-    }
-
-    if (DEBUG_VERSION_CHECK) {
-      System.out.println("Server version has changed to " + version
-          + "; notification will be shown");
-    }
-
-    // Commence nagging.
-    //
-    callback.onUpdateAvailable(html);
+  /**
+   * Check for updates and log to the logger if they are available.
+   * 
+   * @return an UpdateResult or null if there is no new update
+   */
+  public UpdateResult check() {
+    return check(0);
   }
 
-  public void check(final UpdateAvailableCallback callback) {
-
+  /**
+   * Check for updates and log to the logger if they are available.
+   * 
+   * @return an UpdateResult or null if there is no new update
+   */
+  public UpdateResult check(long minCheckMillis) {
+    TreeLogger branch = logger.branch(CHECK_INFO, "Checking for updates");
     try {
-      String forceCheckURL = System.getProperty("gwt.forceVersionCheckURL");
+      String prefsName = System.getProperty(PROPERTY_PREFS_NAME);
+      Preferences prefs;
+      if (prefsName != null) {
+        prefs = Preferences.userRoot().node(prefsName);
+      } else {
+        prefs = Preferences.userNodeForPackage(CheckForUpdates.class);
+      }
 
-      if (forceCheckURL != null && DEBUG_VERSION_CHECK) {
-        System.out.println("Explicit version check URL: " + forceCheckURL);
+      String queryURL = QUERY_URL;
+      String forceCheckURL = System.getProperty(PROPERTY_QUERY_URL);
+
+      if (forceCheckURL != null) {
+        branch.log(CHECK_INFO, "Explicit version check URL: " + forceCheckURL);
+        queryURL = forceCheckURL;
       }
 
       // Get our unique user id (based on absolute timestamp).
       //
       long currentTimeMillis = System.currentTimeMillis();
-      Preferences prefs = Preferences.userNodeForPackage(CheckForUpdates.class);
-
-      // Get our unique user id (based on absolute timestamp).
-      //
       String firstLaunch = prefs.get(FIRST_LAUNCH, null);
       if (firstLaunch == null) {
         firstLaunch = Long.toHexString(currentTimeMillis);
         prefs.put(FIRST_LAUNCH, firstLaunch);
-
-        if (DEBUG_VERSION_CHECK) {
-          System.out.println("Setting first launch to " + firstLaunch);
-        }
+        branch.log(CHECK_SPAM, "Setting first launch to " + firstLaunch);
       } else {
-        if (DEBUG_VERSION_CHECK) {
-          System.out.println("First launch was " + firstLaunch);
+        branch.log(CHECK_SPAM, "First launch was " + firstLaunch);
+      }
+
+      // See if enough time has passed.
+      //
+      String lastPing = prefs.get(LAST_PING, "0");
+      if (lastPing != null) {
+        try {
+          long lastPingTime = Long.parseLong(lastPing);
+          if (currentTimeMillis < lastPingTime + minCheckMillis) {
+            // it's not time yet
+            branch.log(CHECK_INFO, "Last ping was " + new Date(lastPingTime)
+                + ", min wait is " + minCheckMillis + "ms");
+            return null;
+          }
+        } catch (NumberFormatException e) {
+          branch.log(CHECK_WARN, "Error parsing last ping time", e);
         }
       }
 
@@ -363,73 +302,86 @@
           long nextPingTime = Long.parseLong(nextPing);
           if (currentTimeMillis < nextPingTime) {
             // it's not time yet
-            if (DEBUG_VERSION_CHECK) {
-              System.out.println("Next ping is not until "
-                  + new Date(nextPingTime));
-            }
-            return;
+            branch.log(CHECK_INFO, "Next ping is not until "
+                + new Date(nextPingTime));
+            return null;
           }
         } catch (NumberFormatException e) {
-          // ignore
+          branch.log(CHECK_WARN, "Error parsing next ping time", e);
         }
       }
 
       // See if new version is available.
       //
-      String queryURL = forceCheckURL != null ? forceCheckURL : QUERY_URL;
       String url = queryURL + "?v=" + About.GWT_VERSION_NUM + "&id="
-          + firstLaunch;
-
-      if (DEBUG_VERSION_CHECK) {
-        System.out.println("Checking for new version at " + url);
+          + firstLaunch + "&r=" + About.GWT_SVNREV;
+      if (entryPoint != null) {
+        url += "&e=" + entryPoint;
       }
 
+      branch.log(CHECK_INFO, "Checking for new version at " + url);
+
       // Do the HTTP GET.
       //
       byte[] response;
       String fullUserAgent = makeUserAgent();
-      if (System.getProperty("gwt.forceVersionCheckNonNative") == null) {
+      if (System.getProperty(PROPERTY_FORCE_NONNATIVE) == null) {
         // Use subclass.
         //
-        response = doHttpGet(fullUserAgent, url);
+        response = doHttpGet(branch, fullUserAgent, url);
       } else {
         // Use the pure Java version, but it probably doesn't work with proxies.
         //
-        response = httpGetNonNative(fullUserAgent, url);
+        response = httpGetNonNative(branch, fullUserAgent, url);
       }
 
       if (response == null || response.length == 0) {
         // Problem. Quietly fail.
         //
-        if (DEBUG_VERSION_CHECK) {
-          System.out.println("Failed to obtain current version info via HTTP");
-        }
-        return;
+        branch.log(CHECK_ERROR,
+            "Failed to obtain current version info via HTTP");
+        return null;
       }
 
       // Parse and process the response.
       // Bad responses will be silently ignored.
       //
-      parseResponse(prefs, response, callback);
+      return parseResponse(branch, prefs, response);
 
     } catch (Throwable e) {
       // Always silently ignore any errors.
       //
-      if (DEBUG_VERSION_CHECK) {
-        System.out.println("Exception while processing version info");
-        e.printStackTrace();
-      }
+      branch.log(CHECK_INFO, "Exception while processing version info", e);
     }
+    return null;
   }
 
-  protected abstract byte[] doHttpGet(String userAgent, String url);
+  /**
+   * Default implementation just uses the platform-independent method. A
+   * subclass should override this method for platform-dependent proxy handling,
+   * for example.
+   * 
+   * @param branch TreeLogger to use
+   * @param userAgent user agent string to send in request
+   * @param url URL to fetch
+   * @return byte array of response, or null if an error
+   */
+  protected byte[] doHttpGet(TreeLogger branch, String userAgent, String url) {
+    return httpGetNonNative(branch, userAgent, url);
+  }
 
   /**
    * This default implementation uses regular Java HTTP, which doesn't deal with
    * proxies automagically. See the IE6 subclasses for an implementation that
    * does deal with proxies.
+   * 
+   * @param branch TreeLogger to use
+   * @param userAgent user agent string to send in request
+   * @param url URL to fetch
+   * @return byte array of response, or null if an error
    */
-  protected byte[] httpGetNonNative(String userAgent, String url) {
+  protected byte[] httpGetNonNative(TreeLogger branch, String userAgent,
+      String url) {
     Throwable caught;
     InputStream is = null;
     try {
@@ -458,8 +410,8 @@
       }
     }
 
-    if (System.getProperty("gwt.debugLowLevelHttpGet") != null) {
-      caught.printStackTrace();
+    if (System.getProperty(PROPERTY_DEBUG_HTTP_GET) != null) {
+      branch.log(CHECK_ERROR, "Exception in HTTP request", caught);
     }
 
     return null;
@@ -496,4 +448,165 @@
 
     return ua;
   }
+
+  private UpdateResult parseResponse(TreeLogger branch, Preferences prefs,
+      byte[] response) throws IOException, ParserConfigurationException,
+      SAXException {
+
+    branch.log(CHECK_SPAM, "Parsing response (length " + response.length + ")");
+
+    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+    DocumentBuilder builder = factory.newDocumentBuilder();
+    ByteArrayInputStream bais = new ByteArrayInputStream(response);
+
+    // Parse the XML.
+    //
+    builder.setErrorHandler(new ErrorHandler() {
+
+      public void error(SAXParseException exception) throws SAXException {
+        // fail quietly
+      }
+
+      public void fatalError(SAXParseException exception) throws SAXException {
+        // fail quietly
+      }
+
+      public void warning(SAXParseException exception) throws SAXException {
+        // fail quietly
+      }
+    });
+    Document doc = builder.parse(bais);
+
+    // The latest version number.
+    //
+    String versionString = getTextOfLastElementHavingTag(doc, "latest-version");
+    if (versionString == null) {
+      // Not valid; quietly fail.
+      //
+      branch.log(CHECK_ERROR, "Failed to find <latest-version>");
+      return null;
+    }
+    GwtVersion currentReleasedVersion;
+    try {
+      currentReleasedVersion = new GwtVersion(versionString.trim());
+    } catch (NumberFormatException e) {
+      branch.log(CHECK_ERROR, "Bad version: " + versionString, e);
+      return null;
+    }
+
+    // Ping delay for server-controlled throttling.
+    //
+    String pingDelaySecsStr = getTextOfLastElementHavingTag(doc,
+        "min-wait-seconds");
+    int pingDelaySecs = 0;
+    if (pingDelaySecsStr == null) {
+      // Not valid; quietly fail.
+      //
+      branch.log(CHECK_ERROR, "Missing <min-wait-seconds>");
+      return null;
+    }
+    try {
+      pingDelaySecs = Integer.parseInt(pingDelaySecsStr.trim());
+    } catch (NumberFormatException e) {
+      // Not a valid number; quietly fail.
+      //
+      branch.log(CHECK_ERROR, "Bad min-wait-seconds number: "
+          + pingDelaySecsStr);
+      return null;
+    }
+
+    String url = getTextOfLastElementHavingTag(doc, "notification-url");
+
+    if (url == null) {
+      // no URL, so write the HTML locally and provide a URL from that
+
+      // Read the HTML.
+      //
+      String html = getTextOfLastElementHavingTag(doc, "notification");
+
+      if (html == null) {
+        // Not valid; quietly fail.
+        //
+        branch.log(CHECK_ERROR, "Missing <notification>");
+        return null;
+      }
+      PrintWriter writer = null;
+      try {
+        String tempDir = System.getProperty("java.io.tmpdir");
+        File updateHtml = new File(tempDir, "gwt-update-"
+            + currentReleasedVersion + ".html");
+        writer = new PrintWriter(new FileOutputStream(updateHtml));
+        writer.print(html);
+        url = "file://" + updateHtml.getAbsolutePath();
+      } finally {
+        if (writer != null) {
+          writer.close();
+        }
+      }
+    }
+
+    // Okay -- this is a valid response.
+    //
+    return processResponse(branch, prefs, currentReleasedVersion,
+        pingDelaySecs, url);
+  }
+
+  private UpdateResult processResponse(TreeLogger branch, Preferences prefs,
+      final GwtVersion serverVersion, int pingDelaySecs, final String notifyUrl) {
+
+    // Record a ping; don't ping again until the delay is up.
+    //
+    long currentTimeMillis = System.currentTimeMillis();
+    long nextPingTime = currentTimeMillis + pingDelaySecs * 1000;
+    prefs.put(NEXT_PING, String.valueOf(nextPingTime));
+    prefs.put(LAST_PING, String.valueOf(currentTimeMillis));
+
+    branch.log(CHECK_INFO, "Ping delay is " + pingDelaySecs + "; next ping at "
+        + new Date(nextPingTime));
+
+    if (myVersion.isNoNagVersion()) {
+      // If the version number indicates no nagging about updates, exit here
+      // once we have recorded the next ping time. No-nag versions (ie,
+      // trunk builds) should also not update the highest version that has been
+      // run.
+      return null;
+    }
+
+    // Update the highest version of GWT that has been run if we are later.
+    GwtVersion highestRunVersion = new GwtVersion(prefs.get(
+        HIGHEST_RUN_VERSION, null));
+    if (myVersion.compareTo(highestRunVersion) > 0) {
+      highestRunVersion = myVersion;
+      prefs.put(HIGHEST_RUN_VERSION, highestRunVersion.toString());
+    }
+
+    // Are we up to date already?
+    //
+    if (highestRunVersion.compareTo(serverVersion) >= 0) {
+      // Yes, we are.
+      //
+      branch.log(CHECK_INFO, "Server version (" + serverVersion
+          + ") is not newer than " + highestRunVersion);
+      return null;
+    }
+
+    // Commence nagging.
+    //
+    URL url = null;
+    try {
+      url = new URL(notifyUrl);
+    } catch (MalformedURLException e) {
+      logger.log(CHECK_ERROR, "Malformed notify URL: " + notifyUrl, e);
+    }
+    final URL finalUrl = url;
+    return new UpdateResult() {
+      public GwtVersion getNewVersion() {
+        return serverVersion;
+      }
+
+      public URL getURL() {
+        return finalUrl;
+      }
+    };
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/shell/PlatformSpecific.java b/dev/core/src/com/google/gwt/dev/shell/PlatformSpecific.java
index 30a834d..cd25809 100644
--- a/dev/core/src/com/google/gwt/dev/shell/PlatformSpecific.java
+++ b/dev/core/src/com/google/gwt/dev/shell/PlatformSpecific.java
@@ -17,12 +17,20 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.TreeLogger.HelpInfo;
+import com.google.gwt.dev.shell.CheckForUpdates.UpdateResult;
 
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Shell;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
+import java.net.URL;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Performs platform-specific class selection.
@@ -38,12 +46,51 @@
       "com.google.gwt.dev.shell.mac.BrowserWidgetSaf"};
 
   /**
-   * All of these classes must extend CheckForUpdates.
+   * All of these classes must extend CheckForUpdates. Note that currently only
+   * IE has a custom implementation (to handle proxies) and that CheckForUpdates
+   * must be the last one in the list.
    */
   private static final String[] updaterClassNames = new String[] {
       "com.google.gwt.dev.shell.ie.CheckForUpdatesIE6",
-      "com.google.gwt.dev.shell.moz.CheckForUpdatesMoz",
-      "com.google.gwt.dev.shell.mac.CheckForUpdatesSaf"};
+      "com.google.gwt.dev.shell.CheckForUpdates"};
+
+  public static FutureTask<UpdateResult> checkForUpdatesInBackgroundThread(
+      final TreeLogger logger, final long minCheckMillis) {
+    final String entryPoint = PlatformSpecific.computeEntryPoint();
+    FutureTask<UpdateResult> task = new FutureTask<UpdateResult>(
+        new Callable<UpdateResult>() {
+          public UpdateResult call() throws Exception {
+            final CheckForUpdates updateChecker = createUpdateChecker(logger,
+                entryPoint);
+            return updateChecker == null ? null
+                : updateChecker.check(minCheckMillis);
+          }
+        });
+    Thread checkerThread = new Thread(task, "GWT Update Checker");
+    checkerThread.setDaemon(true);
+    checkerThread.start();
+    return task;
+  }
+
+  /**
+   * Find the first method named "main" on the call stack and use its class as
+   * the entry point.
+   */
+  public static String computeEntryPoint() {
+    Throwable t = new Throwable();
+    for (StackTraceElement stackTrace : t.getStackTrace()) {
+      if (stackTrace.getMethodName().equals("main")) {
+        // Strip package name from main's class
+        String className = stackTrace.getClassName();
+        int i = className.lastIndexOf('.');
+        if (i >= 0) {
+          return className.substring(i + 1);
+        }
+        return className;
+      }
+    }
+    return null;
+  }
 
   public static BrowserWidget createBrowserWidget(TreeLogger logger,
       Composite parent, BrowserWidgetHost host)
@@ -51,10 +98,11 @@
     Throwable caught = null;
     try {
       for (int i = 0; i < browserClassNames.length; i++) {
-        Class<BrowserWidget> clazz = null;
+        Class<? extends BrowserWidget> clazz = null;
         try {
-          clazz = (Class<BrowserWidget>) Class.forName(browserClassNames[i]);
-          Constructor<BrowserWidget> ctor = clazz.getDeclaredConstructor(new Class[] {
+          clazz = Class.forName(browserClassNames[i]).asSubclass(
+              BrowserWidget.class);
+          Constructor<? extends BrowserWidget> ctor = clazz.getDeclaredConstructor(new Class[] {
               Shell.class, BrowserWidgetHost.class});
           BrowserWidget bw = ctor.newInstance(new Object[] {parent, host});
           return bw;
@@ -85,16 +133,25 @@
     throw new UnableToCompleteException();
   }
 
-  public static CheckForUpdates createUpdateChecker() {
+  public static CheckForUpdates createUpdateChecker(TreeLogger logger) {
+    return createUpdateChecker(logger, computeEntryPoint());
+  }
+
+  public static CheckForUpdates createUpdateChecker(TreeLogger logger,
+      String entryPoint) {
     try {
       for (int i = 0; i < updaterClassNames.length; i++) {
         try {
-          Class<CheckForUpdates> clazz = (Class<CheckForUpdates>) Class.forName(updaterClassNames[i]);
-          Constructor<CheckForUpdates> ctor = clazz.getDeclaredConstructor(new Class[] {});
-          CheckForUpdates checker = ctor.newInstance(new Object[] {});
+          Class<? extends CheckForUpdates> clazz = Class.forName(
+              updaterClassNames[i]).asSubclass(CheckForUpdates.class);
+          Constructor<? extends CheckForUpdates> ctor = clazz.getDeclaredConstructor(new Class[] {
+              TreeLogger.class, String.class});
+          CheckForUpdates checker = ctor.newInstance(new Object[] {
+              logger, entryPoint});
           return checker;
-        } catch (ClassNotFoundException e) {
-          // keep trying
+        } catch (Exception e) {
+          // Other exceptions can occur besides ClassNotFoundException,
+          // so ignore them all so we can find a functional updater.
         }
       }
     } catch (Throwable e) {
@@ -102,4 +159,34 @@
     }
     return null;
   }
+
+  public static void logUpdateAvailable(TreeLogger logger,
+      FutureTask<UpdateResult> updater) {
+    if (updater != null && updater.isDone()) {
+      UpdateResult result = null;
+      try {
+        result = updater.get(0, TimeUnit.MILLISECONDS);
+      } catch (InterruptedException e) {
+        // Silently ignore exception
+      } catch (ExecutionException e) {
+        // Silently ignore exception
+      } catch (TimeoutException e) {
+        // Silently ignore exception
+      }
+      logUpdateAvailable(logger, result);
+    }
+  }
+
+  public static void logUpdateAvailable(TreeLogger logger, UpdateResult result) {
+    if (result != null) {
+      final URL url = result.getURL();
+      logger.log(TreeLogger.WARN, "A new version of GWT ("
+          + result.getNewVersion() + ") is available", null, new HelpInfo() {
+        @Override
+        public URL getURL() {
+          return url;
+        }
+      });
+    }
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/shell/ShellMainWindow.java b/dev/core/src/com/google/gwt/dev/shell/ShellMainWindow.java
index 031cc44..0e58953 100644
--- a/dev/core/src/com/google/gwt/dev/shell/ShellMainWindow.java
+++ b/dev/core/src/com/google/gwt/dev/shell/ShellMainWindow.java
@@ -181,7 +181,7 @@
   private Toolbar toolbar;
 
   public ShellMainWindow(BrowserWindowController browserWindowController,
-      final Shell parent, int serverPort, boolean checkForUpdates) {
+      final Shell parent, int serverPort) {
     super(parent, SWT.NONE);
 
     this.browserWindowController = browserWindowController;
@@ -227,41 +227,6 @@
       data.verticalAlignment = GridData.FILL;
       logPane.setLayoutData(data);
     }
-
-    // check for updates
-    if (checkForUpdates) {
-      try {
-        final CheckForUpdates updateChecker = PlatformSpecific.createUpdateChecker();
-        if (updateChecker != null) {
-          final CheckForUpdates.UpdateAvailableCallback callback = new CheckForUpdates.UpdateAvailableCallback() {
-            public void onUpdateAvailable(final String html) {
-              // Do this on the main thread.
-              //
-              parent.getDisplay().asyncExec(new Runnable() {
-                public void run() {
-                  new BrowserDialog(parent, getLogger(), html).open(true);
-                }
-
-              });
-            }
-          };
-
-          // Run the update checker on a background thread.
-          //
-          Thread checkerThread = new Thread() {
-            @Override
-            public void run() {
-              updateChecker.check(callback);
-            }
-          };
-
-          checkerThread.setDaemon(true);
-          checkerThread.start();
-        }
-      } catch (Throwable e) {
-        // Always silently ignore any errors.
-      }
-    }
   }
 
   public AbstractTreeLogger getLogger() {
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerDisableUpdateCheck.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerDisableUpdateCheck.java
new file mode 100644
index 0000000..142f69c
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerDisableUpdateCheck.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.arg;
+
+import com.google.gwt.util.tools.ArgHandlerFlag;
+
+/**
+ * Handles the -XdisableUpdateCheck command line flag.
+ */
+public final class ArgHandlerDisableUpdateCheck extends ArgHandlerFlag {
+
+  private final OptionDisableUpdateCheck option;
+
+  public ArgHandlerDisableUpdateCheck(OptionDisableUpdateCheck option) {
+    this.option = option;
+  }
+
+  @Override
+  public String getPurpose() {
+    return "Disable the check to see if an update version of GWT is available";
+  }
+
+  @Override
+  public String getTag() {
+    return "-XdisableUpdateCheck";
+  }
+
+  @Override
+  public boolean isUndocumented() {
+    return true;
+  }
+  
+  @Override
+  public boolean setFlag() {
+    option.setDisableUpdateCheck(true);
+    return true;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionDisableUpdateCheck.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionDisableUpdateCheck.java
new file mode 100644
index 0000000..085d0d2
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionDisableUpdateCheck.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.arg;
+
+/**
+ * Option to set the compiler working directory.
+ */
+public interface OptionDisableUpdateCheck {
+
+  /**
+   * Check to see if update checks are disabled.
+   */
+  boolean isUpdateCheckDisabled();
+
+  /**
+   * Sets the disable update check flag.
+   */
+  void setDisableUpdateCheck(boolean disabled);
+}
diff --git a/dev/core/test/com/google/gwt/dev/shell/CheckForUpdatesTest.java b/dev/core/test/com/google/gwt/dev/shell/CheckForUpdatesTest.java
index c3255c1..dad4351 100644
--- a/dev/core/test/com/google/gwt/dev/shell/CheckForUpdatesTest.java
+++ b/dev/core/test/com/google/gwt/dev/shell/CheckForUpdatesTest.java
@@ -1,56 +1,80 @@
 package com.google.gwt.dev.shell;
 
 import junit.framework.TestCase;
+import com.google.gwt.dev.shell.CheckForUpdates.GwtVersion;
 
 public class CheckForUpdatesTest extends TestCase {
 
   /*
-   * Test method for
-   * 'com.google.gwt.dev.shell.CheckForUpdates.isServerVersionNewer(String,
-   * String)'
+   * Test GwtVersion comparisons
    */
-  public final void testIsServerVersionNewer() {
-    assertFalse(CheckForUpdates.isServerVersionNewer(null, null));
+  public final void testVersionComparison() {
+    GwtVersion v1 = new GwtVersion(null);
+    assertEquals(0, v1.compareTo(v1));
 
-    assertFalse(CheckForUpdates.isServerVersionNewer("1", "2"));
-    assertFalse(CheckForUpdates.isServerVersionNewer("2", "1"));
+    v1 = new GwtVersion("1");
+    assertEquals(0, v1.compareTo(v1));
+    GwtVersion v2 = new GwtVersion("2");
+    assertTrue(v1.compareTo(v2) < 0);
+    assertTrue(v2.compareTo(v1) > 0);
 
-    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.3", null));
-    assertFalse(CheckForUpdates.isServerVersionNewer(null, "1.2.3"));
+    v1 = new GwtVersion("1.2.3");
+    v2 = new GwtVersion(null);
+    assertTrue(v1.compareTo(v2) > 0);
+    assertTrue(v2.compareTo(v1) < 0);
 
-    assertFalse(CheckForUpdates.isServerVersionNewer("2", "1.2.3"));
-    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.3", "2"));
+    v1 = new GwtVersion("1.2.3");
+    v2 = new GwtVersion("2.0.0");
+    assertTrue(v1.compareTo(v2) < 0);
+    assertTrue(v2.compareTo(v1) > 0);
 
-    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.3", "2.3.4.5"));
-    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.3.4", "2.3.4"));
+    v1 = new GwtVersion("1.2.99");
+    v2 = new GwtVersion("2.0.0");
+    assertTrue(v1.compareTo(v2) < 0);
+    assertTrue(v2.compareTo(v1) > 0);
 
-    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.3", "1.2.3"));
-    assertFalse(CheckForUpdates.isServerVersionNewer("1000.2000.3000",
-      "1000.2000.3000"));
-    assertFalse(CheckForUpdates.isServerVersionNewer("001.002.003", "1.2.3"));
-    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.3", "001.002.003"));
+    v1 = new GwtVersion("1.2.99");
+    v2 = new GwtVersion("1.3.0");
+    assertTrue(v1.compareTo(v2) < 0);
+    assertTrue(v2.compareTo(v1) > 0);
 
-    assertTrue(CheckForUpdates.isServerVersionNewer("1.2.3", "2.2.3"));
-    assertFalse(CheckForUpdates.isServerVersionNewer("2.2.3", "1.2.3"));
+    v1 = new GwtVersion("001.002.099");
+    v2 = new GwtVersion("1.2.99");
+    assertEquals(0, v1.compareTo(v2));
 
-    assertTrue(CheckForUpdates.isServerVersionNewer("1.2.3", "1.3.3"));
-    assertFalse(CheckForUpdates.isServerVersionNewer("1.3.3", "1.2.3"));
-
-    assertTrue(CheckForUpdates.isServerVersionNewer("1.2.3", "1.2.4"));
-    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.4", "1.2.3"));
-
-    assertTrue(CheckForUpdates.isServerVersionNewer("1000.2000.3000",
-      "1000.2000.4000"));
-    assertFalse(CheckForUpdates.isServerVersionNewer("1000.2000.4000",
-      "1000.2000.3000"));
-
-    assertTrue(CheckForUpdates.isServerVersionNewer("1000.2000.3000",
-      "1000.2000.3001"));
-    assertFalse(CheckForUpdates.isServerVersionNewer("1000.2000.3001",
-      "1000.2000.3000"));
-
-    assertTrue(CheckForUpdates.isServerVersionNewer("0.2.3", "1.1.3"));
-    assertFalse(CheckForUpdates.isServerVersionNewer("1.1.3", "0.2.3"));
+    // TODO: more tests
+//    assertFalse(CheckForUpdates.isServerVersionNewer("2", "1.2.3"));
+//    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.3", "2"));
+//
+//    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.3", "2.3.4.5"));
+//    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.3.4", "2.3.4"));
+//
+//    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.3", "1.2.3"));
+//    assertFalse(CheckForUpdates.isServerVersionNewer("1000.2000.3000",
+//      "1000.2000.3000"));
+//    assertFalse(CheckForUpdates.isServerVersionNewer("001.002.003", "1.2.3"));
+//    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.3", "001.002.003"));
+//
+//    assertTrue(CheckForUpdates.isServerVersionNewer("1.2.3", "2.2.3"));
+//    assertFalse(CheckForUpdates.isServerVersionNewer("2.2.3", "1.2.3"));
+//
+//    assertTrue(CheckForUpdates.isServerVersionNewer("1.2.3", "1.3.3"));
+//    assertFalse(CheckForUpdates.isServerVersionNewer("1.3.3", "1.2.3"));
+//
+//    assertTrue(CheckForUpdates.isServerVersionNewer("1.2.3", "1.2.4"));
+//    assertFalse(CheckForUpdates.isServerVersionNewer("1.2.4", "1.2.3"));
+//
+//    assertTrue(CheckForUpdates.isServerVersionNewer("1000.2000.3000",
+//      "1000.2000.4000"));
+//    assertFalse(CheckForUpdates.isServerVersionNewer("1000.2000.4000",
+//      "1000.2000.3000"));
+//
+//    assertTrue(CheckForUpdates.isServerVersionNewer("1000.2000.3000",
+//      "1000.2000.3001"));
+//    assertFalse(CheckForUpdates.isServerVersionNewer("1000.2000.3001",
+//      "1000.2000.3000"));
+//
+//    assertTrue(CheckForUpdates.isServerVersionNewer("0.2.3", "1.1.3"));
+//    assertFalse(CheckForUpdates.isServerVersionNewer("1.1.3", "0.2.3"));
   }
-
 }
diff --git a/dev/linux/src/com/google/gwt/dev/shell/moz/CheckForUpdatesMoz.java b/dev/linux/src/com/google/gwt/dev/shell/moz/CheckForUpdatesMoz.java
deleted file mode 100644
index a1ba73d..0000000
--- a/dev/linux/src/com/google/gwt/dev/shell/moz/CheckForUpdatesMoz.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2007 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.dev.shell.moz;
-
-import com.google.gwt.dev.shell.CheckForUpdates;
-
-/**
- * Mozilla specific implementation of CheckForUpdates.
- */
-public class CheckForUpdatesMoz extends CheckForUpdates {
-
-  @Override
-  protected byte[] doHttpGet(String userAgent, String url) {
-    // Don't attempt to support proxies on Linux.
-    //
-    return httpGetNonNative(userAgent, url);
-  }
-}
diff --git a/dev/mac/src/com/google/gwt/dev/shell/mac/CheckForUpdatesSaf.java b/dev/mac/src/com/google/gwt/dev/shell/mac/CheckForUpdatesSaf.java
deleted file mode 100644
index 34ad6e2..0000000
--- a/dev/mac/src/com/google/gwt/dev/shell/mac/CheckForUpdatesSaf.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2007 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.dev.shell.mac;
-
-import com.google.gwt.dev.shell.CheckForUpdates;
-
-/**
- * Safari specific implementation of CheckForUpdates.
- */
-public class CheckForUpdatesSaf extends CheckForUpdates {
-
-  @Override
-  protected byte[] doHttpGet(String userAgent, String url) {
-    // Don't attempt to support proxies on Linux.
-    //
-    return httpGetNonNative(userAgent, url);
-  }
-}
diff --git a/dev/windows/src/com/google/gwt/dev/shell/ie/CheckForUpdatesIE6.java b/dev/windows/src/com/google/gwt/dev/shell/ie/CheckForUpdatesIE6.java
index fd71509..cdd1637 100644
--- a/dev/windows/src/com/google/gwt/dev/shell/ie/CheckForUpdatesIE6.java
+++ b/dev/windows/src/com/google/gwt/dev/shell/ie/CheckForUpdatesIE6.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.dev.shell.ie;
 
+import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.dev.shell.CheckForUpdates;
 
 /**
@@ -22,13 +23,15 @@
  */
 public class CheckForUpdatesIE6 extends CheckForUpdates {
 
-  public CheckForUpdatesIE6() {
+  public CheckForUpdatesIE6(TreeLogger logger, String entryPoint) {
+    super(logger, entryPoint);
     LowLevelIE6.init();
   }
 
   @Override
-  protected byte[] doHttpGet(String userAgent, String url) {
-    byte[] response = LowLevelIE6.httpGet(userAgent, url);
+  protected byte[] doHttpGet(TreeLogger branch, String userAgent, String url) {
+    byte[] response = LowLevelIE6.httpGet(branch, userAgent, url,
+        System.getProperty(PROPERTY_DEBUG_HTTP_GET) != null);
     return response;
   }
 
diff --git a/dev/windows/src/com/google/gwt/dev/shell/ie/LowLevelIE6.java b/dev/windows/src/com/google/gwt/dev/shell/ie/LowLevelIE6.java
index 0a95c60..19b0613 100644
--- a/dev/windows/src/com/google/gwt/dev/shell/ie/LowLevelIE6.java
+++ b/dev/windows/src/com/google/gwt/dev/shell/ie/LowLevelIE6.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.dev.shell.ie;
 
+import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.dev.shell.LowLevel;
 
 /**
@@ -32,15 +33,17 @@
    * @return the bytes of the full response (including headers), or
    *         <code>null</code> if there's a problem
    */
-  public static byte[] httpGet(String userAgent, String url) {
+  public static byte[] httpGet(TreeLogger branch, String userAgent, String url,
+      boolean debugFlag) {
     init();
     byte[][] out = new byte[1][];
     int status = _httpGet(userAgent, url, out);
     if (status == 0) {
       return out[0];
     } else {
-      if (System.getProperty("gwt.debugLowLevelHttpGet") != null) {
-        System.err.println("GET failed with status " + status + " for " + url);
+      if (debugFlag) {
+        branch.log(TreeLogger.ERROR, "GET failed with status " + status
+            + " for " + url);
       }
       return null;
     }
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index b913922..9e06b9e 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -28,6 +28,7 @@
 import com.google.gwt.dev.cfg.Properties;
 import com.google.gwt.dev.cfg.Property;
 import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.shell.CheckForUpdates;
 import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 import com.google.gwt.junit.client.TimeoutException;
@@ -498,11 +499,11 @@
   }
 
   /**
-   * Never check for updates in JUnit mode.
+   * Check for updates once a minute.
    */
   @Override
-  protected boolean doShouldCheckForUpdates() {
-    return false;
+  protected long checkForUpdatesInterval() {
+    return CheckForUpdates.ONE_MINUTE;
   }
 
   @Override