Datapack resolving

This commit is contained in:
Yuhui Huang 2018-09-14 15:44:40 +08:00
parent 2bfc85b18f
commit 1753b4d27e
13 changed files with 434 additions and 7 deletions

View File

@ -0,0 +1,10 @@
package org.jackhuang.hmcl.ui.versions;
import org.jackhuang.hmcl.ui.ListPage;
public class DatapackList extends ListPage<DatapackListItem> {
@Override
public void add() {
}
}

View File

@ -0,0 +1,46 @@
package org.jackhuang.hmcl.ui.versions;
import com.jfoenix.concurrency.JFXUtilities;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXCheckBox;
import com.jfoenix.effects.JFXDepthManager;
import javafx.geometry.Pos;
import javafx.scene.layout.BorderPane;
import org.jackhuang.hmcl.mod.Datapack;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import java.util.function.Consumer;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class DatapackListItem extends BorderPane {
public DatapackListItem(Datapack root, Datapack.Pack info, Consumer<DatapackListItem> deleteCallback) {
JFXCheckBox chkEnabled = new JFXCheckBox();
BorderPane.setAlignment(chkEnabled, Pos.CENTER);
setLeft(chkEnabled);
TwoLineListItem modItem = new TwoLineListItem();
BorderPane.setAlignment(modItem, Pos.CENTER);
setCenter(modItem);
JFXButton btnRemove = new JFXButton();
JFXUtilities.runInFX(() -> {
FXUtils.installTooltip(btnRemove, i18n("mods.remove"));
});
btnRemove.setOnMouseClicked(e -> deleteCallback.accept(this));
btnRemove.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnRemove, Pos.CENTER);
btnRemove.setGraphic(SVG.close(Theme.blackFillBinding(), 15, 15));
setRight(btnRemove);
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
JFXDepthManager.setDepth(this, 1);
modItem.setTitle(info.getId());
modItem.setSubtitle(info.getDescription());
chkEnabled.selectedProperty().bindBidirectional(info.activeProperty());
}
}

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
package org.jackhuang.hmcl.ui.versions;
import com.jfoenix.concurrency.JFXUtilities;
import com.jfoenix.controls.JFXButton;
@ -25,6 +25,8 @@ import javafx.geometry.Pos;
import javafx.scene.layout.BorderPane;
import org.jackhuang.hmcl.mod.ModInfo;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.util.StringUtils;

View File

@ -28,7 +28,6 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.ListPage;
import org.jackhuang.hmcl.ui.ModItem;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Logging;

View File

@ -0,0 +1,12 @@
package org.jackhuang.hmcl.ui.versions;
import org.jackhuang.hmcl.ui.ListPage;
public class WorldList extends ListPage<WorldListItem> {
@Override
public void add() {
// Not adding world here.
}
}

View File

@ -0,0 +1,56 @@
package org.jackhuang.hmcl.ui.versions;
import javafx.beans.property.*;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.image.Image;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.ui.Controllers;
import java.io.File;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class WorldListItem extends Control {
private final StringProperty title = new SimpleStringProperty();
private final StringProperty subtitle = new SimpleStringProperty();
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
private final World world;
public WorldListItem(World world) {
this.world = world;
}
@Override
protected Skin<?> createDefaultSkin() {
return new WorldListItemSkin(this);
}
public StringProperty titleProperty() {
return title;
}
public StringProperty subtitleProperty() {
return subtitle;
}
public ObjectProperty<Image> imageProperty() {
return image;
}
public void export() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle(i18n("world.export.title"));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("world"), "*.zip"));
File file = fileChooser.showSaveDialog(Controllers.getStage());
if (file == null) {
return;
}
}
public void manageDatapacks() {
}
}

View File

