add: redownload corrupt files when game crashed

This commit is contained in:
huanghongxun
2020-02-20 00:02:56 +08:00
parent cd9180c69a
commit 91682aeafa
16 changed files with 122 additions and 76 deletions

View File

@@ -25,7 +25,6 @@ import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import java.io.IOException;
import java.nio.file.Path;
@@ -76,7 +75,7 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
}
@Override
public Task<?> checkGameCompletionAsync(Version original) {
public Task<?> checkGameCompletionAsync(Version original, boolean integrityCheck) {
Version version = original.resolve(repository);
return Task.allOf(
Task.composeAsync(() -> {
@@ -85,14 +84,14 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
else
return null;
}),
new GameAssetDownloadTask(this, version, GameAssetDownloadTask.DOWNLOAD_INDEX_IF_NECESSARY),
new GameLibrariesTask(this, version)
new GameAssetDownloadTask(this, version, GameAssetDownloadTask.DOWNLOAD_INDEX_IF_NECESSARY, integrityCheck),
new GameLibrariesTask(this, version, integrityCheck)
);
}
@Override
public Task<?> checkLibraryCompletionAsync(Version version) {
return new GameLibrariesTask(this, version, version.getLibraries());
public Task<?> checkLibraryCompletionAsync(Version version, boolean integrityCheck) {
return new GameLibrariesTask(this, version, integrityCheck, version.getLibraries());
}
@Override

View File

@@ -46,7 +46,7 @@ public interface DependencyManager {
*
* @return the task to check game completion.
*/
Task<?> checkGameCompletionAsync(Version version);
Task<?> checkGameCompletionAsync(Version version, boolean integrityCheck);
/**
* Check if the game is complete.
@@ -54,7 +54,7 @@ public interface DependencyManager {
*
* @return the task to check game completion.
*/
Task<?> checkLibraryCompletionAsync(Version version);
Task<?> checkLibraryCompletionAsync(Version version, boolean integrityCheck);
/**
* The builder to build a brand new game then libraries such as Forge, LiteLoader and OptiFine.

View File

@@ -84,7 +84,7 @@ public final class FabricInstallTask extends Task<Version> {
public void execute() {
setResult(getPatch(JsonUtils.GSON.fromJson(launchMetaTask.getResult(), FabricInfo.class), remote.getGameVersion(), remote.getSelfVersion()));
dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult()));
dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult(), true));
}
private static String getLaunchMetaUrl(String gameVersion, String loaderVersion) {

View File

@@ -45,14 +45,7 @@ import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.zip.ZipException;
@@ -133,7 +126,7 @@ public class ForgeNewInstallTask extends Task<Version> {
}
}
dependents.add(new GameLibrariesTask(dependencyManager, version, profile.getLibraries()));
dependents.add(new GameLibrariesTask(dependencyManager, version, true, profile.getLibraries()));
}
@Override
@@ -273,7 +266,7 @@ public class ForgeNewInstallTask extends Task<Version> {
.setPriority(30000)
.setId(LibraryAnalyzer.LibraryType.FORGE.getPatchId())
.setVersion(selfVersion));
dependencies.add(dependencyManager.checkLibraryCompletionAsync(forgeVersion));
dependencies.add(dependencyManager.checkLibraryCompletionAsync(forgeVersion, true));
FileUtils.deleteDirectory(temp.toFile());
}

View File

@@ -27,11 +27,7 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.IOUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.*;
import java.nio.file.Path;
import java.util.LinkedList;
import java.util.List;
@@ -90,7 +86,7 @@ public class ForgeOldInstallTask extends Task<Version> {
.setPriority(30000)
.setId(LibraryAnalyzer.LibraryType.FORGE.getPatchId())
.setVersion(selfVersion));
dependencies.add(dependencyManager.checkLibraryCompletionAsync(installProfile.getVersionInfo()));
dependencies.add(dependencyManager.checkLibraryCompletionAsync(installProfile.getVersionInfo(), true));
} catch (ZipException ex) {
throw new ArtifactMalformedException("Malformed forge installer file", ex);
}

View File

@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.CacheRepository;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
@@ -37,6 +38,7 @@ import java.net.URL;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.stream.Collectors;
/**
@@ -49,6 +51,7 @@ public final class GameAssetDownloadTask extends Task<Void> {
private final Version version;
private final AssetIndexInfo assetIndexInfo;
private final File assetIndexFile;
private final boolean integrityCheck;
private final List<Task<?>> dependents = new LinkedList<>();
private final List<Task<?>> dependencies = new LinkedList<>();
@@ -58,11 +61,12 @@ public final class GameAssetDownloadTask extends Task<Void> {
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
* @param version the game version
*/
public GameAssetDownloadTask(AbstractDependencyManager dependencyManager, Version version, boolean forceDownloadingIndex) {
public GameAssetDownloadTask(AbstractDependencyManager dependencyManager, Version version, boolean forceDownloadingIndex, boolean integrityCheck) {
this.dependencyManager = dependencyManager;
this.version = version.resolve(dependencyManager.getGameRepository());
this.assetIndexInfo = this.version.getAssetIndex();
this.assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId());
this.integrityCheck = integrityCheck;
if (!assetIndexFile.exists() || forceDownloadingIndex) {
dependents.add(new GameAssetIndexDownloadTask(dependencyManager, this.version));
@@ -101,9 +105,14 @@ public final class GameAssetDownloadTask extends Task<Void> {
throw new InterruptedException();
File file = dependencyManager.getGameRepository().getAssetObject(version.getId(), assetIndexInfo.getId(), assetObject);
if (file.isFile())
dependencyManager.getCacheRepository().tryCacheFile(file.toPath(), CacheRepository.SHA1, assetObject.getHash());
else {
boolean download = !file.isFile();
try {
if (!download && integrityCheck && !assetObject.validateChecksum(file.toPath(), true))
download = true;
} catch (IOException e) {
Logging.LOG.log(Level.WARNING, "Unable to calc hash value of file " + file.toPath(), e);
}
if (download) {
List<URL> urls = dependencyManager.getPreferredDownloadProviders().stream()
.map(downloadProvider -> downloadProvider.getAssetBaseURL() + assetObject.getLocation())
.map(NetworkUtils::toURL)
@@ -116,6 +125,8 @@ public final class GameAssetDownloadTask extends Task<Void> {
.setCaching(true)
.setCandidate(dependencyManager.getCacheRepository().getCommonDirectory()
.resolve("assets").resolve("objects").resolve(assetObject.getLocation())));
} else {
dependencyManager.getCacheRepository().tryCacheFile(file.toPath(), CacheRepository.SHA1, assetObject.getHash());
}
updateProgress(++progress, index.getObjects().size());

View File

@@ -71,8 +71,8 @@ public class GameInstallTask extends Task<Version> {
Version version = new Version(this.version.getId()).addPatch(patch);
dependencies.add(new GameDownloadTask(dependencyManager, remote.getGameVersion(), version)
.thenComposeAsync(Task.allOf(
new GameAssetDownloadTask(dependencyManager, version, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY),
new GameLibrariesTask(dependencyManager, version)
new GameAssetDownloadTask(dependencyManager, version, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY, true),
new GameLibrariesTask(dependencyManager, version, true)
).withStage("hmcl.install.assets").withComposeAsync(gameRepository.save(version))));
}

View File

@@ -21,10 +21,14 @@ import org.jackhuang.hmcl.download.AbstractDependencyManager;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Logging;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
/**
* This task is to download game libraries.
@@ -36,6 +40,7 @@ public final class GameLibrariesTask extends Task<Void> {
private final AbstractDependencyManager dependencyManager;
private final Version version;
private final boolean integrityCheck;
private final List<Library> libraries;
private final List<Task<?>> dependencies = new LinkedList<>();
@@ -43,21 +48,22 @@ public final class GameLibrariesTask extends Task<Void> {
* Constructor.
*
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
* @param version the game version
* @param version the game version
*/
public GameLibrariesTask(AbstractDependencyManager dependencyManager, Version version) {
this(dependencyManager, version, version.resolve(dependencyManager.getGameRepository()).getLibraries());
public GameLibrariesTask(AbstractDependencyManager dependencyManager, Version version, boolean integrityCheck) {
this(dependencyManager, version, integrityCheck, version.resolve(dependencyManager.getGameRepository()).getLibraries());
}
/**
* Constructor.
*
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
* @param version the game version
* @param version the game version
*/
public GameLibrariesTask(AbstractDependencyManager dependencyManager, Version version, List<Library> libraries) {
public GameLibrariesTask(AbstractDependencyManager dependencyManager, Version version, boolean integrityCheck, List<Library> libraries) {
this.dependencyManager = dependencyManager;
this.version = version;
this.integrityCheck = integrityCheck;
this.libraries = libraries;
setSignificance(TaskSignificance.MODERATE);
@@ -72,10 +78,22 @@ public final class GameLibrariesTask extends Task<Void> {
public void execute() {
libraries.stream().filter(Library::appliesToCurrentEnvironment).forEach(library -> {
File file = dependencyManager.getGameRepository().getLibraryFile(version, library);
if (!file.exists())
Path jar = file.toPath();
boolean download = !file.isFile();
try {
if (!download && integrityCheck && !library.getDownload().validateChecksum(jar, true)) download = true;
if (!download && integrityCheck &&
library.getChecksums() != null && !library.getChecksums().isEmpty() &&
!LibraryDownloadTask.checksumValid(jar.toFile(), library.getChecksums())) download = true;
} catch (IOException e) {
Logging.LOG.log(Level.WARNING, "Unable to calc hash value of file " + jar, e);
}
if (download) {
dependencies.add(new LibraryDownloadTask(dependencyManager, file, library));
else
} else {
dependencyManager.getCacheRepository().tryCacheLibrary(library, file.toPath());
}
});
}

View File

@@ -19,12 +19,7 @@ package org.jackhuang.hmcl.download.liteloader;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.game.Artifact;
import org.jackhuang.hmcl.game.LibrariesDownloadInfo;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.LibraryDownloadInfo;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Lang;
@@ -79,7 +74,7 @@ public final class LiteLoaderInstallTask extends Task<Version> {
.setLogging(Collections.emptyMap()) // Mods may log in malformed format, causing XML parser to crash. So we suppress using official log4j configuration
);
dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult()));
dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult(), true));
}
}

View File

@@ -20,14 +20,7 @@ package org.jackhuang.hmcl.download.optifine;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.download.VersionMismatchException;
import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.game.Artifact;
import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.game.LibrariesDownloadInfo;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.LibraryDownloadInfo;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.io.CompressingUtils;
@@ -184,7 +177,7 @@ public final class OptiFineInstallTask extends Task<Version> {
libraries
));
dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult()));
dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult(), true));
}
public static class UnsupportedOptiFineInstallationException extends Exception {

View File

@@ -18,9 +18,14 @@
package org.jackhuang.hmcl.game;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.util.DigestUtils;
import org.jackhuang.hmcl.util.Hex;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.Validation;
import java.io.IOException;
import java.nio.file.Path;
/**
*
* @author huangyuhui
@@ -56,4 +61,9 @@ public final class AssetObject implements Validation {
if (StringUtils.isBlank(hash) || hash.length() < 2)
throw new JsonParseException("AssetObject hash cannot be blank.");
}
public boolean validateChecksum(Path file, boolean defaultValue) throws IOException {
if (hash == null) return defaultValue;
return Hex.encodeHex(DigestUtils.digest("SHA-1", file)).equalsIgnoreCase(hash);
}
}

View File

@@ -19,12 +19,13 @@ package org.jackhuang.hmcl.game;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.ToStringBuilder;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.gson.TolerableValidationException;
import org.jackhuang.hmcl.util.gson.Validation;
import java.io.IOException;
import java.nio.file.Path;
/**
*
* @author huangyuhui
@@ -79,4 +80,9 @@ public class DownloadInfo implements Validation {
if (StringUtils.isBlank(url))
throw new TolerableValidationException();
}
public boolean validateChecksum(Path file, boolean defaultValue) throws IOException {
if (getSha1() == null) return defaultValue;
return Hex.encodeHex(DigestUtils.digest("SHA-1", file)).equalsIgnoreCase(getSha1());
}
}