将代码中的 URL 替换为 URI (#4131)
This commit is contained in:
@@ -53,7 +53,7 @@ import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
@@ -267,27 +267,27 @@ public final class LauncherHelper {
|
||||
if (ex.getCause() instanceof ResponseCodeException) {
|
||||
ResponseCodeException rce = (ResponseCodeException) ex.getCause();
|
||||
int responseCode = rce.getResponseCode();
|
||||
URL url = rce.getUrl();
|
||||
URI uri = rce.getUri();
|
||||
if (responseCode == 404)
|
||||
message += i18n("download.code.404", url);
|
||||
message += i18n("download.code.404", uri);
|
||||
else
|
||||
message += i18n("download.failed", url, responseCode);
|
||||
message += i18n("download.failed", uri, responseCode);
|
||||
} else {
|
||||
message += StringUtils.getStackTrace(ex.getCause());
|
||||
}
|
||||
} else if (ex instanceof DownloadException) {
|
||||
URL url = ((DownloadException) ex).getUrl();
|
||||
URI uri = ((DownloadException) ex).getUri();
|
||||
if (ex.getCause() instanceof SocketTimeoutException) {
|
||||
message = i18n("install.failed.downloading.timeout", url);
|
||||
message = i18n("install.failed.downloading.timeout", uri);
|
||||
} else if (ex.getCause() instanceof ResponseCodeException) {
|
||||
ResponseCodeException responseCodeException = (ResponseCodeException) ex.getCause();
|
||||
if (I18n.hasKey("download.code." + responseCodeException.getResponseCode())) {
|
||||
message = i18n("download.code." + responseCodeException.getResponseCode(), url);
|
||||
message = i18n("download.code." + responseCodeException.getResponseCode(), uri);
|
||||
} else {
|
||||
message = i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(ex.getCause());
|
||||
message = i18n("install.failed.downloading.detail", uri) + "\n" + StringUtils.getStackTrace(ex.getCause());
|
||||
}
|
||||
} else {
|
||||
message = i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(ex.getCause());
|
||||
message = i18n("install.failed.downloading.detail", uri) + "\n" + StringUtils.getStackTrace(ex.getCause());
|
||||
}
|
||||
} else if (ex instanceof GameAssetIndexDownloadTask.GameAssetIndexMalformedException) {
|
||||
message = i18n("assets.index.malformed");
|
||||
@@ -298,11 +298,11 @@ public final class LauncherHelper {
|
||||
} else if (ex instanceof ResponseCodeException) {
|
||||
ResponseCodeException rce = (ResponseCodeException) ex;
|
||||
int responseCode = rce.getResponseCode();
|
||||
URL url = rce.getUrl();
|
||||
URI uri = rce.getUri();
|
||||
if (responseCode == 404)
|
||||
message = i18n("download.code.404", url);
|
||||
message = i18n("download.code.404", uri);
|
||||
else
|
||||
message = i18n("download.failed", url, responseCode);
|
||||
message = i18n("download.failed", uri, responseCode);
|
||||
} else if (ex instanceof CommandTooLongException) {
|
||||
message = i18n("launch.failed.command_too_long");
|
||||
} else if (ex instanceof ExecutionPolicyLimitException) {
|
||||
|
||||
@@ -45,7 +45,7 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -110,7 +110,7 @@ public final class TexturesLoader {
|
||||
if (!Files.isRegularFile(file)) {
|
||||
// download it
|
||||
try {
|
||||
new FileDownloadTask(new URL(texture.getUrl()), file.toFile()).run();
|
||||
new FileDownloadTask(URI.create(texture.getUrl()), file).run();
|
||||
LOG.info("Texture downloaded: " + texture.getUrl());
|
||||
} catch (Exception e) {
|
||||
if (Files.isRegularFile(file)) {
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
@@ -137,26 +137,26 @@ public final class DownloadProviders {
|
||||
|
||||
public static String localizeErrorMessage(Throwable exception) {
|
||||
if (exception instanceof DownloadException) {
|
||||
URL url = ((DownloadException) exception).getUrl();
|
||||
URI uri = ((DownloadException) exception).getUri();
|
||||
if (exception.getCause() instanceof SocketTimeoutException) {
|
||||
return i18n("install.failed.downloading.timeout", url);
|
||||
return i18n("install.failed.downloading.timeout", uri);
|
||||
} else if (exception.getCause() instanceof ResponseCodeException) {
|
||||
ResponseCodeException responseCodeException = (ResponseCodeException) exception.getCause();
|
||||
if (I18n.hasKey("download.code." + responseCodeException.getResponseCode())) {
|
||||
return i18n("download.code." + responseCodeException.getResponseCode(), url);
|
||||
return i18n("download.code." + responseCodeException.getResponseCode(), uri);
|
||||
} else {
|
||||
return i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(exception.getCause());
|
||||
return i18n("install.failed.downloading.detail", uri) + "\n" + StringUtils.getStackTrace(exception.getCause());
|
||||
}
|
||||
} else if (exception.getCause() instanceof FileNotFoundException) {
|
||||
return i18n("download.code.404", url);
|
||||
return i18n("download.code.404", uri);
|
||||
} else if (exception.getCause() instanceof AccessDeniedException) {
|
||||
return i18n("install.failed.downloading.detail", url) + "\n" + i18n("exception.access_denied", ((AccessDeniedException) exception.getCause()).getFile());
|
||||
return i18n("install.failed.downloading.detail", uri) + "\n" + i18n("exception.access_denied", ((AccessDeniedException) exception.getCause()).getFile());
|
||||
} else if (exception.getCause() instanceof ArtifactMalformedException) {
|
||||
return i18n("install.failed.downloading.detail", url) + "\n" + i18n("exception.artifact_malformed");
|
||||
return i18n("install.failed.downloading.detail", uri) + "\n" + i18n("exception.artifact_malformed");
|
||||
} else if (exception.getCause() instanceof SSLHandshakeException) {
|
||||
return i18n("install.failed.downloading.detail", url) + "\n" + i18n("exception.ssl_handshake");
|
||||
return i18n("install.failed.downloading.detail", uri) + "\n" + i18n("exception.ssl_handshake");
|
||||
} else {
|
||||
return i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(exception.getCause());
|
||||
return i18n("install.failed.downloading.detail", uri) + "\n" + StringUtils.getStackTrace(exception.getCause());
|
||||
}
|
||||
} else if (exception instanceof ArtifactMalformedException) {
|
||||
return i18n("exception.artifact_malformed");
|
||||
|
||||
@@ -18,13 +18,15 @@
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -32,7 +34,10 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
public final class ProxyManager {
|
||||
|
||||
private static final ProxySelector NO_PROXY = new SimpleProxySelector(Proxy.NO_PROXY);
|
||||
private static final ProxySelector SYSTEM_DEFAULT = Lang.requireNonNullElse(ProxySelector.getDefault(), NO_PROXY);
|
||||
private static final ProxySelector SYSTEM_DEFAULT = Objects.requireNonNullElse(ProxySelector.getDefault(), NO_PROXY);
|
||||
|
||||
private static volatile @NotNull ProxySelector defaultProxySelector = SYSTEM_DEFAULT;
|
||||
private static volatile @Nullable SimpleAuthenticator defaultAuthenticator = null;
|
||||
|
||||
private static ProxySelector getProxySelector() {
|
||||
if (config().hasProxy()) {
|
||||
@@ -53,7 +58,7 @@ public final class ProxyManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static Authenticator getAuthenticator() {
|
||||
private static SimpleAuthenticator getAuthenticator() {
|
||||
if (config().hasProxy() && config().hasProxyAuth()) {
|
||||
String username = config().getProxyUser();
|
||||
String password = config().getProxyPass();
|
||||
@@ -67,15 +72,34 @@ public final class ProxyManager {
|
||||
}
|
||||
|
||||
static void init() {
|
||||
ProxySelector.setDefault(getProxySelector());
|
||||
InvalidationListener updateProxySelector = observable -> ProxySelector.setDefault(getProxySelector());
|
||||
ProxySelector.setDefault(new ProxySelector() {
|
||||
@Override
|
||||
public List<Proxy> select(URI uri) {
|
||||
return defaultProxySelector.select(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
|
||||
defaultProxySelector.connectFailed(uri, sa, ioe);
|
||||
}
|
||||
});
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
var defaultAuthenticator = ProxyManager.defaultAuthenticator;
|
||||
return defaultAuthenticator != null ? defaultAuthenticator.getPasswordAuthentication() : null;
|
||||
}
|
||||
});
|
||||
|
||||
defaultProxySelector = getProxySelector();
|
||||
InvalidationListener updateProxySelector = observable -> defaultProxySelector = getProxySelector();
|
||||
config().proxyTypeProperty().addListener(updateProxySelector);
|
||||
config().proxyHostProperty().addListener(updateProxySelector);
|
||||
config().proxyPortProperty().addListener(updateProxySelector);
|
||||
config().hasProxyProperty().addListener(updateProxySelector);
|
||||
|
||||
Authenticator.setDefault(getAuthenticator());
|
||||
InvalidationListener updateAuthenticator = observable -> Authenticator.setDefault(getAuthenticator());
|
||||
defaultAuthenticator = getAuthenticator();
|
||||
InvalidationListener updateAuthenticator = observable -> defaultAuthenticator = getAuthenticator();
|
||||
config().hasProxyProperty().addListener(updateAuthenticator);
|
||||
config().hasProxyAuthProperty().addListener(updateAuthenticator);
|
||||
config().proxyUserProperty().addListener(updateAuthenticator);
|
||||
@@ -124,7 +148,7 @@ public final class ProxyManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
public PasswordAuthentication getPasswordAuthentication() {
|
||||
return getRequestorType() == RequestorType.PROXY ? new PasswordAuthentication(username, password) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -844,14 +844,14 @@ public final class FXUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static Image loadImage(URL url) throws Exception {
|
||||
URLConnection connection = NetworkUtils.createConnection(url);
|
||||
public static Image loadImage(URI uri) throws Exception {
|
||||
URLConnection connection = NetworkUtils.createConnection(uri);
|
||||
if (connection instanceof HttpURLConnection) {
|
||||
connection = NetworkUtils.resolveConnection((HttpURLConnection) connection);
|
||||
}
|
||||
|
||||
try (InputStream input = connection.getInputStream()) {
|
||||
String path = url.getPath();
|
||||
String path = uri.getPath();
|
||||
if (path != null && "webp".equalsIgnoreCase(StringUtils.substringAfterLast(path, '.')))
|
||||
return loadWebPImage(input);
|
||||
else {
|
||||
|
||||
@@ -31,10 +31,10 @@ import org.jackhuang.hmcl.ui.construct.DialogAware;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
@@ -196,7 +196,7 @@ public final class AddAuthlibInjectorServerPane extends TransitionPane implement
|
||||
lblServerName.setText(serverBeingAdded.getName());
|
||||
lblServerUrl.setText(serverBeingAdded.getUrl());
|
||||
|
||||
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol()));
|
||||
lblServerWarning.setVisible("http".equals(URI.create(serverBeingAdded.getUrl()).getScheme()));
|
||||
|
||||
this.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT);
|
||||
} else {
|
||||
|
||||
@@ -26,9 +26,7 @@ import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -43,7 +41,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
public class TaskExecutorDialogPane extends BorderPane {
|
||||
private TaskExecutor executor;
|
||||
private TaskCancellationAction onCancel;
|
||||
private final Consumer<FileDownloadTask.SpeedEvent> speedEventHandler;
|
||||
private final Consumer<FetchTask.SpeedEvent> speedEventHandler;
|
||||
|
||||
private final Label lblTitle;
|
||||
private final Label lblProgress;
|
||||
@@ -108,7 +106,7 @@ public class TaskExecutorDialogPane extends BorderPane {
|
||||
String finalUnit = unit;
|
||||
Platform.runLater(() -> lblProgress.setText(String.format("%.1f %s", finalSpeed, finalUnit)));
|
||||
};
|
||||
FileDownloadTask.speedEvent.channel(FileDownloadTask.SpeedEvent.class).registerWeak(speedEventHandler);
|
||||
FileDownloadTask.speedEvent.channel(FetchTask.SpeedEvent.class).registerWeak(speedEventHandler);
|
||||
|
||||
onEscPressed(this, btnCancel::fire);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -214,7 +214,7 @@ public class DecoratorController {
|
||||
String backgroundImageUrl = config().getBackgroundImageUrl();
|
||||
if (backgroundImageUrl != null) {
|
||||
try {
|
||||
image = FXUtils.loadImage(new URL(backgroundImageUrl));
|
||||
image = FXUtils.loadImage(URI.create(backgroundImageUrl));
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Couldn't load background image", e);
|
||||
}
|
||||
|
||||
@@ -52,10 +52,10 @@ import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
@@ -156,7 +156,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
Path dest = runDirectory.resolve(subdirectoryName).resolve(result);
|
||||
|
||||
Controllers.taskDialog(Task.composeAsync(() -> {
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest.toFile());
|
||||
var task = new FileDownloadTask(URI.create(file.getFile().getUrl()), dest);
|
||||
task.setName(file.getName());
|
||||
return task;
|
||||
}).whenComplete(Schedulers.javafx(), exception -> {
|
||||
|
||||
@@ -29,9 +29,7 @@ import javafx.scene.shape.SVGPath;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackManifest;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
@@ -43,7 +41,7 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
@@ -128,7 +126,7 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
|
||||
private void onChooseRemoteFile() {
|
||||
Controllers.prompt(i18n("modpack.choose.remote.tooltip"), (urlString, resolve, reject) -> {
|
||||
try {
|
||||
URL url = new URL(urlString);
|
||||
URI url = URI.create(urlString);
|
||||
if (urlString.endsWith("server-manifest.json")) {
|
||||
// if urlString ends with .json, we assume that the url is server-manifest.json
|
||||
Controllers.taskDialog(new GetTask(url).whenComplete(Schedulers.javafx(), (result, e) -> {
|
||||
@@ -150,7 +148,7 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
|
||||
resolve.run();
|
||||
|
||||
Controllers.taskDialog(
|
||||
new FileDownloadTask(url, modpack.toFile(), null)
|
||||
new FileDownloadTask(url, modpack, null)
|
||||
.whenComplete(Schedulers.javafx(), e -> {
|
||||
if (e == null) {
|
||||
resolve.run();
|
||||
|
||||
@@ -37,7 +37,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -133,28 +133,28 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
|
||||
if (exception.getCause() instanceof ResponseCodeException) {
|
||||
ResponseCodeException rce = (ResponseCodeException) exception.getCause();
|
||||
int responseCode = rce.getResponseCode();
|
||||
URL url = rce.getUrl();
|
||||
URI uri = rce.getUri();
|
||||
if (responseCode == 404)
|
||||
message += i18n("download.code.404", url);
|
||||
message += i18n("download.code.404", uri);
|
||||
else
|
||||
message += i18n("download.failed", url, responseCode);
|
||||
message += i18n("download.failed", uri, responseCode);
|
||||
} else {
|
||||
message += StringUtils.getStackTrace(exception.getCause());
|
||||
}
|
||||
Controllers.dialog(message, i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next);
|
||||
} else if (exception instanceof DownloadException) {
|
||||
URL url = ((DownloadException) exception).getUrl();
|
||||
URI uri = ((DownloadException) exception).getUri();
|
||||
if (exception.getCause() instanceof SocketTimeoutException) {
|
||||
Controllers.dialog(i18n("install.failed.downloading.timeout", url), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next);
|
||||
Controllers.dialog(i18n("install.failed.downloading.timeout", uri), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next);
|
||||
} else if (exception.getCause() instanceof ResponseCodeException) {
|
||||
ResponseCodeException responseCodeException = (ResponseCodeException) exception.getCause();
|
||||
if (I18n.hasKey("download.code." + responseCodeException.getResponseCode())) {
|
||||
Controllers.dialog(i18n("download.code." + responseCodeException.getResponseCode(), url), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next);
|
||||
Controllers.dialog(i18n("download.code." + responseCodeException.getResponseCode(), uri), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next);
|
||||
} else {
|
||||
Controllers.dialog(i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next);
|
||||
Controllers.dialog(i18n("install.failed.downloading.detail", uri) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next);
|
||||
}
|
||||
} else {
|
||||
Controllers.dialog(i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next);
|
||||
Controllers.dialog(i18n("install.failed.downloading.detail", uri) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next);
|
||||
}
|
||||
} else if (exception instanceof UnsupportedInstallationException) {
|
||||
switch (((UnsupportedInstallationException) exception).getReason()) {
|
||||
|
||||
@@ -363,7 +363,7 @@ public final class JavaDownloadDialog extends StackPane {
|
||||
return getIntegrityCheck
|
||||
.thenComposeAsync(integrityCheck ->
|
||||
new FileDownloadTask(downloadProvider.injectURLWithCandidates(fileInfo.getDirectDownloadUri()),
|
||||
targetFile, integrityCheck).setName(fileInfo.getFileName()))
|
||||
targetFile.toPath(), integrityCheck).setName(fileInfo.getFileName()))
|
||||
.thenSupplyAsync(targetFile::toPath);
|
||||
})
|
||||
.whenComplete(Schedulers.javafx(), ((result, exception) -> {
|
||||
|
||||
@@ -49,12 +49,12 @@ import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -180,7 +180,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
Controllers.taskDialog(
|
||||
Task.composeAsync(() -> {
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest, file.getFile().getIntegrityCheck());
|
||||
var task = new FileDownloadTask(URI.create(file.getFile().getUrl()), dest.toPath(), file.getFile().getIntegrityCheck());
|
||||
task.setName(file.getName());
|
||||
return task;
|
||||
}),
|
||||
|
||||
@@ -45,7 +45,7 @@ import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.CSVTable;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -293,9 +293,9 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
if (isDisabled)
|
||||
fileName += ModManager.DISABLED_EXTENSION;
|
||||
|
||||
FileDownloadTask task = new FileDownloadTask(
|
||||
new URL(remote.getFile().getUrl()),
|
||||
modManager.getModsDirectory().resolve(fileName).toFile());
|
||||
var task = new FileDownloadTask(
|
||||
URI.create(remote.getFile().getUrl()),
|
||||
modManager.getModsDirectory().resolve(fileName));
|
||||
|
||||
task.setName(remote.getName());
|
||||
return task;
|
||||
|
||||
@@ -28,10 +28,7 @@ import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.account.CreateAccountPane;
|
||||
@@ -47,7 +44,8 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.CancellationException;
|
||||
@@ -75,18 +73,18 @@ public final class Versions {
|
||||
|
||||
public static void downloadModpackImpl(Profile profile, String version, RemoteMod.Version file) {
|
||||
Path modpack;
|
||||
URL downloadURL;
|
||||
URI downloadURL;
|
||||
try {
|
||||
modpack = Files.createTempFile("modpack", ".zip");
|
||||
downloadURL = new URL(file.getFile().getUrl());
|
||||
} catch (IOException e) {
|
||||
downloadURL = new URI(file.getFile().getUrl());
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
Controllers.dialog(
|
||||
i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e),
|
||||
i18n("download.failed.no_code"), MessageDialogPane.MessageType.ERROR);
|
||||
return;
|
||||
}
|
||||
Controllers.taskDialog(
|
||||
new FileDownloadTask(downloadURL, modpack.toFile())
|
||||
new FileDownloadTask(downloadURL, modpack)
|
||||
.whenComplete(Schedulers.javafx(), e -> {
|
||||
if (e == null) {
|
||||
if (version != null) {
|
||||
|
||||
@@ -19,21 +19,21 @@ package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.util.Pack200Utils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.tukaani.xz.XZInputStream;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.jar.JarOutputStream;
|
||||
|
||||
class HMCLDownloadTask extends FileDownloadTask {
|
||||
final class HMCLDownloadTask extends FileDownloadTask {
|
||||
|
||||
private RemoteVersion.Type archiveFormat;
|
||||
private final RemoteVersion.Type archiveFormat;
|
||||
|
||||
public HMCLDownloadTask(RemoteVersion version, Path target) {
|
||||
super(NetworkUtils.toURL(version.getUrl()), target.toFile(), version.getIntegrityCheck());
|
||||
super(URI.create(version.getUrl()), target, version.getIntegrityCheck());
|
||||
archiveFormat = version.getType();
|
||||
}
|
||||
|
||||
@@ -42,8 +42,7 @@ class HMCLDownloadTask extends FileDownloadTask {
|
||||
super.execute();
|
||||
|
||||
try {
|
||||
Path target = getFile().toPath();
|
||||
|
||||
Path target = getPath();
|
||||
switch (archiveFormat) {
|
||||
case JAR:
|
||||
break;
|
||||
@@ -51,7 +50,7 @@ class HMCLDownloadTask extends FileDownloadTask {
|
||||
case PACK_XZ:
|
||||
byte[] raw = Files.readAllBytes(target);
|
||||
try (InputStream in = new XZInputStream(new ByteArrayInputStream(raw));
|
||||
JarOutputStream out = new JarOutputStream(Files.newOutputStream(target))) {
|
||||
JarOutputStream out = new JarOutputStream(Files.newOutputStream(target))) {
|
||||
Pack200Utils.unpack(in, out);
|
||||
}
|
||||
break;
|
||||
@@ -60,7 +59,11 @@ class HMCLDownloadTask extends FileDownloadTask {
|
||||
throw new IllegalArgumentException("Unknown format: " + archiveFormat);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
getFile().delete();
|
||||
try {
|
||||
Files.deleteIfExists(getPath());
|
||||
} catch (Throwable e2) {
|
||||
e.addSuppressed(e2);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +26,14 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
|
||||
public class RemoteVersion {
|
||||
|
||||
public static RemoteVersion fetch(UpdateChannel channel, String url) throws IOException {
|
||||
try {
|
||||
JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(NetworkUtils.toURL(url)), JsonObject.class);
|
||||
JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(URI.create(url)), JsonObject.class);
|
||||
String version = Optional.ofNullable(response.get("version")).map(JsonElement::getAsString).orElseThrow(() -> new IOException("version is missing"));
|
||||
String jarUrl = Optional.ofNullable(response.get("jar")).map(JsonElement::getAsString).orElse(null);
|
||||
String jarHash = Optional.ofNullable(response.get("jarsha1")).map(JsonElement::getAsString).orElse(null);
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -122,7 +123,7 @@ public final class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
map.put("version", Metadata.VERSION);
|
||||
map.put("log", LOG.getLogs());
|
||||
try {
|
||||
String response = NetworkUtils.doPost(NetworkUtils.toURL(Metadata.PUBLISH_URL + "/hmcl/crash.php"), map);
|
||||
String response = NetworkUtils.doPost(URI.create(Metadata.PUBLISH_URL + "/hmcl/crash.php"), map);
|
||||
if (StringUtils.isNotBlank(response))
|
||||
LOG.error("Crash server response: " + response);
|
||||
} catch (IOException ex) {
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
@@ -96,7 +96,7 @@ public class AuthlibInjectorDownloader implements AuthlibInjectorArtifactProvide
|
||||
}
|
||||
|
||||
try {
|
||||
new FileDownloadTask(downloadProvider.get().injectURLWithCandidates(latest.downloadUrl), artifactLocation.toFile(),
|
||||
new FileDownloadTask(downloadProvider.get().injectURLWithCandidates(latest.downloadUrl), artifactLocation,
|
||||
Optional.ofNullable(latest.checksums.get("sha256"))
|
||||
.map(checksum -> new IntegrityCheck("SHA-256", checksum))
|
||||
.orElse(null))
|
||||
@@ -110,9 +110,9 @@ public class AuthlibInjectorDownloader implements AuthlibInjectorArtifactProvide
|
||||
|
||||
private AuthlibInjectorVersionInfo getLatestArtifactInfo() throws IOException {
|
||||
IOException exception = null;
|
||||
for (URL url : downloadProvider.get().injectURLWithCandidates(LATEST_BUILD_URL)) {
|
||||
for (URI url : downloadProvider.get().injectURLWithCandidates(LATEST_BUILD_URL)) {
|
||||
try {
|
||||
return HttpRequest.GET(url.toExternalForm()).getJson(AuthlibInjectorVersionInfo.class);
|
||||
return HttpRequest.GET(url.toString()).getJson(AuthlibInjectorVersionInfo.class);
|
||||
} catch (IOException | JsonParseException e) {
|
||||
if (exception == null) {
|
||||
exception = new IOException("Failed to fetch authlib-injector artifact info");
|
||||
|
||||
@@ -17,13 +17,11 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth.authlibinjector;
|
||||
|
||||
import static org.jackhuang.hmcl.util.io.NetworkUtils.toURL;
|
||||
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilProvider;
|
||||
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AuthlibInjectorProvider implements YggdrasilProvider {
|
||||
@@ -35,33 +33,33 @@ public class AuthlibInjectorProvider implements YggdrasilProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getAuthenticationURL() throws AuthenticationException {
|
||||
return toURL(apiRoot + "authserver/authenticate");
|
||||
public URI getAuthenticationURL() throws AuthenticationException {
|
||||
return URI.create(apiRoot + "authserver/authenticate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getRefreshmentURL() throws AuthenticationException {
|
||||
return toURL(apiRoot + "authserver/refresh");
|
||||
public URI getRefreshmentURL() throws AuthenticationException {
|
||||
return URI.create(apiRoot + "authserver/refresh");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getValidationURL() throws AuthenticationException {
|
||||
return toURL(apiRoot + "authserver/validate");
|
||||
public URI getValidationURL() throws AuthenticationException {
|
||||
return URI.create(apiRoot + "authserver/validate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getInvalidationURL() throws AuthenticationException {
|
||||
return toURL(apiRoot + "authserver/invalidate");
|
||||
public URI getInvalidationURL() throws AuthenticationException {
|
||||
return URI.create(apiRoot + "authserver/invalidate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getSkinUploadURL(UUID uuid) throws UnsupportedOperationException {
|
||||
return toURL(apiRoot + "api/user/profile/" + UUIDTypeAdapter.fromUUID(uuid) + "/skin");
|
||||
public URI getSkinUploadURL(UUID uuid) throws UnsupportedOperationException {
|
||||
return URI.create(apiRoot + "api/user/profile/" + UUIDTypeAdapter.fromUUID(uuid) + "/skin");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getProfilePropertiesURL(UUID uuid) throws AuthenticationException {
|
||||
return toURL(apiRoot + "sessionserver/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid));
|
||||
public URI getProfilePropertiesURL(UUID uuid) throws AuthenticationException {
|
||||
return URI.create(apiRoot + "sessionserver/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth.authlibinjector;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -25,14 +24,15 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -58,17 +58,14 @@ public class AuthlibInjectorServer implements Observable {
|
||||
public static AuthlibInjectorServer locateServer(String url) throws IOException {
|
||||
try {
|
||||
url = addHttpsIfMissing(url);
|
||||
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
|
||||
conn.setRequestProperty("Accept-Language", Locale.getDefault().toLanguageTag());
|
||||
|
||||
HttpURLConnection conn = NetworkUtils.createHttpConnection(URI.create(url));
|
||||
String ali = conn.getHeaderField("x-authlib-injector-api-location");
|
||||
if (ali != null) {
|
||||
URL absoluteAli = new URL(conn.getURL(), ali);
|
||||
URI absoluteAli = conn.getURL().toURI().resolve(ali);
|
||||
if (!urlEqualsIgnoreSlash(url, absoluteAli.toString())) {
|
||||
conn.disconnect();
|
||||
url = absoluteAli.toString();
|
||||
conn = (HttpURLConnection) absoluteAli.openConnection();
|
||||
conn.setRequestProperty("Accept-Language", Locale.getDefault().toLanguageTag());
|
||||
conn = NetworkUtils.createHttpConnection(absoluteAli);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,22 +74,26 @@ public class AuthlibInjectorServer implements Observable {
|
||||
|
||||
try {
|
||||
AuthlibInjectorServer server = new AuthlibInjectorServer(url);
|
||||
server.refreshMetadata(new String(conn.getInputStream().readAllBytes(), UTF_8));
|
||||
server.refreshMetadata(NetworkUtils.readFullyAsString(conn));
|
||||
return server;
|
||||
} finally {
|
||||
conn.disconnect();
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
} catch (IllegalArgumentException | URISyntaxException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String addHttpsIfMissing(String url) {
|
||||
String lowercased = url.toLowerCase(Locale.ROOT);
|
||||
if (!lowercased.startsWith("http://") && !lowercased.startsWith("https://")) {
|
||||
url = "https://" + url;
|
||||
private static String addHttpsIfMissing(String url) throws IOException {
|
||||
URI uri = URI.create(url);
|
||||
|
||||
if (uri.getScheme() == null) {
|
||||
return "https://" + url;
|
||||
} else if (!NetworkUtils.isHttpUri(uri)) {
|
||||
throw new IOException("Yggdrasil server should be an HTTP or HTTPS URI, but got: " + url);
|
||||
} else {
|
||||
return url;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
private static boolean urlEqualsIgnoreSlash(String a, String b) {
|
||||
@@ -103,7 +104,7 @@ public class AuthlibInjectorServer implements Observable {
|
||||
return a.equals(b);
|
||||
}
|
||||
|
||||
private String url;
|
||||
private final String url;
|
||||
@Nullable
|
||||
private String metadataResponse;
|
||||
private long metadataTimestamp;
|
||||
|
||||
@@ -34,7 +34,8 @@ import org.jackhuang.hmcl.util.javafx.ObservableOptionalCache;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -164,7 +165,7 @@ public class MicrosoftService {
|
||||
.accept("application/json").createConnection();
|
||||
|
||||
if (request.getResponseCode() != 200) {
|
||||
throw new ResponseCodeException(new URL("https://api.minecraftservices.com/entitlements/mcstore"), request.getResponseCode());
|
||||
throw new ResponseCodeException(URI.create("https://api.minecraftservices.com/entitlements/mcstore"), request.getResponseCode());
|
||||
}
|
||||
|
||||
// Get Minecraft Account UUID
|
||||
@@ -247,22 +248,22 @@ public class MicrosoftService {
|
||||
if (responseCode == HTTP_NOT_FOUND) {
|
||||
throw new NoMinecraftJavaEditionProfileException();
|
||||
} else if (responseCode != 200) {
|
||||
throw new ResponseCodeException(new URL("https://api.minecraftservices.com/minecraft/profile"), responseCode);
|
||||
throw new ResponseCodeException(URI.create("https://api.minecraftservices.com/minecraft/profile"), responseCode);
|
||||
}
|
||||
|
||||
String result = NetworkUtils.readData(conn);
|
||||
String result = NetworkUtils.readFullyAsString(conn);
|
||||
return JsonUtils.fromNonNullJson(result, MinecraftProfileResponse.class);
|
||||
}
|
||||
|
||||
public Optional<CompleteGameProfile> getCompleteGameProfile(UUID uuid) throws AuthenticationException {
|
||||
Objects.requireNonNull(uuid);
|
||||
|
||||
return Optional.ofNullable(GSON.fromJson(request(NetworkUtils.toURL("https://sessionserver.mojang.com/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid)), null), CompleteGameProfile.class));
|
||||
return Optional.ofNullable(GSON.fromJson(request(URI.create("https://sessionserver.mojang.com/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid)), null), CompleteGameProfile.class));
|
||||
}
|
||||
|
||||
public void uploadSkin(String accessToken, boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {
|
||||
try {
|
||||
HttpURLConnection con = NetworkUtils.createHttpConnection(NetworkUtils.toURL("https://api.minecraftservices.com/minecraft/profile/skins"));
|
||||
HttpURLConnection con = NetworkUtils.createHttpConnection(URI.create("https://api.minecraftservices.com/minecraft/profile/skins"));
|
||||
con.setRequestMethod("POST");
|
||||
con.setRequestProperty("Authorization", "Bearer " + accessToken);
|
||||
con.setDoOutput(true);
|
||||
@@ -273,21 +274,21 @@ public class MicrosoftService {
|
||||
}
|
||||
}
|
||||
|
||||
String response = NetworkUtils.readData(con);
|
||||
String response = NetworkUtils.readFullyAsString(con);
|
||||
if (StringUtils.isBlank(response)) {
|
||||
if (con.getResponseCode() / 100 != 2)
|
||||
throw new ResponseCodeException(con.getURL(), con.getResponseCode());
|
||||
throw new ResponseCodeException(con.getURL().toURI(), con.getResponseCode());
|
||||
} else {
|
||||
MinecraftErrorResponse profileResponse = GSON.fromJson(response, MinecraftErrorResponse.class);
|
||||
if (StringUtils.isNotBlank(profileResponse.errorMessage) || con.getResponseCode() / 100 != 2)
|
||||
throw new AuthenticationException("Failed to upload skin, response code: " + con.getResponseCode() + ", response: " + response);
|
||||
}
|
||||
} catch (IOException | JsonParseException e) {
|
||||
} catch (IOException | JsonParseException | URISyntaxException e) {
|
||||
throw new AuthenticationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String request(URL url, Object payload) throws AuthenticationException {
|
||||
private static String request(URI url, Object payload) throws AuthenticationException {
|
||||
try {
|
||||
if (payload == null)
|
||||
return NetworkUtils.doGet(url);
|
||||
|
||||
@@ -33,14 +33,11 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
@@ -169,7 +166,7 @@ public class Skin {
|
||||
String realCslApi = type == Type.LITTLE_SKIN
|
||||
? "https://littleskin.cn/csl"
|
||||
: StringUtils.removeSuffix(Lang.requireNonNullElse(cslApi, ""), "/");
|
||||
return Task.composeAsync(() -> new GetTask(new URL(String.format("%s/%s.json", realCslApi, username))))
|
||||
return Task.composeAsync(() -> new GetTask(URI.create(String.format("%s/%s.json", realCslApi, username))))
|
||||
.thenComposeAsync(json -> {
|
||||
SkinJson result = JsonUtils.GSON.fromJson(json, SkinJson.class);
|
||||
|
||||
@@ -179,8 +176,8 @@ public class Skin {
|
||||
|
||||
return Task.allOf(
|
||||
Task.supplyAsync(result::getModel),
|
||||
result.getHash() == null ? Task.supplyAsync(() -> null) : new FetchBytesTask(new URL(String.format("%s/textures/%s", realCslApi, result.getHash())), 3),
|
||||
result.getCapeHash() == null ? Task.supplyAsync(() -> null) : new FetchBytesTask(new URL(String.format("%s/textures/%s", realCslApi, result.getCapeHash())), 3)
|
||||
result.getHash() == null ? Task.supplyAsync(() -> null) : new FetchBytesTask(URI.create(String.format("%s/textures/%s", realCslApi, result.getHash())), 3),
|
||||
result.getCapeHash() == null ? Task.supplyAsync(() -> null) : new FetchBytesTask(URI.create(String.format("%s/textures/%s", realCslApi, result.getCapeHash())), 3)
|
||||
);
|
||||
}).thenApplyAsync(result -> {
|
||||
if (result == null) {
|
||||
@@ -232,8 +229,8 @@ public class Skin {
|
||||
|
||||
private static class FetchBytesTask extends FetchTask<InputStream> {
|
||||
|
||||
public FetchBytesTask(URL url, int retry) {
|
||||
super(Collections.singletonList(url), retry);
|
||||
public FetchBytesTask(URI uri, int retry) {
|
||||
super(List.of(uri), retry);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -247,7 +244,7 @@ public class Skin {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Context getContext(URLConnection conn, boolean checkETag) throws IOException {
|
||||
protected Context getContext(URLConnection connection, boolean checkETag) throws IOException {
|
||||
return new Context() {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
@@ -263,7 +260,7 @@ public class Skin {
|
||||
setResult(new ByteArrayInputStream(baos.toByteArray()));
|
||||
|
||||
if (checkETag) {
|
||||
repository.cacheBytes(baos.toByteArray(), conn);
|
||||
repository.cacheBytes(connection, baos.toByteArray());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -27,13 +27,13 @@ import java.util.UUID;
|
||||
*/
|
||||
public interface YggdrasilProvider {
|
||||
|
||||
URL getAuthenticationURL() throws AuthenticationException;
|
||||
URI getAuthenticationURL() throws AuthenticationException;
|
||||
|
||||
URL getRefreshmentURL() throws AuthenticationException;
|
||||
URI getRefreshmentURL() throws AuthenticationException;
|
||||
|
||||
URL getValidationURL() throws AuthenticationException;
|
||||
URI getValidationURL() throws AuthenticationException;
|
||||
|
||||
URL getInvalidationURL() throws AuthenticationException;
|
||||
URI getInvalidationURL() throws AuthenticationException;
|
||||
|
||||
/**
|
||||
* URL to upload skin.
|
||||
@@ -51,8 +51,8 @@ public interface YggdrasilProvider {
|
||||
* @throws AuthenticationException if url cannot be generated. e.g. some parameter or query is malformed.
|
||||
* @throws UnsupportedOperationException if the Yggdrasil provider does not support third-party skin uploading.
|
||||
*/
|
||||
URL getSkinUploadURL(UUID uuid) throws AuthenticationException, UnsupportedOperationException;
|
||||
URI getSkinUploadURL(UUID uuid) throws AuthenticationException, UnsupportedOperationException;
|
||||
|
||||
URL getProfilePropertiesURL(UUID uuid) throws AuthenticationException;
|
||||
URI getProfilePropertiesURL(UUID uuid) throws AuthenticationException;
|
||||
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import org.jackhuang.hmcl.util.javafx.ObservableOptionalCache;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -160,7 +160,7 @@ public class YggdrasilService {
|
||||
request.file("file", FileUtils.getName(file), "image/" + FileUtils.getExtension(file), fis);
|
||||
}
|
||||
}
|
||||
requireEmpty(NetworkUtils.readData(con));
|
||||
requireEmpty(NetworkUtils.readFullyAsString(con));
|
||||
} catch (IOException e) {
|
||||
throw new AuthenticationException(e);
|
||||
}
|
||||
@@ -227,12 +227,12 @@ public class YggdrasilService {
|
||||
}
|
||||
}
|
||||
|
||||
private static String request(URL url, Object payload) throws AuthenticationException {
|
||||
private static String request(URI uri, Object payload) throws AuthenticationException {
|
||||
try {
|
||||
if (payload == null)
|
||||
return NetworkUtils.doGet(url);
|
||||
return NetworkUtils.doGet(uri);
|
||||
else
|
||||
return NetworkUtils.doPost(url, payload instanceof String ? (String) payload : GSON.toJson(payload), "application/json");
|
||||
return NetworkUtils.doPost(uri, payload instanceof String ? (String) payload : GSON.toJson(payload), "application/json");
|
||||
} catch (IOException e) {
|
||||
throw new ServerDisconnectException(e);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -59,21 +59,21 @@ public class AdaptedDownloadProvider implements DownloadProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<URL> getAssetObjectCandidates(String assetObjectLocation) {
|
||||
public List<URI> getAssetObjectCandidates(String assetObjectLocation) {
|
||||
return downloadProviderCandidates.stream()
|
||||
.flatMap(d -> d.getAssetObjectCandidates(assetObjectLocation).stream())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<URL> injectURLWithCandidates(String baseURL) {
|
||||
public List<URI> injectURLWithCandidates(String baseURL) {
|
||||
return downloadProviderCandidates.stream()
|
||||
.flatMap(d -> d.injectURLWithCandidates(baseURL).stream())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<URL> injectURLsWithCandidates(List<String> urls) {
|
||||
public List<URI> injectURLsWithCandidates(List<String> urls) {
|
||||
return downloadProviderCandidates.stream()
|
||||
.flatMap(d -> d.injectURLsWithCandidates(urls).stream())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -51,17 +51,17 @@ public class AutoDownloadProvider implements DownloadProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<URL> getAssetObjectCandidates(String assetObjectLocation) {
|
||||
public List<URI> getAssetObjectCandidates(String assetObjectLocation) {
|
||||
return fileProvider.getAssetObjectCandidates(assetObjectLocation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<URL> injectURLWithCandidates(String baseURL) {
|
||||
public List<URI> injectURLWithCandidates(String baseURL) {
|
||||
return fileProvider.injectURLWithCandidates(baseURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<URL> injectURLsWithCandidates(List<String> urls) {
|
||||
public List<URI> injectURLsWithCandidates(List<String> urls) {
|
||||
return fileProvider.injectURLsWithCandidates(urls);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -35,8 +32,8 @@ public interface DownloadProvider {
|
||||
|
||||
String getAssetBaseURL();
|
||||
|
||||
default List<URL> getAssetObjectCandidates(String assetObjectLocation) {
|
||||
return Collections.singletonList(NetworkUtils.toURL(getAssetBaseURL() + assetObjectLocation));
|
||||
default List<URI> getAssetObjectCandidates(String assetObjectLocation) {
|
||||
return List.of(URI.create(getAssetBaseURL() + assetObjectLocation));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,11 +56,11 @@ public interface DownloadProvider {
|
||||
* @param baseURL original URL provided by Mojang and Forge.
|
||||
* @return the URL that is equivalent to [baseURL], but belongs to your own service provider.
|
||||
*/
|
||||
default List<URL> injectURLWithCandidates(String baseURL) {
|
||||
return Collections.singletonList(NetworkUtils.toURL(injectURL(baseURL)));
|
||||
default List<URI> injectURLWithCandidates(String baseURL) {
|
||||
return List.of(URI.create(injectURL(baseURL)));
|
||||
}
|
||||
|
||||
default List<URL> injectURLsWithCandidates(List<String> urls) {
|
||||
default List<URI> injectURLsWithCandidates(List<String> urls) {
|
||||
return urls.stream().flatMap(url -> injectURLWithCandidates(url).stream()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -59,8 +59,8 @@ public final class FabricAPIInstallTask extends Task<Version> {
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
dependencies.add(new FileDownloadTask(
|
||||
new URL(remote.getVersion().getFile().getUrl()),
|
||||
dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("fabric-api-" + remote.getVersion().getVersion() + ".jar").toFile(),
|
||||
URI.create(remote.getVersion().getFile().getUrl()),
|
||||
dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("fabric-api-" + remote.getVersion().getVersion() + ".jar"),
|
||||
remote.getVersion().getFile().getIntegrityCheck())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ public final class ForgeInstallTask extends Task<Version> {
|
||||
|
||||
dependent = new FileDownloadTask(
|
||||
dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()),
|
||||
installer.toFile(), null);
|
||||
installer, null);
|
||||
dependent.setCacheRepository(dependencyManager.getCacheRepository());
|
||||
dependent.setCaching(true);
|
||||
dependent.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER);
|
||||
|
||||
@@ -31,7 +31,6 @@ import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||
@@ -48,7 +47,7 @@ import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -346,12 +345,12 @@ public class ForgeNewInstallTask extends Task<Version> {
|
||||
throw new Exception("client_mappings download info not found");
|
||||
}
|
||||
|
||||
List<URL> mappingsUrl = dependencyManager.getDownloadProvider()
|
||||
List<URI> mappingsUrl = dependencyManager.getDownloadProvider()
|
||||
.injectURLWithCandidates(mappings.getUrl());
|
||||
FileDownloadTask mappingsTask = new FileDownloadTask(
|
||||
var mappingsTask = new FileDownloadTask(
|
||||
mappingsUrl,
|
||||
new File(output),
|
||||
IntegrityCheck.of("SHA-1", mappings.getSha1()));
|
||||
Path.of(output),
|
||||
FileDownloadTask.IntegrityCheck.of("SHA-1", mappings.getSha1()));
|
||||
mappingsTask.setCaching(true);
|
||||
mappingsTask.setCacheRepository(dependencyManager.getCacheRepository());
|
||||
return mappingsTask;
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.jackhuang.hmcl.util.CacheRepository;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
@@ -102,9 +102,9 @@ public final class GameAssetDownloadTask extends Task<Void> {
|
||||
LOG.warning("Unable to calc hash value of file " + file, e);
|
||||
}
|
||||
if (download) {
|
||||
List<URL> urls = dependencyManager.getDownloadProvider().getAssetObjectCandidates(assetObject.getLocation());
|
||||
List<URI> uris = dependencyManager.getDownloadProvider().getAssetObjectCandidates(assetObject.getLocation());
|
||||
|
||||
FileDownloadTask task = new FileDownloadTask(urls, file.toFile(), new FileDownloadTask.IntegrityCheck("SHA-1", assetObject.getHash()));
|
||||
var task = new FileDownloadTask(uris, file, new FileDownloadTask.IntegrityCheck("SHA-1", assetObject.getHash()));
|
||||
task.setName(assetObject.getHash());
|
||||
task.setCandidate(dependencyManager.getCacheRepository().getCommonDirectory()
|
||||
.resolve("assets").resolve("objects").resolve(assetObject.getLocation()));
|
||||
|
||||
@@ -94,9 +94,9 @@ public final class GameAssetIndexDownloadTask extends Task<Void> {
|
||||
|
||||
// We should not check the hash code of asset index file since this file is not consistent
|
||||
// And Mojang will modify this file anytime. So assetIndex.hash might be outdated.
|
||||
FileDownloadTask task = new FileDownloadTask(
|
||||
var task = new FileDownloadTask(
|
||||
dependencyManager.getDownloadProvider().injectURLWithCandidates(assetIndexInfo.getUrl()),
|
||||
assetIndexFile.toFile(),
|
||||
assetIndexFile,
|
||||
verifyHashCode ? new FileDownloadTask.IntegrityCheck("SHA-1", assetIndexInfo.getSha1()) : null
|
||||
);
|
||||
task.setCacheRepository(dependencyManager.getCacheRepository());
|
||||
|
||||
@@ -20,11 +20,10 @@ package org.jackhuang.hmcl.download.game;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.CacheRepository;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -54,12 +53,12 @@ public final class GameDownloadTask extends Task<Void> {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
File jar = dependencyManager.getGameRepository().getVersionJar(version);
|
||||
Path jar = dependencyManager.getGameRepository().getVersionJar(version).toPath();
|
||||
|
||||
FileDownloadTask task = new FileDownloadTask(
|
||||
var task = new FileDownloadTask(
|
||||
dependencyManager.getDownloadProvider().injectURLWithCandidates(version.getDownloadInfo().getUrl()),
|
||||
jar,
|
||||
IntegrityCheck.of(CacheRepository.SHA1, version.getDownloadInfo().getSha1()));
|
||||
FileDownloadTask.IntegrityCheck.of(CacheRepository.SHA1, version.getDownloadInfo().getSha1()));
|
||||
task.setCaching(true);
|
||||
task.setCacheRepository(dependencyManager.getCacheRepository());
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -116,8 +116,8 @@ public class LibraryDownloadTask extends Task<Void> {
|
||||
}
|
||||
|
||||
|
||||
List<URL> urls = dependencyManager.getDownloadProvider().injectURLWithCandidates(url);
|
||||
task = new FileDownloadTask(urls, jar,
|
||||
List<URI> uris = dependencyManager.getDownloadProvider().injectURLWithCandidates(url);
|
||||
task = new FileDownloadTask(uris, jar.toPath(),
|
||||
library.getDownload().getSha1() != null ? new IntegrityCheck("SHA-1", library.getDownload().getSha1()) : null);
|
||||
task.setCacheRepository(cacheRepository);
|
||||
task.setCaching(true);
|
||||
|
||||
@@ -103,7 +103,7 @@ public final class MojangJavaDownloadTask extends Task<MojangJavaDownloadTask.Re
|
||||
if (file.getDownloads().containsKey("lzma")) {
|
||||
DownloadInfo download = file.getDownloads().get("lzma");
|
||||
File tempFile = target.resolve(entry.getKey() + ".lzma").toFile();
|
||||
FileDownloadTask task = new FileDownloadTask(downloadProvider.injectURLWithCandidates(download.getUrl()), tempFile, new FileDownloadTask.IntegrityCheck("SHA-1", download.getSha1()));
|
||||
var task = new FileDownloadTask(downloadProvider.injectURLWithCandidates(download.getUrl()), tempFile.toPath(), new FileDownloadTask.IntegrityCheck("SHA-1", download.getSha1()));
|
||||
task.setName(entry.getKey());
|
||||
dependencies.add(task.thenRunAsync(() -> {
|
||||
Path decompressed = target.resolve(entry.getKey() + ".tmp");
|
||||
@@ -121,7 +121,7 @@ public final class MojangJavaDownloadTask extends Task<MojangJavaDownloadTask.Re
|
||||
}));
|
||||
} else if (file.getDownloads().containsKey("raw")) {
|
||||
DownloadInfo download = file.getDownloads().get("raw");
|
||||
FileDownloadTask task = new FileDownloadTask(downloadProvider.injectURLWithCandidates(download.getUrl()), dest.toFile(), new FileDownloadTask.IntegrityCheck("SHA-1", download.getSha1()));
|
||||
var task = new FileDownloadTask(downloadProvider.injectURLWithCandidates(download.getUrl()), dest, new FileDownloadTask.IntegrityCheck("SHA-1", download.getSha1()));
|
||||
task.setName(entry.getKey());
|
||||
if (file.isExecutable()) {
|
||||
dependencies.add(task.thenRunAsync(() -> dest.toFile().setExecutable(true)));
|
||||
|
||||
@@ -49,7 +49,7 @@ public final class NeoForgeInstallTask extends Task<Version> {
|
||||
|
||||
dependent = new FileDownloadTask(
|
||||
dependencyManager.getDownloadProvider().injectURLsWithCandidates(remoteVersion.getUrls()),
|
||||
installer.toFile(), null
|
||||
installer, null
|
||||
);
|
||||
dependent.setCacheRepository(dependencyManager.getCacheRepository());
|
||||
dependent.setCaching(true);
|
||||
|
||||
@@ -26,7 +26,6 @@ import org.jackhuang.hmcl.download.game.GameLibrariesTask;
|
||||
import org.jackhuang.hmcl.download.game.VersionJsonDownloadTask;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
@@ -44,7 +43,7 @@ import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -342,12 +341,12 @@ public class NeoForgeOldInstallTask extends Task<Version> {
|
||||
throw new Exception("client_mappings download info not found");
|
||||
}
|
||||
|
||||
List<URL> mappingsUrl = dependencyManager.getDownloadProvider()
|
||||
List<URI> mappingsUri = dependencyManager.getDownloadProvider()
|
||||
.injectURLWithCandidates(mappings.getUrl());
|
||||
FileDownloadTask mappingsTask = new FileDownloadTask(
|
||||
mappingsUrl,
|
||||
new File(output),
|
||||
IntegrityCheck.of("SHA-1", mappings.getSha1()));
|
||||
var mappingsTask = new FileDownloadTask(
|
||||
mappingsUri,
|
||||
Path.of(output),
|
||||
FileDownloadTask.IntegrityCheck.of("SHA-1", mappings.getSha1()));
|
||||
mappingsTask.setCaching(true);
|
||||
mappingsTask.setCacheRepository(dependencyManager.getCacheRepository());
|
||||
return mappingsTask;
|
||||
|
||||
@@ -96,9 +96,9 @@ public final class OptiFineInstallTask extends Task<Version> {
|
||||
dest = Files.createTempFile("optifine-installer", ".jar");
|
||||
|
||||
if (installer == null) {
|
||||
FileDownloadTask task = new FileDownloadTask(
|
||||
var task = new FileDownloadTask(
|
||||
dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()),
|
||||
dest.toFile(), null);
|
||||
dest, null);
|
||||
task.setCacheRepository(dependencyManager.getCacheRepository());
|
||||
task.setCaching(true);
|
||||
dependents.add(task);
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -59,8 +59,8 @@ public final class QuiltAPIInstallTask extends Task<Version> {
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
dependencies.add(new FileDownloadTask(
|
||||
new URL(remote.getVersion().getFile().getUrl()),
|
||||
dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("quilt-api-" + remote.getVersion().getVersion() + ".jar").toFile(),
|
||||
URI.create(remote.getVersion().getFile().getUrl()),
|
||||
dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("quilt-api-" + remote.getVersion().getVersion() + ".jar"),
|
||||
remote.getVersion().getFile().getIntegrityCheck())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ public final class CurseCompletionTask extends Task<Void> {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
FileDownloadTask task = new FileDownloadTask(f.getUrl(), path);
|
||||
var task = new FileDownloadTask(f.getUrl(), path.toPath());
|
||||
task.setCacheRepository(dependency.getCacheRepository());
|
||||
task.setCaching(true);
|
||||
return Stream.of(task.withCounter("hmcl.modpack.download"));
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -84,15 +84,15 @@ public final class CurseManifestFile implements Validation {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public URL getUrl() {
|
||||
public URI getUrl() {
|
||||
if (url == null) {
|
||||
if (fileName != null) {
|
||||
return NetworkUtils.toURL(NetworkUtils.encodeLocation(String.format("https://edge.forgecdn.net/files/%d/%d/%s", fileID / 1000, fileID % 1000, fileName)));
|
||||
return URI.create(NetworkUtils.encodeLocation(String.format("https://edge.forgecdn.net/files/%d/%d/%s", fileID / 1000, fileID % 1000, fileName)));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return NetworkUtils.toURL(NetworkUtils.encodeLocation(url));
|
||||
return URI.create(NetworkUtils.encodeLocation(url));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -102,7 +102,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
throw new CustomException();
|
||||
}
|
||||
})).thenComposeAsync(wrap(unused1 -> {
|
||||
return executor.one(new GetTask(new URL(manifest.getFileApi() + "/manifest.json")));
|
||||
return executor.one(new GetTask(URI.create(manifest.getFileApi() + "/manifest.json")));
|
||||
})).thenComposeAsync(wrap(remoteManifestJson -> {
|
||||
McbbsModpackManifest remoteManifest;
|
||||
// We needs to update modpack from online server.
|
||||
@@ -204,7 +204,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
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())));
|
||||
String result = NetworkUtils.doGet(URI.create(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) {
|
||||
@@ -213,7 +213,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
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())));
|
||||
String result = NetworkUtils.doGet(URI.create(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) {
|
||||
@@ -247,7 +247,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
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());
|
||||
var task = new FileDownloadTask(curseFile.getUrl(), modManager.getSimpleModPath(curseFile.getFileName()));
|
||||
task.setCacheRepository(dependency.getCacheRepository());
|
||||
task.setCaching(true);
|
||||
dependencies.add(task.withCounter("hmcl.modpack.download"));
|
||||
@@ -297,8 +297,8 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
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(),
|
||||
URI.create(remoteManifest.getFileApi() + "/overrides/" + NetworkUtils.encodeLocation(addonFile.getPath())),
|
||||
modManager.getSimpleModPath(addonFile.getPath()),
|
||||
addonFile.getHash() != null ? new FileDownloadTask.IntegrityCheck("SHA-1", addonFile.getHash()) : null);
|
||||
} else if (file instanceof McbbsModpackManifest.CurseFile) {
|
||||
// we download it later.
|
||||
|
||||
@@ -31,7 +31,7 @@ import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -329,9 +329,9 @@ public class McbbsModpackManifest implements ModpackManifest, Validation {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public URL getUrl() {
|
||||
return url == null ? NetworkUtils.toURL("https://www.curseforge.com/minecraft/mc-mods/" + projectID + "/download/" + fileID + "/file")
|
||||
: NetworkUtils.toURL(NetworkUtils.encodeLocation(url));
|
||||
public URI getUrl() {
|
||||
return url == null ? URI.create("https://www.curseforge.com/minecraft/mc-mods/" + projectID + "/download/" + fileID + "/file")
|
||||
: URI.create(NetworkUtils.encodeLocation(url));
|
||||
}
|
||||
|
||||
public CurseFile withFileName(String fileName) {
|
||||
|
||||
@@ -121,7 +121,7 @@ public class ModrinthCompletionTask extends Task<Void> {
|
||||
if (modsDirectory.equals(filePath.getParent()) && this.modManager.hasSimpleMod(FileUtils.getName(filePath)))
|
||||
continue;
|
||||
|
||||
FileDownloadTask task = new FileDownloadTask(file.getDownloads(), filePath.toFile());
|
||||
var task = new FileDownloadTask(file.getDownloads(), filePath);
|
||||
task.setCacheRepository(dependency.getCacheRepository());
|
||||
task.setCaching(true);
|
||||
dependencies.add(task.withCounter("hmcl.modpack.download"));
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.jackhuang.hmcl.util.gson.TolerableValidationException;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -98,10 +98,10 @@ public class ModrinthManifest implements ModpackManifest, Validation {
|
||||
private final String path;
|
||||
private final Map<String, String> hashes;
|
||||
private final Map<String, String> env;
|
||||
private final List<URL> downloads;
|
||||
private final List<URI> downloads;
|
||||
private final int fileSize;
|
||||
|
||||
public File(String path, Map<String, String> hashes, Map<String, String> env, List<URL> downloads, int fileSize) {
|
||||
public File(String path, Map<String, String> hashes, Map<String, String> env, List<URI> downloads, int fileSize) {
|
||||
this.path = path;
|
||||
this.hashes = hashes;
|
||||
this.env = env;
|
||||
@@ -121,7 +121,7 @@ public class ModrinthManifest implements ModpackManifest, Validation {
|
||||
return env;
|
||||
}
|
||||
|
||||
public List<URL> getDownloads() {
|
||||
public List<URI> getDownloads() {
|
||||
return downloads;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package org.jackhuang.hmcl.mod.multimc;
|
||||
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -46,7 +45,7 @@ public final class MultiMCComponents {
|
||||
return PAIRS;
|
||||
}
|
||||
|
||||
public static URL getMetaURL(String componentID, String version) {
|
||||
return NetworkUtils.toURL(String.format("https://meta.multimc.org/v1/%s/%s.json", componentID, version));
|
||||
public static URI getMetaURL(String componentID, String version) {
|
||||
return URI.create(String.format("https://meta.multimc.org/v1/%s/%s.json", componentID, version));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -85,7 +85,7 @@ public class ServerModpackCompletionTask extends Task<Void> {
|
||||
@Override
|
||||
public void preExecute() throws Exception {
|
||||
if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return;
|
||||
dependent = new GetTask(new URL(manifest.getManifest().getFileApi() + "/server-manifest.json"));
|
||||
dependent = new GetTask(URI.create(manifest.getManifest().getFileApi() + "/server-manifest.json"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -153,8 +153,8 @@ public class ServerModpackCompletionTask extends Task<Void> {
|
||||
if (download) {
|
||||
total++;
|
||||
dependencies.add(new FileDownloadTask(
|
||||
new URL(remoteManifest.getFileApi() + "/overrides/" + NetworkUtils.encodeLocation(file.getPath())),
|
||||
actualPath.toFile(),
|
||||
URI.create(remoteManifest.getFileApi() + "/overrides/" + NetworkUtils.encodeLocation(file.getPath())),
|
||||
actualPath,
|
||||
new FileDownloadTask.IntegrityCheck("SHA-1", file.getHash()))
|
||||
.withCounter("hmcl.modpack.download"));
|
||||
}
|
||||
|
||||
@@ -20,21 +20,20 @@ package org.jackhuang.hmcl.task;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public class DownloadException extends IOException {
|
||||
|
||||
private final URL url;
|
||||
private final URI uri;
|
||||
|
||||
public DownloadException(URL url, @NotNull Throwable cause) {
|
||||
super("Unable to download " + url + ", " + cause.getMessage(), requireNonNull(cause));
|
||||
|
||||
this.url = url;
|
||||
public DownloadException(URI uri, @NotNull Throwable cause) {
|
||||
super("Unable to download " + uri + ", " + cause.getMessage(), requireNonNull(cause));
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public URL getUrl() {
|
||||
return url;
|
||||
public URI getUri() {
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,36 +24,38 @@ import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.threadPool;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public abstract class FetchTask<T> extends Task<T> {
|
||||
protected final List<URL> urls;
|
||||
protected final List<URI> uris;
|
||||
protected final int retry;
|
||||
protected boolean caching;
|
||||
protected CacheRepository repository = CacheRepository.getInstance();
|
||||
|
||||
public FetchTask(List<URL> urls, int retry) {
|
||||
Objects.requireNonNull(urls);
|
||||
public FetchTask(@NotNull List<@NotNull URI> uris, int retry) {
|
||||
Objects.requireNonNull(uris);
|
||||
|
||||
this.urls = urls.stream().filter(Objects::nonNull).collect(Collectors.toList());
|
||||
this.uris = List.copyOf(uris);
|
||||
this.retry = retry;
|
||||
|
||||
if (this.urls.isEmpty())
|
||||
if (this.uris.isEmpty())
|
||||
throw new IllegalArgumentException("At least one URL is required");
|
||||
|
||||
setExecutor(download());
|
||||
@@ -67,18 +69,19 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
protected void beforeDownload(URL url) throws IOException {}
|
||||
protected void beforeDownload(URI uri) throws IOException {
|
||||
}
|
||||
|
||||
protected abstract void useCachedResult(Path cachedFile) throws IOException;
|
||||
|
||||
protected abstract EnumCheckETag shouldCheckETag();
|
||||
|
||||
protected abstract Context getContext(URLConnection conn, boolean checkETag) throws IOException;
|
||||
protected abstract Context getContext(URLConnection connection, boolean checkETag) throws IOException;
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
Exception exception = null;
|
||||
URL failedURL = null;
|
||||
URI failedURI = null;
|
||||
boolean checkETag;
|
||||
switch (shouldCheckETag()) {
|
||||
case CHECK_E_TAG: checkETag = true; break;
|
||||
@@ -87,7 +90,7 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
}
|
||||
|
||||
int repeat = 0;
|
||||
download: for (URL url : urls) {
|
||||
download: for (URI uri : uris) {
|
||||
for (int retryTime = 0; retryTime < retry; retryTime++) {
|
||||
if (isCancelled()) {
|
||||
break download;
|
||||
@@ -95,11 +98,11 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
|
||||
List<String> redirects = null;
|
||||
try {
|
||||
beforeDownload(url);
|
||||
beforeDownload(uri);
|
||||
|
||||
updateProgress(0);
|
||||
|
||||
URLConnection conn = NetworkUtils.createConnection(url);
|
||||
URLConnection conn = NetworkUtils.createConnection(uri);
|
||||
if (checkETag) repository.injectConnection(conn);
|
||||
|
||||
if (conn instanceof HttpURLConnection) {
|
||||
@@ -111,21 +114,21 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
// Handle cache
|
||||
try {
|
||||
Path cache = repository.getCachedRemoteFile(conn);
|
||||
Path cache = repository.getCachedRemoteFile(conn.getURL().toURI());
|
||||
useCachedResult(cache);
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Unable to use cached file, redownload " + url, e);
|
||||
repository.removeRemoteEntry(conn);
|
||||
LOG.warning("Unable to use cached file, redownload " + uri, e);
|
||||
repository.removeRemoteEntry(conn.getURL().toURI());
|
||||
// Now we must reconnect the server since 304 may result in empty content,
|
||||
// if we want to redownload the file, we must reconnect the server without etag settings.
|
||||
retryTime--;
|
||||
continue;
|
||||
}
|
||||
} else if (responseCode / 100 == 4) {
|
||||
throw new FileNotFoundException(url.toString());
|
||||
throw new FileNotFoundException(uri.toString());
|
||||
} else if (responseCode / 100 != 2) {
|
||||
throw new ResponseCodeException(url, responseCode);
|
||||
throw new ResponseCodeException(uri, responseCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,21 +167,21 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
|
||||
return;
|
||||
} catch (FileNotFoundException ex) {
|
||||
failedURL = url;
|
||||
failedURI = uri;
|
||||
exception = ex;
|
||||
LOG.warning("Failed to download " + url + ", not found" + ((redirects == null || redirects.isEmpty()) ? "" : ", redirects: " + redirects), ex);
|
||||
LOG.warning("Failed to download " + uri + ", not found" + ((redirects == null || redirects.isEmpty()) ? "" : ", redirects: " + redirects), ex);
|
||||
|
||||
break; // we will not try this URL again
|
||||
} catch (IOException ex) {
|
||||
failedURL = url;
|
||||
failedURI = uri;
|
||||
exception = ex;
|
||||
LOG.warning("Failed to download " + url + ", repeat times: " + (++repeat) + ((redirects == null || redirects.isEmpty()) ? "" : ", redirects: " + redirects), ex);
|
||||
LOG.warning("Failed to download " + uri + ", repeat times: " + (++repeat) + ((redirects == null || redirects.isEmpty()) ? "" : ", redirects: " + redirects), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (exception != null)
|
||||
throw new DownloadException(failedURL, exception);
|
||||
throw new DownloadException(failedURI, exception);
|
||||
}
|
||||
|
||||
private static final Timer timer = new Timer("DownloadSpeedRecorder", true);
|
||||
@@ -209,6 +212,7 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
|
||||
/**
|
||||
* Download speed in byte/sec.
|
||||
*
|
||||
* @return download speed
|
||||
*/
|
||||
public int getSpeed() {
|
||||
@@ -240,7 +244,7 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
NOT_CHECK_E_TAG,
|
||||
CACHED
|
||||
}
|
||||
|
||||
|
||||
protected static final class DownloadState {
|
||||
private final int startPosition;
|
||||
private final int endPosition;
|
||||
|
||||
@@ -22,16 +22,20 @@ import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.net.URL;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.jackhuang.hmcl.util.DigestUtils.getDigest;
|
||||
@@ -78,73 +82,76 @@ public class FileDownloadTask extends FetchTask<Void> {
|
||||
}
|
||||
}
|
||||
|
||||
private final File file;
|
||||
private final Path file;
|
||||
private final IntegrityCheck integrityCheck;
|
||||
private Path candidate;
|
||||
private final ArrayList<IntegrityCheckHandler> integrityCheckHandlers = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* @param url the URL of remote file.
|
||||
* @param file the location that download to.
|
||||
* @param uri the URI of remote file.
|
||||
* @param path the location that download to.
|
||||
*/
|
||||
public FileDownloadTask(URL url, File file) {
|
||||
this(url, file, null);
|
||||
public FileDownloadTask(URI uri, Path path) {
|
||||
this(uri, path, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param url the URL of remote file.
|
||||
* @param file the location that download to.
|
||||
* @param uri the URI of remote file.
|
||||
* @param path the location that download to.
|
||||
* @param integrityCheck the integrity check to perform, null if no integrity check is to be performed
|
||||
*/
|
||||
public FileDownloadTask(URL url, File file, IntegrityCheck integrityCheck) {
|
||||
this(Collections.singletonList(url), file, integrityCheck);
|
||||
public FileDownloadTask(URI uri, Path path, IntegrityCheck integrityCheck) {
|
||||
this(List.of(uri), path, integrityCheck);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param url the URL of remote file.
|
||||
* @param file the location that download to.
|
||||
* @param uri the URI of remote file.
|
||||
* @param path the location that download to.
|
||||
* @param integrityCheck the integrity check to perform, null if no integrity check is to be performed
|
||||
* @param retry the times for retrying if downloading fails.
|
||||
* @param retry the times for retrying if downloading fails.
|
||||
*/
|
||||
public FileDownloadTask(URL url, File file, IntegrityCheck integrityCheck, int retry) {
|
||||
this(Collections.singletonList(url), file, integrityCheck, retry);
|
||||
public FileDownloadTask(URI uri, Path path, IntegrityCheck integrityCheck, int retry) {
|
||||
this(List.of(uri), path, integrityCheck, retry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param urls urls of remote file, will be attempted in order.
|
||||
*
|
||||
* @param uris uris of remote file, will be attempted in order.
|
||||
* @param file the location that download to.
|
||||
*/
|
||||
public FileDownloadTask(List<URL> urls, File file) {
|
||||
this(urls, file, null);
|
||||
public FileDownloadTask(List<URI> uris, Path file) {
|
||||
this(uris, file, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param urls urls of remote file, will be attempted in order.
|
||||
* @param file the location that download to.
|
||||
*
|
||||
* @param uris uris of remote file, will be attempted in order.
|
||||
* @param path the location that download to.
|
||||
* @param integrityCheck the integrity check to perform, null if no integrity check is to be performed
|
||||
*/
|
||||
public FileDownloadTask(List<URL> urls, File file, IntegrityCheck integrityCheck) {
|
||||
this(urls, file, integrityCheck, 3);
|
||||
public FileDownloadTask(List<URI> uris, Path path, IntegrityCheck integrityCheck) {
|
||||
this(uris, path, integrityCheck, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param urls urls of remote file, will be attempted in order.
|
||||
* @param file the location that download to.
|
||||
*
|
||||
* @param uris uris of remote file, will be attempted in order.
|
||||
* @param path the location that download to.
|
||||
* @param integrityCheck the integrity check to perform, null if no integrity check is to be performed
|
||||
* @param retry the times for retrying if downloading fails.
|
||||
* @param retry the times for retrying if downloading fails.
|
||||
*/
|
||||
public FileDownloadTask(List<URL> urls, File file, IntegrityCheck integrityCheck, int retry) {
|
||||
super(urls, retry);
|
||||
this.file = file;
|
||||
public FileDownloadTask(List<URI> uris, Path path, IntegrityCheck integrityCheck, int retry) {
|
||||
super(uris, retry);
|
||||
this.file = path;
|
||||
this.integrityCheck = integrityCheck;
|
||||
|
||||
setName(file.getName());
|
||||
setName(path.getFileName().toString());
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
public Path getPath() {
|
||||
return file;
|
||||
}
|
||||
|
||||
@@ -164,8 +171,8 @@ public class FileDownloadTask extends FetchTask<Void> {
|
||||
Optional<Path> cache = repository.checkExistentFile(candidate, integrityCheck.getAlgorithm(), integrityCheck.getChecksum());
|
||||
if (cache.isPresent()) {
|
||||
try {
|
||||
FileUtils.copyFile(cache.get().toFile(), file);
|
||||
LOG.trace("Successfully verified file " + file + " from " + urls.get(0));
|
||||
FileUtils.copyFile(cache.get(), file);
|
||||
LOG.trace("Successfully verified file " + file + " from " + uris.get(0));
|
||||
return EnumCheckETag.CACHED;
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to copy cache files", e);
|
||||
@@ -178,20 +185,20 @@ public class FileDownloadTask extends FetchTask<Void> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void beforeDownload(URL url) {
|
||||
LOG.trace("Downloading " + url + " to " + file);
|
||||
protected void beforeDownload(URI uri) {
|
||||
LOG.trace("Downloading " + uri + " to " + file);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void useCachedResult(Path cache) throws IOException {
|
||||
FileUtils.copyFile(cache.toFile(), file);
|
||||
FileUtils.copyFile(cache, file);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Context getContext(URLConnection conn, boolean checkETag) throws IOException {
|
||||
protected Context getContext(URLConnection connection, boolean checkETag) throws IOException {
|
||||
Path temp = Files.createTempFile(null, null);
|
||||
RandomAccessFile rFile = new RandomAccessFile(temp.toFile(), "rw");
|
||||
MessageDigest digest = integrityCheck == null ? null : integrityCheck.createDigest();
|
||||
OutputStream fileOutput = Files.newOutputStream(temp);
|
||||
|
||||
return new Context() {
|
||||
@Override
|
||||
@@ -200,36 +207,34 @@ public class FileDownloadTask extends FetchTask<Void> {
|
||||
digest.update(buffer, offset, len);
|
||||
}
|
||||
|
||||
rFile.write(buffer, offset, len);
|
||||
fileOutput.write(buffer, offset, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
rFile.close();
|
||||
fileOutput.close();
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to close file: " + rFile, e);
|
||||
LOG.warning("Failed to close file: " + temp, e);
|
||||
}
|
||||
|
||||
if (!isSuccess()) {
|
||||
try {
|
||||
Files.delete(temp);
|
||||
Files.deleteIfExists(temp);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to delete file: " + rFile, e);
|
||||
LOG.warning("Failed to delete file: " + temp, e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (IntegrityCheckHandler handler : integrityCheckHandlers) {
|
||||
handler.checkIntegrity(temp, file.toPath());
|
||||
handler.checkIntegrity(temp, file);
|
||||
}
|
||||
|
||||
Files.deleteIfExists(file.toPath());
|
||||
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
|
||||
throw new IOException("Unable to make parent directory " + file);
|
||||
Files.createDirectories(file.toAbsolutePath().getParent());
|
||||
|
||||
try {
|
||||
FileUtils.moveFile(temp.toFile(), file);
|
||||
Files.move(temp, file, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Unable to move temp file from " + temp + " to " + file, e);
|
||||
}
|
||||
@@ -241,14 +246,14 @@ public class FileDownloadTask extends FetchTask<Void> {
|
||||
|
||||
if (caching && integrityCheck != null) {
|
||||
try {
|
||||
repository.cacheFile(file.toPath(), integrityCheck.getAlgorithm(), integrityCheck.getChecksum());
|
||||
repository.cacheFile(file, integrityCheck.getAlgorithm(), integrityCheck.getChecksum());
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to cache file", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (checkETag) {
|
||||
repository.cacheRemoteFile(file.toPath(), conn);
|
||||
repository.cacheRemoteFile(connection, file);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -257,7 +262,8 @@ public class FileDownloadTask extends FetchTask<Void> {
|
||||
public interface IntegrityCheckHandler {
|
||||
/**
|
||||
* Check whether the file is corrupted or not.
|
||||
* @param filePath the file locates in (maybe in temp directory)
|
||||
*
|
||||
* @param filePath the file locates in (maybe in temp directory)
|
||||
* @param destinationPath for real file name
|
||||
* @throws IOException if the file is corrupted
|
||||
*/
|
||||
|
||||
@@ -19,12 +19,12 @@ package org.jackhuang.hmcl.task;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
@@ -34,25 +34,27 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
*/
|
||||
public final class GetTask extends FetchTask<String> {
|
||||
|
||||
private static final int DEFAULT_RETRY = 3;
|
||||
|
||||
private final Charset charset;
|
||||
|
||||
public GetTask(URL url) {
|
||||
public GetTask(URI url) {
|
||||
this(url, UTF_8);
|
||||
}
|
||||
|
||||
public GetTask(URL url, Charset charset) {
|
||||
this(url, charset, 3);
|
||||
public GetTask(URI url, Charset charset) {
|
||||
this(url, charset, DEFAULT_RETRY);
|
||||
}
|
||||
|
||||
public GetTask(URL url, Charset charset, int retry) {
|
||||
this(Collections.singletonList(url), charset, retry);
|
||||
public GetTask(URI url, Charset charset, int retry) {
|
||||
this(List.of(url), charset, retry);
|
||||
}
|
||||
|
||||
public GetTask(List<URL> url) {
|
||||
this(url, UTF_8, 3);
|
||||
public GetTask(List<URI> url) {
|
||||
this(url, UTF_8, DEFAULT_RETRY);
|
||||
}
|
||||
|
||||
public GetTask(List<URL> urls, Charset charset, int retry) {
|
||||
public GetTask(List<URI> urls, Charset charset, int retry) {
|
||||
super(urls, retry);
|
||||
this.charset = charset;
|
||||
|
||||
@@ -70,7 +72,7 @@ public final class GetTask extends FetchTask<String> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Context getContext(URLConnection conn, boolean checkETag) {
|
||||
protected Context getContext(URLConnection connection, boolean checkETag) {
|
||||
return new Context() {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
@@ -87,7 +89,7 @@ public final class GetTask extends FetchTask<String> {
|
||||
setResult(result);
|
||||
|
||||
if (checkETag) {
|
||||
repository.cacheText(result, conn);
|
||||
repository.cacheText(connection, result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
@@ -157,12 +158,11 @@ public class CacheRepository {
|
||||
return cache;
|
||||
}
|
||||
|
||||
public Path getCachedRemoteFile(URLConnection conn) throws IOException {
|
||||
String url = conn.getURL().toString();
|
||||
public Path getCachedRemoteFile(URI uri) throws IOException {
|
||||
lock.readLock().lock();
|
||||
ETagItem eTagItem;
|
||||
try {
|
||||
eTagItem = index.get(url);
|
||||
eTagItem = index.get(uri.toString());
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
@@ -177,11 +177,10 @@ public class CacheRepository {
|
||||
return file;
|
||||
}
|
||||
|
||||
public void removeRemoteEntry(URLConnection conn) {
|
||||
String url = conn.getURL().toString();
|
||||
public void removeRemoteEntry(URI uri) {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
index.remove(url);
|
||||
index.remove(uri.toString());
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
@@ -203,34 +202,34 @@ public class CacheRepository {
|
||||
// conn.setRequestProperty("If-Modified-Since", eTagItem.getRemoteLastModified());
|
||||
}
|
||||
|
||||
public void cacheRemoteFile(Path downloaded, URLConnection conn) throws IOException {
|
||||
cacheData(() -> {
|
||||
public void cacheRemoteFile(URLConnection connection, Path downloaded) throws IOException {
|
||||
cacheData(connection, () -> {
|
||||
String hash = DigestUtils.digestToString(SHA1, downloaded);
|
||||
Path cached = cacheFile(downloaded, SHA1, hash);
|
||||
return new CacheResult(hash, cached);
|
||||
}, conn);
|
||||
});
|
||||
}
|
||||
|
||||
public void cacheText(String text, URLConnection conn) throws IOException {
|
||||
cacheBytes(text.getBytes(UTF_8), conn);
|
||||
public void cacheText(URLConnection connection, String text) throws IOException {
|
||||
cacheBytes(connection, text.getBytes(UTF_8));
|
||||
}
|
||||
|
||||
public void cacheBytes(byte[] bytes, URLConnection conn) throws IOException {
|
||||
cacheData(() -> {
|
||||
public void cacheBytes(URLConnection connection, byte[] bytes) throws IOException {
|
||||
cacheData(connection, () -> {
|
||||
String hash = DigestUtils.digestToString(SHA1, bytes);
|
||||
Path cached = getFile(SHA1, hash);
|
||||
FileUtils.writeBytes(cached, bytes);
|
||||
return new CacheResult(hash, cached);
|
||||
}, conn);
|
||||
});
|
||||
}
|
||||
|
||||
public synchronized void cacheData(ExceptionalSupplier<CacheResult, IOException> cacheSupplier, URLConnection conn) throws IOException {
|
||||
String eTag = conn.getHeaderField("ETag");
|
||||
if (eTag == null) return;
|
||||
String url = conn.getURL().toString();
|
||||
String lastModified = conn.getHeaderField("Last-Modified");
|
||||
private void cacheData(URLConnection connection, ExceptionalSupplier<CacheResult, IOException> cacheSupplier) throws IOException {
|
||||
String eTag = connection.getHeaderField("ETag");
|
||||
if (eTag == null || eTag.isEmpty()) return;
|
||||
String uri = connection.getURL().toString();
|
||||
String lastModified = connection.getHeaderField("Last-Modified");
|
||||
CacheResult cacheResult = cacheSupplier.get();
|
||||
ETagItem eTagItem = new ETagItem(url, eTag, cacheResult.hash, Files.getLastModifiedTime(cacheResult.cachedFile).toMillis(), lastModified);
|
||||
ETagItem eTagItem = new ETagItem(uri, eTag, cacheResult.hash, Files.getLastModifiedTime(cacheResult.cachedFile).toMillis(), lastModified);
|
||||
Lock writeLock = lock.writeLock();
|
||||
writeLock.lock();
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.io;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URLConnection;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public enum ContentEncoding {
|
||||
NONE {
|
||||
@Override
|
||||
public InputStream wrap(InputStream inputStream) {
|
||||
return inputStream;
|
||||
}
|
||||
},
|
||||
GZIP {
|
||||
@Override
|
||||
public InputStream wrap(InputStream inputStream) throws IOException {
|
||||
return new GZIPInputStream(inputStream);
|
||||
}
|
||||
};
|
||||
|
||||
public static @NotNull ContentEncoding fromConnection(URLConnection connection) throws IOException {
|
||||
String encoding = connection.getContentEncoding();
|
||||
if (encoding == null || encoding.isEmpty()) {
|
||||
return NONE;
|
||||
} else if ("gzip".equalsIgnoreCase(encoding)) {
|
||||
return GZIP;
|
||||
} else {
|
||||
throw new IOException("Unsupported content encoding: " + encoding);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract InputStream wrap(InputStream inputStream) throws IOException;
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -113,18 +114,13 @@ public abstract class HttpRequest {
|
||||
return getStringAsync().thenApplyAsync(jsonString -> JsonUtils.fromNonNullJson(jsonString, type));
|
||||
}
|
||||
|
||||
public HttpRequest filter(ExceptionalBiConsumer<URL, Integer, IOException> responseCodeTester) {
|
||||
this.responseCodeTester = responseCodeTester;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpRequest ignoreHttpErrorCode(int code) {
|
||||
toleratedHttpCodes.add(code);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpURLConnection createConnection() throws IOException {
|
||||
HttpURLConnection con = createHttpConnection(new URL(url));
|
||||
HttpURLConnection con = createHttpConnection(URI.create(url));
|
||||
con.setRequestMethod(method);
|
||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
con.setRequestProperty(entry.getKey(), entry.getValue());
|
||||
@@ -133,7 +129,7 @@ public abstract class HttpRequest {
|
||||
}
|
||||
|
||||
public static class HttpGetRequest extends HttpRequest {
|
||||
public HttpGetRequest(String url) {
|
||||
protected HttpGetRequest(String url) {
|
||||
super(url, "GET");
|
||||
}
|
||||
|
||||
@@ -149,7 +145,7 @@ public abstract class HttpRequest {
|
||||
public static final class HttpPostRequest extends HttpRequest {
|
||||
private byte[] bytes;
|
||||
|
||||
public HttpPostRequest(String url) {
|
||||
private HttpPostRequest(String url) {
|
||||
super(url, "POST");
|
||||
}
|
||||
|
||||
@@ -189,21 +185,17 @@ public abstract class HttpRequest {
|
||||
|
||||
URL url = new URL(this.url);
|
||||
|
||||
if (responseCodeTester != null) {
|
||||
responseCodeTester.accept(url, con.getResponseCode());
|
||||
} else {
|
||||
if (con.getResponseCode() / 100 != 2) {
|
||||
if (!ignoreHttpCode && !toleratedHttpCodes.contains(con.getResponseCode())) {
|
||||
try {
|
||||
throw new ResponseCodeException(url, con.getResponseCode(), NetworkUtils.readData(con));
|
||||
} catch (IOException e) {
|
||||
throw new ResponseCodeException(url, con.getResponseCode(), e);
|
||||
}
|
||||
if (con.getResponseCode() / 100 != 2) {
|
||||
if (!ignoreHttpCode && !toleratedHttpCodes.contains(con.getResponseCode())) {
|
||||
try {
|
||||
throw new ResponseCodeException(NetworkUtils.toURI(url), con.getResponseCode(), NetworkUtils.readFullyAsString(con));
|
||||
} catch (IOException e) {
|
||||
throw new ResponseCodeException(NetworkUtils.toURI(url), con.getResponseCode(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NetworkUtils.readData(con);
|
||||
return NetworkUtils.readFullyAsString(con);
|
||||
}, retryTimes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,16 @@ import org.jackhuang.hmcl.util.Pair;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.*;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
@@ -39,6 +43,10 @@ public final class NetworkUtils {
|
||||
private NetworkUtils() {
|
||||
}
|
||||
|
||||
public static boolean isHttpUri(URI uri) {
|
||||
return "http".equals(uri.getScheme()) || "https".equals(uri.getScheme());
|
||||
}
|
||||
|
||||
public static String withQuery(String baseUrl, Map<String, String> params) {
|
||||
StringBuilder sb = new StringBuilder(baseUrl);
|
||||
boolean first = true;
|
||||
@@ -73,7 +81,7 @@ public final class NetworkUtils {
|
||||
scanner.useDelimiter("&");
|
||||
while (scanner.hasNext()) {
|
||||
String[] nameValue = scanner.next().split(NAME_VALUE_SEPARATOR);
|
||||
if (nameValue.length <= 0 || nameValue.length > 2) {
|
||||
if (nameValue.length == 0 || nameValue.length > 2) {
|
||||
throw new IllegalArgumentException("bad query string");
|
||||
}
|
||||
|
||||
@@ -85,8 +93,8 @@ public final class NetworkUtils {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static URLConnection createConnection(URL url) throws IOException {
|
||||
URLConnection connection = url.openConnection();
|
||||
public static URLConnection createConnection(URI uri) throws IOException {
|
||||
URLConnection connection = uri.toURL().openConnection();
|
||||
connection.setUseCaches(false);
|
||||
connection.setConnectTimeout(TIME_OUT);
|
||||
connection.setReadTimeout(TIME_OUT);
|
||||
@@ -94,15 +102,15 @@ public final class NetworkUtils {
|
||||
return connection;
|
||||
}
|
||||
|
||||
public static HttpURLConnection createHttpConnection(URL url) throws IOException {
|
||||
public static HttpURLConnection createHttpConnection(URI url) throws IOException {
|
||||
return (HttpURLConnection) createConnection(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see <a href=
|
||||
* "https://github.com/curl/curl/blob/3f7b1bb89f92c13e69ee51b710ac54f775aab320/lib/transfer.c#L1427-L1461">Curl</a>
|
||||
* @param location the url to be URL encoded
|
||||
* @return encoded URL
|
||||
* @see <a href=
|
||||
* "https://github.com/curl/curl/blob/3f7b1bb89f92c13e69ee51b710ac54f775aab320/lib/transfer.c#L1427-L1461">Curl</a>
|
||||
*/
|
||||
public static String encodeLocation(String location) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -138,10 +146,10 @@ public final class NetworkUtils {
|
||||
* This method is a work-around that 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 the stupid http connection.
|
||||
* @return manually redirected http connection.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
* @see <a href="https://github.com/curl/curl/issues/473">Issue with libcurl</a>
|
||||
*/
|
||||
public static HttpURLConnection resolveConnection(HttpURLConnection conn, List<String> redirects) throws IOException {
|
||||
int redirect = 0;
|
||||
@@ -153,7 +161,7 @@ public final class NetworkUtils {
|
||||
Map<String, List<String>> properties = conn.getRequestProperties();
|
||||
String method = conn.getRequestMethod();
|
||||
int code = conn.getResponseCode();
|
||||
if (code >= 300 && code <= 307 && code != 306 && code != 304) {
|
||||
if (code >= 300 && code <= 308 && code != 306 && code != 304) {
|
||||
String newURL = conn.getHeaderField("Location");
|
||||
conn.disconnect();
|
||||
|
||||
@@ -178,19 +186,15 @@ public final class NetworkUtils {
|
||||
return conn;
|
||||
}
|
||||
|
||||
public static String doGet(URL url) throws IOException {
|
||||
HttpURLConnection con = createHttpConnection(url);
|
||||
con = resolveConnection(con);
|
||||
return IOUtils.readFullyAsString(con.getInputStream());
|
||||
public static String doGet(URI uri) throws IOException {
|
||||
return readFullyAsString(resolveConnection(createHttpConnection(uri)));
|
||||
}
|
||||
|
||||
public static String doGet(List<URL> urls) throws IOException {
|
||||
public static String doGet(List<URI> uris) throws IOException {
|
||||
List<IOException> exceptions = null;
|
||||
for (URL url : urls) {
|
||||
for (URI uri : uris) {
|
||||
try {
|
||||
HttpURLConnection con = createHttpConnection(url);
|
||||
con = resolveConnection(con);
|
||||
return IOUtils.readFullyAsString(con.getInputStream());
|
||||
return doGet(uri);
|
||||
} catch (IOException e) {
|
||||
if (exceptions == null) {
|
||||
exceptions = new ArrayList<>(1);
|
||||
@@ -212,7 +216,11 @@ public final class NetworkUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static String doPost(URL u, Map<String, String> params) throws IOException {
|
||||
public static String doPost(URI uri, String post) throws IOException {
|
||||
return doPost(uri, post, "application/x-www-form-urlencoded");
|
||||
}
|
||||
|
||||
public static String doPost(URI u, Map<String, String> params) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (params != null) {
|
||||
for (Map.Entry<String, String> e : params.entrySet())
|
||||
@@ -222,50 +230,72 @@ public final class NetworkUtils {
|
||||
return doPost(u, sb.toString());
|
||||
}
|
||||
|
||||
public static String doPost(URL u, String post) throws IOException {
|
||||
return doPost(u, post, "application/x-www-form-urlencoded");
|
||||
}
|
||||
|
||||
public static String doPost(URL url, String post, String contentType) throws IOException {
|
||||
public static String doPost(URI uri, String post, String contentType) throws IOException {
|
||||
byte[] bytes = post.getBytes(UTF_8);
|
||||
|
||||
HttpURLConnection con = createHttpConnection(url);
|
||||
HttpURLConnection con = createHttpConnection(uri);
|
||||
con.setRequestMethod("POST");
|
||||
con.setDoOutput(true);
|
||||
con.setRequestProperty("Content-Type", contentType + "; charset=utf-8");
|
||||
con.setRequestProperty("Content-Length", "" + bytes.length);
|
||||
con.setRequestProperty("Content-Length", String.valueOf(bytes.length));
|
||||
try (OutputStream os = con.getOutputStream()) {
|
||||
os.write(bytes);
|
||||
}
|
||||
return readData(con);
|
||||
return readFullyAsString(con);
|
||||
}
|
||||
|
||||
public static String readData(HttpURLConnection con) throws IOException {
|
||||
try {
|
||||
try (InputStream stdout = con.getInputStream()) {
|
||||
return IOUtils.readFullyAsString("gzip".equals(con.getContentEncoding()) ? IOUtils.wrapFromGZip(stdout) : stdout);
|
||||
static final Pattern CHARSET_REGEX = Pattern.compile("\\s*(charset)\\s*=\\s*['|\"]?(?<charset>[^\"^';,]+)['|\"]?");
|
||||
|
||||
static Charset getCharsetFromContentType(String contentType) {
|
||||
if (contentType == null || contentType.isBlank())
|
||||
return UTF_8;
|
||||
|
||||
Matcher matcher = CHARSET_REGEX.matcher(contentType);
|
||||
if (matcher.find()) {
|
||||
String charsetName = matcher.group("charset");
|
||||
try {
|
||||
return Charset.forName(charsetName);
|
||||
} catch (Throwable e) {
|
||||
// Ignore invalid charset
|
||||
LOG.warning("Bad charset name: " + charsetName + ", using UTF-8 instead", e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
try (InputStream stderr = con.getErrorStream()) {
|
||||
if (stderr == null)
|
||||
}
|
||||
return UTF_8;
|
||||
}
|
||||
|
||||
public static String readFullyAsString(URLConnection con) throws IOException {
|
||||
try {
|
||||
var contentEncoding = ContentEncoding.fromConnection(con);
|
||||
Charset charset = getCharsetFromContentType(con.getHeaderField("Content-Type"));
|
||||
|
||||
try (InputStream stdout = con.getInputStream()) {
|
||||
return IOUtils.readFullyAsString(contentEncoding.wrap(stdout), charset);
|
||||
} catch (IOException e) {
|
||||
if (con instanceof HttpURLConnection) {
|
||||
try (InputStream stderr = ((HttpURLConnection) con).getErrorStream()) {
|
||||
if (stderr == null)
|
||||
throw e;
|
||||
return IOUtils.readFullyAsString(contentEncoding.wrap(stderr), charset);
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
return IOUtils.readFullyAsString("gzip".equals(con.getContentEncoding()) ? IOUtils.wrapFromGZip(stderr) : stderr);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (con instanceof HttpURLConnection) {
|
||||
((HttpURLConnection) con).disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String detectFileName(URL url) throws IOException {
|
||||
HttpURLConnection conn = resolveConnection(createHttpConnection(url));
|
||||
public static String detectFileName(URI uri) throws IOException {
|
||||
HttpURLConnection conn = resolveConnection(createHttpConnection(uri));
|
||||
int code = conn.getResponseCode();
|
||||
if (code / 100 == 4)
|
||||
throw new FileNotFoundException();
|
||||
if (code / 100 != 2)
|
||||
throw new IOException(url + ": response code " + conn.getResponseCode());
|
||||
throw new ResponseCodeException(uri, conn.getResponseCode());
|
||||
|
||||
return detectFileName(conn);
|
||||
}
|
||||
|
||||
public static String detectFileName(HttpURLConnection conn) {
|
||||
String disposition = conn.getHeaderField("Content-Disposition");
|
||||
if (disposition == null || !disposition.contains("filename=")) {
|
||||
String u = conn.getURL().toString();
|
||||
@@ -275,46 +305,22 @@ public final class NetworkUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static URL toURL(String str) {
|
||||
public static URI toURI(URL url) {
|
||||
try {
|
||||
return new URL(str);
|
||||
} catch (MalformedURLException e) {
|
||||
return url.toURI();
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isURL(String str) {
|
||||
try {
|
||||
new URL(str);
|
||||
return true;
|
||||
} catch (MalformedURLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean urlExists(URL url) throws IOException {
|
||||
HttpURLConnection con = createHttpConnection(url);
|
||||
con = resolveConnection(con);
|
||||
int responseCode = con.getResponseCode();
|
||||
con.disconnect();
|
||||
return responseCode / 100 == 2;
|
||||
}
|
||||
|
||||
// ==== Shortcut methods for encoding/decoding URLs in UTF-8 ====
|
||||
public static String encodeURL(String toEncode) {
|
||||
try {
|
||||
return URLEncoder.encode(toEncode, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new Error();
|
||||
}
|
||||
return URLEncoder.encode(toEncode, UTF_8);
|
||||
}
|
||||
|
||||
public static String decodeURL(String toDecode) {
|
||||
try {
|
||||
return URLDecoder.decode(toDecode, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new Error();
|
||||
}
|
||||
return URLDecoder.decode(toDecode, UTF_8);
|
||||
}
|
||||
// ====
|
||||
|
||||
}
|
||||
|
||||
@@ -18,37 +18,37 @@
|
||||
package org.jackhuang.hmcl.util.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
|
||||
public final class ResponseCodeException extends IOException {
|
||||
|
||||
private final URL url;
|
||||
private final URI uri;
|
||||
private final int responseCode;
|
||||
private final String data;
|
||||
|
||||
public ResponseCodeException(URL url, int responseCode) {
|
||||
super("Unable to request url " + url + ", response code: " + responseCode);
|
||||
this.url = url;
|
||||
public ResponseCodeException(URI uri, int responseCode) {
|
||||
super("Unable to request url " + uri + ", response code: " + responseCode);
|
||||
this.uri = uri;
|
||||
this.responseCode = responseCode;
|
||||
this.data = null;
|
||||
}
|
||||
|
||||
public ResponseCodeException(URL url, int responseCode, Throwable cause) {
|
||||
super("Unable to request url " + url + ", response code: " + responseCode, cause);
|
||||
this.url = url;
|
||||
public ResponseCodeException(URI uri, int responseCode, Throwable cause) {
|
||||
super("Unable to request url " + uri + ", response code: " + responseCode, cause);
|
||||
this.uri = uri;
|
||||
this.responseCode = responseCode;
|
||||
this.data = null;
|
||||
}
|
||||
|
||||
public ResponseCodeException(URL url, int responseCode, String data) {
|
||||
super("Unable to request url " + url + ", response code: " + responseCode + ", data: " + data);
|
||||
this.url = url;
|
||||
public ResponseCodeException(URI uri, int responseCode, String data) {
|
||||
super("Unable to request url " + uri + ", response code: " + responseCode + ", data: " + data);
|
||||
this.uri = uri;
|
||||
this.responseCode = responseCode;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public URL getUrl() {
|
||||
return url;
|
||||
public URI getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public int getResponseCode() {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.io;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public class NetworkUtilsTest {
|
||||
@Test
|
||||
public void testGetEncodingFromUrl() {
|
||||
assertEquals(UTF_8, NetworkUtils.getCharsetFromContentType(null));
|
||||
assertEquals(UTF_8, NetworkUtils.getCharsetFromContentType(""));
|
||||
assertEquals(UTF_8, NetworkUtils.getCharsetFromContentType("text/html"));
|
||||
assertEquals(UTF_8, NetworkUtils.getCharsetFromContentType("text/html; charset=utf-8"));
|
||||
assertEquals(US_ASCII, NetworkUtils.getCharsetFromContentType("text/html; charset=ascii"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user