Improve keyapplet referencing

Don't unpack the keyapplet, copy it into place as a cache.jar.  To
avoid linking the Gerrit version number on the applet cache, use a
build time resource to tell us the actual version number of the
applet, thereby avoiding downloads even across Gerrit upgrades.

When loading the applet for the first time in this browser, give
the applet up to 30 seconds to start up.  It may need to show a
dialog to the user and ask them if its OK to execute.  The user
will need a few seconds to respond to this request before we can
actually hit the applet's API.

Change-Id: I0c4b9b8f7a382b343cfabff1d36d9f9034b74530
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/gerrit-gwtui/pom.xml b/gerrit-gwtui/pom.xml
index b34e3d7..eec9d10 100644
--- a/gerrit-gwtui/pom.xml
+++ b/gerrit-gwtui/pom.xml
@@ -157,6 +157,53 @@
   <build>
     <plugins>
       <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>get-keyapplet</id>
+            <phase>prepare-package</phase>
+            <goals>
+              <goal>copy</goal>
+            </goals>
+            <configuration>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>gerrit</groupId>
+                  <artifactId>gerrit-keyapplet</artifactId>
+                  <version>${keyappletVersion}</version>
+                  <outputDirectory>${project.build.directory}/${project.build.finalName}/gerrit</outputDirectory>
+                  <destFileName>gerrit-keyapplet-${keyappletVersion}.cache.jar</destFileName>
+                </artifactItem>
+              </artifactItems>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-antrun-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>name-keyapplet</id>
+            <phase>generate-resources</phase>
+            <goals>
+              <goal>run</goal>
+            </goals>
+            <configuration>
+              <tasks>
+                <property name="dst" location="${project.build.outputDirectory}" />
+                <property name="pkg" location="${dst}/com/google/gerrit/client/account"/>
+                <mkdir dir="${pkg}"/>
+                <echo file="${pkg}/keyapplet_jar">gerrit-keyapplet-${keyappletVersion}.cache.jar</echo>
+              </tasks>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>gwt-maven-plugin</artifactId>
         <configuration>
@@ -174,47 +221,14 @@
       </plugin>
 
       <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-dependency-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>get-keyapplet</id>
-            <phase>generate-resources</phase>
-            <goals>
-              <goal>unpack</goal>
-            </goals>
-            <configuration>
-              <artifactItems>
-                <artifactItem>
-                  <groupId>gerrit</groupId>
-                  <artifactId>gerrit-keyapplet</artifactId>
-                  <overWrite>true</overWrite>
-                  <outputDirectory>${project.build.directory}/gerrit-keyapplet</outputDirectory>
-                  <includes>**/*</includes>
-                </artifactItem>
-              </artifactItems>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-war-plugin</artifactId>
-        <configuration>
-          <packagingExcludes>WEB-INF/classes/**,WEB-INF/lib/**</packagingExcludes>
-          <archive>
-            <addMavenDescriptor>false</addMavenDescriptor>
-          </archive>
-        </configuration>
-      </plugin>
-
-      <plugin>
         <artifactId>maven-antrun-plugin</artifactId>
         <executions>
           <execution>
             <id>compress-html</id>
-            <phase>process-classes</phase>
+            <phase>prepare-package</phase>
+            <goals>
+              <goal>run</goal>
+            </goals>
             <configuration>
               <tasks>
                 <property name="dst" location="${project.build.directory}/${project.build.finalName}"/>
@@ -235,35 +249,20 @@
                 </apply>
               </tasks>
             </configuration>
-            <goals>
-              <goal>run</goal>
-            </goals>
-          </execution>
-
-          <execution>
-            <id>include-keyapplet</id>
-            <phase>process-classes</phase>
-            <configuration>
-              <tasks>
-                <property name="dst" location="${project.build.directory}/${project.build.finalName}"/>
-                <property name="app" location="${dst}/gerrit"/>
-                <property name="src" location="${basedir}/target/gerrit-keyapplet" />
-
-                <mkdir dir="${app}"/>
-                <zip
-                    destfile="${app}/gerrit-keyapplet.cache.jar"
-                    compress="true"
-                    level="9">
-                  <fileset dir="${src}" includes="**/*" />
-                </zip>
-              </tasks>
-            </configuration>
-            <goals>
-              <goal>run</goal>
-            </goals>
           </execution>
         </executions>
       </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-war-plugin</artifactId>
