mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-02-23 17:19:44 +08:00
fix: mcbbs modpack completion
This commit is contained in:
parent
94cd33af5c
commit
13fa713d58
@ -35,7 +35,11 @@ import org.jackhuang.hmcl.launch.ProcessListener;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseCompletionException;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseInstallTask;
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackLocalInstallTask;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackLocalInstallTask;
|
||||
import org.jackhuang.hmcl.setting.LauncherVisibility;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
@ -140,10 +144,12 @@ public final class LauncherHelper {
|
||||
}), Task.composeAsync(() -> {
|
||||
try {
|
||||
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));
|
||||
if ("Curse".equals(configuration.getType()))
|
||||
if (CurseInstallTask.MODPACK_TYPE.equals(configuration.getType()))
|
||||
return new CurseCompletionTask(dependencyManager, selectedVersion);
|
||||
else if ("Server".equals(configuration.getType()))
|
||||
else if (ServerModpackLocalInstallTask.MODPACK_TYPE.equals(configuration.getType()))
|
||||
return new ServerModpackCompletionTask(dependencyManager, selectedVersion);
|
||||
else if (McbbsModpackLocalInstallTask.MODPACK_TYPE.equals(configuration.getType()))
|
||||
return new McbbsModpackCompletionTask(dependencyManager, selectedVersion);
|
||||
else
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
|
@ -199,6 +199,11 @@ public final class ModpackHelper {
|
||||
throw new MismatchedModpackTypeException(HMCLModpackInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
|
||||
|
||||
return new ModpackUpdateTask(profile.getRepository(), name, new HMCLModpackInstallTask(profile, zipFile, modpack, name));
|
||||
case McbbsModpackLocalInstallTask.MODPACK_TYPE:
|
||||
if (!(modpack.getManifest() instanceof McbbsModpackManifest))
|
||||
throw new MismatchedModpackTypeException(McbbsModpackLocalInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
|
||||
|
||||
return new ModpackUpdateTask(profile.getRepository(), name, new McbbsModpackLocalInstallTask(profile.getDependency(), zipFile, modpack, (McbbsModpackManifest) modpack.getManifest(), name));
|
||||
case ServerModpackLocalInstallTask.MODPACK_TYPE:
|
||||
if (!(modpack.getManifest() instanceof ServerModpackManifest))
|
||||
throw new MismatchedModpackTypeException(ServerModpackLocalInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
|
||||
|
@ -82,7 +82,7 @@ public class GameItem extends Control {
|
||||
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
ModpackConfiguration<Void> config = profile.getRepository().readModpackConfiguration(version);
|
||||
ModpackConfiguration<?> config = profile.getRepository().readModpackConfiguration(version);
|
||||
if (config == null) return;
|
||||
tag.set(config.getVersion());
|
||||
} catch (IOException e) {
|
||||
|
@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.stage.FileChooser;
|
||||
@ -40,10 +39,8 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
|
@ -238,11 +238,11 @@ modpack.choose.local.detail=你可以直接将整合包文件拖入本页面以
|
||||
modpack.choose.remote=从互联网下载整合包
|
||||
modpack.choose.remote.detail=需要提供整合包的下载链接
|
||||
modpack.choose.remote.tooltip=要下载的整合包的链接
|
||||
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 Markdown(图片请用网络图)。
|
||||
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 HTML(图片请用网络图)。
|
||||
modpack.description=整合包描述
|
||||
modpack.enter_name=给游戏起个你喜欢的名字
|
||||
modpack.export=导出整合包
|
||||
modpack.export.as=请选择整合包类型 (若无法决定,请选择 HMCL 类型)
|
||||
modpack.export.as=请选择整合包类型 (若无法决定,请选择我的世界中文论坛整合包标准)
|
||||
modpack.file_api=整合包下载链接前缀
|
||||
modpack.files.blueprints=BuildCraft 蓝图
|
||||
modpack.files.config=Mod 配置文件
|
||||
@ -278,7 +278,7 @@ modpack.type.curse.tolerable_error=但未能完成 Curse 整合包文件的下
|
||||
modpack.type.curse.error=未能完成 Curse 整合包的下载,请多次重试或设置代理
|
||||
modpack.type.curse.not_found=部分必需文件已经在网络中被删除并且再也无法下载,请尝试该整合包的最新版本或者安装其他整合包。
|
||||
modpack.type.mcbbs=我的世界中文论坛整合包标准
|
||||
modpack.type.hmcl.export=可以被 Hello Minecraft! Launcher (HMCL) 导入
|
||||
modpack.type.mcbbs.export=可以被 Hello Minecraft! Launcher (HMCL) 导入
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.type.multimc.export=可以被 Hello Minecraft! Launcher (HMCL) 和 MultiMC 导入
|
||||
modpack.type.server=服务器自动更新整合包
|
||||
|
@ -33,7 +33,7 @@ import java.util.List;
|
||||
import static org.jackhuang.hmcl.util.DigestUtils.digest;
|
||||
import static org.jackhuang.hmcl.util.Hex.encodeHex;
|
||||
|
||||
public final class MinecraftInstanceTask<T> extends Task<Void> {
|
||||
public final class MinecraftInstanceTask<T> extends Task<ModpackConfiguration<T>> {
|
||||
|
||||
private final File zipFile;
|
||||
private final Charset encoding;
|
||||
@ -73,6 +73,8 @@ public final class MinecraftInstanceTask<T> extends Task<Void> {
|
||||
});
|
||||
}
|
||||
|
||||
FileUtils.writeText(jsonFile, JsonUtils.GSON.toJson(new ModpackConfiguration<>(manifest, type, name, version, overrides)));
|
||||
ModpackConfiguration<T> configuration = new ModpackConfiguration<>(manifest, type, name, version, overrides);
|
||||
FileUtils.writeText(jsonFile, JsonUtils.GSON.toJson(configuration));
|
||||
setResult(configuration);
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,14 @@ public final class ModManager {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public GameRepository getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private Path getModsDirectory() {
|
||||
return repository.getRunDirectory(id).toPath().resolve("mods");
|
||||
}
|
||||
|
@ -21,181 +21,302 @@ import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseCompletionException;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseMetaMod;
|
||||
import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class McbbsModpackCompletionTask extends Task<Void> {
|
||||
import static org.jackhuang.hmcl.util.DigestUtils.digest;
|
||||
import static org.jackhuang.hmcl.util.Hex.encodeHex;
|
||||
|
||||
public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
|
||||
private final DefaultDependencyManager dependency;
|
||||
private final DefaultGameRepository repository;
|
||||
private final ModManager modManager;
|
||||
private final String version;
|
||||
private ModpackConfiguration<McbbsModpackManifest> manifest;
|
||||
private final File configurationFile;
|
||||
private ModpackConfiguration<McbbsModpackManifest> configuration;
|
||||
private GetTask dependent;
|
||||
private McbbsModpackManifest remoteManifest;
|
||||
private McbbsModpackManifest manifest;
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
|
||||
private final AtomicBoolean allNameKnown = new AtomicBoolean(true);
|
||||
private final AtomicInteger finished = new AtomicInteger(0);
|
||||
private final AtomicBoolean notFound = new AtomicBoolean(false);
|
||||
|
||||
public McbbsModpackCompletionTask(DefaultDependencyManager dependencyManager, String version) {
|
||||
this(dependencyManager, version, null);
|
||||
}
|
||||
|
||||
public McbbsModpackCompletionTask(DefaultDependencyManager dependencyManager, String version, ModpackConfiguration<McbbsModpackManifest> manifest) {
|
||||
public McbbsModpackCompletionTask(DefaultDependencyManager dependencyManager, String version, ModpackConfiguration<McbbsModpackManifest> configuration) {
|
||||
this.dependency = dependencyManager;
|
||||
this.repository = dependencyManager.getGameRepository();
|
||||
this.modManager = repository.getModManager(version);
|
||||
this.version = version;
|
||||
this.configurationFile = repository.getModpackConfiguration(version);
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
if (manifest == null) {
|
||||
try {
|
||||
File manifestFile = repository.getModpackConfiguration(version);
|
||||
if (manifestFile.exists()) {
|
||||
this.manifest = JsonUtils.GSON.fromJson(FileUtils.readText(manifestFile), new TypeToken<ModpackConfiguration<McbbsModpackManifest>>() {
|
||||
@Override
|
||||
public CompletableFuture<Void> getFuture(TaskCompletableFuture executor) {
|
||||
return breakable(CompletableFuture.runAsync(wrap(() -> {
|
||||
if (configuration == null) {
|
||||
// Load configuration from disk
|
||||
try {
|
||||
configuration = JsonUtils.fromNonNullJson(FileUtils.readText(configurationFile), new TypeToken<ModpackConfiguration<McbbsModpackManifest>>() {
|
||||
}.getType());
|
||||
} catch (IOException | JsonParseException e) {
|
||||
throw new IOException("Malformed modpack configuration");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to read mcbbs modpack manifest.json", e);
|
||||
}
|
||||
manifest = configuration.getManifest();
|
||||
if (manifest == null) throw new CustomException();
|
||||
})).thenComposeAsync(unused -> {
|
||||
// we first download latest manifest
|
||||
return breakable(CompletableFuture.runAsync(wrap(() -> {
|
||||
if (StringUtils.isBlank(manifest.getFileApi())) {
|
||||
// skip this phase
|
||||
throw new CustomException();
|
||||
}
|
||||
})).thenComposeAsync(wrap(unused1 -> {
|
||||
return executor.one(new GetTask(new URL(manifest.getFileApi() + "/manifest.json")));
|
||||
})).thenComposeAsync(wrap(unused1 -> {
|
||||
McbbsModpackManifest remoteManifest;
|
||||
// We needs to update modpack from online server.
|
||||
try {
|
||||
remoteManifest = JsonUtils.fromNonNullJson(dependent.getResult(), McbbsModpackManifest.class);
|
||||
} catch (JsonParseException e) {
|
||||
throw new IOException("Unable to parse server manifest.json from " + manifest.getFileApi(), e);
|
||||
}
|
||||
|
||||
Path rootPath = repository.getVersionRoot(version).toPath();
|
||||
|
||||
Map<McbbsModpackManifest.File, McbbsModpackManifest.File> localFiles = manifest.getFiles().stream().collect(Collectors.toMap(Function.identity(), Function.identity()));
|
||||
|
||||
// for files in new modpack
|
||||
List<McbbsModpackManifest.File> newFiles = new ArrayList<>(remoteManifest.getFiles().size());
|
||||
List<Task<?>> tasks = new ArrayList<>();
|
||||
for (McbbsModpackManifest.File file : remoteManifest.getFiles()) {
|
||||
Path actualPath = getFilePath(file);
|
||||
McbbsModpackManifest.File oldFile = localFiles.remove(file);
|
||||
boolean download = false;
|
||||
if (oldFile == null) {
|
||||
// If old modpack does not have this entry, download it
|
||||
download = true;
|
||||
} else if (actualPath != null) {
|
||||
if (!Files.exists(actualPath)) {
|
||||
// If both old and new modpacks have this entry, but the file is missing...
|
||||
// Re-download it since network problem may cause file missing
|
||||
download = true;
|
||||
} else if (getFileHash(file) != null) {
|
||||
// If user modified this entry file, we will not replace this file since this modified file is what user expects.
|
||||
// Or we have downloaded latest file in previous completion task, this time we have no need to download it again.
|
||||
String fileHash = encodeHex(digest("SHA-1", actualPath));
|
||||
String oldHash = getFileHash(oldFile);
|
||||
String newHash = getFileHash(file);
|
||||
if (oldHash == null) {
|
||||
// We don't know whether the file is modified or not, just update it.
|
||||
download = true;
|
||||
} else if (!Objects.equals(fileHash, newHash)) {
|
||||
if (file.isForce()) {
|
||||
// this file is not allowed to be modified, required by modpack author.
|
||||
download = true;
|
||||
} else if (Objects.equals(oldHash, fileHash)) {
|
||||
download = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we resolve files with unknown path later.
|
||||
}
|
||||
|
||||
if (download) {
|
||||
tasks.add(downloadFile(remoteManifest, file));
|
||||
}
|
||||
|
||||
newFiles.add(mergeFile(oldFile, file));
|
||||
}
|
||||
|
||||
// If old modpack have this entry, and new modpack deleted it. Delete this file.
|
||||
// for-loop above removes still existing file in localFiles. Remaining elements
|
||||
// are files removed by next modpack version.
|
||||
// Notice that this loop will also remove Curse mods.
|
||||
for (McbbsModpackManifest.File file : localFiles.keySet()) {
|
||||
Path actualPath = getFilePath(file);
|
||||
if (actualPath != null && Files.exists(actualPath))
|
||||
Files.deleteIfExists(actualPath);
|
||||
}
|
||||
|
||||
manifest = remoteManifest.setFiles(newFiles);
|
||||
return executor.all(tasks.stream().filter(Objects::nonNull).collect(Collectors.toList()));
|
||||
})).thenAcceptAsync(wrap(unused1 -> {
|
||||
File manifestFile = repository.getModpackConfiguration(version);
|
||||
FileUtils.writeText(manifestFile, JsonUtils.GSON.toJson(
|
||||
new ModpackConfiguration<>(manifest, this.configuration.getType(), this.manifest.getName(), this.manifest.getVersion(),
|
||||
this.manifest.getFiles().stream()
|
||||
.flatMap(file -> file instanceof McbbsModpackManifest.AddonFile
|
||||
? Stream.of((McbbsModpackManifest.AddonFile) file)
|
||||
: Stream.empty())
|
||||
.map(file -> new ModpackConfiguration.FileInformation(file.getPath(), file.getHash()))
|
||||
.collect(Collectors.toList()))));
|
||||
})));
|
||||
}).thenComposeAsync(unused -> {
|
||||
AtomicBoolean allNameKnown = new AtomicBoolean(true);
|
||||
AtomicInteger finished = new AtomicInteger(0);
|
||||
AtomicBoolean notFound = new AtomicBoolean(false);
|
||||
|
||||
return breakable(CompletableFuture.completedFuture(null)
|
||||
.thenComposeAsync(wrap(unused1 -> {
|
||||
List<Task<?>> dependencies = new ArrayList<>();
|
||||
// Because in China, Curse is too difficult to visit,
|
||||
// if failed, ignore it and retry next time.
|
||||
McbbsModpackManifest newManifest = manifest.setFiles(
|
||||
manifest.getFiles().parallelStream()
|
||||
.map(rawFile -> {
|
||||
updateProgress(finished.incrementAndGet(), manifest.getFiles().size());
|
||||
if (rawFile instanceof McbbsModpackManifest.CurseFile) {
|
||||
McbbsModpackManifest.CurseFile file = (McbbsModpackManifest.CurseFile) rawFile;
|
||||
if (StringUtils.isBlank(file.getFileName())) {
|
||||
try {
|
||||
return file.withFileName(NetworkUtils.detectFileName(file.getUrl()));
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
String result = NetworkUtils.doGet(NetworkUtils.toURL(String.format("https://cursemeta.dries007.net/%d/%d.json", file.getProjectID(), file.getFileID())));
|
||||
CurseMetaMod mod = JsonUtils.fromNonNullJson(result, CurseMetaMod.class);
|
||||
return file.withFileName(mod.getFileNameOnDisk()).withURL(mod.getDownloadURL());
|
||||
} catch (FileNotFoundException fof) {
|
||||
Logging.LOG.log(Level.WARNING, "Could not query cursemeta for deleted mods: " + file.getUrl(), fof);
|
||||
notFound.set(true);
|
||||
return file;
|
||||
} catch (IOException | JsonParseException e2) {
|
||||
try {
|
||||
String result = NetworkUtils.doGet(NetworkUtils.toURL(String.format("https://addons-ecs.forgesvc.net/api/v2/addon/%d/file/%d", file.getProjectID(), file.getFileID())));
|
||||
CurseMetaMod mod = JsonUtils.fromNonNullJson(result, CurseMetaMod.class);
|
||||
return file.withFileName(mod.getFileName()).withURL(mod.getDownloadURL());
|
||||
} catch (FileNotFoundException fof) {
|
||||
Logging.LOG.log(Level.WARNING, "Could not query forgesvc for deleted mods: " + file.getUrl(), fof);
|
||||
notFound.set(true);
|
||||
return file;
|
||||
} catch (IOException | JsonParseException e3) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to fetch the file name of URL: " + file.getUrl(), e);
|
||||
Logging.LOG.log(Level.WARNING, "Unable to fetch the file name of URL: " + file.getUrl(), e2);
|
||||
Logging.LOG.log(Level.WARNING, "Unable to fetch the file name of URL: " + file.getUrl(), e3);
|
||||
allNameKnown.set(false);
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return file;
|
||||
}
|
||||
} else {
|
||||
return rawFile;
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
manifest = newManifest;
|
||||
configuration = configuration.setManifest(newManifest);
|
||||
FileUtils.writeText(configurationFile, JsonUtils.GSON.toJson(configuration));
|
||||
|
||||
for (McbbsModpackManifest.File file : newManifest.getFiles())
|
||||
if (file instanceof McbbsModpackManifest.CurseFile) {
|
||||
McbbsModpackManifest.CurseFile curseFile = (McbbsModpackManifest.CurseFile) file;
|
||||
if (StringUtils.isNotBlank(curseFile.getFileName())) {
|
||||
if (!modManager.hasSimpleMod(curseFile.getFileName())) {
|
||||
FileDownloadTask task = new FileDownloadTask(curseFile.getUrl(), modManager.getSimpleModPath(curseFile.getFileName()).toFile());
|
||||
task.setCacheRepository(dependency.getCacheRepository());
|
||||
task.setCaching(true);
|
||||
dependencies.add(task.withCounter());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!dependencies.isEmpty()) {
|
||||
getProperties().put("total", dependencies.size());
|
||||
}
|
||||
|
||||
return executor.all(dependencies);
|
||||
})).whenComplete(wrap((unused1, ex) -> {
|
||||
// Let this task fail if the curse manifest has not been completed.
|
||||
// But continue other downloads.
|
||||
if (notFound.get())
|
||||
throw new CurseCompletionException(new FileNotFoundException());
|
||||
if (!allNameKnown.get() || ex != null)
|
||||
throw new CurseCompletionException();
|
||||
})));
|
||||
}));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Path getFilePath(McbbsModpackManifest.File file) {
|
||||
if (file instanceof McbbsModpackManifest.AddonFile) {
|
||||
return modManager.getRepository().getRunDirectory(modManager.getVersion()).toPath().resolve(((McbbsModpackManifest.AddonFile) file).getPath());
|
||||
} else if (file instanceof McbbsModpackManifest.CurseFile) {
|
||||
String fileName = ((McbbsModpackManifest.CurseFile) file).getFileName();
|
||||
if (fileName == null) return null;
|
||||
return modManager.getSimpleModPath(fileName);
|
||||
} else {
|
||||
this.manifest = manifest;
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doPreExecute() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preExecute() throws Exception {
|
||||
if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return;
|
||||
dependent = new GetTask(new URL(manifest.getManifest().getFileApi() + "/manifest.json"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task<?>> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task<?>> getDependents() {
|
||||
return dependent == null ? Collections.emptySet() : Collections.singleton(dependent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return;
|
||||
|
||||
try {
|
||||
remoteManifest = JsonUtils.fromNonNullJson(dependent.getResult(), McbbsModpackManifest.class);
|
||||
} catch (JsonParseException e) {
|
||||
throw new IOException(e);
|
||||
private String getFileHash(McbbsModpackManifest.File file) {
|
||||
if (file instanceof McbbsModpackManifest.AddonFile) {
|
||||
return ((McbbsModpackManifest.AddonFile) file).getHash();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
Path rootPath = repository.getVersionRoot(version).toPath();
|
||||
|
||||
// Because in China, Curse is too difficult to visit,
|
||||
// if failed, ignore it and retry next time.
|
||||
// CurseManifest newManifest = manifest.setFiles(
|
||||
// manifest.getFiles().parallelStream()
|
||||
// .map(file -> {
|
||||
// updateProgress(finished.incrementAndGet(), manifest.getFiles().size());
|
||||
// if (StringUtils.isBlank(file.getFileName())) {
|
||||
// try {
|
||||
// return file.withFileName(NetworkUtils.detectFileName(file.getUrl()));
|
||||
// } catch (IOException e) {
|
||||
// try {
|
||||
// String result = NetworkUtils.doGet(NetworkUtils.toURL(String.format("https://cursemeta.dries007.net/%d/%d.json", file.getProjectID(), file.getFileID())));
|
||||
// CurseMetaMod mod = JsonUtils.fromNonNullJson(result, CurseMetaMod.class);
|
||||
// return file.withFileName(mod.getFileNameOnDisk()).withURL(mod.getDownloadURL());
|
||||
// } catch (FileNotFoundException fof) {
|
||||
// Logging.LOG.log(Level.WARNING, "Could not query cursemeta for deleted mods: " + file.getUrl(), fof);
|
||||
// notFound.set(true);
|
||||
// return file;
|
||||
// } catch (IOException | JsonParseException e2) {
|
||||
// try {
|
||||
// String result = NetworkUtils.doGet(NetworkUtils.toURL(String.format("https://addons-ecs.forgesvc.net/api/v2/addon/%d/file/%d", file.getProjectID(), file.getFileID())));
|
||||
// CurseMetaMod mod = JsonUtils.fromNonNullJson(result, CurseMetaMod.class);
|
||||
// return file.withFileName(mod.getFileName()).withURL(mod.getDownloadURL());
|
||||
// } catch (FileNotFoundException fof) {
|
||||
// Logging.LOG.log(Level.WARNING, "Could not query forgesvc for deleted mods: " + file.getUrl(), fof);
|
||||
// notFound.set(true);
|
||||
// return file;
|
||||
// } catch (IOException | JsonParseException e3) {
|
||||
// Logging.LOG.log(Level.WARNING, "Unable to fetch the file name of URL: " + file.getUrl(), e);
|
||||
// Logging.LOG.log(Level.WARNING, "Unable to fetch the file name of URL: " + file.getUrl(), e2);
|
||||
// Logging.LOG.log(Level.WARNING, "Unable to fetch the file name of URL: " + file.getUrl(), e3);
|
||||
// allNameKnown.set(false);
|
||||
// return file;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// return file;
|
||||
// }
|
||||
// })
|
||||
// .collect(Collectors.toList()));
|
||||
//
|
||||
// Map<String, ModpackConfiguration.FileInformation> files = manifest.getManifest().getFiles().stream()
|
||||
// .collect(Collectors.toMap(ModpackConfiguration.FileInformation::getPath,
|
||||
// Function.identity()));
|
||||
//
|
||||
// Set<String> remoteFiles = remoteManifest.getFiles().stream().map(ModpackConfiguration.FileInformation::getPath)
|
||||
// .collect(Collectors.toSet());
|
||||
//
|
||||
// // for files in new modpack
|
||||
// for (ModpackConfiguration.FileInformation file : remoteManifest.getFiles()) {
|
||||
// Path actualPath = rootPath.resolve(file.getPath());
|
||||
// boolean download;
|
||||
// if (!files.containsKey(file.getPath())) {
|
||||
// // If old modpack does not have this entry, download it
|
||||
// download = true;
|
||||
// } else if (!Files.exists(actualPath)) {
|
||||
// // If both old and new modpacks have this entry, but the file is missing...
|
||||
// // Re-download it since network problem may cause file missing
|
||||
// download = true;
|
||||
// } else {
|
||||
// // If user modified this entry file, we will not replace this file since this modified file is that user expects.
|
||||
// String fileHash = encodeHex(digest("SHA-1", actualPath));
|
||||
// String oldHash = files.get(file.getPath()).getHash();
|
||||
// download = !Objects.equals(oldHash, file.getHash()) && Objects.equals(oldHash, fileHash);
|
||||
// }
|
||||
//
|
||||
// if (download) {
|
||||
// dependencies.add(new FileDownloadTask(
|
||||
// new URL(remoteManifest.getFileApi() + "/overrides/" + NetworkUtils.encodeLocation(file.getPath())),
|
||||
// actualPath.toFile(),
|
||||
// new FileDownloadTask.IntegrityCheck("SHA-1", file.getHash())));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // If old modpack have this entry, and new modpack deleted it. Delete this file.
|
||||
// for (ModpackConfiguration.FileInformation file : manifest.getManifest().getFiles()) {
|
||||
// Path actualPath = rootPath.resolve(file.getPath());
|
||||
// if (Files.exists(actualPath) && !remoteFiles.contains(file.getPath()))
|
||||
// Files.deleteIfExists(actualPath);
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doPostExecute() {
|
||||
return true;
|
||||
private Task<?> downloadFile(McbbsModpackManifest remoteManifest, McbbsModpackManifest.File file) throws IOException {
|
||||
if (file instanceof McbbsModpackManifest.AddonFile) {
|
||||
McbbsModpackManifest.AddonFile addonFile = (McbbsModpackManifest.AddonFile) file;
|
||||
return new FileDownloadTask(
|
||||
new URL(remoteManifest.getFileApi() + "/overrides/" + NetworkUtils.encodeLocation(addonFile.getPath())),
|
||||
modManager.getSimpleModPath(addonFile.getPath()).toFile(),
|
||||
addonFile.getHash() != null ? new FileDownloadTask.IntegrityCheck("SHA-1", addonFile.getHash()) : null);
|
||||
} else if (file instanceof McbbsModpackManifest.CurseFile) {
|
||||
// we download it later.
|
||||
return null;
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postExecute() throws Exception {
|
||||
// if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return;
|
||||
// File manifestFile = repository.getModpackConfiguration(version);
|
||||
// FileUtils.writeText(manifestFile, JsonUtils.GSON.toJson(new ModpackConfiguration<>(remoteManifest, this.manifest.getType(), this.manifest.getName(), this.manifest.getVersion(), remoteManifest.getFiles())));
|
||||
@NotNull
|
||||
private McbbsModpackManifest.File mergeFile(@Nullable McbbsModpackManifest.File oldFile, @NotNull McbbsModpackManifest.File newFile) {
|
||||
if (newFile instanceof McbbsModpackManifest.AddonFile) {
|
||||
return newFile;
|
||||
} else if (newFile instanceof McbbsModpackManifest.CurseFile) {
|
||||
// Preserves prefetched file names and urls.
|
||||
return oldFile != null ? oldFile : newFile;
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,16 +41,19 @@ import java.util.stream.Stream;
|
||||
|
||||
public class McbbsModpackLocalInstallTask extends Task<Void> {
|
||||
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final File zipFile;
|
||||
private final Modpack modpack;
|
||||
private final McbbsModpackManifest manifest;
|
||||
private final String name;
|
||||
private final boolean update;
|
||||
private final DefaultGameRepository repository;
|
||||
private final MinecraftInstanceTask<McbbsModpackManifest> instanceTask;
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
private final List<Task<?>> dependents = new LinkedList<>();
|
||||
|
||||
public McbbsModpackLocalInstallTask(DefaultDependencyManager dependencyManager, File zipFile, Modpack modpack, McbbsModpackManifest manifest, String name) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.zipFile = zipFile;
|
||||
this.modpack = modpack;
|
||||
this.manifest = manifest;
|
||||
@ -87,7 +90,8 @@ public class McbbsModpackLocalInstallTask extends Task<Void> {
|
||||
} catch (JsonParseException | IOException ignore) {
|
||||
}
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), "/overrides", any -> true, config).withStage("hmcl.modpack"));
|
||||
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/overrides", manifest, MODPACK_TYPE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack"));
|
||||
instanceTask = new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/overrides", manifest, MODPACK_TYPE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name));
|
||||
dependents.add(instanceTask.withStage("hmcl.modpack"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -115,6 +119,8 @@ public class McbbsModpackLocalInstallTask extends Task<Void> {
|
||||
// This mcbbs modpack was installed by other launchers.
|
||||
// TODO: maintain libraries.
|
||||
}
|
||||
|
||||
dependencies.add(new McbbsModpackCompletionTask(dependencyManager, name, instanceTask.getResult()).withStage("hmcl.modpack.download"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -143,6 +143,10 @@ public class McbbsModpackManifest implements Validation {
|
||||
return launchInfo;
|
||||
}
|
||||
|
||||
public McbbsModpackManifest setFiles(List<File> files) {
|
||||
return new McbbsModpackManifest(manifestType, manifestVersion, name, version, author, description, fileApi, url, forceUpdate, origins, addons, libraries, files, settings, launchInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws JsonParseException, TolerableValidationException {
|
||||
if (!MANIFEST_TYPE.equals(manifestType))
|
||||
@ -230,7 +234,7 @@ public class McbbsModpackManifest implements Validation {
|
||||
}
|
||||
)
|
||||
public static abstract class File implements Validation {
|
||||
private final boolean force;
|
||||
protected final boolean force;
|
||||
|
||||
public File(boolean force) {
|
||||
this.force = force;
|
||||
@ -251,8 +255,8 @@ public class McbbsModpackManifest implements Validation {
|
||||
|
||||
public AddonFile(boolean force, String path, String hash) {
|
||||
super(force);
|
||||
this.path = path;
|
||||
this.hash = hash;
|
||||
this.path = Objects.requireNonNull(path);
|
||||
this.hash = Objects.requireNonNull(hash);
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
@ -270,6 +274,19 @@ public class McbbsModpackManifest implements Validation {
|
||||
Validation.requireNonNull(path, "AddonFile.path cannot be null");
|
||||
Validation.requireNonNull(hash, "AddonFile.hash cannot be null");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
AddonFile addonFile = (AddonFile) o;
|
||||
return path.equals(addonFile.path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(path);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class CurseFile extends File {
|
||||
@ -298,6 +315,7 @@ public class McbbsModpackManifest implements Validation {
|
||||
return fileID;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
@ -307,6 +325,14 @@ public class McbbsModpackManifest implements Validation {
|
||||
: NetworkUtils.toURL(NetworkUtils.encodeLocation(url));
|
||||
}
|
||||
|
||||
public CurseFile withFileName(String fileName) {
|
||||
return new CurseFile(force, projectID, fileID, fileName, url);
|
||||
}
|
||||
|
||||
public CurseFile withURL(String url) {
|
||||
return new CurseFile(force, projectID, fileID, fileName, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws JsonParseException, TolerableValidationException {
|
||||
super.validate();
|
||||
|
@ -23,13 +23,13 @@ import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackManifest;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@ -40,9 +40,9 @@ public class McbbsModpackRemoteInstallTask extends Task<Void> {
|
||||
private final DefaultGameRepository repository;
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
private final List<Task<?>> dependents = new LinkedList<>();
|
||||
private final ServerModpackManifest manifest;
|
||||
private final McbbsModpackManifest manifest;
|
||||
|
||||
public McbbsModpackRemoteInstallTask(DefaultDependencyManager dependencyManager, ServerModpackManifest manifest, String name) {
|
||||
public McbbsModpackRemoteInstallTask(DefaultDependencyManager dependencyManager, McbbsModpackManifest manifest, String name) {
|
||||
this.name = name;
|
||||
this.dependency = dependencyManager;
|
||||
this.repository = dependencyManager.getGameRepository();
|
||||
@ -53,7 +53,7 @@ public class McbbsModpackRemoteInstallTask extends Task<Void> {
|
||||
throw new IllegalArgumentException("Version " + name + " already exists.");
|
||||
|
||||
GameBuilder builder = dependencyManager.gameBuilder().name(name);
|
||||
for (ServerModpackManifest.Addon addon : manifest.getAddons()) {
|
||||
for (McbbsModpackManifest.Addon addon : manifest.getAddons()) {
|
||||
builder.version(addon.getId(), addon.getVersion());
|
||||
}
|
||||
|
||||
@ -63,14 +63,14 @@ public class McbbsModpackRemoteInstallTask extends Task<Void> {
|
||||
repository.removeVersionFromDisk(name);
|
||||
});
|
||||
|
||||
ModpackConfiguration<ServerModpackManifest> config = null;
|
||||
ModpackConfiguration<McbbsModpackManifest> config = null;
|
||||
try {
|
||||
if (json.exists()) {
|
||||
config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<ServerModpackManifest>>() {
|
||||
config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<McbbsModpackManifest>>() {
|
||||
}.getType());
|
||||
|
||||
if (!MODPACK_TYPE.equals(config.getType()))
|
||||
throw new IllegalArgumentException("Version " + name + " is not a Server modpack. Cannot update this version.");
|
||||
throw new IllegalArgumentException("Version " + name + " is not a Mcbbs modpack. Cannot update this version.");
|
||||
}
|
||||
} catch (JsonParseException | IOException ignore) {
|
||||
}
|
||||
@ -88,7 +88,7 @@ public class McbbsModpackRemoteInstallTask extends Task<Void> {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
// dependencies.add(new McbbsModpackCompletionTask(dependency, name, new ModpackConfiguration<>(manifest, MODPACK_TYPE, manifest.getName(), manifest.getVersion(), Collections.emptyList())));
|
||||
dependencies.add(new McbbsModpackCompletionTask(dependency, name, new ModpackConfiguration<>(manifest, MODPACK_TYPE, manifest.getName(), manifest.getVersion(), Collections.emptyList())));
|
||||
}
|
||||
|
||||
public static final String MODPACK_TYPE = "Server";
|
||||
|
@ -99,7 +99,7 @@ public final class AsyncTaskExecutor extends TaskExecutor {
|
||||
future.cancel(true);
|
||||
}
|
||||
|
||||
private CompletableFuture<Exception> executeTasks(Task<?> parentTask, Collection<Task<?>> tasks) {
|
||||
private CompletableFuture<?> executeTasksExceptionally(Task<?> parentTask, Collection<Task<?>> tasks) {
|
||||
if (tasks == null || tasks.isEmpty())
|
||||
return CompletableFuture.completedFuture(null);
|
||||
|
||||
@ -114,7 +114,11 @@ public final class AsyncTaskExecutor extends TaskExecutor {
|
||||
.map(task -> CompletableFuture.completedFuture(null)
|
||||
.thenComposeAsync(unused2 -> executeTask(parentTask, task))
|
||||
).toArray(CompletableFuture<?>[]::new));
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<Exception> executeTasks(Task<?> parentTask, Collection<Task<?>> tasks) {
|
||||
return executeTasksExceptionally(parentTask, tasks)
|
||||
.thenApplyAsync(unused -> (Exception) null)
|
||||
.exceptionally(throwable -> {
|
||||
Throwable resolved = resolveException(throwable);
|
||||
@ -127,7 +131,79 @@ public final class AsyncTaskExecutor extends TaskExecutor {
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<?> executeTask(Task<?> parentTask, Task<?> task) {
|
||||
private <T> CompletableFuture<T> executeCompletableFutureTask(Task<?> parentTask, CompletableFutureTask<T> task) {
|
||||
checkCancellation();
|
||||
|
||||
return CompletableFuture.completedFuture(null)
|
||||
.thenComposeAsync(unused -> {
|
||||
checkCancellation();
|
||||
|
||||
task.setCancelled(this::isCancelled);
|
||||
task.setState(Task.TaskState.READY);
|
||||
if (parentTask != null && task.getStage() == null)
|
||||
task.setStage(parentTask.getStage());
|
||||
|
||||
if (task.getSignificance().shouldLog())
|
||||
Logging.LOG.log(Level.FINE, "Executing task: " + task.getName());
|
||||
|
||||
taskListeners.forEach(it -> it.onReady(task));
|
||||
|
||||
return task.getFuture(new TaskCompletableFuture() {
|
||||
@Override
|
||||
public <T2> CompletableFuture<T2> one(Task<T2> subtask) {
|
||||
return executeTask(task, subtask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> all(Collection<Task<?>> tasks) {
|
||||
return executeTasksExceptionally(task, tasks);
|
||||
}
|
||||
});
|
||||
})
|
||||
.thenApplyAsync(result -> {
|
||||
checkCancellation();
|
||||
|
||||
if (task.getSignificance().shouldLog()) {
|
||||
Logging.LOG.log(Level.FINER, "Task finished: " + task.getName());
|
||||
}
|
||||
|
||||
task.setResult(result);
|
||||
task.onDone().fireEvent(new TaskEvent(this, task, false));
|
||||
taskListeners.forEach(it -> it.onFinished(task));
|
||||
|
||||
task.setState(Task.TaskState.SUCCEEDED);
|
||||
|
||||
return result;
|
||||
})
|
||||
.exceptionally(throwable -> {
|
||||
Throwable resolved = resolveException(throwable);
|
||||
if (resolved instanceof Exception) {
|
||||
Exception e = (Exception) resolved;
|
||||
if (e instanceof InterruptedException || e instanceof CancellationException) {
|
||||
task.setException(null);
|
||||
if (task.getSignificance().shouldLog()) {
|
||||
Logging.LOG.log(Level.FINE, "Task aborted: " + task.getName());
|
||||
}
|
||||
task.onDone().fireEvent(new TaskEvent(this, task, true));
|
||||
taskListeners.forEach(it -> it.onFailed(task, e));
|
||||
} else {
|
||||
task.setException(e);
|
||||
exception = e;
|
||||
if (task.getSignificance().shouldLog()) {
|
||||
Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e);
|
||||
}
|
||||
task.onDone().fireEvent(new TaskEvent(this, task, true));
|
||||
taskListeners.forEach(it -> it.onFailed(task, e));
|
||||
}
|
||||
|
||||
task.setState(Task.TaskState.FAILED);
|
||||
}
|
||||
|
||||
throw new CompletionException(resolved); // rethrow error
|
||||
});
|
||||
}
|
||||
|
||||
private <T> CompletableFuture<T> executeNormalTask(Task<?> parentTask, Task<T> task) {
|
||||
checkCancellation();
|
||||
|
||||
return CompletableFuture.completedFuture(null)
|
||||
@ -185,7 +261,7 @@ public final class AsyncTaskExecutor extends TaskExecutor {
|
||||
return CompletableFuture.completedFuture(dependenciesException);
|
||||
}
|
||||
})
|
||||
.thenAcceptAsync(dependenciesException -> {
|
||||
.thenApplyAsync(dependenciesException -> {
|
||||
boolean isDependenciesSucceeded = dependenciesException == null;
|
||||
|
||||
if (!isDependenciesSucceeded && task.isRelyingOnDependencies()) {
|
||||
@ -204,6 +280,8 @@ public final class AsyncTaskExecutor extends TaskExecutor {
|
||||
taskListeners.forEach(it -> it.onFinished(task));
|
||||
|
||||
task.setState(Task.TaskState.SUCCEEDED);
|
||||
|
||||
return task.getResult();
|
||||
})
|
||||
.exceptionally(throwable -> {
|
||||
Throwable resolved = resolveException(throwable);
|
||||
@ -233,6 +311,14 @@ public final class AsyncTaskExecutor extends TaskExecutor {
|
||||
});
|
||||
}
|
||||
|
||||
private <T> CompletableFuture<T> executeTask(Task<?> parentTask, Task<T> task) {
|
||||
if (task instanceof CompletableFutureTask<?>) {
|
||||
return executeCompletableFutureTask(parentTask, (CompletableFutureTask<T>) task);
|
||||
} else {
|
||||
return executeNormalTask(parentTask, task);
|
||||
}
|
||||
}
|
||||
|
||||
private static Throwable resolveException(Throwable e) {
|
||||
if (e instanceof ExecutionException || e instanceof CompletionException)
|
||||
return resolveException(e.getCause());
|
||||
|
@ -0,0 +1,92 @@
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalBiConsumer;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public abstract class CompletableFutureTask<T> extends Task<T> {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
}
|
||||
|
||||
public abstract CompletableFuture<T> getFuture(TaskCompletableFuture executor);
|
||||
|
||||
protected static Runnable wrap(ExceptionalRunnable<?> runnable) {
|
||||
return () -> {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Exception e) {
|
||||
rethrow(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected static <T, R> Function<T, R> wrap(ExceptionalFunction<T, R, ?> fn) {
|
||||
return t -> {
|
||||
try {
|
||||
return fn.apply(t);
|
||||
} catch (Exception e) {
|
||||
rethrow(e);
|
||||
throw new InternalError("Unreachable code");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected static <T> Consumer<T> wrap(ExceptionalConsumer<T, ?> fn) {
|
||||
return t -> {
|
||||
try {
|
||||
fn.accept(t);
|
||||
} catch (Exception e) {
|
||||
rethrow(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected static <T, E> BiConsumer<T, E> wrap(ExceptionalBiConsumer<T, E, ?> fn) {
|
||||
return (t, e) -> {
|
||||
try {
|
||||
fn.accept(t, e);
|
||||
} catch (Exception ex) {
|
||||
rethrow(ex);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected static void rethrow(Throwable e) {
|
||||
if (e == null)
|
||||
return;
|
||||
if (e instanceof ExecutionException || e instanceof CompletionException) { // including UncheckedException and UncheckedThrowable
|
||||
rethrow(e.getCause());
|
||||
} else if (e instanceof RuntimeException) {
|
||||
throw (RuntimeException) e;
|
||||
} else {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected static Throwable resolveException(Throwable e) {
|
||||
if (e instanceof ExecutionException || e instanceof CompletionException)
|
||||
return resolveException(e.getCause());
|
||||
else
|
||||
return e;
|
||||
}
|
||||
|
||||
public static class CustomException extends RuntimeException {}
|
||||
|
||||
protected static CompletableFuture<Void> breakable(CompletableFuture<?> future) {
|
||||
return future.thenApplyAsync(unused1 -> (Void) null).exceptionally(throwable -> {
|
||||
if (resolveException(throwable) instanceof CustomException) return null;
|
||||
else throw new CompletionException(throwable);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface TaskCompletableFuture {
|
||||
|
||||
<T> CompletableFuture<T> one(Task<T> task);
|
||||
|
||||
CompletableFuture<?> all(Collection<Task<?>> tasks);
|
||||
}
|
@ -23,6 +23,7 @@ import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -43,6 +44,13 @@ public final class JsonUtils {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
public static <T> T fromNonNullJson(String json, Type type) throws JsonParseException {
|
||||
T parsed = GSON.fromJson(json, type);
|
||||
if (parsed == null)
|
||||
throw new JsonParseException("Json object cannot be null.");
|
||||
return parsed;
|
||||
}
|
||||
|
||||
public static <T> T fromMaybeMalformedJson(String json, Class<T> classOfT) throws JsonParseException {
|
||||
try {
|
||||
return GSON.fromJson(json, classOfT);
|
||||
|
Loading…
Reference in New Issue
Block a user