World and datapacks management
This commit is contained in:
@@ -55,7 +55,10 @@ public class DefaultCacheRepository extends CacheRepository {
|
||||
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
index = Constants.GSON.fromJson(FileUtils.readText(indexFile.toFile()), Index.class);
|
||||
if (Files.isRegularFile(indexFile))
|
||||
index = Constants.GSON.fromJson(FileUtils.readText(indexFile.toFile()), Index.class);
|
||||
else
|
||||
index = new Index();
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to read index file", e);
|
||||
index = new Index();
|
||||
|
||||
@@ -1,4 +1,162 @@
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.github.steveice10.opennbt.NBTIO;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.LongTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import org.jackhuang.hmcl.util.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Unzipper;
|
||||
import org.jackhuang.hmcl.util.Zipper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
public class World {
|
||||
|
||||
private final Path file;
|
||||
private String fileName;
|
||||
private String worldName;
|
||||
private String gameVersion;
|
||||
private long lastPlayed, seed;
|
||||
|
||||
public World(Path file) throws IOException {
|
||||
this.file = file;
|
||||
|
||||
if (Files.isDirectory(file))
|
||||
loadFromDirectory();
|
||||
else if (Files.isRegularFile(file))
|
||||
loadFromZip();
|
||||
else
|
||||
throw new IOException("Path " + file + " cannot be recognized as a Minecraft world");
|
||||
}
|
||||
|
||||
private void loadFromDirectory() throws IOException {
|
||||
fileName = FileUtils.getName(file);
|
||||
Path levelDat = file.resolve("level.dat");
|
||||
getWorldName(levelDat);
|
||||
}
|
||||
|
||||
public Path getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public String getWorldName() {
|
||||
return worldName;
|
||||
}
|
||||
|
||||
public long getLastPlayed() {
|
||||
return lastPlayed;
|
||||
}
|
||||
|
||||
public long getSeed() {
|
||||
return seed;
|
||||
}
|
||||
|
||||
public String getGameVersion() {
|
||||
return gameVersion;
|
||||
}
|
||||
|
||||
private void loadFromZip() throws IOException {
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(file)) {
|
||||
Path root = Files.list(fs.getPath("/")).filter(Files::isDirectory).findAny()
|
||||
.orElseThrow(() -> new IOException("Not a valid world zip file"));
|
||||
|
||||
Path levelDat = root.resolve("level.dat");
|
||||
if (!Files.exists(levelDat))
|
||||
throw new IllegalArgumentException("Not a valid world zip file since level.dat cannot be found.");
|
||||
|
||||
fileName = FileUtils.getName(root);
|
||||
getWorldName(levelDat);
|
||||
}
|
||||
}
|
||||
|
||||
private void getWorldName(Path levelDat) throws IOException {
|
||||
CompoundTag nbt = parseLevelDat(levelDat);
|
||||
|
||||
CompoundTag data = nbt.get("Data");
|
||||
String name = data.<StringTag>get("LevelName").getValue();
|
||||
lastPlayed = data.<LongTag>get("LastPlayed").getValue();
|
||||
seed = data.<LongTag>get("RandomSeed").getValue();
|
||||
CompoundTag version = data.get("Version");
|
||||
gameVersion = version.<StringTag>get("Name").getValue();
|
||||
worldName = name;
|
||||
}
|
||||
|
||||
public void rename(String newName) throws IOException {
|
||||
if (!Files.isDirectory(file))
|
||||
throw new IOException("Not a valid world directory");
|
||||
|
||||
// Change the name recorded in level.dat
|
||||
Path levelDat = file.resolve("level.dat");
|
||||
CompoundTag nbt = parseLevelDat(levelDat);
|
||||
CompoundTag data = nbt.get("Data");
|
||||
data.put(new StringTag("LevelName", newName));
|
||||
|
||||
NBTIO.writeTag(new GZIPOutputStream(Files.newOutputStream(levelDat)), nbt);
|
||||
|
||||
// then change the folder's name
|
||||
Files.move(file, file.resolveSibling(newName));
|
||||
}
|
||||
|
||||
public void install(Path savesDir, String name) throws IOException {
|
||||
Path worldDir = savesDir.resolve(name);
|
||||
if (Files.isDirectory(worldDir)) {
|
||||
throw new FileAlreadyExistsException("World already exists");
|
||||
}
|
||||
|
||||
if (Files.isRegularFile(file)) {
|
||||
String subDirectoryName;
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(file)) {
|
||||
List<Path> subDirs = Files.list(fs.getPath("/")).collect(Collectors.toList());
|
||||
if (subDirs.size() != 1) {
|
||||
throw new IOException("World zip malformed");
|
||||
}
|
||||
subDirectoryName = FileUtils.getName(subDirs.get(0));
|
||||
}
|
||||
new Unzipper(file, savesDir)
|
||||
.setSubDirectory("/" + subDirectoryName + "/")
|
||||
.unzip();
|
||||
} else if (Files.isDirectory(file)) {
|
||||
FileUtils.copyDirectory(file, worldDir);
|
||||
}
|
||||
}
|
||||
|
||||
public void export(Path zip, String worldName) throws IOException {
|
||||
if (!Files.isDirectory(file))
|
||||
throw new IOException();
|
||||
|
||||
try (Zipper zipper = new Zipper(zip)) {
|
||||
zipper.putDirectory(file, "/" + worldName + "/");
|
||||
}
|
||||
}
|
||||
|
||||
private static CompoundTag parseLevelDat(Path path) throws IOException {
|
||||
Tag nbt = NBTIO.readTag(new GZIPInputStream(Files.newInputStream(path)));
|
||||
if (nbt instanceof CompoundTag)
|
||||
return (CompoundTag) nbt;
|
||||
else
|
||||
throw new IOException("level.dat malformed");
|
||||
}
|
||||
|
||||
public static List<World> getWorlds(Path worldDir) throws IOException {
|
||||
List<World> worlds = new ArrayList<>();
|
||||
for (Path world : Files.newDirectoryStream(worldDir)) {
|
||||
worlds.add(new World(world));
|
||||
}
|
||||
return worlds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ package org.jackhuang.hmcl.mod;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -11,25 +14,21 @@ import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class Datapack {
|
||||
private final Path path;
|
||||
private final List<Pack> info;
|
||||
private final ObservableList<Pack> info = FXCollections.observableArrayList();
|
||||
|
||||
private Datapack(Path path, List<Pack> info) {
|
||||
public Datapack(Path path) {
|
||||
this.path = path;
|
||||
this.info = Collections.unmodifiableList(info);
|
||||
|
||||
for (Pack pack : info) {
|
||||
pack.datapack = this;
|
||||
}
|
||||
}
|
||||
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public List<Pack> getInfo() {
|
||||
public ObservableList<Pack> getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -39,40 +38,53 @@ public class Datapack {
|
||||
Set<String> packs = new HashSet<>();
|
||||
for (Pack pack : info) packs.add(pack.getId());
|
||||
|
||||
for (Path datapack : Files.newDirectoryStream(datapacks)) {
|
||||
if (packs.contains(FileUtils.getName(datapack)))
|
||||
FileUtils.deleteDirectory(datapack.toFile());
|
||||
}
|
||||
if (Files.isDirectory(datapacks))
|
||||
for (Path datapack : Files.newDirectoryStream(datapacks)) {
|
||||
if (packs.contains(FileUtils.getName(datapack)))
|
||||
FileUtils.deleteDirectory(datapack.toFile());
|
||||
}
|
||||
|
||||
new Unzipper(path, worldPath).setReplaceExistentFile(true).unzip();
|
||||
}
|
||||
|
||||
public static Datapack fromZip(Path path) throws IOException {
|
||||
public void deletePack(String pack) throws IOException {
|
||||
FileUtils.deleteDirectory(path.resolve(pack).toFile());
|
||||
Platform.runLater(() -> info.removeIf(p -> p.getId().equals(pack)));
|
||||
}
|
||||
|
||||
public void loadFromZip() throws IOException {
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(path)) {
|
||||
Datapack datapack = fromDir(fs.getPath("/datapacks/"));
|
||||
return new Datapack(path, datapack.info);
|
||||
loadFromDir(fs.getPath("/datapacks/"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param dir
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static Datapack fromDir(Path dir) throws IOException {
|
||||
List<Pack> info = new LinkedList<>();
|
||||
|
||||
for (Path subDir : Files.newDirectoryStream(dir)) {
|
||||
Path mcmeta = subDir.resolve("pack.mcmeta");
|
||||
|
||||
if (!Files.exists(mcmeta))
|
||||
continue;
|
||||
|
||||
PackMcMeta pack = JsonUtils.fromNonNullJson(FileUtils.readText(mcmeta), PackMcMeta.class);
|
||||
info.add(new Pack(mcmeta, FileUtils.getName(subDir), pack.getPackInfo().getDescription()));
|
||||
public void loadFromDir() {
|
||||
try {
|
||||
loadFromDir(path);
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to read datapacks " + path, e);
|
||||
}
|
||||
return new Datapack(dir, info);
|
||||
}
|
||||
|
||||
private void loadFromDir(Path dir) throws IOException {
|
||||
List<Pack> info = new ArrayList<>();
|
||||
|
||||
if (Files.isDirectory(dir))
|
||||
for (Path subDir : Files.newDirectoryStream(dir)) {
|
||||
Path mcmeta = subDir.resolve("pack.mcmeta");
|
||||
|
||||
if (!Files.exists(mcmeta))
|
||||
continue;
|
||||
|
||||
try {
|
||||
PackMcMeta pack = JsonUtils.fromNonNullJson(FileUtils.readText(mcmeta), PackMcMeta.class);
|
||||
info.add(new Pack(mcmeta, FileUtils.getName(subDir), pack.getPackInfo().getDescription(), this));
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to read datapack " + subDir, e);
|
||||
}
|
||||
}
|
||||
|
||||
this.info.setAll(info);
|
||||
}
|
||||
|
||||
public static class Pack {
|
||||
@@ -80,12 +92,13 @@ public class Datapack {
|
||||
private final BooleanProperty active;
|
||||
private final String id;
|
||||
private final String description;
|
||||
private Datapack datapack;
|
||||
private final Datapack datapack;
|
||||
|
||||
public Pack(Path packMcMeta, String id, String description) {
|
||||
public Pack(Path packMcMeta, String id, String description, Datapack datapack) {
|
||||
this.packMcMeta = packMcMeta;
|
||||
this.id = id;
|
||||
this.description = description;
|
||||
this.datapack = datapack;
|
||||
|
||||
active = new SimpleBooleanProperty(this, "active", !DISABLED_EXT.equals(FileUtils.getExtension(packMcMeta))) {
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user