Generalize purge task for all storages

This commit is contained in:
Lukas Rieger (Blue) 2023-03-04 12:40:12 +01:00
parent c2499df3a7
commit f3f609c573
No known key found for this signature in database
GPG Key ID: 2D09EC5ED2687FF2
7 changed files with 177 additions and 186 deletions

View File

@ -61,7 +61,7 @@ public boolean scheduleMapUpdateTask(BlueMapMap map, Collection<Vector2i> region
@Override
public boolean scheduleMapPurgeTask(BlueMapMap map) throws IOException {
BlueMapMapImpl cmap = castMap(map);
return renderManager.scheduleRenderTask(MapPurgeTask.create(cmap.getBmMap()));
return renderManager.scheduleRenderTask(new MapPurgeTask(cmap.getBmMap()));
}
@Override

View File

@ -796,7 +796,7 @@ public int purgeCommand(CommandContext<S> context) {
new Thread(() -> {
try {
// delete map
MapPurgeTask purgeTask = MapPurgeTask.create(map);
MapPurgeTask purgeTask = new MapPurgeTask(map);
plugin.getRenderManager().scheduleRenderTaskNext(purgeTask);
source.sendMessage(Text.of(TextColor.GREEN, "Created new Task to purge map '" + map.getId() + "'"));
@ -817,7 +817,7 @@ public int purgeCommand(CommandContext<S> context) {
}
source.sendMessage(Text.of(TextColor.GREEN, "Use ", TextColor.GRAY, "/bluemap", TextColor.GREEN, " to see the progress."));
} catch (IOException | IllegalArgumentException e) {
} catch (IllegalArgumentException e) {
source.sendMessage(Text.of(TextColor.RED, "There was an error trying to purge '" + map.getId() + "', see console for details."));
Logger.global.logError("Failed to purge map '" + map.getId() + "'!", e);
}

View File

@ -26,191 +26,78 @@
import de.bluecolored.bluemap.api.debug.DebugDump;
import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.core.storage.Storage;
import de.bluecolored.bluemap.core.storage.file.FileStorage;
import de.bluecolored.bluemap.core.util.DeletingPathVisitor;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedList;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class MapPurgeTask implements RenderTask {
public class MapPurgeTask implements RenderTask {
public static MapPurgeTask create(BmMap map) throws IOException {
Storage storage = map.getStorage();
if (storage instanceof FileStorage) {
return new MapFilePurgeTask(map, (FileStorage) storage);
} else {
return new MapStoragePurgeTask(map);
}
private final BmMap map;
private volatile double progress;
private volatile boolean hasMoreWork;
private volatile boolean cancelled;
public MapPurgeTask(BmMap map) {
this.map = Objects.requireNonNull(map);
this.progress = 0d;
this.hasMoreWork = true;
this.cancelled = false;
}
public static MapPurgeTask create(Path mapDirectory) throws IOException {
return new MapFilePurgeTask(mapDirectory);
}
@DebugDump
private static class MapFilePurgeTask extends MapPurgeTask {
@Nullable private final BmMap map;
private final Path directory;
private final int subFilesCount;
private final LinkedList<Path> subFiles;
private volatile boolean hasMoreWork;
private volatile boolean cancelled;
public MapFilePurgeTask(Path mapDirectory) throws IOException {
this(null, mapDirectory);
}
public MapFilePurgeTask(BmMap map, FileStorage fileStorage) throws IOException {
this(map, fileStorage.getFilePath(map.getId()));
}
private MapFilePurgeTask(@Nullable BmMap map, Path directory) throws IOException {
this.map = map;
this.directory = directory;
try (Stream<Path> pathStream = Files.walk(directory, 3)) {
this.subFiles = pathStream.collect(Collectors.toCollection(LinkedList::new));
}
this.subFilesCount = subFiles.size();
this.hasMoreWork = true;
this.cancelled = false;
}
@Override
public void doWork() throws Exception {
synchronized (this) {
if (!this.hasMoreWork) return;
this.hasMoreWork = false;
}
try {
// save lowres-tile-manager to clear/flush any buffered data
if (this.map != null) this.map.getLowresTileManager().save();
// delete subFiles first to be able to track the progress and cancel
while (!subFiles.isEmpty()) {
Path subFile = subFiles.getLast();
Files.walkFileTree(subFile, DeletingPathVisitor.INSTANCE);
subFiles.removeLast();
if (this.cancelled) return;
}
// make sure everything is deleted
if (Files.exists(directory)) {
Files.walkFileTree(directory, DeletingPathVisitor.INSTANCE);
}
// reset texture-gallery
if (this.map != null) this.map.resetTextureGallery();
} finally {
// reset map render state
if (this.map != null) {
this.map.getRenderState().reset();
}
}
}
@Override
public boolean hasMoreWork() {
return this.hasMoreWork;
}
@Override
@DebugDump
public double estimateProgress() {
return 1d - (subFiles.size() / (double) subFilesCount);
}
@Override
public void cancel() {
this.cancelled = true;
}
@Override
public boolean contains(RenderTask task) {
if (task == this) return true;
if (task instanceof MapFilePurgeTask) {
return ((MapFilePurgeTask) task).directory.toAbsolutePath().normalize()
.startsWith(this.directory.toAbsolutePath().normalize());
}
return false;
}
@Override
public String getDescription() {
return "Purge Map " + directory.getFileName();
}
}
@DebugDump
private static class MapStoragePurgeTask extends MapPurgeTask {
private final BmMap map;
private volatile boolean hasMoreWork;
public MapStoragePurgeTask(BmMap map) {
this.map = Objects.requireNonNull(map);
this.hasMoreWork = true;
}
@Override
public void doWork() throws Exception {
synchronized (this) {
if (!this.hasMoreWork) return;
this.hasMoreWork = false;
}
try {
map.getStorage().purgeMap(map.getId());
} finally {
// reset map render state
map.getRenderState().reset();
}
}
@Override
public boolean hasMoreWork() {
return this.hasMoreWork;
}
@Override
@DebugDump
public double estimateProgress() {
return 0d;
}
@Override
public void cancel() {
@Override
public void doWork() throws Exception {
synchronized (this) {
if (!this.hasMoreWork) return;
this.hasMoreWork = false;
}
@Override
public boolean contains(RenderTask task) {
if (task == this) return true;
if (task instanceof MapStoragePurgeTask) {
return map.equals(((MapStoragePurgeTask) task).map);
}
// save lowres-tile-manager to clear/flush any buffered data
this.map.getLowresTileManager().save();
return false;
try {
// purge the map
map.getStorage().purgeMap(map.getId(), progressInfo -> {
this.progress = progressInfo.getProgress();
return !this.cancelled;
});
// reset texture gallery
map.resetTextureGallery();
} finally {
// reset renderstate
map.getRenderState().reset();
}
@Override
public String getDescription() {
return "Purge Map " + map.getId();
}
}
}
@Override
public boolean hasMoreWork() {
return this.hasMoreWork && !this.cancelled;
}
@Override
@DebugDump
public double estimateProgress() {
return this.progress;
}
@Override
public void cancel() {
this.cancelled = true;
}
@Override
public boolean contains(RenderTask task) {
if (task == this) return true;
if (task instanceof MapPurgeTask) {
return map.equals(((MapPurgeTask) task).map);
}
return false;
}
@Override
public String getDescription() {
return "Purge Map " + map.getId();
}
}

View File

@ -30,7 +30,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Optional;
import java.util.function.Function;
public abstract class Storage implements Closeable {
@ -52,7 +54,11 @@ public abstract class Storage implements Closeable {
public abstract void deleteMeta(String mapId, String name) throws IOException;
public abstract void purgeMap(String mapId) throws IOException;
public abstract void purgeMap(String mapId, Function<ProgressInfo, Boolean> onProgress) throws IOException;
public abstract Collection<String> collectMapIds() throws IOException;
public abstract long estimateMapSize(String mapId) throws IOException;
public MapStorage mapStorage(final String mapId) {
return new MapStorage(mapId);
@ -118,6 +124,20 @@ public Storage getStorage() {
}
public static class ProgressInfo {
private final double progress;
public ProgressInfo(double progress) {
this.progress = progress;
}
public double getProgress() {
return progress;
}
}
public static String escapeMetaName(String name) {
return name.replaceAll("[^\\w\\d.\\-_/]", "_").replace("..", "_.");
}

View File

@ -27,16 +27,18 @@
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.api.debug.DebugDump;
import de.bluecolored.bluemap.core.storage.*;
import de.bluecolored.bluemap.core.util.FileHelper;
import de.bluecolored.bluemap.core.util.DeletingPathVisitor;
import de.bluecolored.bluemap.core.util.FileHelper;
import de.bluecolored.bluemap.core.util.SizeCollectingPathVisitor;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@DebugDump
public class FileStorage extends Storage {
@ -188,8 +190,48 @@ public void deleteMeta(String mapId, String name) throws IOException {
}
@Override
public void purgeMap(String mapId) throws IOException {
Files.walkFileTree(getFilePath(mapId), DeletingPathVisitor.INSTANCE);
public void purgeMap(String mapId, Function<ProgressInfo, Boolean> onProgress) throws IOException {
final Path directory = getFilePath(mapId);
final int subFilesCount;
final LinkedList<Path> subFiles;
// collect sub-files to be able to provide progress-updates
try (Stream<Path> pathStream = Files.walk(directory, 3)) {
subFiles = pathStream.collect(Collectors.toCollection(LinkedList::new));
}
subFilesCount = subFiles.size();
// delete subFiles first to be able to track the progress and cancel
while (!subFiles.isEmpty()) {
Path subFile = subFiles.getLast();
Files.walkFileTree(subFile, DeletingPathVisitor.INSTANCE);
subFiles.removeLast();
if (!onProgress.apply(
new ProgressInfo(1d - (subFiles.size() / (double) subFilesCount))
)) return;
}
// make sure everything is deleted
if (Files.exists(directory))
Files.walkFileTree(directory, DeletingPathVisitor.INSTANCE);
}
@Override
public Collection<String> collectMapIds() throws IOException {
try (Stream<Path> fileStream = Files.list(root)) {
return fileStream
.filter(Files::isDirectory)
.map(path -> path.getFileName().toString())
.collect(Collectors.toList());
}
}
@Override
public long estimateMapSize(String mapId) throws IOException {
SizeCollectingPathVisitor visitor = new SizeCollectingPathVisitor();
Files.walkFileTree(getFilePath(mapId), visitor);
return visitor.getSize();
}
public Path getFilePath(String mapId, int lod, Vector2i tile){

View File

@ -46,6 +46,7 @@
import java.time.Duration;
import java.util.*;
import java.util.concurrent.CompletionException;
import java.util.function.Function;
public class SQLStorage extends Storage {
@ -375,7 +376,7 @@ public void deleteMeta(String mapId, String name) throws IOException {
}
@Override
public void purgeMap(String mapId) throws IOException {
public void purgeMap(String mapId, Function<ProgressInfo, Boolean> onProgress) throws IOException {
try {
recoveringConnection(connection -> {
executeUpdate(connection,
@ -395,12 +396,29 @@ public void purgeMap(String mapId) throws IOException {
"WHERE m.`map_id` = ?",
mapId
);
executeUpdate(connection,
"DELETE t " +
"FROM `bluemap_map`" +
"WHERE `map_id` = ?",
mapId
);
}, 2);
} catch (SQLException ex) {
throw new IOException(ex);
}
}
@Override
public Collection<String> collectMapIds() throws IOException {
return Collections.emptyList(); //TODO
}
@Override
public long estimateMapSize(String mapId) throws IOException {
return 0; //TODO
}
@SuppressWarnings("UnusedAssignment")
public void initialize() throws IOException {
try {

View File

@ -0,0 +1,24 @@
package de.bluecolored.bluemap.core.util;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.atomic.AtomicLong;
public class SizeCollectingPathVisitor extends SimpleFileVisitor<Path> {
private final AtomicLong size = new AtomicLong(0);
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
size.addAndGet(attrs.size());
return FileVisitResult.CONTINUE;
}
public long getSize() {
return size.get();
}
}