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 fc87c18b3..c4120f3cf 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -27,6 +27,7 @@ import org.jackhuang.hmcl.auth.ServerDisconnectException; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.MaintainTask; import org.jackhuang.hmcl.launch.*; +import org.jackhuang.hmcl.mod.CurseCompletionException; import org.jackhuang.hmcl.mod.CurseCompletionTask; import org.jackhuang.hmcl.mod.ModpackConfiguration; import org.jackhuang.hmcl.setting.LauncherVisibility; @@ -177,10 +178,17 @@ public final class LauncherHelper { // because onStop will be invoked if tasks fail when the executor service shut down. if (!Controllers.isStopped()) { Controllers.closeDialog(launchingStepsPane); - if (executor.getLastException() != null) - Controllers.dialog(I18nException.getStackTrace(executor.getLastException()), + Exception ex = executor.getLastException(); + if (ex != null) { + String message; + if (ex instanceof CurseCompletionException) + message = Launcher.i18n("modpack.type.curse.error"); + else + message = I18nException.getStackTrace(ex); + Controllers.dialog(message, scriptFile == null ? Launcher.i18n("launch.failed") : Launcher.i18n("version.launch_script.failed"), MessageBox.ERROR_MESSAGE); + } } }); } diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 013c0a68b..9d7320f43 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -204,6 +204,7 @@ modpack.task.install.error=Failed to install the modpack. Maybe the files is inc modpack.task.install.will=Install the modpack: modpack.type.curse=Curse modpack.type.curse.completion=Install relative files to Curse modpack +modpack.type.curse.error=Unable to complete this Curse modpack. Please retry. modpack.type.hmcl=HMCL modpack.type.multimc=MultiMC modpack.unsupported=Unsupported modpack, only HMCL, MultiMC, Curse modpacks are supported. diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 3871c9cb3..bd90503ff 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -204,6 +204,7 @@ modpack.task.install.error=安装失败,可能是整合包格式不正确或 modpack.task.install.will=将会安装整合包: modpack.type.curse=Curse modpack.type.curse.completion=下载 Curse 整合包相关文件 +modpack.type.curse.error=未能完成 Curse 整合包的下载,请多次重试或设置代理 modpack.type.hmcl=HMCL modpack.type.multimc=MultiMC modpack.unsupported=该整合包不被支持。仅 HMCL、MultiMC、Curse 整合包受支持。 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseCompletionException.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseCompletionException.java new file mode 100644 index 000000000..20dcd6720 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseCompletionException.java @@ -0,0 +1,35 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.mod; + +public class CurseCompletionException extends Exception { + public CurseCompletionException() { + } + + public CurseCompletionException(String message) { + super(message); + } + + public CurseCompletionException(String message, Throwable cause) { + super(message, cause); + } + + public CurseCompletionException(Throwable cause) { + super(cause); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseCompletionTask.java index 2a5cf85c3..da7f1b355 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseCompletionTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseCompletionTask.java @@ -24,9 +24,11 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.*; import java.io.File; +import java.io.IOException; import java.util.Collection; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.stream.Collectors; @@ -49,7 +51,7 @@ public final class CurseCompletionTask extends Task { * Constructor. * * @param dependencyManager the dependency manager. - * @param version the existent and physical version. + * @param version the existent and physical version. */ public CurseCompletionTask(DefaultDependencyManager dependencyManager, String version) { this(dependencyManager, version, null); @@ -59,8 +61,8 @@ public final class CurseCompletionTask extends Task { * Constructor. * * @param dependencyManager the dependency manager. - * @param version the existent and physical version. - * @param manifest the CurseForgeModpack manifest. + * @param version the existent and physical version. + * @param manifest the CurseForgeModpack manifest. */ public CurseCompletionTask(DefaultDependencyManager dependencyManager, String version, CurseManifest manifest) { this.dependencyManager = dependencyManager; @@ -96,6 +98,7 @@ public final class CurseCompletionTask extends Task { File root = repository.getVersionRoot(version); File run = repository.getRunDirectory(version); + AtomicBoolean flag = new AtomicBoolean(true); AtomicInteger finished = new AtomicInteger(0); // Because in China, Curse is too difficult to visit, @@ -104,14 +107,32 @@ public final class CurseCompletionTask extends Task { manifest.getFiles().parallelStream() .map(file -> { updateProgress(finished.incrementAndGet(), manifest.getFiles().size()); - return Lang.ignoringException(() -> file.setFileName(NetworkUtils.detectFileName(file.getUrl(), dependencyManager.getProxy())), file); + if (StringUtils.isBlank(file.getFileName())) { + try { + return file.withFileName(NetworkUtils.detectFileName(file.getUrl(), dependencyManager.getProxy())); + } catch (IOException ioe) { + Logging.LOG.log(Level.WARNING, "Unable to fetch the file name of URL: " + file.getUrl(), ioe); + flag.set(false); + return file; + } + } else + return file; }) .collect(Collectors.toList())); FileUtils.writeText(new File(root, "manifest.json"), Constants.GSON.toJson(newManifest)); for (CurseManifestFile file : newManifest.getFiles()) - if (StringUtils.isNotBlank(file.getFileName())) - dependencies.add(new FileDownloadTask(file.getUrl(), new File(run, "mods/" + file.getFileName()), dependencyManager.getProxy())); + if (StringUtils.isNotBlank(file.getFileName())) { + File dest = new File(run, "mods/" + file.getFileName()); + if (!dest.exists()) + dependencies.add(new FileDownloadTask(file.getUrl(), dest, dependencyManager.getProxy())); + } + + // Let this task fail if the curse manifest has not been completed. + if (!flag.get()) + dependencies.add(Task.of(() -> { + throw new CurseCompletionException(); + })); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifestFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifestFile.java index dc3566450..738eddebb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifestFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifestFile.java @@ -82,7 +82,7 @@ public final class CurseManifestFile implements Validation { return NetworkUtils.toURL("https://minecraft.curseforge.com/projects/" + projectID + "/files/" + fileID + "/download"); } - public CurseManifestFile setFileName(String fileName) { + public CurseManifestFile withFileName(String fileName) { return new CurseManifestFile(projectID, fileID, fileName, required); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/NetworkUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/NetworkUtils.java index 63595c69b..8323e2586 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/NetworkUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/NetworkUtils.java @@ -20,10 +20,8 @@ package org.jackhuang.hmcl.util; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.Proxy; -import java.net.URL; +import java.net.*; +import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Objects; import java.util.function.Supplier; @@ -138,9 +136,9 @@ public final class NetworkUtils { String disposition = conn.getHeaderField("Content-Disposition"); if (disposition == null || !disposition.contains("filename=")) { String u = conn.getURL().toString(); - return substringAfterLast(u, '/'); + return Lang.invoke(() -> URLDecoder.decode(substringAfterLast(u, '/'), StandardCharsets.UTF_8.name())); } else - return removeSurrounding(substringAfter(disposition, "filename="), "\""); + return Lang.invoke(() -> URLDecoder.decode(removeSurrounding(substringAfter(disposition, "filename="), "\""), StandardCharsets.UTF_8.name())); } public static URL toURL(String str) {