Add UI projects actions for copy and resume copy

On the ProjectInfoScreen there are now action buttons to:
a) copy this project to a new project
b) resume copy for the project from the copy source

The action for resume copy is only shown if the the project is a copy
of another project (in the same Gerrit server) and if this source
project still exists.

Change-Id: I99cf6365ef7e94bb7cf292486640c14a9a9d1e4c
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
diff --git a/BUCK b/BUCK
index 8f8013f..63446b4 100644
--- a/BUCK
+++ b/BUCK
@@ -20,6 +20,7 @@
     'Gerrit-ApiVersion: 2.11-SNAPSHOT',
     'Gerrit-Module: com.googlesource.gerrit.plugins.importer.Module',
     'Gerrit-SshModule: com.googlesource.gerrit.plugins.importer.SshModule',
+    'Gerrit-HttpModule: com.googlesource.gerrit.plugins.importer.HttpModule',
   ],
   deps = [
     HTTP_LIB,
diff --git a/pom.xml b/pom.xml
index 0870deb..2698efb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -41,6 +41,7 @@
               <Gerrit-PluginName>importer</Gerrit-PluginName>
               <Gerrit-Module>com.googlesource.gerrit.plugins.importer.Module</Gerrit-Module>
               <Gerrit-SshModule>com.googlesource.gerrit.plugins.importer.SshModule</Gerrit-SshModule>
+              <Gerrit-HttpModule>com.googlesource.gerrit.plugins.importer.HttpModule</Gerrit-HttpModule>
 
               <Implementation-Vendor>Gerrit Code Review</Implementation-Vendor>
               <Implementation-URL>http://code.google.com/p/gerrit/</Implementation-URL>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/CopyProject.java b/src/main/java/com/googlesource/gerrit/plugins/importer/CopyProject.java
index 7bbc414..eba963c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/CopyProject.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/CopyProject.java
@@ -16,15 +16,18 @@
 
 import com.google.common.base.Strings;
 import com.google.gerrit.common.errors.NoSuchAccountException;
+import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.CapabilityControl;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.ConfigResource;
 import com.google.gerrit.server.project.NoSuchChangeException;
@@ -43,7 +46,8 @@
 
 @Singleton
 @RequiresCapability(CopyProjectCapability.ID)
-class CopyProject implements RestModifyView<ProjectResource, Input> {
+class CopyProject implements RestModifyView<ProjectResource, Input>,
+    UiAction<ProjectResource> {
   public static class Input {
     public String name;
   }
@@ -51,15 +55,18 @@
   private final ImportProject.Factory importProjectFactory;
   private final String canonicalWebUrl;
   private final Provider<CurrentUser> currentUserProvider;
+  private final String pluginName;
 
   @Inject
   CopyProject(
       ImportProject.Factory importProjectFactory,
       @CanonicalWebUrl String canonicalWebUrl,
-      Provider<CurrentUser> currentUserProvider) {
+      Provider<CurrentUser> currentUserProvider,
+      @PluginName String pluginName) {
     this.importProjectFactory = importProjectFactory;
     this.canonicalWebUrl = canonicalWebUrl;
     this.currentUserProvider = currentUserProvider;
+    this.pluginName = pluginName;
   }
 
   @Override
@@ -81,4 +88,18 @@
     return importProjectFactory.create(new Project.NameKey(input.name))
         .apply(new ConfigResource(), in);
   }
+
+  @Override
+  public UiAction.Description getDescription(ProjectResource rsrc) {
+    return new UiAction.Description()
+        .setLabel("Copy...")
+        .setTitle(String.format("Copy project %s", rsrc.getName()))
+        .setVisible(canCopy(rsrc));
+  }
+
+  private boolean canCopy(ProjectResource rsrc) {
+    CapabilityControl ctl = currentUserProvider.get().getCapabilities();
+    return ctl.canAdministrateServer()
+        || ctl.canPerform(pluginName + "-" + CopyProjectCapability.ID);
+  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/importer/HttpModule.java
new file mode 100644
index 0000000..e5b6ce5
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/HttpModule.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2015 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.googlesource.gerrit.plugins.importer;
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.webui.JavaScriptPlugin;
+import com.google.gerrit.extensions.webui.WebUiPlugin;
+import com.google.gerrit.httpd.plugins.HttpPluginModule;
+
+public class HttpModule extends HttpPluginModule {
+  @Override
+  protected void configureServlets() {
+    DynamicSet.bind(binder(), WebUiPlugin.class)
+        .toInstance(new JavaScriptPlugin("copy-project.js"));
+    DynamicSet.bind(binder(), WebUiPlugin.class)
+        .toInstance(new JavaScriptPlugin("resume-copy-project.js"));
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/ResumeCopyProject.java b/src/main/java/com/googlesource/gerrit/plugins/importer/ResumeCopyProject.java
index 7383137..00e22e1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ResumeCopyProject.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ResumeCopyProject.java
@@ -15,16 +15,23 @@
 package com.googlesource.gerrit.plugins.importer;
 
 import com.google.gerrit.common.errors.NoSuchAccountException;
+import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.ConfigResource;
 import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectResource;
 import com.google.gerrit.server.validators.ValidationException;
 import com.google.gwtorm.server.OrmException;
@@ -40,19 +47,29 @@
 
 @Singleton
 @RequiresCapability(CopyProjectCapability.ID)
-class ResumeCopyProject implements RestModifyView<ProjectResource, Input> {
+class ResumeCopyProject implements RestModifyView<ProjectResource, Input>,
+    UiAction<ProjectResource> {
   private final ResumeProjectImport resumeProjectImport;
   private final ProjectsCollection projectsCollection;
   private final Provider<CurrentUser> currentUserProvider;
+  private final String pluginName;
+  private final String canonicalWebUrl;
+  private final ProjectCache projectCache;
 
   @Inject
   ResumeCopyProject(
       ResumeProjectImport resumeProjectImport,
       ProjectsCollection projectsCollection,
-      Provider<CurrentUser> currentUserProvider) {
+      Provider<CurrentUser> currentUserProvider,
+      @PluginName String pluginName,
+      @CanonicalWebUrl String canonicalWebUrl,
+      ProjectCache projectCache) {
     this.resumeProjectImport = resumeProjectImport;
     this.projectsCollection = projectsCollection;
     this.currentUserProvider = currentUserProvider;
+    this.pluginName = pluginName;
+    this.canonicalWebUrl = canonicalWebUrl;
+    this.projectCache = projectCache;
   }
 
   @Override
@@ -70,4 +87,38 @@
             IdString.fromDecoded(rsrc.getName()));
     return resumeProjectImport.apply(projectResource, in);
   }
+
+  @Override
+  public UiAction.Description getDescription(
+      ProjectResource rsrc) {
+    return new UiAction.Description()
+        .setLabel("Resume Copy...")
+        .setTitle(String.format("Resume copy for project %s", rsrc.getName()))
+        .setVisible(canResumeCopy(rsrc) && isCopied(rsrc));
+  }
+
+  private boolean canResumeCopy(ProjectResource rsrc) {
+    CapabilityControl ctl = currentUserProvider.get().getCapabilities();
+    return ctl.canAdministrateServer()
+        || (ctl.canPerform(pluginName + "-" + CopyProjectCapability.ID)
+            && rsrc.getControl().isOwner());
+  }
+
+  private boolean isCopied(ProjectResource rsrc) {
+    try {
+      ImportProjectResource projectResource =
+          projectsCollection.parse(new ConfigResource(),
+              IdString.fromDecoded(rsrc.getName()));
+      ImportProjectInfo info = projectResource.getInfo();
+      if (!canonicalWebUrl.equals(info.from)) {
+        // no copy, but an import from another system
+        return false;
+      }
+
+      // check that source project still exists
+      return projectCache.get(new Project.NameKey(info.name)) != null;
+    } catch (ResourceNotFoundException | IOException e) {
+      return false;
+    }
+  }
 }
diff --git a/src/main/resources/static/copy-project.js b/src/main/resources/static/copy-project.js
new file mode 100644
index 0000000..a7bc91d
--- /dev/null
+++ b/src/main/resources/static/copy-project.js
@@ -0,0 +1,42 @@
+// Copyright (C) 2015 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.
+
+Gerrit.install(function(self) {
+    function onCopyProject(c) {
+      var t = c.textfield();
+      var b = c.button('Copy',
+        {onclick: function(){
+          c.call(
+            {name: t.value},
+            function(r) {
+              c.hide();
+              window.alert('The project: "'
+                + c.project
+                + '" was copied to "'
+                + t.value
+                + '".'),
+              Gerrit.go('/admin/projects/' + t.value);
+            });
+        }});
+      c.popup(c.div(
+        c.msg('Copy project "'
+          + c.project
+          + '" to '),
+        t,
+        c.br(),
+        c.br(),
+        b));
+    }
+    self.onAction('project', 'copy', onCopyProject);
+  });
diff --git a/src/main/resources/static/resume-copy-project.js b/src/main/resources/static/resume-copy-project.js
new file mode 100644
index 0000000..9d61631
--- /dev/null
+++ b/src/main/resources/static/resume-copy-project.js
@@ -0,0 +1,38 @@
+// Copyright (C) 2015 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.
+
+Gerrit.install(function(self) {
+    function onResumeCopyProject(c) {
+      var b = c.button('Resume',
+        {onclick: function(){
+          c.call(
+            {},
+            function(r) {
+              c.hide();
+              window.alert('Copy for project "'
+                + c.project
+                + '" was resumed."'),
+              Gerrit.go('/admin/projects/' + c.project);
+            });
+        }});
+      c.popup(c.div(
+        c.msg('Resume copy for project "'
+          + c.project
+          + '" ?'),
+        c.br(),
+        c.br(),
+        b));
+    }
+    self.onAction('project', 'copy.resume', onResumeCopyProject);
+  });