2021-11-06 16:14:14 +01:00
|
|
|
/*
|
|
|
|
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
|
|
|
*
|
|
|
|
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
|
|
|
* Copyright (c) contributors
|
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
* furnished to do so, subject to the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice shall be included in
|
|
|
|
* all copies or substantial portions of the Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
* THE SOFTWARE.
|
|
|
|
*/
|
2021-11-14 14:18:31 +01:00
|
|
|
package de.bluecolored.bluemap.core.storage.file;
|
2021-11-06 16:14:14 +01:00
|
|
|
|
|
|
|
import com.flowpowered.math.vector.Vector2i;
|
2022-07-24 12:10:00 +02:00
|
|
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
2021-11-14 14:18:31 +01:00
|
|
|
import de.bluecolored.bluemap.core.storage.*;
|
2022-05-28 21:55:41 +02:00
|
|
|
import de.bluecolored.bluemap.core.util.DeletingPathVisitor;
|
2023-03-04 12:40:12 +01:00
|
|
|
import de.bluecolored.bluemap.core.util.FileHelper;
|
2021-11-06 16:14:14 +01:00
|
|
|
|
|
|
|
import java.io.*;
|
|
|
|
import java.nio.file.Files;
|
|
|
|
import java.nio.file.Path;
|
|
|
|
import java.nio.file.StandardOpenOption;
|
2023-03-04 12:40:12 +01:00
|
|
|
import java.util.*;
|
|
|
|
import java.util.function.Function;
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
import java.util.stream.Stream;
|
2021-11-06 16:14:14 +01:00
|
|
|
|
|
|
|
@DebugDump
|
|
|
|
public class FileStorage extends Storage {
|
|
|
|
|
|
|
|
private final Path root;
|
2022-08-07 20:32:55 +02:00
|
|
|
private final Compression hiresCompression;
|
2021-11-06 16:14:14 +01:00
|
|
|
|
2022-04-20 22:54:27 +02:00
|
|
|
public FileStorage(FileStorageSettings config) {
|
2021-11-14 14:18:31 +01:00
|
|
|
this.root = config.getRoot();
|
2022-08-07 20:32:55 +02:00
|
|
|
this.hiresCompression = config.getCompression();
|
2021-11-14 14:18:31 +01:00
|
|
|
}
|
|
|
|
|
2021-11-06 16:14:14 +01:00
|
|
|
public FileStorage(Path root, Compression compression) {
|
|
|
|
this.root = root;
|
2022-08-07 20:32:55 +02:00
|
|
|
this.hiresCompression = compression;
|
2021-11-06 16:14:14 +01:00
|
|
|
}
|
|
|
|
|
2021-11-14 14:18:31 +01:00
|
|
|
@Override
|
|
|
|
public void initialize() {}
|
|
|
|
|
2022-08-27 15:19:32 +02:00
|
|
|
@Override
|
|
|
|
public boolean isClosed() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-11-14 14:18:31 +01:00
|
|
|
@Override
|
|
|
|
public void close() throws IOException {}
|
|
|
|
|
2021-11-06 16:14:14 +01:00
|
|
|
@Override
|
2022-08-07 20:32:55 +02:00
|
|
|
public OutputStream writeMapTile(String mapId, int lod, Vector2i tile) throws IOException {
|
|
|
|
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
|
|
|
|
Path file = getFilePath(mapId, lod, tile);
|
2021-11-06 16:14:14 +01:00
|
|
|
|
2022-10-14 10:24:19 +02:00
|
|
|
OutputStream os = FileHelper.createFilepartOutputStream(file);
|
2021-11-06 16:14:14 +01:00
|
|
|
os = new BufferedOutputStream(os);
|
2022-01-09 15:54:06 +01:00
|
|
|
|
|
|
|
try {
|
|
|
|
os = compression.compress(os);
|
|
|
|
} catch (IOException ex) {
|
|
|
|
os.close();
|
|
|
|
throw ex;
|
|
|
|
}
|
2021-11-06 16:14:14 +01:00
|
|
|
|
|
|
|
return os;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-08-07 20:32:55 +02:00
|
|
|
public Optional<CompressedInputStream> readMapTile(String mapId, int lod, Vector2i tile) throws IOException {
|
|
|
|
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
|
|
|
|
Path file = getFilePath(mapId, lod, tile);
|
2021-11-06 16:14:14 +01:00
|
|
|
|
|
|
|
if (!Files.exists(file)) return Optional.empty();
|
|
|
|
|
|
|
|
InputStream is = Files.newInputStream(file, StandardOpenOption.READ);
|
|
|
|
is = new BufferedInputStream(is);
|
|
|
|
|
2021-11-14 14:18:31 +01:00
|
|
|
return Optional.of(new CompressedInputStream(is, compression));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-12-13 16:54:31 +01:00
|
|
|
public Optional<TileInfo> readMapTileInfo(String mapId, int lod, Vector2i tile) throws IOException {
|
2022-08-07 20:32:55 +02:00
|
|
|
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
|
|
|
|
Path file = getFilePath(mapId, lod, tile);
|
2021-11-14 14:18:31 +01:00
|
|
|
|
|
|
|
if (!Files.exists(file)) return Optional.empty();
|
|
|
|
|
|
|
|
final long size = Files.size(file);
|
|
|
|
final long lastModified = Files.getLastModifiedTime(file).toMillis();
|
|
|
|
|
2022-12-13 16:54:31 +01:00
|
|
|
return Optional.of(new TileInfo() {
|
2021-11-14 14:18:31 +01:00
|
|
|
@Override
|
|
|
|
public CompressedInputStream readMapTile() throws IOException {
|
2022-08-07 20:32:55 +02:00
|
|
|
return FileStorage.this.readMapTile(mapId, lod, tile)
|
2021-11-14 14:18:31 +01:00
|
|
|
.orElseThrow(() -> new IOException("Tile no longer present!"));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Compression getCompression() {
|
|
|
|
return compression;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public long getSize() {
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public long getLastModified() {
|
|
|
|
return lastModified;
|
|
|
|
}
|
|
|
|
});
|
2021-11-06 16:14:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-08-07 20:32:55 +02:00
|
|
|
public void deleteMapTile(String mapId, int lod, Vector2i tile) throws IOException {
|
|
|
|
Path file = getFilePath(mapId, lod, tile);
|
2022-05-28 21:55:41 +02:00
|
|
|
Files.deleteIfExists(file);
|
2021-11-06 16:14:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-12-13 16:54:31 +01:00
|
|
|
public OutputStream writeMeta(String mapId, String name) throws IOException {
|
|
|
|
Path file = getMetaFilePath(mapId, name);
|
2021-11-06 16:14:14 +01:00
|
|
|
|
2022-10-14 10:24:19 +02:00
|
|
|
OutputStream os = FileHelper.createFilepartOutputStream(file);
|
2021-11-06 16:14:14 +01:00
|
|
|
os = new BufferedOutputStream(os);
|
|
|
|
|
|
|
|
return os;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-12-13 16:54:31 +01:00
|
|
|
public Optional<InputStream> readMeta(String mapId, String name) throws IOException {
|
|
|
|
Path file = getMetaFilePath(mapId, name);
|
2021-11-06 16:14:14 +01:00
|
|
|
|
|
|
|
if (!Files.exists(file)) return Optional.empty();
|
|
|
|
|
|
|
|
InputStream is = Files.newInputStream(file, StandardOpenOption.READ);
|
|
|
|
is = new BufferedInputStream(is);
|
|
|
|
|
2022-12-13 16:54:31 +01:00
|
|
|
return Optional.of(is);
|
2021-11-14 14:18:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-12-13 16:54:31 +01:00
|
|
|
public Optional<MetaInfo> readMetaInfo(String mapId, String name) throws IOException {
|
|
|
|
Path file = getMetaFilePath(mapId, name);
|
|
|
|
|
|
|
|
if (!Files.exists(file)) return Optional.empty();
|
|
|
|
|
|
|
|
final long size = Files.size(file);
|
|
|
|
|
|
|
|
return Optional.of(new MetaInfo() {
|
|
|
|
@Override
|
|
|
|
public InputStream readMeta() throws IOException {
|
|
|
|
return FileStorage.this.readMeta(mapId, name)
|
|
|
|
.orElseThrow(() -> new IOException("Meta no longer present!"));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public long getSize() {
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void deleteMeta(String mapId, String name) throws IOException {
|
|
|
|
Path file = getMetaFilePath(mapId, name);
|
2022-05-28 21:55:41 +02:00
|
|
|
Files.deleteIfExists(file);
|
2021-11-06 16:14:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2023-03-04 12:40:12 +01:00
|
|
|
public void purgeMap(String mapId, Function<ProgressInfo, Boolean> onProgress) throws IOException {
|
|
|
|
final Path directory = getFilePath(mapId);
|
2023-11-18 16:32:05 +01:00
|
|
|
if (!Files.exists(directory)) return;
|
|
|
|
|
2023-03-04 12:40:12 +01:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-07 20:32:55 +02:00
|
|
|
public Path getFilePath(String mapId, int lod, Vector2i tile){
|
2021-11-06 16:14:14 +01:00
|
|
|
String path = "x" + tile.getX() + "z" + tile.getY();
|
|
|
|
char[] cs = path.toCharArray();
|
|
|
|
List<String> folders = new ArrayList<>();
|
|
|
|
StringBuilder folder = new StringBuilder();
|
|
|
|
for (char c : cs){
|
|
|
|
folder.append(c);
|
|
|
|
if (c >= '0' && c <= '9'){
|
|
|
|
folders.add(folder.toString());
|
|
|
|
folder.delete(0, folder.length());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
String fileName = folders.remove(folders.size() - 1);
|
|
|
|
|
2022-08-07 20:32:55 +02:00
|
|
|
Path p = getFilePath(mapId).resolve("tiles").resolve(Integer.toString(lod));
|
2021-11-06 16:14:14 +01:00
|
|
|
for (String s : folders){
|
|
|
|
p = p.resolve(s);
|
|
|
|
}
|
|
|
|
|
2022-08-07 20:32:55 +02:00
|
|
|
if (lod == 0) {
|
|
|
|
return p.resolve(fileName + ".json" + hiresCompression.getFileSuffix());
|
|
|
|
} else {
|
|
|
|
return p.resolve(fileName + ".png");
|
|
|
|
}
|
2021-11-06 16:14:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public Path getFilePath(String mapId) {
|
|
|
|
return root.resolve(mapId);
|
|
|
|
}
|
|
|
|
|
2022-12-13 16:54:31 +01:00
|
|
|
public Path getMetaFilePath(String mapId, String name) {
|
|
|
|
return getFilePath(mapId).resolve(escapeMetaName(name)
|
|
|
|
.replace("/", root.getFileSystem().getSeparator()));
|
|
|
|
}
|
|
|
|
|
2021-11-06 16:14:14 +01:00
|
|
|
}
|