diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/RenderManagerImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/RenderManagerImpl.java index 6606a1a6..69a15334 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/RenderManagerImpl.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/RenderManagerImpl.java @@ -61,7 +61,7 @@ public class RenderManagerImpl implements RenderManager { @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 diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java index 10373ef7..854dc7fe 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java @@ -796,7 +796,7 @@ public class Commands { 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 class Commands { } 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); } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java index 7d27c012..bdd25283 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java @@ -26,191 +26,78 @@ package de.bluecolored.bluemap.common.rendermanager; 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 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 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(); - } - } -} \ No newline at end of file + @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(); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Storage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Storage.java index 61355a2b..0152c141 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Storage.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Storage.java @@ -30,7 +30,9 @@ import java.io.Closeable; 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 onProgress) throws IOException; + + public abstract Collection 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 abstract class Storage implements Closeable { } + 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("..", "_."); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileStorage.java index e06b4728..de1daaba 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileStorage.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileStorage.java @@ -27,16 +27,18 @@ package de.bluecolored.bluemap.core.storage.file; 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 class FileStorage extends Storage { } @Override - public void purgeMap(String mapId) throws IOException { - Files.walkFileTree(getFilePath(mapId), DeletingPathVisitor.INSTANCE); + public void purgeMap(String mapId, Function onProgress) throws IOException { + final Path directory = getFilePath(mapId); + final int subFilesCount; + final LinkedList subFiles; + + // collect sub-files to be able to provide progress-updates + try (Stream 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 collectMapIds() throws IOException { + try (Stream 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){ diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLStorage.java index 49445221..d80b084e 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLStorage.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLStorage.java @@ -46,6 +46,7 @@ import java.sql.*; 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 class SQLStorage extends Storage { } @Override - public void purgeMap(String mapId) throws IOException { + public void purgeMap(String mapId, Function onProgress) throws IOException { try { recoveringConnection(connection -> { executeUpdate(connection, @@ -395,12 +396,29 @@ public class SQLStorage extends Storage { "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 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 { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/SizeCollectingPathVisitor.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/SizeCollectingPathVisitor.java new file mode 100644 index 00000000..3f470b5c --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/SizeCollectingPathVisitor.java @@ -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 { + + 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(); + } + +}