// Copyright (C) 2013 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.deleteproject.fs;

import java.io.File;
import java.io.IOException;

import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gerrit.extensions.events.ProjectDeletedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;

public class FilesystemDeleteHandler {
  private static final Logger log = LoggerFactory
      .getLogger(FilesystemDeleteHandler.class);

  private final File gitDir;
  private final GitRepositoryManager repoManager;
  private final DynamicSet<ProjectDeletedListener> deletedListener;

  @Inject
  public FilesystemDeleteHandler(GitRepositoryManager repoManager,
      SitePaths site,
      @GerritServerConfig Config cfg,
      DynamicSet<ProjectDeletedListener> deletedListener) {
    gitDir = site.resolve(cfg.getString("gerrit", null, "basePath"));
    this.repoManager = repoManager;
    this.deletedListener = deletedListener;
  }

  public void delete(Project project, boolean preserveGitRepository)
      throws IOException, RepositoryNotFoundException {
    // Remove from the jgit cache
    final Repository repository =
        repoManager.openRepository(project.getNameKey());
    if (repository == null) {
      throw new RepositoryNotFoundException("There was an error finding the project.");
    }

    cleanCache(repository);
    if (!preserveGitRepository) {
      deleteGitRepository(project.getNameKey(), repository);
    }
  }

  private void deleteGitRepository(final Project.NameKey project,
      final Repository repository)
      throws IOException {
    // Delete the repository from disk
    File parentFile = repository.getDirectory().getParentFile();
    if (!recursiveDelete(repository.getDirectory())) {
      throw new IOException("Error trying to delete "
          + repository.getDirectory().getAbsolutePath());
    }

    // Delete parent folders while they are (now) empty
    recursiveDeleteParent(parentFile, gitDir);

    // Send an event that the repository was deleted
    ProjectDeletedListener.Event event = new ProjectDeletedListener.Event() {
      @Override
      public String getProjectName() {
        return project.get();
      }
    };
    for (ProjectDeletedListener l : deletedListener) {
      try {
        l.onProjectDeleted(event);
      } catch (RuntimeException e) {
        log.warn("Failure in ProjectDeletedListener", e);
      }
    }
  }

  private void cleanCache(final Repository repository) {
    repository.close();
    RepositoryCache.close(repository);
  }

  /**
   * Recursively delete the specified file and all of its contents.
   *
   * @return true on success, false if there was an error.
   */
  private boolean recursiveDelete(File file) {
    if (file.isDirectory()) {
      File[] contents = file.listFiles();
      if (contents == null) {
        return false;
      }
      for (File f : contents) {
        if (!recursiveDelete(f)) {
          return false;
        }
      }
    }
    return file.delete();
  }

  /**
   * Recursively delete the specified file and its parent files until we hit the
   * file {@code Until} or the parent file is populated. This is used when we
   * have a tree structure such as a/b/c/d.git and a/b/e.git - if we delete
   * a/b/c/d.git, we no longer need a/b/c/.
   */
  private void recursiveDeleteParent(File file, File until) {
    if (file.equals(until)) {
      return;
    }
    if (file.listFiles().length == 0) {
      File parent = file.getParentFile();
      file.delete();
      recursiveDeleteParent(parent, until);
    }
  }
}

@@ -709,16 +709,201 @@
 ception;
+%0Aimport java.nio.file.FileVisitResult;%0Aimport java.nio.file.Files;%0Aimport java.nio.file.Path;%0Aimport java.nio.file.SimpleFileVisitor;%0Aimport java.nio.file.attribute.BasicFileAttributes;
 %0A%0Aimport
@@ -2755,38 +2755,32 @@
 tory repository)
-%0A     
  throws IOExcept
@@ -2889,25 +2889,33 @@
 File();%0A
+%0A
     
-if (!
+try %7B%0A      
 recursiv
@@ -2947,17 +2947,54 @@
 ectory()
-)
+.toPath());%0A    %7D catch (IOException e
 ) %7B%0A    
@@ -3094,24 +3094,27 @@
 solutePath()
+, e
 );%0A    %7D%0A%0A  
@@ -3876,60 +3876,26 @@
  * @
-return true on success, false if there was an error.
+throws IOException
 %0A   
@@ -3907,23 +3907,20 @@
 private 
-boolean
+void
  recursi
@@ -3932,143 +3932,292 @@
 ete(
-File
+Path
  file) 
-%7B%0A    if (file.isDirectory()) %7B%0A      File%5B%5D contents = file.listFiles();%0A      if (contents == null) %7B%0A        return false
+throws IOException %7B%0A    Files.walkFileTree(file, new SimpleFileVisitor%3CPath%3E() %7B%0A      @Override%0A      public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)%0A          throws IOException %7B%0A        Files.delete(file);%0A        return FileVisitResult.CONTINUE
 ;%0A  
@@ -4226,71 +4226,150 @@
   %7D%0A
+%0A
       
-for (File f : contents) %7B%0A        if (!recursiveDelete(f)
+@Override%0A      public FileVisitResult postVisitDirectory(Path dir, IOException e)%0A          throws IOException %7B%0A        if (e != null
 ) %7B%0A
@@ -4382,19 +4382,14 @@
     
-return fals
+throw 
 e;%0A 
@@ -4403,24 +4403,41 @@
 %7D%0A      
-%7D
+  Files.delete(dir);
 %0A    
-%7D%0A
     retu
@@ -4439,28 +4439,55 @@
  return 
-file.delete(
+FileVisitResult.CONTINUE;%0A      %7D%0A    %7D
 );%0A  %7D%0A%0A

Use NIO to recursively delete files