清理 org.jackhuang.hmcl.download.game (#4495)

1. 将 `java.io.File` 迁移至 NIO;
2. 将部分类迁移至 record。
This commit is contained in:
Glavo 2025-09-16 21:22:02 +08:00 committed by GitHub
parent fc1cce5e5a
commit 6a497df0d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 65 additions and 162 deletions

View File

@ -173,7 +173,7 @@ public final class LauncherHelper {
String agent = file.getAbsolutePath() + "=" + renderer.name().toLowerCase(Locale.ROOT); String agent = file.getAbsolutePath() + "=" + renderer.name().toLowerCase(Locale.ROOT);
if (GameLibrariesTask.shouldDownloadLibrary(repository, version.get(), lib, integrityCheck)) { if (GameLibrariesTask.shouldDownloadLibrary(repository, version.get(), lib, integrityCheck)) {
return new LibraryDownloadTask(dependencyManager, file, lib) return new LibraryDownloadTask(dependencyManager, file.toPath(), lib)
.thenRunAsync(() -> javaAgents.add(agent)); .thenRunAsync(() -> javaAgents.add(agent));
} else { } else {
javaAgents.add(agent); javaAgents.add(agent);

View File

@ -104,7 +104,7 @@ public class DefaultCacheRepository extends CacheRepository {
if (hash.equalsIgnoreCase(checksum)) if (hash.equalsIgnoreCase(checksum))
cacheLibrary(library, jar, false); cacheLibrary(library, jar, false);
} else if (library.getChecksums() != null && !library.getChecksums().isEmpty()) { } else if (library.getChecksums() != null && !library.getChecksums().isEmpty()) {
if (LibraryDownloadTask.checksumValid(jar.toFile(), library.getChecksums())) if (LibraryDownloadTask.checksumValid(jar, library.getChecksums()))
cacheLibrary(library, jar, true); cacheLibrary(library, jar, true);
} else { } else {
// or we will not cache the library // or we will not cache the library
@ -139,7 +139,7 @@ public class DefaultCacheRepository extends CacheRepository {
if (fileExists(SHA1, libIndex.getHash())) { if (fileExists(SHA1, libIndex.getHash())) {
Path file = getFile(SHA1, libIndex.getHash()); Path file = getFile(SHA1, libIndex.getHash());
if (libIndex.getType().equalsIgnoreCase(LibraryIndex.TYPE_FORGE)) { if (libIndex.getType().equalsIgnoreCase(LibraryIndex.TYPE_FORGE)) {
if (LibraryDownloadTask.checksumValid(file.toFile(), library.getChecksums())) if (LibraryDownloadTask.checksumValid(file, library.getChecksums()))
return Optional.of(file); return Optional.of(file);
} }
} }
@ -157,7 +157,7 @@ public class DefaultCacheRepository extends CacheRepository {
if (hash.equalsIgnoreCase(checksum)) if (hash.equalsIgnoreCase(checksum))
return Optional.of(restore(jar, () -> cacheLibrary(library, jar, false))); return Optional.of(restore(jar, () -> cacheLibrary(library, jar, false)));
} else if (library.getChecksums() != null && !library.getChecksums().isEmpty()) { } else if (library.getChecksums() != null && !library.getChecksums().isEmpty()) {
if (LibraryDownloadTask.checksumValid(jar.toFile(), library.getChecksums())) if (LibraryDownloadTask.checksumValid(jar, library.getChecksums()))
return Optional.of(restore(jar, () -> cacheLibrary(library, jar, true))); return Optional.of(restore(jar, () -> cacheLibrary(library, jar, true)));
} else { } else {
return Optional.of(jar); return Optional.of(jar);

View File

@ -32,7 +32,6 @@ import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import org.jackhuang.hmcl.util.versioning.VersionNumber; import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.nio.file.FileSystem; import java.nio.file.FileSystem;
@ -89,30 +88,29 @@ public final class GameLibrariesTask extends Task<Void> {
} }
public static boolean shouldDownloadLibrary(GameRepository gameRepository, Version version, Library library, boolean integrityCheck) { public static boolean shouldDownloadLibrary(GameRepository gameRepository, Version version, Library library, boolean integrityCheck) {
File file = gameRepository.getLibraryFile(version, library); Path file = gameRepository.getLibraryFile(version, library).toPath();
Path jar = file.toPath(); if (!Files.isRegularFile(file)) return true;
if (!file.isFile()) return true;
if (!integrityCheck) { if (!integrityCheck) {
return false; return false;
} }
try { try {
if (!library.getDownload().validateChecksum(jar, true)) { if (!library.getDownload().validateChecksum(file, true)) {
return true; return true;
} }
if (library.getChecksums() != null && !library.getChecksums().isEmpty() && !LibraryDownloadTask.checksumValid(file, library.getChecksums())) { if (library.getChecksums() != null && !library.getChecksums().isEmpty() && !LibraryDownloadTask.checksumValid(file, library.getChecksums())) {
return true; return true;
} }
if (FileUtils.getExtension(file.getName()).equals("jar")) { if (FileUtils.getExtension(file).equals("jar")) {
try { try {
FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER.checkIntegrity(jar, jar); FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER.checkIntegrity(file, file);
} catch (IOException ignored) { } catch (IOException ignored) {
// the Jar file is malformed, so re-download it. // the Jar file is malformed, so re-download it.
return true; return true;
} }
} }
} catch (IOException e) { } catch (IOException e) {
LOG.warning("Unable to calc hash value of file " + jar, e); LOG.warning("Unable to calc hash value of file " + file, e);
} }
return false; return false;
@ -160,13 +158,13 @@ public final class GameLibrariesTask extends Task<Void> {
} }
} }
File file = gameRepository.getLibraryFile(version, library); Path file = gameRepository.getLibraryFile(version, library).toPath();
if ("optifine".equals(library.getGroupId()) && file.exists() && GameVersionNumber.asGameVersion(gameRepository.getGameVersion(version)).compareTo("1.20.4") == 0) { if ("optifine".equals(library.getGroupId()) && Files.exists(file) && GameVersionNumber.asGameVersion(gameRepository.getGameVersion(version)).compareTo("1.20.4") == 0) {
String forgeVersion = LibraryAnalyzer.analyze(version, "1.20.4") String forgeVersion = LibraryAnalyzer.analyze(version, "1.20.4")
.getVersion(LibraryAnalyzer.LibraryType.FORGE) .getVersion(LibraryAnalyzer.LibraryType.FORGE)
.orElse(null); .orElse(null);
if (forgeVersion != null && LibraryAnalyzer.FORGE_OPTIFINE_BROKEN_RANGE.contains(VersionNumber.asVersion(forgeVersion))) { if (forgeVersion != null && LibraryAnalyzer.FORGE_OPTIFINE_BROKEN_RANGE.contains(VersionNumber.asVersion(forgeVersion))) {
try (FileSystem fs2 = CompressingUtils.createWritableZipFileSystem(file.toPath())) { try (FileSystem fs2 = CompressingUtils.createWritableZipFileSystem(file)) {
Files.deleteIfExists(fs2.getPath("/META-INF/mods.toml")); Files.deleteIfExists(fs2.getPath("/META-INF/mods.toml"));
} catch (IOException e) { } catch (IOException e) {
throw new IOException("Cannot fix optifine", e); throw new IOException("Cannot fix optifine", e);
@ -176,7 +174,7 @@ public final class GameLibrariesTask extends Task<Void> {
if (shouldDownloadLibrary(gameRepository, version, library, integrityCheck) && (library.hasDownloadURL() || !"optifine".equals(library.getGroupId()))) { if (shouldDownloadLibrary(gameRepository, version, library, integrityCheck) && (library.hasDownloadURL() || !"optifine".equals(library.getGroupId()))) {
dependencies.add(new LibraryDownloadTask(dependencyManager, file, library).withCounter("hmcl.install.libraries")); dependencies.add(new LibraryDownloadTask(dependencyManager, file, library).withCounter("hmcl.install.libraries"));
} else { } else {
dependencyManager.getCacheRepository().tryCacheLibrary(library, file.toPath()); dependencyManager.getCacheRepository().tryCacheLibrary(library, file);
} }
updateProgress(++progress, libraries.size()); updateProgress(++progress, libraries.size());

View File

@ -19,34 +19,14 @@ package org.jackhuang.hmcl.download.game;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.gson.JsonSerializable;
/** /**
*
* @author huangyuhui * @author huangyuhui
*/ */
@Immutable @Immutable
public final class GameRemoteLatestVersions { @JsonSerializable
public record GameRemoteLatestVersions(
@SerializedName("snapshot") @SerializedName("snapshot") String snapshot,
private final String snapshot; @SerializedName("release") String release) {
@SerializedName("release")
private final String release;
public GameRemoteLatestVersions() {
this(null, null);
}
public GameRemoteLatestVersions(String snapshot, String release) {
this.snapshot = snapshot;
this.release = release;
}
public String getRelease() {
return release;
}
public String getSnapshot() {
return snapshot;
}
} }

View File

@ -68,17 +68,12 @@ public final class GameRemoteVersion extends RemoteVersion {
private static Type getReleaseType(ReleaseType type) { private static Type getReleaseType(ReleaseType type) {
if (type == null) return Type.UNCATEGORIZED; if (type == null) return Type.UNCATEGORIZED;
switch (type) { return switch (type) {
case RELEASE: case RELEASE -> Type.RELEASE;
return Type.RELEASE; case SNAPSHOT -> Type.SNAPSHOT;
case SNAPSHOT: case UNKNOWN -> Type.UNCATEGORIZED;
return Type.SNAPSHOT; case PENDING -> Type.PENDING;
case UNKNOWN: default -> Type.OLD;
return Type.UNCATEGORIZED; };
case PENDING:
return Type.PENDING;
default:
return Type.OLD;
}
} }
} }

View File

@ -20,8 +20,8 @@ package org.jackhuang.hmcl.download.game;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.game.ReleaseType; import org.jackhuang.hmcl.game.ReleaseType;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonSerializable;
import org.jackhuang.hmcl.util.gson.Validation; import org.jackhuang.hmcl.util.gson.Validation;
import java.time.Instant; import java.time.Instant;
@ -30,58 +30,13 @@ import java.time.Instant;
* *
* @author huangyuhui * @author huangyuhui
*/ */
public final class GameRemoteVersionInfo implements Validation { @JsonSerializable
public record GameRemoteVersionInfo(
@SerializedName("id") @SerializedName("id") String gameVersion,
private final String gameVersion; @SerializedName("time") Instant time,
@SerializedName("releaseTime") Instant releaseTime,
@SerializedName("time") @SerializedName("type") ReleaseType type,
private final Instant time; @SerializedName("url") String url) implements Validation {
@SerializedName("releaseTime")
private final Instant releaseTime;
@SerializedName("type")
private final ReleaseType type;
@SerializedName("url")
private final String url;
public GameRemoteVersionInfo() {
this("", Instant.now(), Instant.now(), ReleaseType.UNKNOWN);
}
public GameRemoteVersionInfo(String gameVersion, Instant time, Instant releaseTime, ReleaseType type) {
this(gameVersion, time, releaseTime, type, Constants.DEFAULT_LIBRARY_URL + gameVersion + "/" + gameVersion + ".json");
}
public GameRemoteVersionInfo(String gameVersion, Instant time, Instant releaseTime, ReleaseType type, String url) {
this.gameVersion = gameVersion;
this.time = time;
this.releaseTime = releaseTime;
this.type = type;
this.url = url;
}
public String getGameVersion() {
return gameVersion;
}
public Instant getTime() {
return time;
}
public Instant getReleaseTime() {
return releaseTime;
}
public ReleaseType getType() {
return type;
}
public String getUrl() {
return url;
}
@Override @Override
public void validate() throws JsonParseException { public void validate() throws JsonParseException {

View File

@ -20,9 +20,9 @@ package org.jackhuang.hmcl.download.game;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.gson.JsonSerializable;
import org.jackhuang.hmcl.util.gson.Validation; import org.jackhuang.hmcl.util.gson.Validation;
import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
@ -30,34 +30,10 @@ import java.util.List;
* @author huangyuhui * @author huangyuhui
*/ */
@Immutable @Immutable
public final class GameRemoteVersions implements Validation { @JsonSerializable
public record GameRemoteVersions(
@SerializedName("versions") @SerializedName("versions") List<GameRemoteVersionInfo> versions,
private final List<GameRemoteVersionInfo> versions; @SerializedName("latest") GameRemoteLatestVersions latest) implements Validation {
@SerializedName("latest")
private final GameRemoteLatestVersions latest;
/**
* No-arg constructor for Gson.
*/
@SuppressWarnings("unused")
public GameRemoteVersions() {
this(Collections.emptyList(), null);
}
public GameRemoteVersions(List<GameRemoteVersionInfo> versions, GameRemoteLatestVersions latest) {
this.versions = versions;
this.latest = latest;
}
public GameRemoteLatestVersions getLatest() {
return latest;
}
public List<GameRemoteVersionInfo> getVersions() {
return versions;
}
@Override @Override
public void validate() throws JsonParseException { public void validate() throws JsonParseException {

View File

@ -24,11 +24,11 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem; import java.nio.file.FileSystem;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -62,11 +62,11 @@ public final class GameVerificationFixTask extends Task<Void> {
@Override @Override
public void execute() throws IOException { public void execute() throws IOException {
File jar = dependencyManager.getGameRepository().getVersionJar(version); Path jar = dependencyManager.getGameRepository().getVersionJar(version).toPath();
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, gameVersion); LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, gameVersion);
if (jar.exists() && GameVersionNumber.compare(gameVersion, "1.6") < 0 && analyzer.has(LibraryAnalyzer.LibraryType.FORGE)) { if (Files.exists(jar) && GameVersionNumber.compare(gameVersion, "1.6") < 0 && analyzer.has(LibraryAnalyzer.LibraryType.FORGE)) {
try (FileSystem fs = CompressingUtils.createWritableZipFileSystem(jar.toPath(), StandardCharsets.UTF_8)) { try (FileSystem fs = CompressingUtils.createWritableZipFileSystem(jar, StandardCharsets.UTF_8)) {
Files.deleteIfExists(fs.getPath("META-INF/MOJANG_C.DSA")); Files.deleteIfExists(fs.getPath("META-INF/MOJANG_C.DSA"));
Files.deleteIfExists(fs.getPath("META-INF/MOJANG_C.SF")); Files.deleteIfExists(fs.getPath("META-INF/MOJANG_C.SF"));
} }

View File

@ -70,21 +70,21 @@ public final class GameVersionList extends VersionList<GameRemoteVersion> {
versions.clear(); versions.clear();
if (unlistedVersions != null) { if (unlistedVersions != null) {
for (GameRemoteVersionInfo unlistedVersion : unlistedVersions.getVersions()) { for (GameRemoteVersionInfo unlistedVersion : unlistedVersions.versions()) {
versions.put(unlistedVersion.getGameVersion(), new GameRemoteVersion( versions.put(unlistedVersion.gameVersion(), new GameRemoteVersion(
unlistedVersion.getGameVersion(), unlistedVersion.gameVersion(),
unlistedVersion.getGameVersion(), unlistedVersion.gameVersion(),
Collections.singletonList(unlistedVersion.getUrl()), Collections.singletonList(unlistedVersion.url()),
unlistedVersion.getType(), unlistedVersion.getReleaseTime())); unlistedVersion.type(), unlistedVersion.releaseTime()));
} }
} }
for (GameRemoteVersionInfo remoteVersion : root.getVersions()) { for (GameRemoteVersionInfo remoteVersion : root.versions()) {
versions.put(remoteVersion.getGameVersion(), new GameRemoteVersion( versions.put(remoteVersion.gameVersion(), new GameRemoteVersion(
remoteVersion.getGameVersion(), remoteVersion.gameVersion(),
remoteVersion.getGameVersion(), remoteVersion.gameVersion(),
Collections.singletonList(remoteVersion.getUrl()), Collections.singletonList(remoteVersion.url()),
remoteVersion.getType(), remoteVersion.getReleaseTime())); remoteVersion.type(), remoteVersion.releaseTime()));
} }
} finally { } finally {
lock.writeLock().unlock(); lock.writeLock().unlock();

View File

@ -28,7 +28,6 @@ import org.jackhuang.hmcl.util.DigestUtils;
import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -43,7 +42,7 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public class LibraryDownloadTask extends Task<Void> { public class LibraryDownloadTask extends Task<Void> {
private FileDownloadTask task; private FileDownloadTask task;
protected final File jar; protected final Path jar;
protected final DefaultCacheRepository cacheRepository; protected final DefaultCacheRepository cacheRepository;
protected final AbstractDependencyManager dependencyManager; protected final AbstractDependencyManager dependencyManager;
protected final Library library; protected final Library library;
@ -51,7 +50,7 @@ public class LibraryDownloadTask extends Task<Void> {
private final Library originalLibrary; private final Library originalLibrary;
private boolean cached = false; private boolean cached = false;
public LibraryDownloadTask(AbstractDependencyManager dependencyManager, File file, Library library) { public LibraryDownloadTask(AbstractDependencyManager dependencyManager, Path file, Library library) {
this.dependencyManager = dependencyManager; this.dependencyManager = dependencyManager;
this.originalLibrary = library; this.originalLibrary = library;
@ -105,7 +104,7 @@ public class LibraryDownloadTask extends Task<Void> {
Optional<Path> libPath = cacheRepository.getLibrary(originalLibrary); Optional<Path> libPath = cacheRepository.getLibrary(originalLibrary);
if (libPath.isPresent()) { if (libPath.isPresent()) {
try { try {
FileUtils.copyFile(libPath.get(), jar.toPath()); FileUtils.copyFile(libPath.get(), jar);
cached = true; cached = true;
return; return;
} catch (IOException e) { } catch (IOException e) {
@ -117,7 +116,7 @@ public class LibraryDownloadTask extends Task<Void> {
List<URI> uris = dependencyManager.getDownloadProvider().injectURLWithCandidates(url); List<URI> uris = dependencyManager.getDownloadProvider().injectURLWithCandidates(url);
task = new FileDownloadTask(uris, jar.toPath(), task = new FileDownloadTask(uris, jar,
library.getDownload().getSha1() != null ? new IntegrityCheck("SHA-1", library.getDownload().getSha1()) : null); library.getDownload().getSha1() != null ? new IntegrityCheck("SHA-1", library.getDownload().getSha1()) : null);
task.setCacheRepository(cacheRepository); task.setCacheRepository(cacheRepository);
task.setCaching(true); task.setCaching(true);
@ -133,26 +132,26 @@ public class LibraryDownloadTask extends Task<Void> {
public void postExecute() throws Exception { public void postExecute() throws Exception {
if (!cached) { if (!cached) {
try { try {
cacheRepository.cacheLibrary(library, jar.toPath(), false); cacheRepository.cacheLibrary(library, jar, false);
} catch (IOException e) { } catch (IOException e) {
LOG.warning("Failed to cache downloaded library " + library, e); LOG.warning("Failed to cache downloaded library " + library, e);
} }
} }
} }
public static boolean checksumValid(File libPath, List<String> checksums) { public static boolean checksumValid(Path libPath, List<String> checksums) {
try { try {
if (checksums == null || checksums.isEmpty()) { if (checksums == null || checksums.isEmpty()) {
return true; return true;
} }
byte[] fileData = Files.readAllBytes(libPath.toPath()); byte[] fileData = Files.readAllBytes(libPath);
boolean valid = checksums.contains(DigestUtils.digestToString("SHA-1", fileData)); boolean valid = checksums.contains(DigestUtils.digestToString("SHA-1", fileData));
if (!valid && libPath.getName().endsWith(".jar")) { if (!valid && FileUtils.getName(libPath).endsWith(".jar")) {
valid = validateJar(fileData, checksums); valid = validateJar(fileData, checksums);
} }
return valid; return valid;
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); LOG.warning("Failed to validate " + libPath, e);
} }
return false; return false;
} }
@ -177,7 +176,7 @@ public class LibraryDownloadTask extends Task<Void> {
boolean failed = !checksums.contains(files.get("checksums.sha1")); boolean failed = !checksums.contains(files.get("checksums.sha1"));
if (!failed) { if (!failed) {
for (String hash : hashes) { for (String hash : hashes) {
if ((!hash.trim().equals("")) && (hash.contains(" "))) { if (!hash.trim().isEmpty() && hash.contains(" ")) {
String[] e = hash.split(" "); String[] e = hash.split(" ");
String validChecksum = e[0]; String validChecksum = e[0];
String target = hash.substring(validChecksum.length() + 1); String target = hash.substring(validChecksum.length() + 1);