diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXCheckBoxTreeTableCell.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXCheckBoxTreeTableCell.java new file mode 100644 index 000000000..a31a11116 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/JFXCheckBoxTreeTableCell.java @@ -0,0 +1,136 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2019 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.construct; + +import com.jfoenix.controls.JFXCheckBox; +import javafx.beans.binding.Bindings; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.geometry.Pos; +import javafx.scene.control.CheckBox; +import javafx.scene.control.TreeTableCell; +import javafx.util.Callback; +import javafx.util.StringConverter; + +public class JFXCheckBoxTreeTableCell extends TreeTableCell { + + private final CheckBox checkBox; + private boolean showLabel; + private ObservableValue booleanProperty; + + public JFXCheckBoxTreeTableCell() { + this(null, null); + } + + public JFXCheckBoxTreeTableCell( + final Callback> getSelectedProperty) { + this(getSelectedProperty, null); + } + + public JFXCheckBoxTreeTableCell( + final Callback> getSelectedProperty, + final StringConverter converter) { + this.getStyleClass().add("check-box-tree-table-cell"); + this.checkBox = new JFXCheckBox(); + setGraphic(null); + setSelectedStateCallback(getSelectedProperty); + setConverter(converter); + } + + private ObjectProperty> converter = + new SimpleObjectProperty>(this, "converter") { + protected void invalidated() { + updateShowLabel(); + } + }; + + public final ObjectProperty> converterProperty() { + return converter; + } + + public final void setConverter(StringConverter value) { + converterProperty().set(value); + } + + public final StringConverter getConverter() { + return converterProperty().get(); + } + + private ObjectProperty>> + selectedStateCallback = + new SimpleObjectProperty>>( + this, "selectedStateCallback"); + + public final ObjectProperty>> selectedStateCallbackProperty() { + return selectedStateCallback; + } + + public final void setSelectedStateCallback(Callback> value) { + selectedStateCallbackProperty().set(value); + } + + public final Callback> getSelectedStateCallback() { + return selectedStateCallbackProperty().get(); + } + + @SuppressWarnings("unchecked") + @Override + public void updateItem(T item, boolean empty) { + super.updateItem(item, empty); + + if (empty) { + setText(null); + setGraphic(null); + } else { + StringConverter c = getConverter(); + + if (showLabel) { + setText(c.toString(item)); + } + setGraphic(checkBox); + + if (booleanProperty instanceof BooleanProperty) { + checkBox.selectedProperty().unbindBidirectional((BooleanProperty) booleanProperty); + } + ObservableValue obsValue = getSelectedProperty(); + if (obsValue instanceof BooleanProperty) { + booleanProperty = (ObservableValue) obsValue; + checkBox.selectedProperty().bindBidirectional((BooleanProperty) booleanProperty); + } + + checkBox.disableProperty().bind(Bindings.not( + getTreeTableView().editableProperty().and( + getTableColumn().editableProperty()).and( + editableProperty()) + )); + } + } + + private void updateShowLabel() { + this.showLabel = converter != null; + this.checkBox.setAlignment(showLabel ? Pos.CENTER_LEFT : Pos.CENTER); + } + + private ObservableValue getSelectedProperty() { + return getSelectedStateCallback() != null ? + getSelectedStateCallback().call(getIndex()) : + getTableColumn().getCellObservableValue(getIndex()); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdateTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModCheckUpdatesTask.java similarity index 85% rename from HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdateTask.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModCheckUpdatesTask.java index 98bbb3eee..46a26268d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdateTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModCheckUpdatesTask.java @@ -17,7 +17,7 @@ */ package org.jackhuang.hmcl.ui.versions; -import org.jackhuang.hmcl.mod.LocalMod; +import org.jackhuang.hmcl.mod.LocalModFile; import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository; import org.jackhuang.hmcl.task.Task; @@ -25,13 +25,13 @@ import java.util.Collection; import java.util.List; import java.util.stream.Collectors; -public class ModUpdateTask extends Task> { +public class ModCheckUpdatesTask extends Task> { private final String gameVersion; - private final Collection mods; - private final Collection> dependents; + private final Collection mods; + private final Collection> dependents; - public ModUpdateTask(String gameVersion, Collection mods) { + public ModCheckUpdatesTask(String gameVersion, Collection mods) { this.gameVersion = gameVersion; this.mods = mods; @@ -51,7 +51,7 @@ public class ModUpdateTask extends Task> { } @Override - public void preExecute() throws Exception { + public void preExecute() { notifyPropertiesChanged(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java index fd3085c16..62d49f4fe 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java @@ -17,7 +17,7 @@ */ package org.jackhuang.hmcl.ui.versions; -import org.jackhuang.hmcl.mod.LocalMod; +import org.jackhuang.hmcl.mod.LocalModFile; import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.mod.RemoteModRepository; import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository; @@ -87,7 +87,7 @@ public class ModDownloadListPage extends DownloadListPage { } @Override - public Optional getRemoteVersionByLocalFile(LocalMod localMod, Path file) { + public Optional getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) { throw new UnsupportedOperationException(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java index ceecebe5f..a9ca7a57b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java @@ -24,7 +24,7 @@ import javafx.collections.ObservableList; import javafx.scene.control.Skin; import javafx.stage.FileChooser; import org.jackhuang.hmcl.download.LibraryAnalyzer; -import org.jackhuang.hmcl.mod.LocalMod; +import org.jackhuang.hmcl.mod.LocalModFile; import org.jackhuang.hmcl.mod.ModManager; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.task.Schedulers; @@ -33,6 +33,7 @@ import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.ListPageBase; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; +import org.jackhuang.hmcl.ui.construct.PageAware; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.io.FileUtils; @@ -47,7 +48,7 @@ import java.util.stream.Collectors; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public final class ModListPage extends ListPageBase implements VersionPage.VersionLoadable { +public final class ModListPage extends ListPageBase implements VersionPage.VersionLoadable, PageAware { private final BooleanProperty modded = new SimpleBooleanProperty(this, "modded", false); private ModManager modManager; @@ -149,7 +150,7 @@ public final class ModListPage extends ListPageBase { Optional gameVersion = profile.getRepository().getGameVersion(versionId); if (gameVersion.isPresent()) { - return new ModUpdateTask(gameVersion.get(), modManager.getMods()); + return new ModCheckUpdatesTask(gameVersion.get(), modManager.getMods()); } return null; }) @@ -187,7 +188,7 @@ public final class ModListPage extends ListPageBase { static class ModInfoObject extends RecursiveTreeObject implements Comparable { private final BooleanProperty active; - private final LocalMod localMod; + private final LocalModFile localModFile; private final String message; private final ModTranslations.Mod mod; - ModInfoObject(LocalMod localMod) { - this.localMod = localMod; - this.active = localMod.activeProperty(); - StringBuilder message = new StringBuilder(localMod.getName()); - if (isNotBlank(localMod.getVersion())) - message.append(", ").append(i18n("archive.version")).append(": ").append(localMod.getVersion()); - if (isNotBlank(localMod.getGameVersion())) - message.append(", ").append(i18n("archive.game_version")).append(": ").append(localMod.getGameVersion()); - if (isNotBlank(localMod.getAuthors())) - message.append(", ").append(i18n("archive.author")).append(": ").append(localMod.getAuthors()); + ModInfoObject(LocalModFile localModFile) { + this.localModFile = localModFile; + this.active = localModFile.activeProperty(); + StringBuilder message = new StringBuilder(localModFile.getName()); + if (isNotBlank(localModFile.getVersion())) + message.append(", ").append(i18n("archive.version")).append(": ").append(localModFile.getVersion()); + if (isNotBlank(localModFile.getGameVersion())) + message.append(", ").append(i18n("archive.game_version")).append(": ").append(localModFile.getGameVersion()); + if (isNotBlank(localModFile.getAuthors())) + message.append(", ").append(i18n("archive.author")).append(": ").append(localModFile.getAuthors()); this.message = message.toString(); - this.mod = ModTranslations.getModById(localMod.getId()); + this.mod = ModTranslations.getModById(localModFile.getId()); } String getTitle() { - return localMod.getFileName(); + return localModFile.getFileName(); } String getSubtitle() { return message; } - LocalMod getModInfo() { - return localMod; + LocalModFile getModInfo() { + return localModFile; } public ModTranslations.Mod getMod() { @@ -162,7 +162,7 @@ class ModListPageSkin extends SkinBase { @Override public int compareTo(@NotNull ModListPageSkin.ModInfoObject o) { - return localMod.getFileName().toLowerCase().compareTo(o.localMod.getFileName().toLowerCase()); + return localModFile.getFileName().toLowerCase().compareTo(o.localModFile.getFileName().toLowerCase()); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java new file mode 100644 index 000000000..b6b8ca8e0 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java @@ -0,0 +1,294 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.versions; + +import com.jfoenix.controls.*; +import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; +import javafx.beans.property.*; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import org.jackhuang.hmcl.mod.LocalModFile; +import org.jackhuang.hmcl.mod.ModManager; +import org.jackhuang.hmcl.mod.RemoteMod; +import org.jackhuang.hmcl.mod.curse.CurseAddon; +import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.ui.Controllers; +import org.jackhuang.hmcl.ui.construct.JFXCheckBoxTreeTableCell; +import org.jackhuang.hmcl.ui.construct.MDListCell; +import org.jackhuang.hmcl.ui.construct.PageCloseEvent; +import org.jackhuang.hmcl.ui.construct.TwoLineListItem; +import org.jackhuang.hmcl.ui.decorator.DecoratorPage; +import org.jackhuang.hmcl.util.Pair; + +import java.util.Collection; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; +import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class ModUpdatesPage extends BorderPane implements DecoratorPage { + private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("mods.check_updates"), -1)); + + private final ModManager modManager; + private final ObservableList objects; + + public ModUpdatesPage(ModManager modManager, List updates) { + this.modManager = modManager; + + JFXTreeTableColumn enabledColumn = new JFXTreeTableColumn<>(); + enabledColumn.setCellFactory(column -> new JFXCheckBoxTreeTableCell<>()); + setupCellValueFactory(enabledColumn, ModUpdateObject::enabledProperty); + enabledColumn.setEditable(true); + enabledColumn.setMaxWidth(40); + enabledColumn.setMinWidth(40); + + JFXTreeTableColumn fileNameColumn = new JFXTreeTableColumn<>(i18n("mods.check_updates.file")); + setupCellValueFactory(fileNameColumn, ModUpdateObject::fileNameProperty); + + JFXTreeTableColumn currentVersionColumn = new JFXTreeTableColumn<>(i18n("mods.check_updates.current_version")); + setupCellValueFactory(currentVersionColumn, ModUpdateObject::currentVersionProperty); + + JFXTreeTableColumn targetVersionColumn = new JFXTreeTableColumn<>(i18n("mods.check_updates.target_version")); + setupCellValueFactory(targetVersionColumn, ModUpdateObject::targetVersionProperty); + + JFXTreeTableColumn sourceColumn = new JFXTreeTableColumn<>(i18n("mods.check_updates.source")); + setupCellValueFactory(sourceColumn, ModUpdateObject::sourceProperty); + + objects = FXCollections.observableList(updates.stream().map(ModUpdateObject::new).collect(Collectors.toList())); + + RecursiveTreeItem root = new RecursiveTreeItem<>( + objects, + RecursiveTreeObject::getChildren); + + JFXTreeTableView table = new JFXTreeTableView<>(root); + table.setShowRoot(false); + table.setEditable(true); + table.getColumns().setAll(enabledColumn, fileNameColumn, currentVersionColumn, targetVersionColumn, sourceColumn); + + setCenter(table); + + HBox actions = new HBox(8); + actions.setPadding(new Insets(8)); + actions.setAlignment(Pos.CENTER_RIGHT); + + JFXButton nextButton = new JFXButton(i18n("mods.check_updates.update")); + nextButton.getStyleClass().add("jfx-button-raised"); + nextButton.setButtonType(JFXButton.ButtonType.RAISED); + nextButton.setOnAction(e -> updateMods()); + + JFXButton cancelButton = new JFXButton(i18n("button.cancel")); + cancelButton.getStyleClass().add("jfx-button-raised"); + cancelButton.setButtonType(JFXButton.ButtonType.RAISED); + cancelButton.setOnAction(e -> fireEvent(new PageCloseEvent())); + onEscPressed(this, cancelButton::fire); + + actions.getChildren().setAll(nextButton, cancelButton); + setBottom(actions); + } + + private void setupCellValueFactory(JFXTreeTableColumn column, Function> mapper) { + column.setCellValueFactory(param -> { + if (column.validateValue(param)) + return mapper.apply(param.getValue().getValue()); + else + return column.getComputedValue(param); + }); + } + + private void updateMods() { + Controllers.taskDialog( + new ModUpdateTask( + modManager, + objects.stream() + .filter(o -> o.enabled.get()) + .map(object -> pair(object.data.getLocalMod(), object.data.getCandidates().get(0))) + .collect(Collectors.toList())), + i18n("mods.check_updates.update"), + t -> { + }); + } + + @Override + public ReadOnlyObjectWrapper stateProperty() { + return state; + } + + public static class ModUpdateCell extends MDListCell { + TwoLineListItem content = new TwoLineListItem(); + + public ModUpdateCell(JFXListView listView) { + super(listView); + + getContainer().getChildren().setAll(content); + } + + @Override + protected void updateControl(LocalModFile.ModUpdate item, boolean empty) { + if (empty) return; + ModTranslations.Mod mod = ModTranslations.getModById(item.getLocalMod().getId()); + content.setTitle(mod != null ? mod.getDisplayName() : item.getCurrentVersion().getName()); + content.setSubtitle(item.getLocalMod().getFileName()); + content.getTags().setAll(); + + if (item.getCurrentVersion().getSelf() instanceof CurseAddon.LatestFile) { + content.getTags().add("Curseforge"); + } else if (item.getCurrentVersion().getSelf() instanceof ModrinthRemoteModRepository.ModVersion) { + content.getTags().add("Modrinth"); + } + } + } + + private static class ModUpdateObject extends RecursiveTreeObject { + final LocalModFile.ModUpdate data; + final BooleanProperty enabled = new SimpleBooleanProperty(); + final StringProperty fileName = new SimpleStringProperty(); + final StringProperty currentVersion = new SimpleStringProperty(); + final StringProperty targetVersion = new SimpleStringProperty(); + final StringProperty source = new SimpleStringProperty(); + + public ModUpdateObject(LocalModFile.ModUpdate data) { + this.data = data; + + enabled.set(true); + fileName.set(data.getLocalMod().getFileName()); + currentVersion.set(data.getCurrentVersion().getVersion()); + targetVersion.set(data.getCandidates().get(0).getVersion()); + switch (data.getCurrentVersion().getSelf().getType()) { + case CURSEFORGE: + source.set(i18n("mods.curseforge")); + break; + case MODRINTH: + source.set(i18n("mods.modrinth")); + } + } + + public boolean isEnabled() { + return enabled.get(); + } + + public BooleanProperty enabledProperty() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled.set(enabled); + } + + public String getFileName() { + return fileName.get(); + } + + public StringProperty fileNameProperty() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName.set(fileName); + } + + public String getCurrentVersion() { + return currentVersion.get(); + } + + public StringProperty currentVersionProperty() { + return currentVersion; + } + + public void setCurrentVersion(String currentVersion) { + this.currentVersion.set(currentVersion); + } + + public String getTargetVersion() { + return targetVersion.get(); + } + + public StringProperty targetVersionProperty() { + return targetVersion; + } + + public void setTargetVersion(String targetVersion) { + this.targetVersion.set(targetVersion); + } + + public String getSource() { + return source.get(); + } + + public StringProperty sourceProperty() { + return source; + } + + public void setSource(String source) { + this.source.set(source); + } + } + + public static class ModUpdateTask extends Task { + private final Collection> dependents; + + ModUpdateTask(ModManager modManager, List> mods) { + setStage("mods.check_updates.update"); + getProperties().put("total", mods.size()); + + dependents = mods.stream() + .map(mod -> { + return Task + .supplyAsync(() -> { + return null; + }) + .setName(mod.getKey().getName()) + .setSignificance(TaskSignificance.MAJOR) + .withCounter("mods.check_updates.update"); + }) + .collect(Collectors.toList()); + } + + @Override + public Collection> getDependents() { + return dependents; + } + + @Override + public boolean doPreExecute() { + return true; + } + + @Override + public void preExecute() { + notifyPropertiesChanged(); + } + + @Override + public boolean isRelyingOnDependents() { + return false; + } + + @Override + public void execute() throws Exception { + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPane.java deleted file mode 100644 index a1c246057..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPane.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2021 huangyuhui and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.jackhuang.hmcl.ui.versions; - -import com.jfoenix.controls.*; -import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; -import javafx.beans.property.ReadOnlyObjectWrapper; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.geometry.Pos; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.HBox; -import org.jackhuang.hmcl.mod.LocalMod; -import org.jackhuang.hmcl.mod.curse.CurseAddon; -import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository; -import org.jackhuang.hmcl.ui.construct.MDListCell; -import org.jackhuang.hmcl.ui.construct.PageCloseEvent; -import org.jackhuang.hmcl.ui.construct.TwoLineListItem; -import org.jackhuang.hmcl.ui.decorator.DecoratorPage; - -import java.util.List; -import java.util.stream.Collectors; - -import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; - -public class ModUpdatesPane extends BorderPane implements DecoratorPage { - private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("download"), -1)); - - public ModUpdatesPane(List updates) { - - JFXTreeTableColumn fileNameColumn = new JFXTreeTableColumn<>(i18n("mods.check_updates.file")); - fileNameColumn.setCellValueFactory(data -> { - if (fileNameColumn.validateValue(data)) { - return data.getValue().getValue().fileName; - } else { - return fileNameColumn.getComputedValue(data); - } - }); - - JFXTreeTableColumn currentVersionColumn = new JFXTreeTableColumn<>(i18n("mods.check_updates.current_version")); - currentVersionColumn.setCellValueFactory(data -> { - if (currentVersionColumn.validateValue(data)) { - return data.getValue().getValue().currentVersion; - } else { - return currentVersionColumn.getComputedValue(data); - } - }); - - JFXTreeTableColumn targetVersionColumn = new JFXTreeTableColumn<>(i18n("mods.check_updates.target_version")); - targetVersionColumn.setCellValueFactory(data -> { - if (targetVersionColumn.validateValue(data)) { - return data.getValue().getValue().targetVersion; - } else { - return targetVersionColumn.getComputedValue(data); - } - }); - - ObservableList objects = FXCollections.observableList(updates.stream().map(ModUpdateObject::new).collect(Collectors.toList())); - - RecursiveTreeItem root = new RecursiveTreeItem<>( - objects, - RecursiveTreeObject::getChildren); - - JFXTreeTableView table = new JFXTreeTableView<>(root); - table.setShowRoot(false); - table.setEditable(true); - table.getColumns().setAll(fileNameColumn, currentVersionColumn, targetVersionColumn); - - setCenter(table); - - HBox actions = new HBox(8); - actions.setAlignment(Pos.CENTER_RIGHT); - - JFXButton nextButton = new JFXButton(); - nextButton.getStyleClass().add("jfx-button-raised"); - nextButton.setButtonType(JFXButton.ButtonType.RAISED); - nextButton.setOnAction(e -> updateMods()); - - JFXButton cancelButton = new JFXButton(); - cancelButton.getStyleClass().add("jfx-button-raised"); - cancelButton.setButtonType(JFXButton.ButtonType.RAISED); - cancelButton.setOnAction(e -> fireEvent(new PageCloseEvent())); - onEscPressed(this, cancelButton::fire); - - actions.getChildren().setAll(nextButton, cancelButton); - setBottom(actions); - } - - private void updateMods() { - - } - - @Override - public ReadOnlyObjectWrapper stateProperty() { - return state; - } - - public static class ModUpdateCell extends MDListCell { - TwoLineListItem content = new TwoLineListItem(); - - public ModUpdateCell(JFXListView listView) { - super(listView); - - getContainer().getChildren().setAll(content); - } - - @Override - protected void updateControl(LocalMod.ModUpdate item, boolean empty) { - if (empty) return; - ModTranslations.Mod mod = ModTranslations.getModById(item.getLocalMod().getId()); - content.setTitle(mod != null ? mod.getDisplayName() : item.getCurrentVersion().getName()); - content.setSubtitle(item.getLocalMod().getFileName()); - content.getTags().setAll(); - - if (item.getCurrentVersion().getSelf() instanceof CurseAddon.LatestFile) { - content.getTags().add("Curseforge"); - } else if (item.getCurrentVersion().getSelf() instanceof ModrinthRemoteModRepository.ModVersion) { - content.getTags().add("Modrinth"); - } - } - } - - private static class ModUpdateObject extends RecursiveTreeObject { - final LocalMod.ModUpdate data; - final StringProperty fileName = new SimpleStringProperty(); - final StringProperty currentVersion = new SimpleStringProperty(); - final StringProperty targetVersion = new SimpleStringProperty(); - - public ModUpdateObject(LocalMod.ModUpdate data) { - this.data = data; - - fileName.set(data.getLocalMod().getFileName()); - currentVersion.set(data.getCurrentVersion().getName()); - targetVersion.set(data.getCandidates().get(0).getName()); - } - } -} diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index b1e756744..f9fb34785 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -430,6 +430,7 @@ launch.advice.newer_java=Java 8 is recommended to make the game faster. For many launch.advice.not_enough_space=You have allocated too much memory, because the physical memory size is %dMB, your game may crash. Shall we continue launching? launch.advice.require_newer_java_version=Minecraft %1$s requires Java %2$s or later, are you willing to download one now? launch.advice.too_large_memory_for_32bit=You have allocated too much memory, because of your 32-Bit Java Runtime Environment, your game may crash. The maximum memory capacity for 32 bit systems is is 1024MB. Shall we continue launching? +launch.advice.vanilla_linux_java_8=For Linux x86-64, Minecraft 1.12.2 and below can only run on Java 8.\nJava 9 and above versions cannot load 32-bit native libraries like liblwjgl.so. launch.failed=Unable to launch launch.failed.cannot_create_jvm=Java virtual machine could not be created. Java arguments may cause issues. Please restart without JVM arguments. launch.failed.creating_process=Failed to create process. Check your Java path. @@ -581,6 +582,11 @@ mods.add.failed=Failed to install mods %s. mods.add.success=Successfully installed mods %s. mods.category=Category mods.check_updates=Check updates +mods.check_updates.current_version=Current +mods.check_updates.file=File +mods.check_updates.source=Source +mods.check_updates.target_version=Target +mods.check_updates.update=Update mods.choose_mod=Choose your mods mods.curseforge=CurseForge mods.dependencies=Dependencies diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index e13ab5786..a1f7cadc8 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -430,6 +430,7 @@ launch.advice.newer_java=偵測到您未使用 Java 8 及更新版本,Java 8 launch.advice.not_enough_space=您設定的記憶體大小過大,由於超過了系統記憶體大小 %dMB,所以可能影響遊戲體驗或無法啟動遊戲。是否繼續啟動? launch.advice.require_newer_java_version=Minecraft %1$s 僅能運行在 Java %2$s 或更高版本上,是否下載? launch.advice.too_large_memory_for_32bit=您設定的記憶體大小過大,由於可能超過了 32 位元 Java 的記憶體分配限制,所以可能無法啟動遊戲,請將記憶體調至低於 1024MB 的值。是否繼續啟動? +launch.advice.vanilla_linux_java_8=對於 Linux x86-64 平台,Minecraft 1.12.2 及以下版本僅支持 Java 8。\nJava 9+ 版本會無法載入 32 位的 liblwjgl.so。 launch.failed=啟動失敗 launch.failed.cannot_create_jvm=偵測到無法建立 Java 虛擬機,可能是 Java 參數有問題。可以在設定中開啟無參數模式啟動。 launch.failed.creating_process=啟動失敗,在建立新處理程式時發生錯誤。可能是 Java 路徑錯誤。 @@ -581,6 +582,11 @@ mods.add.failed=新增模組 %s 失敗。 mods.add.success=成功新增模組 %s。 mods.category=類別 mods.check_updates=檢查模組更新 +mods.check_updates.current_version=當前版本 +mods.check_updates.file=文件 +mods.check_updates.source=來源 +mods.check_updates.target_version=目標版本 +mods.check_updates.update=更新 mods.choose_mod=選擇模組 mods.curseforge=CurseForge mods.dependencies=前置 Mod diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index c541801b4..1d65b7c50 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -430,6 +430,7 @@ launch.advice.newer_java=检测到您未使用 Java 8 及更新版本,Java 8 launch.advice.not_enough_space=您设置的内存大小过大,由于超过了系统内存大小 %dMB,所以可能影响游戏体验或无法启动游戏。是否继续启动? launch.advice.require_newer_java_version=Minecraft %1$s 仅能运行在 Java %2$s 或更高版本上,是否下载? launch.advice.too_large_memory_for_32bit=您设置的内存大小过大,由于可能超过了 32 位 Java 的内存分配限制,所以可能无法启动游戏,请将内存调至 1024MB 或更小。是否继续启动? +launch.advice.vanilla_linux_java_8=对于 Linux x86-64 平台,Minecraft 1.12.2 及以下版本仅支持 Java 8。\nJava 9+ 版本会无法加载 32 位的 liblwjgl.so。 launch.failed=启动失败 launch.failed.cannot_create_jvm=截获到无法创建 Java 虚拟机,可能是 Java 参数有问题,可以在设置中开启无参数模式启动。 launch.failed.creating_process=启动失败,在创建新进程时发生错误,可能是 Java 路径错误。 @@ -583,7 +584,9 @@ mods.category=类别 mods.check_updates=检查模组更新 mods.check_updates.current_version=当前版本 mods.check_updates.file=文件 +mods.check_updates.source=来源 mods.check_updates.target_version=目标版本 +mods.check_updates.update=更新 mods.choose_mod=选择模组 mods.curseforge=CurseForge mods.dependencies=前置 Mod diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Datapack.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Datapack.java index ba9480a69..7ec30301c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Datapack.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Datapack.java @@ -202,10 +202,10 @@ public class Datapack { private Path file; private final BooleanProperty active; private final String id; - private final LocalMod.Description description; + private final LocalModFile.Description description; private final Datapack datapack; - public Pack(Path file, String id, LocalMod.Description description, Datapack datapack) { + public Pack(Path file, String id, LocalModFile.Description description, Datapack datapack) { this.file = file; this.id = id; this.description = description; @@ -235,7 +235,7 @@ public class Datapack { return id; } - public LocalMod.Description getDescription() { + public LocalModFile.Description getDescription() { return description; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/FabricModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/FabricModMetadata.java index 9bd39aac1..f28ab1c6f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/FabricModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/FabricModMetadata.java @@ -64,14 +64,14 @@ public final class FabricModMetadata { this.contact = contact; } - public static LocalMod fromFile(File modFile) throws IOException, JsonParseException { + public static LocalModFile fromFile(File modFile) throws IOException, JsonParseException { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile.toPath())) { Path mcmod = fs.getPath("fabric.mod.json"); if (Files.notExists(mcmod)) throw new IOException("File " + modFile + " is not a Fabric mod."); FabricModMetadata metadata = JsonUtils.fromNonNullJson(FileUtils.readText(mcmod), FabricModMetadata.class); String authors = metadata.authors == null ? "" : metadata.authors.stream().map(author -> author.name).collect(Collectors.joining(", ")); - return new LocalMod(modFile, ModLoaderType.FABRIC, metadata.id, metadata.name, new LocalMod.Description(metadata.description), + return new LocalModFile(modFile, ModLoaderType.FABRIC, metadata.id, metadata.name, new LocalModFile.Description(metadata.description), authors, metadata.version, "", metadata.contact != null ? metadata.contact.getOrDefault("homepage", "") : "", metadata.icon); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeNewModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeNewModMetadata.java index 15be9ba56..1245d3af8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeNewModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeNewModMetadata.java @@ -116,7 +116,7 @@ public final class ForgeNewModMetadata { } } - public static LocalMod fromFile(File modFile) throws IOException, JsonParseException { + public static LocalModFile fromFile(File modFile) throws IOException, JsonParseException { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile.toPath())) { Path modstoml = fs.getPath("META-INF/mods.toml"); if (Files.notExists(modstoml)) @@ -135,7 +135,7 @@ public final class ForgeNewModMetadata { LOG.log(Level.WARNING, "Failed to parse MANIFEST.MF in file " + modFile.getPath()); } } - return new LocalMod(modFile, ModLoaderType.FORGE, mod.getModId(), mod.getDisplayName(), new LocalMod.Description(mod.getDescription()), + return new LocalModFile(modFile, ModLoaderType.FORGE, mod.getModId(), mod.getDisplayName(), new LocalModFile.Description(mod.getDescription()), mod.getAuthors(), mod.getVersion().replace("${file.jarVersion}", jarVersion), "", mod.getDisplayURL(), metadata.getLogoFile()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeOldModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeOldModMetadata.java index 06e60318f..81680926a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeOldModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeOldModMetadata.java @@ -120,7 +120,7 @@ public final class ForgeOldModMetadata { return authors; } - public static LocalMod fromFile(File modFile) throws IOException, JsonParseException { + public static LocalModFile fromFile(File modFile) throws IOException, JsonParseException { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile.toPath())) { Path mcmod = fs.getPath("mcmod.info"); if (Files.notExists(mcmod)) @@ -138,7 +138,7 @@ public final class ForgeOldModMetadata { authors = String.join(", ", metadata.getAuthorList()); if (StringUtils.isBlank(authors)) authors = metadata.getCredits(); - return new LocalMod(modFile, ModLoaderType.FORGE, metadata.getModId(), metadata.getName(), new LocalMod.Description(metadata.getDescription()), + return new LocalModFile(modFile, ModLoaderType.FORGE, metadata.getModId(), metadata.getName(), new LocalModFile.Description(metadata.getDescription()), authors, metadata.getVersion(), metadata.getGameVersion(), StringUtils.isBlank(metadata.getUrl()) ? metadata.getUpdateUrl() : metadata.url, metadata.getLogoFile()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java index 982f318b2..98289ff11 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java @@ -108,7 +108,7 @@ public final class LiteModMetadata { return updateURI; } - public static LocalMod fromFile(File modFile) throws IOException, JsonParseException { + public static LocalModFile fromFile(File modFile) throws IOException, JsonParseException { try (ZipFile zipFile = new ZipFile(modFile)) { ZipEntry entry = zipFile.getEntry("litemod.json"); if (entry == null) @@ -116,7 +116,7 @@ public final class LiteModMetadata { LiteModMetadata metadata = JsonUtils.GSON.fromJson(IOUtils.readFullyAsString(zipFile.getInputStream(entry)), LiteModMetadata.class); if (metadata == null) throw new IOException("Mod " + modFile + " `litemod.json` is malformed."); - return new LocalMod(modFile, ModLoaderType.FORGE, null, metadata.getName(), new LocalMod.Description(metadata.getDescription()), metadata.getAuthor(), + return new LocalModFile(modFile, ModLoaderType.FORGE, null, metadata.getName(), new LocalModFile.Description(metadata.getDescription()), metadata.getAuthor(), metadata.getVersion(), metadata.getGameVersion(), metadata.getUpdateURI(), ""); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalMod.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java similarity index 85% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalMod.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java index 1d269a9ff..72dbaea10 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalMod.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java @@ -34,7 +34,7 @@ import java.util.stream.Collectors; * * @author huangyuhui */ -public final class LocalMod implements Comparable { +public final class LocalModFile implements Comparable { private Path file; private final ModLoaderType modLoaderType; @@ -49,11 +49,11 @@ public final class LocalMod implements Comparable { private final String logoPath; private final BooleanProperty activeProperty; - public LocalMod(File file, ModLoaderType modLoaderType, String id, String name, Description description) { + public LocalModFile(File file, ModLoaderType modLoaderType, String id, String name, Description description) { this(file, modLoaderType, id, name, description, "", "", "", "", ""); } - public LocalMod(File file, ModLoaderType modLoaderType, String id, String name, Description description, String authors, String version, String gameVersion, String url, String logoPath) { + public LocalModFile(File file, ModLoaderType modLoaderType, String id, String name, Description description, String authors, String version, String gameVersion, String url, String logoPath) { this.file = file.toPath(); this.modLoaderType = modLoaderType; this.id = id; @@ -68,13 +68,13 @@ public final class LocalMod implements Comparable { activeProperty = new SimpleBooleanProperty(this, "active", !ModManager.isDisabled(file)) { @Override protected void invalidated() { - Path path = LocalMod.this.file.toAbsolutePath(); + Path path = LocalModFile.this.file.toAbsolutePath(); try { if (get()) - LocalMod.this.file = ModManager.enableMod(path); + LocalModFile.this.file = ModManager.enableMod(path); else - LocalMod.this.file = ModManager.disableMod(path); + LocalModFile.this.file = ModManager.disableMod(path); } catch (IOException e) { Logging.LOG.log(Level.SEVERE, "Unable to invert state of mod file " + path, e); } @@ -153,13 +153,13 @@ public final class LocalMod implements Comparable { } @Override - public int compareTo(LocalMod o) { + public int compareTo(LocalModFile o) { return getFileName().compareTo(o.getFileName()); } @Override public boolean equals(Object obj) { - return obj instanceof LocalMod && Objects.equals(getFileName(), ((LocalMod) obj).getFileName()); + return obj instanceof LocalModFile && Objects.equals(getFileName(), ((LocalModFile) obj).getFileName()); } @Override @@ -168,18 +168,18 @@ public final class LocalMod implements Comparable { } public static class ModUpdate { - private final LocalMod localMod; + private final LocalModFile localModFile; private final RemoteMod.Version currentVersion; private final List candidates; - public ModUpdate(LocalMod localMod, RemoteMod.Version currentVersion, List candidates) { - this.localMod = localMod; + public ModUpdate(LocalModFile localModFile, RemoteMod.Version currentVersion, List candidates) { + this.localModFile = localModFile; this.currentVersion = currentVersion; this.candidates = candidates; } - public LocalMod getLocalMod() { - return localMod; + public LocalModFile getLocalMod() { + return localModFile; } public RemoteMod.Version getCurrentVersion() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java index 8af7112e6..a468e2ef5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java @@ -32,7 +32,7 @@ import java.util.TreeSet; public final class ModManager { private final GameRepository repository; private final String id; - private final TreeSet localMods = new TreeSet<>(); + private final TreeSet localModFiles = new TreeSet<>(); private boolean loaded = false; @@ -55,12 +55,12 @@ public final class ModManager { private void addModInfo(File file) { try { - localMods.add(getModInfo(file)); + localModFiles.add(getModInfo(file)); } catch (IllegalArgumentException ignore) { } } - public static LocalMod getModInfo(File modFile) { + public static LocalModFile getModInfo(File modFile) { File file = isDisabled(modFile) ? new File(modFile.getAbsoluteFile().getParentFile(), FileUtils.getNameWithoutExtension(modFile)) : modFile; String description, extension = FileUtils.getExtension(file); switch (extension) { @@ -98,11 +98,11 @@ public final class ModManager { default: throw new IllegalArgumentException("File " + modFile + " is not a mod file."); } - return new LocalMod(modFile, ModLoaderType.UNKNOWN, null, FileUtils.getNameWithoutExtension(modFile), new LocalMod.Description(description)); + return new LocalModFile(modFile, ModLoaderType.UNKNOWN, null, FileUtils.getNameWithoutExtension(modFile), new LocalModFile.Description(description)); } public void refreshMods() throws IOException { - localMods.clear(); + localModFiles.clear(); if (Files.isDirectory(getModsDirectory())) { try (DirectoryStream modsDirectoryStream = Files.newDirectoryStream(getModsDirectory())) { for (Path subitem : modsDirectoryStream) { @@ -122,10 +122,10 @@ public final class ModManager { loaded = true; } - public Collection getMods() throws IOException { + public Collection getMods() throws IOException { if (!loaded) refreshMods(); - return localMods; + return localModFiles; } public void addMod(File file) throws IOException { @@ -145,9 +145,9 @@ public final class ModManager { addModInfo(newFile); } - public void removeMods(LocalMod... localMods) throws IOException { - for (LocalMod localMod : localMods) { - Files.deleteIfExists(localMod.getFile()); + public void removeMods(LocalModFile... localModFiles) throws IOException { + for (LocalModFile localModFile : localModFiles) { + Files.deleteIfExists(localModFile.getFile()); } } @@ -165,6 +165,10 @@ public final class ModManager { return enabled; } + public static boolean isOld(File file) { + return file.getPath().endsWith(OLD_EXTENSION); + } + public static boolean isDisabled(File file) { return file.getPath().endsWith(DISABLED_EXTENSION); } @@ -228,4 +232,5 @@ public final class ModManager { } public static final String DISABLED_EXTENSION = ".disabled"; + public static final String OLD_EXTENSION = ".old"; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/PackMcMeta.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/PackMcMeta.java index 123e8ddf8..a8b1136a6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/PackMcMeta.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/PackMcMeta.java @@ -66,13 +66,13 @@ public class PackMcMeta implements Validation { private final int packFormat; @SerializedName("description") - private final LocalMod.Description description; + private final LocalModFile.Description description; public PackInfo() { - this(0, new LocalMod.Description(Collections.emptyList())); + this(0, new LocalModFile.Description(Collections.emptyList())); } - public PackInfo(int packFormat, LocalMod.Description description) { + public PackInfo(int packFormat, LocalModFile.Description description) { this.packFormat = packFormat; this.description = description; } @@ -81,7 +81,7 @@ public class PackMcMeta implements Validation { return packFormat; } - public LocalMod.Description getDescription() { + public LocalModFile.Description getDescription() { return description; } } @@ -112,13 +112,13 @@ public class PackMcMeta implements Validation { } } - public LocalMod.Description.Part deserialize(JsonElement json, JsonDeserializationContext context) throws JsonParseException { + public LocalModFile.Description.Part deserialize(JsonElement json, JsonDeserializationContext context) throws JsonParseException { if (json.isJsonPrimitive()) { - return new LocalMod.Description.Part(parseText(json)); + return new LocalModFile.Description.Part(parseText(json)); } else if (json.isJsonObject()) { JsonObject obj = json.getAsJsonObject(); String text = parseText(obj.get("text")); - return new LocalMod.Description.Part(text); + return new LocalModFile.Description.Part(text); } else { throw new JsonParseException("pack.mcmeta Raw JSON text should be string or an object"); } @@ -126,31 +126,31 @@ public class PackMcMeta implements Validation { @Override public PackInfo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - List parts = new ArrayList<>(); + List parts = new ArrayList<>(); JsonObject packInfo = json.getAsJsonObject(); int packFormat = packInfo.get("pack_format").getAsInt(); JsonElement description = packInfo.get("description"); if (description.isJsonPrimitive()) { - parts.add(new LocalMod.Description.Part(parseText(description))); + parts.add(new LocalModFile.Description.Part(parseText(description))); } else if (description.isJsonArray()) { for (JsonElement element : description.getAsJsonArray()) { JsonObject descriptionPart = element.getAsJsonObject(); - parts.add(new LocalMod.Description.Part(descriptionPart.get("text").getAsString(), descriptionPart.get("color").getAsString())); + parts.add(new LocalModFile.Description.Part(descriptionPart.get("text").getAsString(), descriptionPart.get("color").getAsString())); } } else { throw new JsonParseException("pack.mcmeta::pack::description should be String or array of text objects with text and color fields"); } - return new PackInfo(packFormat, new LocalMod.Description(parts)); + return new PackInfo(packFormat, new LocalModFile.Description(parts)); } } - public static LocalMod fromFile(File modFile) throws IOException, JsonParseException { + public static LocalModFile fromFile(File modFile) throws IOException, JsonParseException { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile.toPath())) { Path mcmod = fs.getPath("pack.mcmeta"); if (Files.notExists(mcmod)) throw new IOException("File " + modFile + " is not a resource pack."); PackMcMeta metadata = JsonUtils.fromNonNullJson(FileUtils.readText(mcmod), PackMcMeta.class); - return new LocalMod(modFile, ModLoaderType.PACK, null, FileUtils.getNameWithoutExtension(modFile), metadata.pack.description, "", "", "", "", ""); + return new LocalModFile(modFile, ModLoaderType.PACK, null, FileUtils.getNameWithoutExtension(modFile), metadata.pack.description, "", "", "", "", ""); } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java index cb0791c82..6800fa5b6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java @@ -82,14 +82,23 @@ public class RemoteMod { Alpha } + public enum Type { + CURSEFORGE, + MODRINTH + } + public interface IMod { List loadDependencies() throws IOException; Stream loadVersions() throws IOException; } + public interface IVersion { + Type getType(); + } + public static class Version { - private final Object self; + private final IVersion self; private final String modid; private final String name; private final String version; @@ -101,7 +110,7 @@ public class RemoteMod { private final List gameVersions; private final List loaders; - public Version(Object self, String modid, String name, String version, String changelog, Instant datePublished, VersionType versionType, File file, List dependencies, List gameVersions, List loaders) { + public Version(IVersion self, String modid, String name, String version, String changelog, Instant datePublished, VersionType versionType, File file, List dependencies, List gameVersions, List loaders) { this.self = self; this.modid = modid; this.name = name; @@ -115,7 +124,7 @@ public class RemoteMod { this.loaders = loaders; } - public Object getSelf() { + public IVersion getSelf() { return self; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java index f46f45b8c..936f1e8cb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java @@ -28,7 +28,7 @@ public interface RemoteModRepository { Stream search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, int sort) throws IOException; - Optional getRemoteVersionByLocalFile(LocalMod localMod, Path file) throws IOException; + Optional getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException; RemoteMod getModById(String id) throws IOException; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java index c13e9d615..f1f4920df 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java @@ -341,7 +341,7 @@ public class CurseAddon implements RemoteMod.IMod { } @Immutable - public static class LatestFile { + public static class LatestFile implements RemoteMod.IVersion { private final int id; private final String displayName; private final String fileName; @@ -481,6 +481,11 @@ public class CurseAddon implements RemoteMod.IMod { return fileDataInstant; } + @Override + public RemoteMod.Type getType() { + return RemoteMod.Type.CURSEFORGE; + } + public RemoteMod.Version toVersion() { RemoteMod.VersionType versionType; switch (getReleaseType()) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java index 0a90861fb..39e9d3f84 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java @@ -18,7 +18,7 @@ package org.jackhuang.hmcl.mod.curse; import com.google.gson.reflect.TypeToken; -import org.jackhuang.hmcl.mod.LocalMod; +import org.jackhuang.hmcl.mod.LocalModFile; import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.mod.RemoteModRepository; import org.jackhuang.hmcl.util.MurmurHash2; @@ -71,7 +71,7 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository } @Override - public Optional getRemoteVersionByLocalFile(LocalMod localMod, Path file) throws IOException { + public Optional getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (InputStream stream = Files.newInputStream(file)) { byte[] buf = new byte[1024]; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java index 4b07c2c36..b301ea5e9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java @@ -19,7 +19,7 @@ package org.jackhuang.hmcl.mod.modrinth; import com.google.gson.annotations.SerializedName; import com.google.gson.reflect.TypeToken; -import org.jackhuang.hmcl.mod.LocalMod; +import org.jackhuang.hmcl.mod.LocalModFile; import org.jackhuang.hmcl.mod.ModLoaderType; import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.mod.RemoteModRepository; @@ -74,7 +74,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository { } @Override - public Optional getRemoteVersionByLocalFile(LocalMod localMod, Path file) throws IOException { + public Optional getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException { String sha1 = Hex.encodeHex(DigestUtils.digest("SHA-1", file)); try { @@ -198,7 +198,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository { } } - public static class ModVersion { + public static class ModVersion implements RemoteMod.IVersion { private final String id; @SerializedName("mod_id") @@ -299,6 +299,11 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository { return loaders; } + @Override + public RemoteMod.Type getType() { + return RemoteMod.Type.MODRINTH; + } + public Optional toVersion() { RemoteMod.VersionType type; if ("release".equals(versionType)) {