diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index 031272097..7032f73a2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -133,7 +133,7 @@ public final class LauncherHelper { HMCLGameRepository repository = profile.getRepository(); DefaultDependencyManager dependencyManager = profile.getDependency(); Version version = MaintainTask.maintain(repository, repository.getResolvedVersion(selectedVersion)); - Optional gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version)); + Optional gameVersion = repository.getGameVersion(version); boolean integrityCheck = repository.unmarkVersionLaunchedAbnormally(selectedVersion); CountDownLatch launchingLatch = new CountDownLatch(1); @@ -311,7 +311,7 @@ public final class LauncherHelper { // Without onAccept called, the launching operation will be terminated. - VersionNumber gameVersion = VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse("Unknown")); + VersionNumber gameVersion = VersionNumber.asVersion(profile.getRepository().getGameVersion(version).orElse("Unknown")); JavaVersion java = setting.getJavaVersion(); if (java == null) { Controllers.dialog(i18n("launch.wrong_javadir"), i18n("message.warning"), MessageType.WARNING, onAccept); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItem.java index cd01f6bee..97d747d05 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItem.java @@ -26,7 +26,6 @@ import javafx.scene.control.Control; import javafx.scene.control.Skin; import javafx.scene.image.Image; import org.jackhuang.hmcl.download.LibraryAnalyzer; -import org.jackhuang.hmcl.game.GameVersion; import org.jackhuang.hmcl.mod.ModpackConfiguration; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.util.i18n.I18n; @@ -41,8 +40,6 @@ import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT; import static org.jackhuang.hmcl.util.Lang.handleUncaught; import static org.jackhuang.hmcl.util.Lang.threadPool; import static org.jackhuang.hmcl.util.Logging.LOG; -import static org.jackhuang.hmcl.util.StringUtils.removePrefix; -import static org.jackhuang.hmcl.util.StringUtils.removeSuffix; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class GameItem extends Control { @@ -61,7 +58,7 @@ public class GameItem extends Control { this.version = id; // GameVersion.minecraftVersion() is a time-costing job (up to ~200 ms) - CompletableFuture.supplyAsync(() -> GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse(i18n("message.unknown")), POOL_VERSION_RESOLVE) + CompletableFuture.supplyAsync(() -> profile.getRepository().getGameVersion(id).orElse(i18n("message.unknown")), POOL_VERSION_RESOLVE) .thenAcceptAsync(game -> { StringBuilder libraries = new StringBuilder(game); LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id)); @@ -72,7 +69,7 @@ public class GameItem extends Control { if (I18n.hasKey("install.installer." + libraryId)) { libraries.append(", ").append(i18n("install.installer." + libraryId)); if (libraryVersion != null) - libraries.append(": ").append(modifyVersion("", libraryVersion.replaceAll("(?i)" + libraryId, ""))); + libraries.append(": ").append(LibraryAnalyzer.modifyVersion(game, libraryVersion.replaceAll("(?i)" + libraryId, ""))); } } @@ -123,8 +120,4 @@ public class GameItem extends Control { public ObjectProperty imageProperty() { return image; } - - private static String modifyVersion(String gameVersion, String version) { - return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_"); - } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java index 495e66dc9..4639d5a5f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java @@ -22,7 +22,6 @@ import javafx.scene.Node; import javafx.scene.control.Skin; import javafx.stage.FileChooser; import org.jackhuang.hmcl.download.LibraryAnalyzer; -import org.jackhuang.hmcl.game.GameVersion; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.task.Schedulers; @@ -70,7 +69,7 @@ public class InstallerListPage extends ListPageBase implements Ve this.gameVersion = null; CompletableFuture.supplyAsync(() -> { - gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse(null); + gameVersion = profile.getRepository().getGameVersion(version).orElse(null); return LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(versionId)); }).thenAcceptAsync(analyzer -> { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java index 495b07a8b..bce0f6bba 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java @@ -23,7 +23,6 @@ import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.scene.Node; import javafx.stage.FileChooser; -import org.jackhuang.hmcl.game.GameVersion; import org.jackhuang.hmcl.game.World; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Theme; @@ -87,7 +86,7 @@ public class WorldListPage extends ListPageBase implements Versio setLoading(true); return CompletableFuture - .runAsync(() -> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse(null)) + .runAsync(() -> gameVersion = profile.getRepository().getGameVersion(id).orElse(null)) .thenApplyAsync(unused -> { try (Stream stream = World.getWorlds(savesDir)) { return stream.parallel().collect(Collectors.toList()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java index 2a19dee13..6783e5deb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java @@ -23,7 +23,6 @@ import org.jackhuang.hmcl.download.game.GameDownloadTask; import org.jackhuang.hmcl.download.game.GameLibrariesTask; import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; import org.jackhuang.hmcl.game.DefaultGameRepository; -import org.jackhuang.hmcl.game.GameVersion; import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.task.Task; @@ -99,7 +98,7 @@ public class DefaultDependencyManager extends AbstractDependencyManager { return Task.composeAsync(() -> { List> tasks = new ArrayList<>(); - Optional gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version)); + Optional gameVersion = repository.getGameVersion(version); if (!gameVersion.isPresent()) return null; LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version.resolvePreservingPatches(getGameRepository())); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java index af1a5e007..1760244a8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java @@ -29,6 +29,8 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.StringUtils.removePrefix; +import static org.jackhuang.hmcl.util.StringUtils.removeSuffix; public final class LibraryAnalyzer implements Iterable { private Version version; @@ -153,6 +155,10 @@ public final class LibraryAnalyzer implements Iterable { * @throws VersionMismatchException if required game version of installer does not match the actual one. */ public static boolean detectForgeInstallerType(DependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException { - Optional gameVersion = GameVersion.minecraftVersion(dependencyManager.getGameRepository().getVersionJar(version)); + Optional gameVersion = dependencyManager.getGameRepository().getGameVersion(version); if (!gameVersion.isPresent()) throw new IOException(); try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { String installProfileText = FileUtils.readText(fs.getPath("install_profile.json")); @@ -165,7 +164,7 @@ public final class ForgeInstallTask extends Task { * @throws VersionMismatchException if required game version of installer does not match the actual one. */ public static Task install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException { - Optional gameVersion = GameVersion.minecraftVersion(dependencyManager.getGameRepository().getVersionJar(version)); + Optional gameVersion = dependencyManager.getGameRepository().getGameVersion(version); if (!gameVersion.isPresent()) throw new IOException(); try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { String installProfileText = FileUtils.readText(fs.getPath("install_profile.json")); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java index 8d47c882c..72c4afaa3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java @@ -208,8 +208,7 @@ public final class OptiFineInstallTask extends Task { * @throws VersionMismatchException if required game version of installer does not match the actual one. */ public static Task install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException { - File jar = dependencyManager.getGameRepository().getVersionJar(version); - Optional gameVersion = GameVersion.minecraftVersion(jar); + Optional gameVersion = dependencyManager.getGameRepository().getGameVersion(version); if (!gameVersion.isPresent()) throw new IOException(); try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { Path configClass = fs.getPath("Config.class"); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java index 926ce7e09..b44504517 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java @@ -50,6 +50,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.stream.Stream; @@ -64,6 +65,7 @@ public class DefaultGameRepository implements GameRepository { private File baseDirectory; protected Map versions; + private ConcurrentHashMap> gameVersions = new ConcurrentHashMap<>(); public DefaultGameRepository(File baseDirectory) { this.baseDirectory = baseDirectory; @@ -136,6 +138,21 @@ public class DefaultGameRepository implements GameRepository { return new File(getVersionRoot(id), id + ".jar"); } + @Override + public Optional getGameVersion(Version version) { + // This implementation may cause multiple flows against the same version entering + // this function, which is accepted because GameVersion::minecraftVersion should + // be consistent. + File versionJar = getVersionJar(version); + if (gameVersions.containsKey(versionJar)) { + return gameVersions.get(versionJar); + } else { + Optional gameVersion = GameVersion.minecraftVersion(versionJar); + gameVersions.put(versionJar, gameVersion); + return gameVersion; + } + } + @Override public File getNativeDirectory(String id) { return new File(getVersionRoot(id), "natives"); @@ -321,6 +338,7 @@ public class DefaultGameRepository implements GameRepository { } } + this.gameVersions.clear(); this.versions = versions; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameRepository.java index 6eda47f52..551ef912e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameRepository.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Optional; /** * Supports operations on versioning. @@ -134,6 +135,30 @@ public interface GameRepository extends VersionProvider { */ File getVersionJar(Version version); + /** + * Detect game version. + * + * This method is time-consuming, but the result will be cached. + * Consider running this job in IO scheduler. + * + * @param version version + * @return game version, or empty if an error occurred in detection. + */ + Optional getGameVersion(Version version); + + /** + * Detect game version. + * + * This method is time-consuming, but the result will be cached. + * Consider running this job in IO scheduler. + * + * @param versionId id of version + * @return game version, or empty if an error occurred in detection. + */ + default Optional getGameVersion(String versionId) throws VersionNotFoundException { + return getGameVersion(getVersion(versionId)); + } + /** * Get minecraft jar * diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackExportTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackExportTask.java index 81f5cbf8d..973e8b13d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackExportTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackExportTask.java @@ -19,7 +19,6 @@ package org.jackhuang.hmcl.mod.mcbbs; import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.game.DefaultGameRepository; -import org.jackhuang.hmcl.game.GameVersion; import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.mod.ModAdviser; import org.jackhuang.hmcl.mod.Modpack; @@ -81,7 +80,7 @@ public class McbbsModpackExportTask extends Task { }); LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(version)); - String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version)) + String gameVersion = repository.getGameVersion(version) .orElseThrow(() -> new IOException("Cannot parse the version of " + version)); List addons = new ArrayList<>(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackExportTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackExportTask.java index ee3f7d4ad..0f1778a93 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackExportTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackExportTask.java @@ -19,7 +19,6 @@ package org.jackhuang.hmcl.mod.multimc; import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.game.DefaultGameRepository; -import org.jackhuang.hmcl.game.GameVersion; import org.jackhuang.hmcl.mod.ModAdviser; import org.jackhuang.hmcl.mod.Modpack; import org.jackhuang.hmcl.mod.ModpackExportInfo; @@ -72,7 +71,7 @@ public class MultiMCModpackExportTask extends Task { zip.putDirectory(repository.getRunDirectory(versionId).toPath(), ".minecraft", path -> Modpack.acceptFile(path, blackList, whitelist)); LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId)); - String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(versionId)) + String gameVersion = repository.getGameVersion(versionId) .orElseThrow(() -> new IOException("Cannot parse the version of " + versionId)); List components = new ArrayList<>(); components.add(new MultiMCManifest.MultiMCManifestComponent(true, false, "net.minecraft", gameVersion)); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackExportTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackExportTask.java index 4e0487e14..3869f1d39 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackExportTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackExportTask.java @@ -19,7 +19,6 @@ package org.jackhuang.hmcl.mod.server; import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.game.DefaultGameRepository; -import org.jackhuang.hmcl.game.GameVersion; import org.jackhuang.hmcl.mod.ModAdviser; import org.jackhuang.hmcl.mod.Modpack; import org.jackhuang.hmcl.mod.ModpackConfiguration; @@ -81,7 +80,7 @@ public class ServerModpackExportTask extends Task { }); LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId)); - String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(versionId)) + String gameVersion = repository.getGameVersion(versionId) .orElseThrow(() -> new IOException("Cannot parse the version of " + versionId)); List addons = new ArrayList<>(); addons.add(new ServerModpackManifest.Addon(MINECRAFT.getPatchId(), gameVersion));