Merge branch 'javafx' of https://github.com/huanghongxun/HMCL into javafx
This commit is contained in:
@@ -54,6 +54,7 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
|||||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
@@ -216,7 +217,10 @@ public final class LauncherHelper {
|
|||||||
if (ex != null) {
|
if (ex != null) {
|
||||||
String message;
|
String message;
|
||||||
if (ex instanceof CurseCompletionException) {
|
if (ex instanceof CurseCompletionException) {
|
||||||
message = i18n("modpack.type.curse.error");
|
if (ex.getCause() instanceof FileNotFoundException)
|
||||||
|
message = i18n("modpack.type.curse.not_found");
|
||||||
|
else
|
||||||
|
message = i18n("modpack.type.curse.error");
|
||||||
} else if (ex instanceof PermissionException) {
|
} else if (ex instanceof PermissionException) {
|
||||||
message = i18n("launch.failed.executable_permission");
|
message = i18n("launch.failed.executable_permission");
|
||||||
} else if (ex instanceof ProcessCreationException) {
|
} else if (ex instanceof ProcessCreationException) {
|
||||||
|
|||||||
@@ -223,6 +223,7 @@ modpack.task.install.will=Install the modpack:
|
|||||||
modpack.type.curse=Curse
|
modpack.type.curse=Curse
|
||||||
modpack.type.curse.completion=Install relative files to Curse modpack
|
modpack.type.curse.completion=Install relative files to Curse modpack
|
||||||
modpack.type.curse.error=Unable to complete this Curse modpack. Please retry.
|
modpack.type.curse.error=Unable to complete this Curse modpack. Please retry.
|
||||||
|
modpack.type.curse.not_found=Some of required resources are deleted and cannot be downloaded. Please consider the latest version or other modpacks.
|
||||||
modpack.type.hmcl=HMCL
|
modpack.type.hmcl=HMCL
|
||||||
modpack.type.multimc=MultiMC
|
modpack.type.multimc=MultiMC
|
||||||
modpack.unsupported=Unsupported modpack, only HMCL, MultiMC, Curse modpacks are supported.
|
modpack.unsupported=Unsupported modpack, only HMCL, MultiMC, Curse modpacks are supported.
|
||||||
|
|||||||
@@ -223,6 +223,7 @@ modpack.task.install.will=將會安裝整合包:
|
|||||||
modpack.type.curse=Curse
|
modpack.type.curse=Curse
|
||||||
modpack.type.curse.completion=下載 Curse 整合包相關檔案
|
modpack.type.curse.completion=下載 Curse 整合包相關檔案
|
||||||
modpack.type.curse.error=無法完成 Curse 整合包的下載,請多次重試或設定代理
|
modpack.type.curse.error=無法完成 Curse 整合包的下載,請多次重試或設定代理
|
||||||
|
modpack.type.curse.not_found=部分必需檔案已經從網路中被刪除並且再也無法下載,請嘗試該整合包的最新版本或者安裝其他整合包。
|
||||||
modpack.type.hmcl=HMCL
|
modpack.type.hmcl=HMCL
|
||||||
modpack.type.multimc=MultiMC
|
modpack.type.multimc=MultiMC
|
||||||
modpack.unsupported=不支援該整合包。僅 HMCL、MultiMC、Curse 整合包受支援。
|
modpack.unsupported=不支援該整合包。僅 HMCL、MultiMC、Curse 整合包受支援。
|
||||||
|
|||||||
@@ -223,6 +223,7 @@ modpack.task.install.will=将会安装整合包:
|
|||||||
modpack.type.curse=Curse
|
modpack.type.curse=Curse
|
||||||
modpack.type.curse.completion=下载 Curse 整合包相关文件
|
modpack.type.curse.completion=下载 Curse 整合包相关文件
|
||||||
modpack.type.curse.error=未能完成 Curse 整合包的下载,请多次重试或设置代理
|
modpack.type.curse.error=未能完成 Curse 整合包的下载,请多次重试或设置代理
|
||||||
|
modpack.type.curse.not_found=部分必需文件已经在网络中被删除并且再也无法下载,请尝试该整合包的最新版本或者安装其他整合包。
|
||||||
modpack.type.hmcl=HMCL
|
modpack.type.hmcl=HMCL
|
||||||
modpack.type.multimc=MultiMC
|
modpack.type.multimc=MultiMC
|
||||||
modpack.unsupported=该整合包不被支持。仅 HMCL、MultiMC、Curse 整合包受支持。
|
modpack.unsupported=该整合包不被支持。仅 HMCL、MultiMC、Curse 整合包受支持。
|
||||||
|
|||||||
@@ -35,9 +35,12 @@ public class LibraryDownloadTask extends Task {
|
|||||||
protected final Library library;
|
protected final Library library;
|
||||||
protected final String url;
|
protected final String url;
|
||||||
protected boolean xz;
|
protected boolean xz;
|
||||||
|
private final Library originalLibrary;
|
||||||
private boolean cached = false;
|
private boolean cached = false;
|
||||||
|
|
||||||
public LibraryDownloadTask(AbstractDependencyManager dependencyManager, File file, Library library) {
|
public LibraryDownloadTask(AbstractDependencyManager dependencyManager, File file, Library library) {
|
||||||
|
this.originalLibrary = library;
|
||||||
|
|
||||||
setSignificance(TaskSignificance.MODERATE);
|
setSignificance(TaskSignificance.MODERATE);
|
||||||
|
|
||||||
if (library.is("net.minecraftforge", "forge"))
|
if (library.is("net.minecraftforge", "forge"))
|
||||||
@@ -86,7 +89,7 @@ public class LibraryDownloadTask extends Task {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preExecute() throws Exception {
|
public void preExecute() throws Exception {
|
||||||
Optional<Path> libPath = cacheRepository.getLibrary(library.setClassifier(null));
|
Optional<Path> libPath = cacheRepository.getLibrary(originalLibrary);
|
||||||
if (libPath.isPresent()) {
|
if (libPath.isPresent()) {
|
||||||
try {
|
try {
|
||||||
FileUtils.copyFile(libPath.get().toFile(), jar);
|
FileUtils.copyFile(libPath.get().toFile(), jar);
|
||||||
|
|||||||
@@ -27,10 +27,12 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
|||||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ConcurrentSkipListSet;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
@@ -103,6 +105,7 @@ public final class CurseCompletionTask extends Task {
|
|||||||
|
|
||||||
AtomicBoolean flag = new AtomicBoolean(true);
|
AtomicBoolean flag = new AtomicBoolean(true);
|
||||||
AtomicInteger finished = new AtomicInteger(0);
|
AtomicInteger finished = new AtomicInteger(0);
|
||||||
|
AtomicBoolean notFound = new AtomicBoolean(false);
|
||||||
|
|
||||||
// Because in China, Curse is too difficult to visit,
|
// Because in China, Curse is too difficult to visit,
|
||||||
// if failed, ignore it and retry next time.
|
// if failed, ignore it and retry next time.
|
||||||
@@ -113,6 +116,9 @@ public final class CurseCompletionTask extends Task {
|
|||||||
if (StringUtils.isBlank(file.getFileName())) {
|
if (StringUtils.isBlank(file.getFileName())) {
|
||||||
try {
|
try {
|
||||||
return file.withFileName(NetworkUtils.detectFileName(file.getUrl()));
|
return file.withFileName(NetworkUtils.detectFileName(file.getUrl()));
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
notFound.set(true);
|
||||||
|
return file;
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
Logging.LOG.log(Level.WARNING, "Unable to fetch the file name of URL: " + file.getUrl(), ioe);
|
Logging.LOG.log(Level.WARNING, "Unable to fetch the file name of URL: " + file.getUrl(), ioe);
|
||||||
flag.set(false);
|
flag.set(false);
|
||||||
@@ -132,9 +138,13 @@ public final class CurseCompletionTask extends Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Let this task fail if the curse manifest has not been completed.
|
// Let this task fail if the curse manifest has not been completed.
|
||||||
if (!flag.get())
|
// But continue other downloads.
|
||||||
|
if (!flag.get() || notFound.get())
|
||||||
dependencies.add(Task.of(() -> {
|
dependencies.add(Task.of(() -> {
|
||||||
throw new CurseCompletionException();
|
if (notFound.get())
|
||||||
|
throw new CurseCompletionException(new FileNotFoundException());
|
||||||
|
else
|
||||||
|
throw new CurseCompletionException();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -218,9 +218,9 @@ public class FileDownloadTask extends Task {
|
|||||||
|
|
||||||
HttpURLConnection con = NetworkUtils.createConnection(url);
|
HttpURLConnection con = NetworkUtils.createConnection(url);
|
||||||
if (checkETag) repository.injectConnection(con);
|
if (checkETag) repository.injectConnection(con);
|
||||||
con.connect();
|
con = NetworkUtils.resolveConnection(con);
|
||||||
|
|
||||||
if (con.getResponseCode() == 304) {
|
if (con.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||||
// Handle cache
|
// Handle cache
|
||||||
Path cache = repository.getCachedRemoteFile(con);
|
Path cache = repository.getCachedRemoteFile(con);
|
||||||
FileUtils.copyFile(cache.toFile(), file);
|
FileUtils.copyFile(cache.toFile(), file);
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ package org.jackhuang.hmcl.util.io;
|
|||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
@@ -57,13 +59,76 @@ public final class NetworkUtils {
|
|||||||
|
|
||||||
public static HttpURLConnection createConnection(URL url) throws IOException {
|
public static HttpURLConnection createConnection(URL url) throws IOException {
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
connection.setDoInput(true);
|
|
||||||
connection.setUseCaches(false);
|
connection.setUseCaches(false);
|
||||||
connection.setConnectTimeout(15000);
|
connection.setConnectTimeout(15000);
|
||||||
connection.setReadTimeout(15000);
|
connection.setReadTimeout(15000);
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see <a href="https://github.com/curl/curl/blob/3f7b1bb89f92c13e69ee51b710ac54f775aab320/lib/transfer.c#L1427-L1461">Curl</a>
|
||||||
|
* @param location
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String encodeLocation(String location) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
boolean left = true;
|
||||||
|
for (char ch : location.toCharArray()) {
|
||||||
|
switch (ch) {
|
||||||
|
case ' ':
|
||||||
|
if (left) sb.append("%20");
|
||||||
|
else sb.append('+');
|
||||||
|
break;
|
||||||
|
case '?':
|
||||||
|
left = false;
|
||||||
|
default:
|
||||||
|
if (ch >= 0x80)
|
||||||
|
sb.append(encodeURL(Character.toString(ch)));
|
||||||
|
else
|
||||||
|
sb.append(ch);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method aims to solve problem when "Location" in stupid server's response is not encoded.
|
||||||
|
* @see <a href="https://github.com/curl/curl/issues/473">Issue with libcurl</a>
|
||||||
|
* @param conn
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static HttpURLConnection resolveConnection(HttpURLConnection conn) throws IOException {
|
||||||
|
int redirect = 0;
|
||||||
|
while (true) {
|
||||||
|
|
||||||
|
conn.setUseCaches(false);
|
||||||
|
conn.setConnectTimeout(15000);
|
||||||
|
conn.setReadTimeout(15000);
|
||||||
|
conn.setInstanceFollowRedirects(false);
|
||||||
|
Map<String, List<String>> properties = conn.getRequestProperties();
|
||||||
|
int code = conn.getResponseCode();
|
||||||
|
if (code >= 300 && code <= 307 && code != 306 && code != 304) {
|
||||||
|
String newURL = conn.getHeaderField("Location");
|
||||||
|
conn.disconnect();
|
||||||
|
|
||||||
|
if (redirect > 20) {
|
||||||
|
throw new IOException("Too much redirects");
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpURLConnection redirected = (HttpURLConnection) new URL(conn.getURL(), encodeLocation(newURL)).openConnection();
|
||||||
|
properties.forEach((key, value) -> value.forEach(element -> redirected.addRequestProperty(key, element)));
|
||||||
|
conn = redirected;
|
||||||
|
++redirect;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
public static String doGet(URL url) throws IOException {
|
public static String doGet(URL url) throws IOException {
|
||||||
return IOUtils.readFullyAsString(createConnection(url).getInputStream());
|
return IOUtils.readFullyAsString(createConnection(url).getInputStream());
|
||||||
}
|
}
|
||||||
@@ -110,10 +175,12 @@ public final class NetworkUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String detectFileName(URL url) throws IOException {
|
public static String detectFileName(URL url) throws IOException {
|
||||||
HttpURLConnection conn = createConnection(url);
|
HttpURLConnection conn = resolveConnection(createConnection(url));
|
||||||
conn.connect();
|
int code = conn.getResponseCode();
|
||||||
if (conn.getResponseCode() / 100 != 2)
|
if (code == 404)
|
||||||
throw new IOException("Response code " + conn.getResponseCode());
|
throw new FileNotFoundException();
|
||||||
|
if (code / 100 != 2)
|
||||||
|
throw new IOException(url + ": response code " + conn.getResponseCode());
|
||||||
|
|
||||||
return detectFileName(conn);
|
return detectFileName(conn);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user