@ -0,0 +1,90 @@
package org.jackhuang.hmcl.ui.versions;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXPopup;
import com.jfoenix.effects.JFXDepthManager;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.SkinBase;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import java.util.function.Function;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class WorldListItemSkin extends SkinBase<WorldListItem> {
public WorldListItemSkin(WorldListItem skinnable) {
super(skinnable);
BorderPane root = new BorderPane();
HBox center = new HBox();
center.setSpacing(8);
center.setAlignment(Pos.CENTER_LEFT);
StackPane imageViewContainer = new StackPane();
FXUtils.setLimitWidth(imageViewContainer, 32);
FXUtils.setLimitHeight(imageViewContainer, 32);
ImageView imageView = new ImageView();
FXUtils.limitSize(imageView, 32, 32);
imageView.imageProperty().bind(skinnable.imageProperty());
imageViewContainer.getChildren().setAll(imageView);
TwoLineListItem item = new TwoLineListItem();
BorderPane.setAlignment(item, Pos.CENTER);
center.getChildren().setAll(imageView, item);
root.setCenter(center);
VBox menu = new VBox();
JFXPopup popup = new JFXPopup(menu);
Function<Runnable, Runnable> wrap = r -> () -> {
r.run();
popup.hide();
};
Function<Node, Node> limitWidth = node -> {
StackPane pane = new StackPane(node);
pane.setAlignment(Pos.CENTER);
FXUtils.setLimitWidth(pane, 14);
FXUtils.setLimitHeight(pane, 14);
return pane;
};
menu.getChildren().setAll(
new IconedMenuItem(limitWidth.apply(SVG.gear(Theme.blackFillBinding(), 14, 14)), i18n("world.datapack"), wrap.apply(skinnable::manageDatapacks)),
new IconedMenuItem(limitWidth.apply(SVG.export(Theme.blackFillBinding(), 14, 14)), i18n("world.export"), wrap.apply(skinnable::export)));
HBox right = new HBox();
right.setAlignment(Pos.CENTER_RIGHT);
JFXButton btnManage = new JFXButton();
btnManage.setOnMouseClicked(e -> {
popup.show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight());
});
btnManage.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnManage, Pos.CENTER);
btnManage.setGraphic(SVG.dotsVertical(Theme.blackFillBinding(), -1, -1));
right.getChildren().add(btnManage);
root.setRight(right);
root.setStyle("-fx-background-color: white; -fx-padding: 8 8 8 0;");
JFXDepthManager.setDepth(root, 1);
item.titleProperty().bind(skinnable.titleProperty());
item.subtitleProperty().bind(skinnable.subtitleProperty());
getChildren().setAll(root);
}
}

View File

@ -240,6 +240,15 @@ mods.add.success=Successfully added mods %s.
mods.choose_mod=Choose your mods
mods.remove=Remove
datapack=Data packs
datapack.add=Add data pack
datapack.remove=Remove
world=Worlds
world.datapack=Manage data packs
world.export=Export this world
world.export.title=Choose a file location to hold your world
profile=Game Directories
profile.default=Current directory
profile.home=User home

View File

