/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.core.util;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.AbstractModule;
import org.eclipse.che.commons.lang.IoUtil;
import org.eclipse.che.commons.lang.Pair;
import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PreDestroy;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Delete files and directories added with method {@link @addFile}. In environments with multiple class loaders, {@code FileCleaner} should
* be stopped if it isn't needed. In Codenvy environment {@code FileCleaner} is stopped automatically by {@link FileCleanerModule}
* otherwise
* need call method {@link #stop()} manually.
*
* @author andrew00x
*/
public class FileCleaner {
private static final Logger LOG = LoggerFactory.getLogger(FileCleaner.class);
/** Number of attempts to delete file or directory before start write log error messages. */
static int logAfterAttempts = 10;
private static ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder()
.setNameFormat("FileCleaner")
.setUncaughtExceptionHandler(
LoggingUncaughtExceptionHandler
.getInstance())
.setDaemon(true).build());
static {
exec.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
clean();
}
}, 30, 30, TimeUnit.SECONDS);
}
private static ConcurrentLinkedQueue<Pair<File, Integer>> files = new ConcurrentLinkedQueue<>();
/** Registers new file or directory in FileCleaner. */
public static void addFile(File file) {
files.offer(Pair.of(file, 0));
}
/** Stops FileCleaner. */
public static void stop() {
exec.shutdownNow();
try {
exec.awaitTermination(3, TimeUnit.SECONDS);
} catch (InterruptedException ignored) {
}
clean();
files.clear();
LOG.info("File cleaner is stopped");
}
private static void clean() {
Pair<File, Integer> pair;
final Set<Pair<File, Integer>> failToDelete = new HashSet<>();
while ((pair = files.poll()) != null) {
final File file = pair.first;
int deleteAttempts = pair.second;
if (file.exists()) {
if (!IoUtil.deleteRecursive(file)) {
failToDelete.add(Pair.of(file, ++deleteAttempts));
if (deleteAttempts > logAfterAttempts) {
LOG.error("Unable delete file '{}' after {} tries", file, deleteAttempts);
}
} else if (LOG.isDebugEnabled()) {
LOG.debug("Delete file '{}'", file);
}
}
}
if (!failToDelete.isEmpty()) {
files.addAll(failToDelete);
}
}
/** Guice module that stops FileCleaner when Guice container destroyed. */
public static class FileCleanerModule extends AbstractModule {
@Override
protected void configure() {
bind(Finalizer.class).asEagerSingleton();
}
}
/** Helper component that stops FileCleaner. */
static class Finalizer {
@PreDestroy
void stop() {
FileCleaner.stop();
}
}
private FileCleaner() {
}
}