feat(mod): mod update.

This commit is contained in:
huanghongxun
2021-10-06 02:55:19 +08:00
parent d48591a4cd
commit 6c3178a831
16 changed files with 376 additions and 130 deletions

View File

@@ -60,7 +60,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)), mods -> {
mods.forEach(it -> {
try {
modManager.addMod(it);
modManager.addMod(it.toPath());
} catch (IOException | IllegalArgumentException e) {
Logging.LOG.log(Level.WARNING, "Unable to parse mod file " + it, e);
}
@@ -125,7 +125,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
Task.runAsync(() -> {
for (File file : res) {
try {
modManager.addMod(file);
modManager.addMod(file.toPath());
succeeded.add(file.getName());
} catch (Exception e) {
Logging.LOG.log(Level.WARNING, "Unable to add mod " + file, e);
@@ -196,6 +196,15 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
});
}
public void rollback(LocalModFile from, LocalModFile to) {
try {
modManager.rollback(from, to);
refresh();
} catch (IOException ex) {
Controllers.showToast(i18n("message.failed"));
}
}
public boolean isModded() {
return modded.get();
}

View File

@@ -17,10 +17,7 @@
*/
package org.jackhuang.hmcl.ui.versions;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXCheckBox;
import com.jfoenix.controls.JFXDialogLayout;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.*;
import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
@@ -43,6 +40,7 @@ import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.util.Lazy;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.io.CompressingUtils;
@@ -56,6 +54,7 @@ import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2;
@@ -259,9 +258,13 @@ class ModListPageSkin extends SkinBase<ModListPage> {
}
}
static class ModInfoListCell extends MDListCell<ModInfoObject> {
private static Lazy<PopupMenu> menu = new Lazy<>(PopupMenu::new);
private static Lazy<JFXPopup> popup = new Lazy<>(() -> new JFXPopup(menu.get()));
class ModInfoListCell extends MDListCell<ModInfoObject> {
JFXCheckBox checkBox = new JFXCheckBox();
TwoLineListItem content = new TwoLineListItem();
JFXButton restoreButton = new JFXButton();
JFXButton infoButton = new JFXButton();
JFXButton revealButton = new JFXButton();
BooleanProperty booleanProperty;
@@ -276,13 +279,18 @@ class ModListPageSkin extends SkinBase<ModListPage> {
content.setMouseTransparent(true);
setSelectable();
restoreButton.getStyleClass().add("toggle-icon4");
restoreButton.setGraphic(FXUtils.limitingSize(SVG.restore(Theme.blackFillBinding(), 24, 24), 24, 24));
FXUtils.installFastTooltip(restoreButton, i18n("mods.restore"));
revealButton.getStyleClass().add("toggle-icon4");
revealButton.setGraphic(FXUtils.limitingSize(SVG.folderOutline(Theme.blackFillBinding(), 24, 24), 24, 24));
infoButton.getStyleClass().add("toggle-icon4");
infoButton.setGraphic(FXUtils.limitingSize(SVG.informationOutline(Theme.blackFillBinding(), 24, 24), 24, 24));
container.getChildren().setAll(checkBox, content, revealButton, infoButton);
container.getChildren().setAll(checkBox, content, restoreButton, revealButton, infoButton);
StackPane.setMargin(container, new Insets(8));
getContainer().getChildren().setAll(container);
@@ -302,6 +310,17 @@ class ModListPageSkin extends SkinBase<ModListPage> {
checkBox.selectedProperty().unbindBidirectional(booleanProperty);
}
checkBox.selectedProperty().bindBidirectional(booleanProperty = dataItem.active);
restoreButton.setVisible(!dataItem.getModInfo().getMod().getOldFiles().isEmpty());
restoreButton.setOnMouseClicked(e -> {
menu.get().getContent().setAll(dataItem.getModInfo().getMod().getOldFiles().stream()
.map(localModFile -> new IconedMenuItem(null, localModFile.getVersion(), () -> {
getSkinnable().rollback(dataItem.getModInfo(), localModFile);
}))
.collect(Collectors.toList())
);
popup.get().show(restoreButton, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, restoreButton.getHeight());
});
revealButton.setOnMouseClicked(e -> {
FXUtils.showFileInExplorer(dataItem.getModInfo().getFile());
});

View File

@@ -32,15 +32,16 @@ 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.FileDownloadTask;
import org.jackhuang.hmcl.task.Schedulers;
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.construct.*;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.Pair;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
@@ -120,13 +121,26 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
}
private void updateMods() {
ModUpdateTask task = new ModUpdateTask(
modManager,
objects.stream()
.filter(o -> o.enabled.get())
.map(object -> pair(object.data.getLocalMod(), object.data.getCandidates().get(0)))
.collect(Collectors.toList()));
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())),
task.whenComplete(Schedulers.javafx(), exception -> {
fireEvent(new PageCloseEvent());
if (!task.getFailedMods().isEmpty()) {
Controllers.dialog(i18n("mods.check_updates.failed") + "\n" +
task.getFailedMods().stream().map(LocalModFile::getFileName).collect(Collectors.joining("\n")),
i18n("install.failed"),
MessageDialogPane.MessageType.ERROR);
}
if (exception == null) {
Controllers.dialog(i18n("install.success"));
}
}),
i18n("mods.check_updates.update"),
t -> {
});
@@ -249,6 +263,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
public static class ModUpdateTask extends Task<Void> {
private final Collection<Task<?>> dependents;
private final List<LocalModFile> failedMods = new ArrayList<>();
ModUpdateTask(ModManager modManager, List<Pair<LocalModFile, RemoteMod.Version>> mods) {
setStage("mods.check_updates.update");
@@ -257,16 +272,33 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
dependents = mods.stream()
.map(mod -> {
return Task
.supplyAsync(() -> {
return null;
.runAsync(Schedulers.javafx(), () -> {
mod.getKey().setOld(true);
})
.thenComposeAsync(() -> {
FileDownloadTask task = new FileDownloadTask(
new URL(mod.getValue().getFile().getUrl()),
modManager.getModsDirectory().resolve(mod.getValue().getFile().getFilename()).toFile());
task.setName(mod.getValue().getName());
return task;
})
.whenComplete(Schedulers.javafx(), exception -> {
if (exception != null) {
// restore state if failed
mod.getKey().setOld(false);
failedMods.add(mod.getKey());
}
})
.setName(mod.getKey().getName())
.setSignificance(TaskSignificance.MAJOR)
.withCounter("mods.check_updates.update");
})
.collect(Collectors.toList());
}
public List<LocalModFile> getFailedMods() {
return failedMods;
}
@Override
public Collection<Task<?>> getDependents() {
return dependents;

View File

@@ -486,6 +486,7 @@ message.default=Default
message.doing=Please wait
message.downloading=Downloading...
message.error=Error
message.failed=Operation failed
message.info=Info
message.success=Job completed successfully
message.unknown=Unknown
@@ -583,6 +584,7 @@ mods.add.success=Successfully installed mods %s.
mods.category=Category
mods.check_updates=Check updates
mods.check_updates.current_version=Current
mods.check_updates.failed=Failed to download some of files
mods.check_updates.file=File
mods.check_updates.source=Source
mods.check_updates.target_version=Target
@@ -602,6 +604,7 @@ mods.mcmod.search=Search in MCMOD
mods.modrinth=Modrinth
mods.name=Name
mods.not_modded=You should install a modloader first (Fabric, Forge or LiteLoader)
mods.restore=Restore
mods.url=Official Page
multiplayer=Multiplayer

View File

@@ -486,6 +486,7 @@ message.default=預設
message.doing=請耐心等待
message.downloading=正在下載…
message.error=錯誤
message.failed=操作失敗
message.info=資訊
message.success=完成
message.unknown=未知
@@ -583,6 +584,7 @@ mods.add.success=成功新增模組 %s。
mods.category=類別
mods.check_updates=檢查模組更新
mods.check_updates.current_version=當前版本
mods.check_updates.failed=部分文件下載失敗
mods.check_updates.file=文件
mods.check_updates.source=來源
mods.check_updates.target_version=目標版本
@@ -602,6 +604,7 @@ mods.mcmod.search=MC 百科蒐索
mods.modrinth=Modrinth
mods.name=名稱
mods.not_modded=你需要先在自動安裝頁面安裝 Fabric、Forge 或 LiteLoader 才能進行模組管理。
mods.restore=回退
mods.url=官方頁面
multiplayer=多人聯機

View File

@@ -486,6 +486,7 @@ message.default=默认
message.doing=请耐心等待
message.downloading=正在下载
message.error=错误
message.failed=操作失败
message.info=提示
message.success=已完成
message.unknown=未知
@@ -583,6 +584,7 @@ mods.add.success=成功添加模组 %s。
mods.category=类别
mods.check_updates=检查模组更新
mods.check_updates.current_version=当前版本
mods.check_updates.failed=部分文件下载失败
mods.check_updates.file=文件
mods.check_updates.source=来源
mods.check_updates.target_version=目标版本
@@ -602,6 +604,7 @@ mods.mcmod.search=MC 百科搜索
mods.modrinth=Modrinth
mods.name=名称
mods.not_modded=你需要先在自动安装页面安装 Fabric、Forge 或 LiteLoader 才能进行模组管理。
mods.restore=回退
mods.url=官方页面
multiplayer=多人联机