@ -15,6 +15,7 @@ import org.tukaani.xz.XZInputStream;
import java.io.*;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.jar.JarEntry;
@ -75,7 +76,7 @@ public class LibraryDownloadTask extends Task {
else
throw new LibraryDownloadException(library, t);
} else {
if (xz) unpackLibrary(jar, FileUtils.readBytes(xzFile));
if (xz) unpackLibrary(jar, Files.readAllBytes(xzFile.toPath()));
if (!checksumValid(jar, library.getChecksums())) {
jar.delete();
throw new IOException("Checksum failed for " + library);
@ -125,7 +126,7 @@ public class LibraryDownloadTask extends Task {
if (checksums == null || checksums.isEmpty()) {
return true;
}
byte[] fileData = FileUtils.readBytes(libPath);
byte[] fileData = Files.readAllBytes(libPath.toPath());
boolean valid = checksums.contains(encodeHex(digest("SHA-1", fileData)));
if (!valid && libPath.getName().endsWith(".jar")) {
valid = validateJar(fileData, checksums);

View File

@ -0,0 +1,4 @@
package org.jackhuang.hmcl.game;
public class World {
}

View File

@ -0,0 +1,185 @@
package org.jackhuang.hmcl.mod;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import org.jackhuang.hmcl.util.*;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
public class Datapack {
private final Path path;
private final List<Pack> info;
private Datapack(Path path, List<Pack> info) {
this.path = path;
this.info = Collections.unmodifiableList(info);
for (Pack pack : info) {
pack.datapack = this;
}
}
public Path getPath() {
return path;
}
public List<Pack> getInfo() {
return info;
}
public void installTo(Path worldPath) throws IOException {
Path datapacks = worldPath.resolve("datapacks");
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());
}
new Unzipper(path, worldPath).setReplaceExistentFile(true).unzip();
}
public static Datapack fromZip(Path path) throws IOException {
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(path)) {
Datapack datapack = fromDir(fs.getPath("/datapacks/"));
return new Datapack(path, datapack.info);
}
}
/**
*
* @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()));
}
return new Datapack(dir, info);
}
public static class Pack {
private Path packMcMeta;
private final BooleanProperty active;
private final String id;
private final String description;
private Datapack datapack;
public Pack(Path packMcMeta, String id, String description) {
this.packMcMeta = packMcMeta;
this.id = id;
this.description = description;
active = new SimpleBooleanProperty(this, "active", !DISABLED_EXT.equals(FileUtils.getExtension(packMcMeta))) {
@Override
protected void invalidated() {
Path f = Pack.this.packMcMeta.toAbsolutePath(), newF;
if (DISABLED_EXT.equals(FileUtils.getExtension(f)))
newF = f.getParent().resolve(FileUtils.getNameWithoutExtension(f));
else
newF = f.getParent().resolve(FileUtils.getName(f) + DISABLED_EXT);
try {
Files.move(f, newF);
Pack.this.packMcMeta = newF;
} catch (IOException e) {
// Mod file is occupied.
Logging.LOG.warning("Unable to rename file " + f + " to " + newF);
}
}
};
}
public String getId() {
return id;
}
public String getDescription() {
return description;
}
public Datapack getDatapack() {
return datapack;
}
public BooleanProperty activeProperty() {
return active;
}
public boolean isActive() {
return active.get();
}
public void setActive(boolean active) {
this.active.set(active);
}
}
private static class PackMcMeta implements Validation {
@SerializedName("pack")
private final PackInfo pack;
public PackMcMeta() {
this(new PackInfo());
}
public PackMcMeta(PackInfo packInfo) {
this.pack = packInfo;
}
public PackInfo getPackInfo() {
return pack;
}
@Override
public void validate() throws JsonParseException {
if (pack == null)
throw new JsonParseException("pack cannot be null");
}
public static class PackInfo {
@SerializedName("pack_format")
private final int packFormat;
@SerializedName("description")
private final String description;
public PackInfo() {
this(0, "");
}
public PackInfo(int packFormat, String description) {
this.packFormat = packFormat;
this.description = description;
}
public int getPackFormat() {
return packFormat;
}
public String getDescription() {
return description;
}
}
}
private static final String DISABLED_EXT = ".disabled";
}

View File

@ -43,10 +43,18 @@ public final class FileUtils {
return StringUtils.substringBeforeLast(file.getName(), '.');
}
public static String getNameWithoutExtension(Path file) {
return StringUtils.substringBeforeLast(getName(file), '.');
}
public static String getExtension(File file) {
return StringUtils.substringAfterLast(file.getName(), '.');
}
public static String getExtension(Path file) {
return StringUtils.substringAfterLast(getName(file), '.');
}
/**
* This method is for normalizing ZipPath since Path.normalize of ZipFileSystem does not work properly.
*/
@ -63,11 +71,15 @@ public final class FileUtils {
}
public static String readText(File file, Charset charset) throws IOException {
return new String(readBytes(file), charset);
return new String(Files.readAllBytes(file.toPath()), charset);
}
public static byte[] readBytes(File file) throws IOException {
return Files.readAllBytes(file.toPath());
public static String readText(Path file) throws IOException {
return readText(file, UTF_8);
}
public static String readText(Path file, Charset charset) throws IOException {
return new String(Files.readAllBytes(file), charset);
}
public static void writeText(File file, String text) throws IOException {

View File

@ -45,6 +45,7 @@ subprojects {
compile group: 'org.tukaani', name: 'xz', version: '1.8'
compile group: 'org.hildan.fxgson', name: 'fx-gson', version: '3.1.0'
compile group: 'org.jenkins-ci', name: 'constant-pool-scanner', version: '1.2'
compile group: 'org.spacehq', name: 'opennbt', version: '1.0'
testCompile group: 'junit', name: 'junit', version: '4.12'
}