package ini.trakem2.persistence;
import ini.trakem2.Project;
import ini.trakem2.display.Layer;
import ini.trakem2.display.Patch;
import ini.trakem2.utils.IJError;
import ini.trakem2.utils.Utils;
import java.io.File;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/** Methods to remove files that are no longer referenced from the current {@link Patch} instances of a {@link Project}. */
public class StaleFiles {
static public interface Path {
public String get(Patch patch);
public String extension();
}
static public final class CoordinateTransformPath implements Path {
@Override
public final String get(final Patch patch) {
return patch.getCoordinateTransformFilePath();
}
@Override
public final String extension() {
return ".ct";
}
}
static public final class AlphaMaskPath implements Path {
@Override
public final String get(final Patch patch) {
return patch.getAlphaMaskFilePath();
}
@Override
public final String extension() {
return ".zip";
}
}
/**
* Delete all files storing a coordinate transform that are now stale.
* Stale files are files that are no longer referenced by the {@link Patch#getCoordinateTransformId()}.
*
* This method uses the {@link Utils#removeFile(File)}, which limits itself to files
* under the trakem2.* temp data directories for safety. */
static public final boolean deleteCoordinateTransforms(final Project project) {
return delete(project, project.getLoader().getCoordinateTransformsFolder(), new CoordinateTransformPath());
}
/**
* Delete all files storing a zipped image representing the alpha mask and which are now stale.
* Stale files are files that are no longer referenced by the {@link Patch#getAlphaMaskId()}.
*
* This method uses the {@link Utils#removeFile(File)}, which limits itself to files
* under the trakem2.* temp data directories for safety. */
static public final boolean deleteAlphaMasks(final Project project) {
return delete(project, project.getLoader().getMasksFolder(), new AlphaMaskPath());
}
/** Generic method called by other methods of this class. */
static public final boolean delete(final Project project, final String topDir, final Path g) {
if (null == topDir) return true;
// Collect the set of files to keep
final HashSet<String> keepers = new HashSet<String>();
for (final Layer l : project.getRootLayerSet().getLayers()) {
for (final Patch p : l.getAll(Patch.class)) {
String path = g.get(p);
if (null != path) keepers.add(path);
}
}
// Iterate all directories, recursively
final String extension = g.extension();
final LinkedList<File> subdirs = new LinkedList<File>();
subdirs.add(new File(topDir));
final AtomicInteger counter = new AtomicInteger(0);
final ExecutorService exec = Utils.newFixedThreadPool(Math.max(2, Runtime.getRuntime().availableProcessors()), "Stale-file-remover");
while (!subdirs.isEmpty()) {
final File fdir = subdirs.removeFirst();
final String absPath = fdir.getAbsolutePath();
for (final String s : fdir.list()) {
final String path = absPath + "/" + s;
if (s.endsWith(extension)) {
if (keepers.contains(path)) continue;
// Else, delete the file, which is by definition stale
exec.submit(new Runnable() {
public void run() {
if (!new File(path).delete()) {
Utils.log2("Failed to delete: " + path);
counter.incrementAndGet();
}
}
});
} else {
final File f = new File(path);
if (f.isDirectory()) {
subdirs.add(f);
}
}
}
}
// Do not accept more tasks, but execute all submitted tasks
exec.shutdown();
// Wait maximum for an unreasonable amount of time
try {
exec.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
IJError.print(e);
}
if (counter.get() > 0) {
Utils.log("ERROR: failed to delete " + counter.get() + " files.\n See the stdout log for details.");
}
return 0 == counter.get();
}
}