Add force-deletion utilities for cleaning up dirs

Windows sucks.
This commit is contained in:
Octavia Togami 2020-07-07 14:46:29 -07:00
parent 624e416e9b
commit 176418bad1
No known key found for this signature in database
GPG Key ID: CC364524D1983C99
4 changed files with 141 additions and 39 deletions

View File

@ -0,0 +1,23 @@
package com.sk89q.worldedit.util;
public class ShutdownHook<V> implements AutoCloseable {
private final Thread hook;
private final V value;
public ShutdownHook(Thread hook, V value) {
this.hook = hook;
this.value = value;
Runtime.getRuntime().addShutdownHook(hook);
}
public V getValue() {
return value;
}
@Override
public void close() throws Exception {
Runtime.getRuntime().removeShutdownHook(hook);
}
}

View File

@ -1,30 +1,34 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.io.file;
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.io.file;
import com.sk89q.worldedit.util.ShutdownHook;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
public class SafeFiles {
@ -64,6 +68,90 @@ private static String dropSlash(String name) {
return name.substring(0, name.length() - 1);
}
public static ShutdownHook<Path> tryHardToDeleteDirOnExit(Path directory) {
Thread hook = new Thread(() -> {
try {
tryHardToDeleteDir(directory);
} catch (IOException ignored) {
}
});
return new ShutdownHook<>(hook, directory);
}
/**
* Recursively uses {@link #tryHardToDelete(Path)} to cleanup directories before deleting them.
*
* @param directory the directory to delete
* @throws IOException if an error occurs trying to delete the directory
*/
public static void tryHardToDeleteDir(Path directory) throws IOException {
if (!Files.isDirectory(directory)) {
if (!Files.exists(directory)) {
return;
}
throw new IOException(directory + " is not a directory");
}
try (Stream<Path> files = Files.list(directory)) {
for (Iterator<Path> iter = files.iterator(); iter.hasNext(); ) {
Path next = iter.next();
if (Files.isDirectory(next)) {
tryHardToDeleteDir(next);
} else {
tryHardToDelete(next);
}
}
}
tryHardToDelete(directory);
}
/**
* Tries to delete a path. If it fails the first time, uses an implementation detail to try
* and make it possible to delete the path, and then tries again. If that fails, throws an
* {@link IOException} with both errors.
*
* @param path the path to delete
* @throws IOException if the path could not be deleted after multiple attempts
*/
public static void tryHardToDelete(Path path) throws IOException {
IOException suppressed = tryDelete(path);
if (suppressed == null) {
return;
}
// This is copied from Ant (see org.apache.tools.ant.util.FileUtils.tryHardToDelete).
// It mentions that there is a bug in the Windows JDK implementations that this is a valid
// workaround for. I've been unable to find a definitive reference to this bug.
// The thinking is that if this is good enough for Ant, it's good enough for us.
System.gc();
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
IOException suppressed2 = tryDelete(path);
if (suppressed2 == null) {
return;
}
IOException ex = new IOException("Failed to delete " + path, suppressed2);
ex.addSuppressed(suppressed);
throw ex;
}
@Nullable
private static IOException tryDelete(Path path) {
try {
Files.deleteIfExists(path);
if (Files.exists(path)) {
return new IOException(path + " still exists after deleting");
}
return null;
} catch (IOException e) {
return e;
}
}
private SafeFiles() {
}
}

View File

@ -47,9 +47,11 @@
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.Direction;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.util.ShutdownHook;
import com.sk89q.worldedit.util.SideEffect;
import com.sk89q.worldedit.util.SideEffectSet;
import com.sk89q.worldedit.util.TreeGenerator.TreeType;
import com.sk89q.worldedit.util.io.file.SafeFiles;
import com.sk89q.worldedit.world.AbstractWorld;
import com.sk89q.worldedit.world.RegenOptions;
import com.sk89q.worldedit.world.biome.BiomeType;
@ -94,9 +96,7 @@
import net.minecraft.world.gen.feature.Feature;
import net.minecraft.world.level.ServerWorldProperties;
import net.minecraft.world.level.storage.LevelStorage;
import org.apache.commons.io.FileUtils;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.file.Files;
import java.nio.file.Path;
@ -113,7 +113,6 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
@ -302,14 +301,9 @@ public boolean regenerate(Region region, EditSession editSession, RegenOptions o
private void doRegen(Region region, EditSession editSession, RegenOptions options) throws Exception {
Path tempDir = Files.createTempDirectory("WorldEditWorldGen");
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
FileUtils.deleteDirectory(tempDir.toFile());
} catch (IOException ignored) {
}
}));
LevelStorage levelStorage = LevelStorage.create(tempDir);
try (LevelStorage.Session session = levelStorage.createSession("WorldEditTempGen")) {
try (ShutdownHook<Path> ignored = SafeFiles.tryHardToDeleteDirOnExit(tempDir);
LevelStorage.Session session = levelStorage.createSession("WorldEditTempGen")) {
ServerWorld originalWorld = (ServerWorld) getWorld();
long seed = options.getSeed().orElse(originalWorld.getSeed());
AccessorLevelProperties levelProperties = (AccessorLevelProperties)
@ -355,7 +349,7 @@ private void doRegen(Region region, EditSession editSession, RegenOptions option
levelProperties.setGeneratorOptions(originalOpts);
}
} finally {
FileUtils.deleteDirectory(tempDir.toFile());
SafeFiles.tryHardToDeleteDir(tempDir);
}
}

View File

@ -47,9 +47,11 @@
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.Direction;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.util.ShutdownHook;
import com.sk89q.worldedit.util.SideEffect;
import com.sk89q.worldedit.util.SideEffectSet;
import com.sk89q.worldedit.util.TreeGenerator.TreeType;
import com.sk89q.worldedit.util.io.file.SafeFiles;
import com.sk89q.worldedit.world.AbstractWorld;
import com.sk89q.worldedit.world.RegenOptions;
import com.sk89q.worldedit.world.biome.BiomeType;
@ -312,14 +314,9 @@ public boolean regenerate(Region region, EditSession editSession, RegenOptions o
private void doRegen(Region region, EditSession editSession, RegenOptions options) throws Exception {
Path tempDir = Files.createTempDirectory("WorldEditWorldGen");
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
FileUtils.deleteDirectory(tempDir.toFile());
} catch (IOException ignored) {
}
}));
SaveFormat levelStorage = SaveFormat.func_237269_a_(tempDir);
try (SaveFormat.LevelSave session = levelStorage.func_237274_c_("WorldEditTempGen")) {
try (ShutdownHook<Path> ignored = SafeFiles.tryHardToDeleteDirOnExit(tempDir);
SaveFormat.LevelSave session = levelStorage.func_237274_c_("WorldEditTempGen")) {
ServerWorld originalWorld = (ServerWorld) getWorld();
long seed = options.getSeed().orElse(originalWorld.getSeed());
ServerWorldInfo levelProperties =
@ -367,7 +364,7 @@ private void doRegen(Region region, EditSession editSession, RegenOptions option
levelProperties.field_237343_c_ = originalOpts;
}
} finally {
FileUtils.deleteDirectory(tempDir.toFile());
SafeFiles.tryHardToDeleteDir(tempDir);
}
}