Fix forge installer 404

This commit is contained in:
huanghongxun 2019-09-09 16:49:36 +08:00
parent e69d149c34
commit 5eba896e6d
13 changed files with 103 additions and 69 deletions

View File

@ -17,6 +17,9 @@
*/
package org.jackhuang.hmcl.download;
import java.util.Arrays;
import java.util.stream.Stream;
/**
* The service provider that provides Minecraft online file downloads.
*
@ -39,6 +42,12 @@ public interface DownloadProvider {
*/
String injectURL(String baseURL);
default Stream<String> injectURLs(String[] baseURLs) {
Stream<String> urls = Arrays.stream(baseURLs);
Stream<String> jsonURLs = Arrays.stream(baseURLs).map(this::injectURL);
return Stream.concat(jsonURLs, urls);
}
/**
* the specific version list that this download provider provides. i.e. "forge", "liteloader", "game", "optifine"
*

View File

@ -22,6 +22,7 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.ToStringBuilder;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.util.List;
import java.util.Objects;
/**
@ -34,7 +35,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
private final String libraryId;
private final String gameVersion;
private final String selfVersion;
private final String url;
private final String[] url;
private final Type type;
/**
@ -44,8 +45,8 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
* @param selfVersion the version string of the remote version.
* @param url the installer or universal jar URL.
*/
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, String url) {
this(libraryId, gameVersion, selfVersion, url, Type.UNCATEGORIZED);
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, String... url) {
this(libraryId, gameVersion, selfVersion, Type.UNCATEGORIZED, url);
}
/**
@ -55,7 +56,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
* @param selfVersion the version string of the remote version.
* @param url the installer or universal jar URL.
*/
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, String url, Type type) {
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Type type, String... url) {
this.libraryId = Objects.requireNonNull(libraryId);
this.gameVersion = Objects.requireNonNull(gameVersion);
this.selfVersion = Objects.requireNonNull(selfVersion);
@ -75,7 +76,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
return selfVersion;
}
public String getUrl() {
public String[] getUrl() {
return url;
}

View File

@ -31,6 +31,7 @@ import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@ -81,22 +82,28 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
for (ForgeVersion version : forgeVersions) {
if (version == null)
continue;
String jar = null;
List<String> urls = new ArrayList<>();
for (ForgeVersion.File file : version.getFiles())
if ("installer".equals(file.getCategory()) && "jar".equals(file.getFormat())) {
jar = NetworkUtils.withQuery("https://bmclapi2.bangbang93.com/forge/download", mapOf(
String classifier = gameVersion + "-" + version.getVersion()
+ (StringUtils.isNotBlank(version.getBranch()) ? "-" + version.getBranch() : "");
String fileName1 = "forge-" + classifier + "-" + file.getCategory() + "." + file.getFormat();
String fileName2 = "forge-" + classifier + "-" + gameVersion + "-" + file.getCategory() + "." + file.getFormat();
urls.add(NetworkUtils.withQuery("https://bmclapi2.bangbang93.com/forge/download", mapOf(
pair("mcversion", version.getGameVersion()),
pair("version", version.getVersion()),
pair("branch", version.getBranch()),
pair("category", file.getCategory()),
pair("format", file.getFormat())
));
)));
urls.add("https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" + classifier + "/" + fileName1);
urls.add("https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" + classifier + "-" + gameVersion + "/" + fileName2);
}
if (jar == null)
if (urls.isEmpty())
continue;
versions.put(gameVersion, new ForgeRemoteVersion(
version.getGameVersion(), version.getVersion(), jar
version.getGameVersion(), version.getVersion(), urls.toArray(new String[0])
));
}
} finally {

View File

@ -33,10 +33,8 @@ import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
@ -69,7 +67,11 @@ public final class ForgeInstallTask extends Task<Version> {
public void preExecute() throws Exception {
installer = Files.createTempFile("forge-installer", ".jar");
dependent = new FileDownloadTask(NetworkUtils.toURL(remote.getUrl()), installer.toFile())
dependent = new FileDownloadTask(
Arrays.stream(remote.getUrl())
.map(NetworkUtils::toURL)
.collect(Collectors.toList()),
installer.toFile(), null)
.setCacheRepository(dependencyManager.getCacheRepository())
.setCaching(true);
}

View File

@ -31,7 +31,7 @@ public class ForgeRemoteVersion extends RemoteVersion {
* @param selfVersion the version string of the remote version.
* @param url the installer or universal jar URL.
*/
public ForgeRemoteVersion(String gameVersion, String selfVersion, String url) {
public ForgeRemoteVersion(String gameVersion, String selfVersion, String... url) {
super(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), gameVersion, selfVersion, url);
}

View File

@ -38,7 +38,7 @@ public final class GameRemoteVersion extends RemoteVersion {
private final Date time;
public GameRemoteVersion(String gameVersion, String selfVersion, String url, ReleaseType type, Date time) {
super(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), gameVersion, selfVersion, url, getReleaseType(type));
super(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), gameVersion, selfVersion, getReleaseType(type), url);
this.type = type;
this.time = time;
}

View File

@ -25,9 +25,14 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
*
@ -65,7 +70,9 @@ public final class VersionJsonDownloadTask extends Task<String> {
public void execute() throws IOException {
RemoteVersion remoteVersion = gameVersionList.getVersion(gameVersion, gameVersion)
.orElseThrow(() -> new IOException("Cannot find specific version " + gameVersion + " in remote repository"));
String jsonURL = dependencyManager.getDownloadProvider().injectURL(remoteVersion.getUrl());
dependencies.add(new GetTask(NetworkUtils.toURL(jsonURL)).storeTo(this::setResult));
dependencies.add(new GetTask(
dependencyManager.getDownloadProvider().injectURLs(remoteVersion.getUrl())
.map(NetworkUtils::toURL).collect(Collectors.toList()),
UTF_8).storeTo(this::setResult));
}
}

View File

@ -67,7 +67,7 @@ public final class LiteLoaderInstallTask extends Task<Version> {
Library library = new Library(
new Artifact("com.mumfrey", "liteloader", remote.getSelfVersion()),
"http://dl.liteloader.com/versions/",
new LibrariesDownloadInfo(new LibraryDownloadInfo(null, remote.getUrl()))
new LibrariesDownloadInfo(new LibraryDownloadInfo(null, remote.getUrl()[0]))
);
setResult(new Version(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId(),

View File

@ -72,7 +72,7 @@ public final class OptiFineBMCLVersionList extends VersionList<OptiFineRemoteVer
continue;
String gameVersion = VersionNumber.normalize(element.getGameVersion());
versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, () -> mirror, isPre));
versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, mirror, isPre));
}
}
};