+        <configuration>
+          <packagingExcludes>WEB-INF/classes/**,WEB-INF/lib/**</packagingExcludes>
+          <archive>
+            <addMavenDescriptor>false</addMavenDescriptor>
+          </archive>
+        </configuration>
+      </plugin>
     </plugins>
   </build>
 </project>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountResources.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountResources.java
new file mode 100644
index 0000000..a4b9514
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountResources.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2009 The Android Open Source Project
+//
+// 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.gerrit.client.account;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.TextResource;
+
+public interface AccountResources extends ClientBundle {
+  public static final AccountResources I = GWT.create(AccountResources.class);
+
+  @Source("keyapplet_jar")
+  TextResource keyapplet_jar();
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshPanel.java
index fe6495d..a416ddd 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshPanel.java
@@ -33,9 +33,8 @@
 import com.google.gwt.event.dom.client.KeyPressEvent;
 import com.google.gwt.event.dom.client.KeyPressHandler;
 import com.google.gwt.i18n.client.LocaleInfo;
-import com.google.gwt.user.client.Command;
 import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.Timer;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.CheckBox;
 import com.google.gwt.user.client.ui.Composite;
@@ -77,6 +76,7 @@
   private Button clearNew;
   private Button addNew;
   private Button browse;
+  private Timer appletLoadTimer;
   private NpTextArea addTxt;
   private Button delSel;
 
@@ -184,7 +184,7 @@
         doBrowse();
       }
     });
-    browse.setVisible(GWT.isScript() && (!loadedApplet || applet != null));
+    browse.setVisible(!loadedApplet || applet != null);
     buttons.add(browse);
 
     addNew = new Button(Util.C.buttonAddSshKey());
@@ -281,12 +281,13 @@
 
   void doBrowse() {
     browse.setEnabled(false);
+
     if (!loadedApplet) {
       applet = DOM.createElement("applet");
       applet.setAttribute("code",
           "com.google.gerrit.keyapplet.ReadPublicKey.class");
       applet.setAttribute("archive", GWT.getModuleBaseURL()
-          + "gerrit-keyapplet.cache.jar?v=" + Gerrit.getVersion());
+          + AccountResources.I.keyapplet_jar().getText());
       applet.setAttribute("mayscript", "true");
       applet.setAttribute("width", "0");
       applet.setAttribute("height", "0");
@@ -297,14 +298,27 @@
       // new applet tag we just created above, and actually load the
       // applet into the runtime.
       //
-      DeferredCommand.addCommand(new Command() {
-        public void execute() {
-          doBrowse();
+      appletLoadTimer = new Timer() {
+        private int attempts;
+
+        @Override
+        public void run() {
+          if (isAppletRunning(applet)) {
+            appletLoadTimer = null;
+            cancel();
+            doBrowse();
+          } else if (30000 / 200 < attempts++) {
+            appletLoadTimer = null;
+            cancel();
+            noBrowse();
+          }
         }
-      });
+      };
+      appletLoadTimer.scheduleRepeating(200);
       return;
     }
-    if (applet == null) {
+
+    if (applet == null || !isAppletRunning(applet)) {
       // If the applet element is null, the applet was determined
       // to have failed to load, and we are dead. Hide the button.
       //
@@ -355,6 +369,9 @@
     new ErrorDialog(Util.C.sshJavaAppletNotAvailable()).center();
   }
 
+  private static native boolean isAppletRunning(Element keyapp)
+  /*-{ return keyapp['openPublicKey'] ? true : false }-*/;
+
   private static native String openPublicKey(Element keyapp)
   /*-{ var r = keyapp.openPublicKey(); return r == null ? null : ''+r; }-*/;
 
@@ -436,6 +453,16 @@
     });
   }
 
+  @Override
+  protected void onUnload() {
+    if (appletLoadTimer != null) {
+      appletLoadTimer.cancel();
+      appletLoadTimer = null;
+    }
+
+    super.onUnload();
+  }
+
   private void showAddKeyBlock(final boolean show) {
     showAddKeyBlock.setVisible(!show);
     addKeyBlock.setVisible(show);
diff --git a/pom.xml b/pom.xml
index 99a4454..11254b2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -54,6 +54,7 @@
     <slf4jVersion>1.5.8</slf4jVersion>
     <guiceVersion>2.0</guiceVersion>
     <jettyVersion>7.0.1.v20091125</jettyVersion>
+    <keyappletVersion>1.0</keyappletVersion>
 
     <gwt.soyc>false</gwt.soyc>
 
@@ -445,12 +446,6 @@
       </dependency>
 
       <dependency>
-        <groupId>gerrit</groupId>
-        <artifactId>gerrit-keyapplet</artifactId>
-        <version>1.0</version>
-      </dependency>
-
-      <dependency>
         <groupId>org.openid4java</groupId>
         <artifactId>openid4java-consumer</artifactId>
         <version>0.9.5</version>