diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java index 6b8010424..b97e024d9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java @@ -35,8 +35,6 @@ import java.io.File; import java.io.IOException; import java.util.LinkedList; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; public final class HMCLModpackInstallTask extends Task { private final File zipFile; @@ -107,13 +105,5 @@ public final class HMCLModpackInstallTask extends Task { dependencies.add(libraryTask.thenComposeAsync(repository::saveAsync)); } - @Override - public List getStages() { - return Stream.concat( - dependents.stream().flatMap(task -> task.getStages().stream()), - Stream.of("hmcl.modpack") - ).collect(Collectors.toList()); - } - public static final String MODPACK_TYPE = "HMCL"; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java index 2645fe78a..64a366234 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java @@ -50,6 +50,7 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; @@ -167,7 +168,8 @@ public final class ModpackHelper { }; return new ServerModpackRemoteInstallTask(profile.getDependency(), manifest, name) - .whenComplete(Schedulers.defaultScheduler(), success, failure); + .whenComplete(Schedulers.defaultScheduler(), success, failure) + .withStagesHint(Arrays.asList("hmcl.modpack", "hmcl.modpack.download")); } public static boolean isExternalGameNameConflicts(String name) { @@ -223,7 +225,8 @@ public final class ModpackHelper { public static Task getUpdateTask(Profile profile, ServerModpackManifest manifest, Charset charset, String name, ModpackConfiguration configuration) throws UnsupportedModpackException { switch (configuration.getType()) { case ServerModpackRemoteInstallTask.MODPACK_TYPE: - return new ModpackUpdateTask(profile.getRepository(), name, new ServerModpackRemoteInstallTask(profile.getDependency(), manifest, name)); + return new ModpackUpdateTask(profile.getRepository(), name, new ServerModpackRemoteInstallTask(profile.getDependency(), manifest, name)) + .withStagesHint(Arrays.asList("hmcl.modpack", "hmcl.modpack.download")); default: throw new UnsupportedModpackException(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index 3880ca0bd..28d71738c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -275,6 +275,16 @@ public final class Controllers { return pane; } + public static TaskExecutorDialogPane taskDialog(Task task, String title, Consumer onCancel) { + TaskExecutor executor = task.executor(); + TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel); + pane.setTitle(title); + pane.setExecutor(executor); + dialog(pane); + executor.start(); + return pane; + } + public static void navigate(Node node) { decorator.getNavigator().navigate(node, ContainerAnimations.FADE.getAnimationProducer()); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java index 1a608fb19..a050f1b9e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java @@ -61,6 +61,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.Lang.tryCast; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -96,7 +97,7 @@ public final class TaskListPane extends StackPane { @Override public void onReady(Task task) { - if (task instanceof Task.StageTask) { + if (task.getStage() != null) { Platform.runLater(() -> { stageNodes.stream().filter(x -> x.stage.equals(task.getStage())).findAny().ifPresent(StageNode::begin); }); @@ -150,7 +151,7 @@ public final class TaskListPane extends StackPane { @Override public void onFinished(Task task) { - if (task instanceof Task.StageTask) { + if (task.getStage() != null) { Platform.runLater(() -> { stageNodes.stream().filter(x -> x.stage.equals(task.getStage())).findAny().ifPresent(StageNode::succeed); }); @@ -167,7 +168,7 @@ public final class TaskListPane extends StackPane { @Override public void onFailed(Task task, Throwable throwable) { - if (task instanceof Task.StageTask) { + if (task.getStage() != null) { Platform.runLater(() -> { stageNodes.stream().filter(x -> x.stage.equals(task.getStage())).findAny().ifPresent(StageNode::fail); }); @@ -181,14 +182,29 @@ public final class TaskListPane extends StackPane { } @Override - public void onPropertiesUpdate(Map> stageProperties) { - stageProperties.forEach((stage, properties) -> { - int count = tryCast(properties.get("count"), Integer.class).orElse(0), - total = tryCast(properties.get("total"), Integer.class).orElse(0); - if (total > 0) - Platform.runLater(() -> - stageNodes.stream().filter(x -> x.stage.equals(stage)).findAny().ifPresent(stageNode -> stageNode.updateCounter(count, total))); - }); + public void onPropertiesUpdate(Task task) { + if (task instanceof Task.CountTask) { + runInFX(() -> { + stageNodes.stream() + .filter(x -> x.stage.equals(((Task.CountTask) task).getCountStage())) + .findAny() + .ifPresent(StageNode::count); + }); + + return; + } + + if (task.getStage() != null) { + int total = tryCast(task.getProperties().get("total"), Integer.class).orElse(0); + runInFX(() -> { + stageNodes.stream() + .filter(x -> x.stage.equals(task.getStage())) + .findAny() + .ifPresent(stageNode -> { + stageNode.setTotal(total); + }); + }); + } } }); } @@ -197,6 +213,8 @@ public final class TaskListPane extends StackPane { private final String stage; private final Label title = new Label(); private final String message; + private int count = 0; + private int total = 0; private boolean started = false; public StageNode(String stage) { @@ -242,6 +260,15 @@ public final class TaskListPane extends StackPane { setLeft(FXUtils.limitingSize(SVG.check(Theme.blackFillBinding(), 14, 14), 14, 14)); } + public void count() { + updateCounter(++count, total); + } + + public void setTotal(int total) { + this.total = total; + updateCounter(count, total); + } + public void updateCounter(int count, int total) { if (total > 0) title.setText(String.format("%s - %d/%d", message, count, total)); 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 e612755a0..fd3085c16 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,6 +17,7 @@ */ package org.jackhuang.hmcl.ui.versions; +import org.jackhuang.hmcl.mod.LocalMod; import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.mod.RemoteModRepository; import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository; @@ -76,11 +77,6 @@ public class ModDownloadListPage extends DownloadListPage { } } - @Override - public Optional getRemoteVersionByLocalFile(Path file) { - throw new UnsupportedOperationException(); - } - @Override public Stream getCategories() throws IOException { if ("mods.modrinth".equals(downloadSource.get())) { @@ -89,6 +85,21 @@ public class ModDownloadListPage extends DownloadListPage { return CurseForgeRemoteModRepository.MODS.getCategories(); } } + + @Override + public Optional getRemoteVersionByLocalFile(LocalMod localMod, Path file) { + throw new UnsupportedOperationException(); + } + + @Override + public RemoteMod getModById(String id) { + throw new UnsupportedOperationException(); + } + + @Override + public Stream getRemoteVersionsById(String id) throws IOException { + throw new UnsupportedOperationException(); + } } @Override 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 285a7ddec..42b7dd8d2 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 @@ -32,16 +32,14 @@ import org.jackhuang.hmcl.task.Task; 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.util.Logging; import org.jackhuang.hmcl.util.io.FileUtils; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.logging.Level; import java.util.stream.Collectors; @@ -176,6 +174,27 @@ public final class ModListPage extends ListPageBase { + Optional gameVersion = profile.getRepository().getGameVersion(versionId); + if (gameVersion.isPresent()) { + return new ModUpdateTask(gameVersion.get(), modManager.getMods()); + } + return null; + }) + .whenComplete(Schedulers.javafx(), (result, exception) -> { + if (exception != null) { + Controllers.dialog("Failed to check updates", "failed", MessageDialogPane.MessageType.ERROR); + } else { + Controllers.dialog(new ModUpdatesDialog(result)); + } + }) + .withStagesHint(Collections.singletonList("mods.check_updates")) + , i18n("update.checking"), pane -> { + }); + } + public boolean isModded() { return modded.get(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index fe3dbe855..c42ae2152 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -92,7 +92,9 @@ class ModListPageSkin extends SkinBase { createToolbarButton2(i18n("mods.disable"), SVG::close, () -> skinnable.disableSelected(listView.getSelectionModel().getSelectedItems())), createToolbarButton2(i18n("folder.mod"), SVG::folderOpen, () -> - skinnable.openModFolder())); + skinnable.openModFolder()), + createToolbarButton2(i18n("mods.check_updates"), SVG::update, () -> + skinnable.checkUpdates())); root.getContent().add(toolbar); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdateTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdateTask.java new file mode 100644 index 000000000..cbc026991 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdateTask.java @@ -0,0 +1,75 @@ +/* + * 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 org.jackhuang.hmcl.mod.LocalMod; +import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository; +import org.jackhuang.hmcl.task.Task; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +public class ModUpdateTask extends Task> { + + private final String gameVersion; + private final Collection mods; + private final Collection> dependents; + + public ModUpdateTask(String gameVersion, Collection mods) { + this.gameVersion = gameVersion; + this.mods = mods; + + dependents = mods.stream() + .map(mod -> Task.supplyAsync(() -> { + return mod.checkUpdates(gameVersion, CurseForgeRemoteModRepository.MODS); + }).setSignificance(TaskSignificance.MAJOR).withCounter("mods.check_updates")) + .collect(Collectors.toList()); + + setStage("mods.check_updates"); + getProperties().put("total", dependents.size()); + } + + @Override + public boolean doPreExecute() { + return true; + } + + @Override + public void preExecute() throws Exception { + notifyPropertiesChanged(); + } + + @Override + public Collection> getDependents() { + return dependents; + } + + @Override + public boolean isRelyingOnDependents() { + return false; + } + + @Override + public void execute() throws Exception { + setResult(dependents.stream() + .filter(task -> task.getResult() != null) + .map(Task::getResult) + .collect(Collectors.toList())); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesDialog.java new file mode 100644 index 000000000..28f262a90 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesDialog.java @@ -0,0 +1,67 @@ +/* + * 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.JFXListView; +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.DialogPane; +import org.jackhuang.hmcl.ui.construct.MDListCell; +import org.jackhuang.hmcl.ui.construct.TwoLineListItem; + +import java.util.List; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class ModUpdatesDialog extends DialogPane { + + public ModUpdatesDialog(List updates) { + setTitle(i18n("mods.check_updates")); + + JFXListView listView = new JFXListView<>(); + listView.getItems().setAll(updates); + listView.setCellFactory(l -> new ModUpdateCell(listView)); + setBody(listView); + } + + 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"); + } + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/JavaRuntimeDownloadTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/JavaRuntimeDownloadTask.java index b6731eb24..54bd0ce65 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/JavaRuntimeDownloadTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/JavaRuntimeDownloadTask.java @@ -18,7 +18,7 @@ public class JavaRuntimeDownloadTask extends Task { } @Override - public Collection> getDependencies() { + public Collection> getDependencies() { return super.getDependencies(); } diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index d0b1656c4..b1e756744 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -580,6 +580,7 @@ mods.add=Install mods mods.add.failed=Failed to install mods %s. mods.add.success=Successfully installed mods %s. mods.category=Category +mods.check_updates=Check updates 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 e9a2b6274..e13ab5786 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -580,6 +580,7 @@ mods.add=新增模組 mods.add.failed=新增模組 %s 失敗。 mods.add.success=成功新增模組 %s。 mods.category=類別 +mods.check_updates=檢查模組更新 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 3a1a8a96e..62e6701d2 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -580,6 +580,7 @@ mods.add=添加模组 mods.add.failed=添加模组 %s 失败。 mods.add.success=成功添加模组 %s。 mods.category=类别 +mods.check_updates=检查模组更新 mods.choose_mod=选择模组 mods.curseforge=CurseForge mods.dependencies=前置 Mod diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java index 8ae3ded4c..3899c54e2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java @@ -65,6 +65,7 @@ public final class GameAssetDownloadTask extends Task { this.assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId()); this.integrityCheck = integrityCheck; + setStage("hmcl.install.assets"); dependents.add(new GameAssetIndexDownloadTask(dependencyManager, this.version, forceDownloadingIndex)); } @@ -109,7 +110,7 @@ public final class GameAssetDownloadTask extends Task { .resolve("assets").resolve("objects").resolve(assetObject.getLocation())); task.setCacheRepository(dependencyManager.getCacheRepository()); task.setCaching(true); - dependencies.add(task.withCounter()); + dependencies.add(task.withCounter("hmcl.install.assets")); } else { dependencyManager.getCacheRepository().tryCacheFile(file.toPath(), CacheRepository.SHA1, assetObject.getHash()); } @@ -119,6 +120,7 @@ public final class GameAssetDownloadTask extends Task { if (!dependencies.isEmpty()) { getProperties().put("total", dependencies.size()); + notifyPropertiesChanged(); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameInstallTask.java index 0806a2a30..048deec2e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameInstallTask.java @@ -74,7 +74,7 @@ public class GameInstallTask extends Task { Task.allOf( new GameAssetDownloadTask(dependencyManager, version, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY, true), new GameLibrariesTask(dependencyManager, version, true) - ).withStage("hmcl.install.assets").withRunAsync(() -> { + ).withRunAsync(() -> { // ignore failure }) ).thenComposeAsync(gameRepository.saveAsync(version))); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalMod.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalMod.java index 5bb1ee7b7..2a1e518fe 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalMod.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalMod.java @@ -26,10 +26,9 @@ import org.jackhuang.hmcl.util.io.FileUtils; import java.io.File; import java.io.IOException; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.logging.Level; +import java.util.stream.Collectors; /** * @@ -141,6 +140,17 @@ public final class LocalMod implements Comparable { return fileName; } + public ModUpdate checkUpdates(String gameVersion, RemoteModRepository repository) throws IOException { + Optional currentVersion = repository.getRemoteVersionByLocalFile(this, file); + if (!currentVersion.isPresent()) return null; + List remoteVersions = repository.getRemoteVersionsById(currentVersion.get().getModid()) + .filter(version -> version.getGameVersions().contains(gameVersion)) + .filter(version -> version.getLoaders().contains(modLoaderType)) + .sorted(Comparator.comparing(RemoteMod.Version::getDatePublished).reversed()) + .collect(Collectors.toList()); + return new ModUpdate(this, currentVersion.get(), remoteVersions); + } + @Override public int compareTo(LocalMod o) { return getFileName().compareTo(o.getFileName()); @@ -156,6 +166,30 @@ public final class LocalMod implements Comparable { return Objects.hash(getFileName()); } + public static class ModUpdate { + private final LocalMod localMod; + private final RemoteMod.Version currentVersion; + private final List candidates; + + public ModUpdate(LocalMod localMod, RemoteMod.Version currentVersion, List candidates) { + this.localMod = localMod; + this.currentVersion = currentVersion; + this.candidates = candidates; + } + + public LocalMod getLocalMod() { + return localMod; + } + + public RemoteMod.Version getCurrentVersion() { + return currentVersion; + } + + public List getCandidates() { + return candidates; + } + } + public static class Description { private final List parts; 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 63dadd478..cb0791c82 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java @@ -90,6 +90,7 @@ public class RemoteMod { public static class Version { private final Object self; + private final String modid; private final String name; private final String version; private final String changelog; @@ -98,10 +99,11 @@ public class RemoteMod { private final File file; private final List dependencies; private final List gameVersions; - private final List loaders; + private final List loaders; - public Version(Object self, String name, String version, String changelog, Instant datePublished, VersionType versionType, File file, List dependencies, List gameVersions, 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) { this.self = self; + this.modid = modid; this.name = name; this.version = version; this.changelog = changelog; @@ -117,6 +119,10 @@ public class RemoteMod { return self; } + public String getModid() { + return modid; + } + public String getName() { return name; } @@ -149,7 +155,7 @@ public class RemoteMod { return gameVersions; } - public List getLoaders() { + public List getLoaders() { return loaders; } } 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 fe30e3b1a..f46f45b8c 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,11 @@ public interface RemoteModRepository { Stream search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, int sort) throws IOException; - Optional getRemoteVersionByLocalFile(Path file) throws IOException; + Optional getRemoteVersionByLocalFile(LocalMod localMod, Path file) throws IOException; + + RemoteMod getModById(String id) throws IOException; + + Stream getRemoteVersionsById(String id) throws IOException; Stream getCategories() 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 4597b9f96..f946249d6 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 @@ -176,15 +176,14 @@ public class CurseAddon implements RemoteMod.IMod { .collect(Collectors.toSet()); List mods = new ArrayList<>(); for (int dependencyId : dependencies) { - mods.add(CurseForgeRemoteModRepository.MODS.getAddon(dependencyId).toMod()); + mods.add(CurseForgeRemoteModRepository.MODS.getModById(Integer.toString(dependencyId))); } return mods; } @Override public Stream loadVersions() throws IOException { - return CurseForgeRemoteModRepository.MODS.getFiles(this).stream() - .map(CurseAddon.LatestFile::toVersion); + return CurseForgeRemoteModRepository.MODS.getRemoteVersionsById(Integer.toString(id)); } public RemoteMod toMod() { @@ -500,6 +499,7 @@ public class CurseAddon implements RemoteMod.IMod { return new RemoteMod.Version( this, + Integer.toString(projectId), getDisplayName(), null, null, diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java index ee29685e7..289bbe2b6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java @@ -90,6 +90,8 @@ public final class CurseCompletionTask extends Task { } catch (Exception e) { Logging.LOG.log(Level.WARNING, "Unable to read CurseForge modpack manifest.json", e); } + + setStage("hmcl.modpack.download"); } @Override @@ -158,12 +160,13 @@ public final class CurseCompletionTask extends Task { FileDownloadTask task = new FileDownloadTask(file.getUrl(), modManager.getSimpleModPath(file.getFileName()).toFile()); task.setCacheRepository(dependency.getCacheRepository()); task.setCaching(true); - dependencies.add(task.withCounter()); + dependencies.add(task.withCounter("hmcl.modpack.download")); } } if (!dependencies.isEmpty()) { getProperties().put("total", dependencies.size()); + notifyPropertiesChanged(); } } 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 277339792..7ddf7630b 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,6 +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.RemoteMod; import org.jackhuang.hmcl.mod.RemoteModRepository; import org.jackhuang.hmcl.util.MurmurHash; @@ -73,7 +74,7 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository } @Override - public Optional getRemoteVersionByLocalFile(Path file) throws IOException { + public Optional getRemoteVersionByLocalFile(LocalMod localMod, Path file) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(Files.newInputStream(file)))) { int b; @@ -97,15 +98,18 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository return Optional.of(response.getExactMatches().get(0).getFile().toVersion()); } - public CurseAddon getAddon(int id) throws IOException { + @Override + public RemoteMod getModById(String id) throws IOException { String response = NetworkUtils.doGet(NetworkUtils.toURL(PREFIX + "/addon/" + id)); - return JsonUtils.fromNonNullJson(response, CurseAddon.class); + return JsonUtils.fromNonNullJson(response, CurseAddon.class).toMod(); } - public List getFiles(CurseAddon addon) throws IOException { - String response = NetworkUtils.doGet(NetworkUtils.toURL(PREFIX + "/addon/" + addon.getId() + "/files")); - return JsonUtils.fromNonNullJson(response, new TypeToken>() { + @Override + public Stream getRemoteVersionsById(String id) throws IOException { + String response = NetworkUtils.doGet(NetworkUtils.toURL(PREFIX + "/addon/" + id + "/files")); + List files = JsonUtils.fromNonNullJson(response, new TypeToken>() { }.getType()); + return files.stream().map(CurseAddon.LatestFile::toVersion); } public List getCategoriesImpl() throws IOException { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java index 0f9d79b54..439180665 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java @@ -36,8 +36,6 @@ import java.io.IOException; import java.util.Collection; import java.util.LinkedList; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; /** * Install a downloaded CurseForge modpack. @@ -111,7 +109,7 @@ public final class CurseInstallTask extends Task { dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), manifest.getOverrides(), any -> true, config).withStage("hmcl.modpack")); dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), manifest.getOverrides(), manifest, MODPACK_TYPE, manifest.getName(), manifest.getVersion(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack")); - dependencies.add(new CurseCompletionTask(dependencyManager, name, manifest).withStage("hmcl.modpack.download")); + dependencies.add(new CurseCompletionTask(dependencyManager, name, manifest)); } @Override @@ -142,13 +140,5 @@ public final class CurseInstallTask extends Task { FileUtils.writeText(new File(root, "manifest.json"), JsonUtils.GSON.toJson(manifest)); } - @Override - public List getStages() { - return Stream.concat( - dependents.stream().flatMap(task -> task.getStages().stream()), - Stream.of("hmcl.modpack", "hmcl.modpack.download") - ).collect(Collectors.toList()); - } - public static final String MODPACK_TYPE = "Curse"; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackCompletionTask.java index c6c34d58c..d5523620f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackCompletionTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackCompletionTask.java @@ -81,6 +81,8 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask { this.version = version; this.configurationFile = repository.getModpackConfiguration(version); this.configuration = configuration; + + setStage("hmcl.modpack.download"); } @Override @@ -253,13 +255,14 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask { FileDownloadTask task = new FileDownloadTask(curseFile.getUrl(), modManager.getSimpleModPath(curseFile.getFileName()).toFile()); task.setCacheRepository(dependency.getCacheRepository()); task.setCaching(true); - dependencies.add(task.withCounter()); + dependencies.add(task.withCounter("hmcl.modpack.download")); } } } if (!dependencies.isEmpty()) { getProperties().put("total", dependencies.size()); + notifyPropertiesChanged(); } return executor.all(dependencies); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackLocalInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackLocalInstallTask.java index e9ff4090b..4612ae674 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackLocalInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackLocalInstallTask.java @@ -36,8 +36,6 @@ import java.io.IOException; import java.util.LinkedList; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; public class McbbsModpackLocalInstallTask extends Task { @@ -120,15 +118,7 @@ public class McbbsModpackLocalInstallTask extends Task { // TODO: maintain libraries. } - dependencies.add(new McbbsModpackCompletionTask(dependencyManager, name, instanceTask.getResult()).withStage("hmcl.modpack.download")); - } - - @Override - public List getStages() { - return Stream.concat( - dependents.stream().flatMap(task -> task.getStages().stream()), - Stream.of("hmcl.modpack") - ).collect(Collectors.toList()); + dependencies.add(new McbbsModpackCompletionTask(dependencyManager, name, instanceTask.getResult())); } private static final String PATCH_NAME = "mcbbs"; 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 67a7ff2cd..4b07c2c36 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,6 +19,8 @@ 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.ModLoaderType; import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.mod.RemoteModRepository; import org.jackhuang.hmcl.util.DigestUtils; @@ -36,6 +38,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.Stream; import static org.jackhuang.hmcl.util.Lang.mapOf; @@ -71,7 +74,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository { } @Override - public Optional getRemoteVersionByLocalFile(Path file) throws IOException { + public Optional getRemoteVersionByLocalFile(LocalMod localMod, Path file) throws IOException { String sha1 = Hex.encodeHex(DigestUtils.digest("SHA-1", file)); try { @@ -88,11 +91,18 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository { } } - public List getFiles(ModResult mod) throws IOException { - String id = StringUtils.removePrefix(mod.getModId(), "local-"); - return HttpRequest.GET("https://api.modrinth.com/api/v1/mod/" + id + "/version") + @Override + public RemoteMod getModById(String id) { + throw new UnsupportedOperationException(); + } + + @Override + public Stream getRemoteVersionsById(String id) throws IOException { + id = StringUtils.removePrefix(id, "local-"); + List versions = HttpRequest.GET("https://api.modrinth.com/api/v1/mod/" + id + "/version") .getJson(new TypeToken>() { }.getType()); + return versions.stream().map(ModVersion::toVersion).flatMap(Lang::toStream); } public List getCategoriesImpl() throws IOException { @@ -307,6 +317,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository { return Optional.of(new RemoteMod.Version( this, + modId, name, versionNumber, changelog, @@ -315,7 +326,11 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository { files.get(0).toFile(), dependencies, gameVersions, - loaders + loaders.stream().flatMap(loader -> { + if ("fabric".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.FABRIC); + else if ("forge".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.FORGE); + else return Stream.empty(); + }).collect(Collectors.toList()) )); } } @@ -464,9 +479,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository { @Override public Stream loadVersions() throws IOException { - return ModrinthRemoteModRepository.INSTANCE.getFiles(this).stream() - .map(ModVersion::toVersion) - .flatMap(Lang::toStream); + return ModrinthRemoteModRepository.INSTANCE.getRemoteVersionsById(getModId()); } public RemoteMod toMod() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index 77f662f14..93a591a98 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -39,9 +39,10 @@ import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; /** * @@ -179,13 +180,5 @@ public final class MultiMCModpackInstallTask extends Task { dependencies.add(repository.saveAsync(version)); } - @Override - public List getStages() { - return Stream.concat( - dependents.stream().flatMap(task -> task.getStages().stream()), - Stream.of("hmcl.modpack") - ).collect(Collectors.toList()); - } - public static final String MODPACK_TYPE = "MultiMC"; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java index 58fb67051..cdaf091d9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java @@ -77,6 +77,8 @@ public class ServerModpackCompletionTask extends Task { } else { this.manifest = manifest; } + + setStage("hmcl.modpack.download"); } @Override @@ -133,6 +135,7 @@ public class ServerModpackCompletionTask extends Task { Set remoteFiles = remoteManifest.getFiles().stream().map(ModpackConfiguration.FileInformation::getPath) .collect(Collectors.toSet()); + int total = 0; // for files in new modpack for (ModpackConfiguration.FileInformation file : remoteManifest.getFiles()) { Path actualPath = rootPath.resolve(file.getPath()); @@ -152,10 +155,12 @@ public class ServerModpackCompletionTask extends Task { } if (download) { + total++; dependencies.add(new FileDownloadTask( new URL(remoteManifest.getFileApi() + "/overrides/" + NetworkUtils.encodeLocation(file.getPath())), actualPath.toFile(), - new FileDownloadTask.IntegrityCheck("SHA-1", file.getHash()))); + new FileDownloadTask.IntegrityCheck("SHA-1", file.getHash())) + .withCounter("hmcl.modpack.download")); } } @@ -165,6 +170,9 @@ public class ServerModpackCompletionTask extends Task { if (Files.exists(actualPath) && !remoteFiles.contains(file.getPath())) Files.deleteIfExists(actualPath); } + + getProperties().put("total", dependencies.size()); + notifyPropertiesChanged(); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackLocalInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackLocalInstallTask.java index 8406c9fbc..209c5e887 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackLocalInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackLocalInstallTask.java @@ -34,8 +34,6 @@ import java.io.File; import java.io.IOException; import java.util.LinkedList; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; public class ServerModpackLocalInstallTask extends Task { @@ -99,13 +97,5 @@ public class ServerModpackLocalInstallTask extends Task { public void execute() throws Exception { } - @Override - public List getStages() { - return Stream.concat( - dependents.stream().flatMap(task -> task.getStages().stream()), - Stream.of("hmcl.modpack") - ).collect(Collectors.toList()); - } - public static final String MODPACK_TYPE = "Server"; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/AsyncTaskExecutor.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/AsyncTaskExecutor.java index 0d1e24741..11f0bb626 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/AsyncTaskExecutor.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/AsyncTaskExecutor.java @@ -97,7 +97,7 @@ public final class AsyncTaskExecutor extends TaskExecutor { cancelled.set(true); } - private CompletableFuture executeTasksExceptionally(Task parentTask, Collection> tasks) { + private CompletableFuture executeTasksExceptionally(Task parentTask, Collection> tasks) { if (tasks == null || tasks.isEmpty()) return CompletableFuture.completedFuture(null); @@ -117,7 +117,7 @@ public final class AsyncTaskExecutor extends TaskExecutor { }); } - private CompletableFuture executeTasks(Task parentTask, Collection> tasks) { + private CompletableFuture executeTasks(Task parentTask, Collection> tasks) { return executeTasksExceptionally(parentTask, tasks) .thenApplyAsync(unused -> (Exception) null) .exceptionally(throwable -> { @@ -208,8 +208,7 @@ public final class AsyncTaskExecutor extends TaskExecutor { task.setCancelled(this::isCancelled); task.setState(Task.TaskState.READY); - if (parentTask != null && task.getStage() == null) - task.setStage(parentTask.getStage()); + task.setNotifyPropertiesChanged(() -> taskListeners.forEach(it -> it.onPropertiesUpdate(task))); if (task.getSignificance().shouldLog()) Logging.LOG.log(Level.FINE, "Executing task: " + task.getName()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java index e8103b2d9..c81ed4eb1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java @@ -24,7 +24,6 @@ import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.ReadOnlyStringWrapper; import org.jackhuang.hmcl.event.EventManager; import org.jackhuang.hmcl.util.InvocationDispatcher; -import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.ReflectionHelper; import org.jackhuang.hmcl.util.function.ExceptionalConsumer; @@ -39,10 +38,8 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import java.util.logging.Level; import java.util.stream.Collectors; -import java.util.stream.Stream; /** * Disposable task. @@ -98,21 +95,30 @@ public abstract class Task { * You must initialize stage in constructor. * @param stage the stage */ - final void setStage(String stage) { + protected final void setStage(String stage) { this.stage = stage; } - public List getStages() { - return getStage() == null ? Collections.emptyList() : Collections.singletonList(getStage()); - } - + // properties Map properties; - protected Map getProperties() { + public Map getProperties() { if (properties == null) properties = new HashMap<>(); return properties; } + private Runnable notifyPropertiesChanged; + + void setNotifyPropertiesChanged(Runnable runnable) { + this.notifyPropertiesChanged = runnable; + } + + protected void notifyPropertiesChanged() { + if (notifyPropertiesChanged != null) { + notifyPropertiesChanged.run(); + } + } + // state private TaskState state = TaskState.READY; @@ -283,7 +289,7 @@ public abstract class Task { /** * The collection of sub-tasks that should execute **before** this task running. */ - public Collection> getDependents() { + public Collection> getDependents() { return Collections.emptySet(); } @@ -291,7 +297,7 @@ public abstract class Task { * The collection of sub-tasks that should execute **after** this task running. * Will not be executed if execution fails. */ - public Collection> getDependencies() { + public Collection> getDependencies() { return Collections.emptySet(); } @@ -715,11 +721,6 @@ public abstract class Task { public boolean isRelyingOnDependents() { return false; } - - @Override - public List getStages() { - return Lang.merge(Task.this.getStages(), super.getStages()); - } }.setExecutor(executor).setName(getCaller()).setSignificance(TaskSignificance.MODERATE); } @@ -801,27 +802,33 @@ public abstract class Task { } public Task withStagesHint(List stages) { - return new Task() { - - @Override - public Collection> getDependents() { - return Collections.singleton(Task.this); - } - - @Override - public void execute() throws Exception { - setResult(Task.this.getResult()); - } - - @Override - public List getStages() { - return stages; - } - }; + return new StagesHintTask(stages); } - public Task withCounter() { - return new CountTask(); + public class StagesHintTask extends Task { + private final List stages; + + public StagesHintTask(List stages) { + this.stages = stages; + } + + @Override + public Collection> getDependents() { + return Collections.singleton(Task.this); + } + + @Override + public void execute() { + setResult(Task.this.getResult()); + } + + public List getStages() { + return stages; + } + } + + public Task withCounter(String countStage) { + return new CountTask(countStage); } public static Task runAsync(ExceptionalRunnable closure) { @@ -859,11 +866,6 @@ public abstract class Task { public Collection> getDependencies() { return then == null ? Collections.emptySet() : Collections.singleton(then); } - - @Override - public List getStages() { - return Lang.merge(super.getStages(), then == null ? null : then.getStages()); - } }.setName(name); } @@ -932,11 +934,6 @@ public abstract class Task { public Collection> getDependents() { return tasks; } - - @Override - public List getStages() { - return tasks.stream().flatMap(task -> task.getStages().stream()).collect(Collectors.toList()); - } }; } @@ -1032,11 +1029,6 @@ public abstract class Task { public void execute() throws Exception { setResult(callable.apply(Task.this.getResult())); } - - @Override - public List getStages() { - return Lang.merge(Task.this.getStages(), super.getStages()); - } } /** @@ -1096,13 +1088,6 @@ public abstract class Task { public boolean isRelyingOnDependents() { return relyingOnDependents; } - - @Override - public List getStages() { - return Stream.of(Task.this.getStages(), super.getStages(), succ == null ? Collections.emptyList() : succ.getStages()) - .flatMap(Collection::stream) - .collect(Collectors.toList()); - } } public class StageTask extends Task { @@ -1116,19 +1101,19 @@ public abstract class Task { public void execute() throws Exception { setResult(Task.this.getResult()); } - - @Override - public List getStages() { - return Lang.merge(Task.this.getStages(), super.getStages()); - } } - private class CountTask extends Task { - private final UnaryOperator COUNTER = a -> { - int result = 0; - if (a != null) result += a; - return result + 1; - }; + public class CountTask extends Task { + private final String countStage; + + private CountTask(String countStage) { + this.countStage = countStage; + setSignificance(TaskSignificance.MINOR); + } + + public String getCountStage() { + return countStage; + } @Override public Collection> getDependents() { @@ -1146,13 +1131,8 @@ public abstract class Task { } @Override - public void postExecute() { - getProperties().put("count", COUNTER); - } - - @Override - public List getStages() { - return Lang.merge(Task.this.getStages(), super.getStages()); + public void postExecute() throws Exception { + notifyPropertiesChanged(); } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java index cc0ec8a5e..6cfd32a7a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java @@ -19,10 +19,7 @@ package org.jackhuang.hmcl.task; import org.jetbrains.annotations.Nullable; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -33,11 +30,10 @@ public abstract class TaskExecutor { protected final AtomicBoolean cancelled = new AtomicBoolean(false); protected Exception exception; private final List stages; - protected final Map> stageProperties = new HashMap<>(); public TaskExecutor(Task task) { this.firstTask = task; - this.stages = task.getStages(); + this.stages = task instanceof Task.StagesHintTask ? ((Task.StagesHintTask) task).getStages() : Collections.emptyList(); } public void addTaskListener(TaskListener taskListener) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskListener.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskListener.java index 36c876b1b..ed87b7d53 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskListener.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskListener.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.task; import java.util.EventListener; -import java.util.Map; /** * @@ -80,6 +79,6 @@ public abstract class TaskListener implements EventListener { public void onStop(boolean success, TaskExecutor executor) { } - public void onPropertiesUpdate(Map> stageProperties) { + public void onPropertiesUpdate(Task task) { } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java index 12c11215d..d31c6d5e1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java @@ -1,6 +1,6 @@ /* * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors + * 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 @@ -19,6 +19,7 @@ package org.jackhuang.hmcl.util; import com.google.gson.JsonParseException; import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.util.function.ExceptionalSupplier; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.FileUtils; @@ -53,6 +54,7 @@ public class CacheRepository { private Path cacheDirectory; private Path indexFile; private Map index; + private Map storages = new HashMap<>(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); public void changeDirectory(Path commonDir) { @@ -62,6 +64,10 @@ public class CacheRepository { lock.writeLock().lock(); try { + for (Storage storage : storages.values()) { + storage.changeDirectory(cacheDirectory); + } + if (Files.isRegularFile(indexFile)) { ETagIndex raw = JsonUtils.GSON.fromJson(FileUtils.readText(indexFile.toFile()), ETagIndex.class); if (raw == null) @@ -76,6 +82,7 @@ public class CacheRepository { } finally { lock.writeLock().unlock(); } + } public Path getCommonDirectory() { @@ -86,6 +93,15 @@ public class CacheRepository { return cacheDirectory; } + public Storage getStorage(String key) { + lock.readLock().lock(); + try { + return storages.computeIfAbsent(key, Storage::new); + } finally { + lock.readLock().unlock(); + } + } + protected Path getFile(String algorithm, String hash) { return getCacheDirectory().resolve(algorithm).resolve(hash.substring(0, 2)).resolve(hash); } @@ -353,6 +369,78 @@ public class CacheRepository { } } + /** + * Universal cache + */ + public static class Storage { + private final String name; + private Map storage; + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private Path indexFile; + + public Storage(String name) { + this.name = name; + } + + public Object getEntry(String key) { + lock.readLock().lock(); + try { + return storage.get(key); + } finally { + lock.readLock().unlock(); + } + } + + public void putEntry(String key, Object value) { + lock.writeLock().lock(); + try { + storage.put(key, value); + saveToFile(); + } finally { + lock.writeLock().unlock(); + } + } + + private void joinEntries(Map storage) { + this.storage.putAll(storage); + } + + private void changeDirectory(Path cacheDirectory) { + lock.writeLock().lock(); + try { + indexFile = cacheDirectory.resolve(name + ".json"); + if (Files.isRegularFile(indexFile)) { + joinEntries(JsonUtils.fromNonNullJson(FileUtils.readText(indexFile.toFile()), new TypeToken>() { + }.getType())); + } + } catch (IOException | JsonParseException e) { + LOG.log(Level.WARNING, "Unable to read storage {" + name + "} file"); + } finally { + lock.writeLock().unlock(); + } + } + + public void saveToFile() { + try (RandomAccessFile file = new RandomAccessFile(indexFile.toFile(), "rw"); FileChannel channel = file.getChannel()) { + FileLock lock = channel.lock(); + try { + Map indexOnDisk = JsonUtils.fromMaybeMalformedJson(new String(IOUtils.readFullyWithoutClosing(Channels.newInputStream(channel)), UTF_8), new TypeToken>() { + }.getType()); + if (indexOnDisk == null) indexOnDisk = new HashMap<>(); + indexOnDisk.putAll(storage); + channel.truncate(0); + OutputStream os = Channels.newOutputStream(channel); + IOUtils.write(JsonUtils.GSON.toJson(storage).getBytes(UTF_8), os); + this.storage = indexOnDisk; + } finally { + lock.release(); + } + } catch (IOException e) { + LOG.log(Level.WARNING, "Unable to write storage {" + name + "} file"); + } + } + } + private static CacheRepository instance = new CacheRepository(); public static CacheRepository getInstance() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java index cf0516ad8..67dc43d1b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java @@ -66,6 +66,14 @@ public final class JsonUtils { } } + public static T fromMaybeMalformedJson(String json, Type type) throws JsonParseException { + try { + return GSON.fromJson(json, type); + } catch (JsonSyntaxException e) { + return null; + } + } + public static GsonBuilder defaultGsonBuilder() { return new GsonBuilder() .enableComplexMapKeySerialization()