package org.simantics.internal; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.util.ArrayDeque; import java.util.Collection; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.simantics.utils.FileService; import org.simantics.utils.IOperation; import org.simantics.utils.IOperationListener; /** * @author Tuukka Lehtonen */ public class FileServiceImpl implements FileService { private static final long DELETION_DELAY_MS = 10000; private static final long DELETION_ATTEMPT_INTERVAL_MS = 10000; private static abstract class Op implements IOperation { protected boolean done; protected R result; @SuppressWarnings("unused") protected E lastException; @Override public R waitFor() throws E { synchronized(this) { while (!isDone()) { try { wait(); } catch (InterruptedException e) { } } } return result; } @Override public boolean isDone() { return done; } @Override public void addListener(IOperationListener listener) { throw new UnsupportedOperationException(); } public abstract boolean tryExecute(); } private static class Deletion extends Op { File f; EffortOption effort; int tries; public Deletion(File f, DeleteOption... options) { this.f = f; parseOptions(options); } private void parseOptions(DeleteOption[] options) { for (DeleteOption opt : options) { if (opt instanceof EffortOption) { effort = (EffortOption) opt; } } } @Override public boolean tryExecute() { if (effort != null) { if (tries > effort.maxTries) { // Give up. return true; } } try { deleteAll(f); lastException = null; result = true; return true; } catch (IOException e) { ++tries; lastException = e; return false; } } public void deleteAll(File dir) throws IOException { if (dir.isFile()) { if (!dir.delete()) throw new IOException("Could not delete file: " + dir.getAbsolutePath()); return; } if (dir.isDirectory()) { File[] fs = dir.listFiles((FileFilter) null); if (fs == null) return; for (File f : fs) { if (f.isDirectory()) { deleteAll(f); } else { if (!f.delete()) { throw new IOException("Could not delete file: " + f.getAbsolutePath()); } } } if (!dir.delete()) { throw new IOException("Could not delete directory: " + dir.getAbsolutePath()); } } else if (dir.exists()) { if (!dir.delete()) { throw new IOException("Could not delete file: " + dir.getAbsolutePath()); } } } } private ArrayDeque deletionQueue = new ArrayDeque(); private DeletionJob deletionJob = new DeletionJob(); @Override public IOperation scheduleDeleteIfExists(File file, DeleteOption... options) { if (!file.exists()) return null; synchronized (deletionQueue) { Deletion d = new Deletion(file, options); if (deletionQueue.contains(d)) return null; deletionQueue.addLast(d); deletionJob.schedule(DELETION_DELAY_MS); return d; } } class DeletionJob extends Job { public DeletionJob() { super("File background deletion"); } @SuppressWarnings("unchecked") private > void process(Collection ops) { Object[] opa = ops.toArray(); for (int i = 0; i < opa.length; ++i) { O op = (O) opa[i]; if (op.tryExecute()) { // Success. Will be removed from queue. } else { opa[i] = null; } } synchronized (ops) { for (int i = 0; i < opa.length; ++i) { if (opa[i] != null) ops.remove(opa[i]); } } } @Override protected IStatus run(IProgressMonitor monitor) { process(deletionQueue); if (!deletionQueue.isEmpty()) schedule(DELETION_ATTEMPT_INTERVAL_MS); return Status.OK_STATUS; } } }