View File

@ -45,11 +45,8 @@ import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.util.Lang.getOrDefault;
@ -91,7 +88,7 @@ public final class OptiFineInstallTask extends Task<Version> {
new Artifact("optifine", "OptiFine", mavenVersion, "installer"), null,
new LibrariesDownloadInfo(new LibraryDownloadInfo(
"optifine/OptiFine/" + mavenVersion + "/OptiFine-" + mavenVersion + "-installer.jar",
remote.getUrl()))
remote.getUrl()[0]))
);
}
@ -105,7 +102,9 @@ public final class OptiFineInstallTask extends Task<Version> {
dest = Files.createTempFile("optifine-installer", ".jar");
if (installer == null) {
dependents.add(new FileDownloadTask(NetworkUtils.toURL(remote.getUrl()), dest.toFile())
dependents.add(new FileDownloadTask(
Arrays.stream(remote.getUrl()).map(NetworkUtils::toURL).collect(Collectors.toList()),
dest.toFile(), null)
.setCacheRepository(dependencyManager.getCacheRepository())
.setCaching(true));
} else {
@ -223,7 +222,7 @@ public final class OptiFineInstallTask extends Task<Version> {
throw new VersionMismatchException(mcVersion, gameVersion.get());
return new OptiFineInstallTask(dependencyManager, version,
new OptiFineRemoteVersion(mcVersion, ofEdition + "_" + ofRelease, () -> null, false), installer);
new OptiFineRemoteVersion(mcVersion, ofEdition + "_" + ofRelease, "", false), installer);
}
}
}

View File

@ -26,17 +26,9 @@ import org.jackhuang.hmcl.task.Task;
import java.util.function.Supplier;
public class OptiFineRemoteVersion extends RemoteVersion {
private final Supplier<String> url;
public OptiFineRemoteVersion(String gameVersion, String selfVersion, Supplier<String> url, boolean snapshot) {
super(LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId(), gameVersion, selfVersion, "", snapshot ? Type.SNAPSHOT : Type.RELEASE);
this.url = url;
}
@Override
public String getUrl() {
return url.get();
public OptiFineRemoteVersion(String gameVersion, String selfVersion, String url, boolean snapshot) {
super(LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId(), gameVersion, selfVersion, snapshot ? Type.SNAPSHOT : Type.RELEASE, url);
}
@Override

View File

@ -33,6 +33,9 @@ import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
@ -80,11 +83,10 @@ public class FileDownloadTask extends Task<Void> {
}
}
private final URL url;
private final List<URL> urls;
private final File file;
private final IntegrityCheck integrityCheck;
private final int retry;
private final EventManager<FailedEvent<URL>> onFailed = new EventManager<>();
private Path candidate;
private boolean caching;
private CacheRepository repository = CacheRepository.getInstance();
@ -115,7 +117,7 @@ public class FileDownloadTask extends Task<Void> {
* @param retry the times for retrying if downloading fails.
*/
public FileDownloadTask(URL url, File file, IntegrityCheck integrityCheck, int retry) {
this.url = url;
this.urls = Collections.singletonList(url);
this.file = file;
this.integrityCheck = integrityCheck;
this.retry = retry;
@ -124,6 +126,25 @@ public class FileDownloadTask extends Task<Void> {
setExecutor(Schedulers.io());
}
/**
* Constructor.
* @param urls urls of remote file, will be attempted in order.
* @param file 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) {
if (urls == null || urls.isEmpty())
throw new IllegalArgumentException("At least one URL is required");
this.urls = new ArrayList<>(urls);
this.file = file;
this.integrityCheck = integrityCheck;
this.retry = urls.size();
setName(file.getName());
setExecutor(Schedulers.io());
}
private void closeFiles() {
if (rFile != null)
try {
@ -143,14 +164,6 @@ public class FileDownloadTask extends Task<Void> {
stream = null;
}
public EventManager<FailedEvent<URL>> getOnFailed() {
return onFailed;
}
public URL getUrl() {
return url;
}
public File getFile() {
return file;
}
@ -172,8 +185,6 @@ public class FileDownloadTask extends Task<Void> {
@Override
public void execute() throws Exception {
URL currentURL = url;
boolean checkETag;
// Check cache
if (integrityCheck != null && caching) {
@ -182,7 +193,7 @@ public class FileDownloadTask extends Task<Void> {
if (cache.isPresent()) {
try {
FileUtils.copyFile(cache.get().toFile(), file);
Logging.LOG.log(Level.FINER, "Successfully verified file " + file + " from " + currentURL);
Logging.LOG.log(Level.FINER, "Successfully verified file " + file + " from " + urls.get(0));
return;
} catch (IOException e) {
Logging.LOG.log(Level.WARNING, "Failed to copy cache files", e);
@ -192,15 +203,11 @@ public class FileDownloadTask extends Task<Void> {
checkETag = true;
}
Logging.LOG.log(Level.FINER, "Downloading " + currentURL + " to " + file);
Logging.LOG.log(Level.FINER, "Downloading " + urls.get(0) + " to " + file);
Exception exception = null;
for (int repeat = 0; repeat < retry; repeat++) {
if (repeat > 0) {
FailedEvent<URL> event = new FailedEvent<>(this, repeat, currentURL);
onFailed.fireEvent(event);
currentURL = event.getNewResult();
}
URL url = urls.get(repeat % urls.size());
if (Thread.interrupted()) {
Thread.currentThread().interrupt();
break;
@ -226,7 +233,7 @@ public class FileDownloadTask extends Task<Void> {
repository.removeRemoteEntry(con);
}
} else if (con.getResponseCode() / 100 != 2) {
throw new ResponseCodeException(currentURL, con.getResponseCode());
throw new ResponseCodeException(url, con.getResponseCode());
}
int contentLength = con.getContentLength();
@ -316,16 +323,14 @@ public class FileDownloadTask extends Task<Void> {
if (temp != null)
temp.toFile().delete();
exception = e;
if (e instanceof ResponseCodeException && ((ResponseCodeException) e).getResponseCode() == 404)
break;
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", repeat times: " + repeat + 1, e);
} finally {
closeFiles();
}
}
if (exception != null)
throw new DownloadException(currentURL, exception);
throw new DownloadException(urls.get(0), exception);
}
}

View File

@ -30,6 +30,9 @@ import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import static java.nio.charset.StandardCharsets.UTF_8;
@ -40,7 +43,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
*/
public final class GetTask extends Task<String> {
private final URL url;
private final List<URL> urls;
private final Charset charset;
private final int retry;
private CacheRepository repository = CacheRepository.getInstance();
@ -54,7 +57,7 @@ public final class GetTask extends Task<String> {
}
public GetTask(URL url, Charset charset, int retry) {
this.url = url;
this.urls = Collections.singletonList(url);
this.charset = charset;
this.retry = retry;
@ -62,6 +65,15 @@ public final class GetTask extends Task<String> {
setExecutor(Schedulers.io());
}
public GetTask(List<URL> urls, Charset charset) {
this.urls = new ArrayList<>(urls);
this.charset = charset;
this.retry = urls.size();
setName(urls.get(0).toString());
setExecutor(Schedulers.io());
}
public GetTask setCacheRepository(CacheRepository repository) {
this.repository = repository;
return this;
@ -72,8 +84,7 @@ public final class GetTask extends Task<String> {
Exception exception = null;
boolean checkETag = true;
for (int time = 0; time < retry; ++time) {
if (time > 0)
Logging.LOG.log(Level.WARNING, "Failed to download, repeat times: " + time);
URL url = urls.get(time % urls.size());
try {
updateProgress(0);
HttpURLConnection conn = NetworkUtils.createConnection(url);
@ -122,10 +133,11 @@ public final class GetTask extends Task<String> {
return;
} catch (IOException ex) {
exception = ex;
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", repeat times: " + time + 1, ex);
}
}
if (exception != null)
throw new DownloadException(url, exception);
throw new DownloadException(urls.get(0), exception);
}
}