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; package org.jackhuang.hmcl.download;
import java.util.Arrays;
import java.util.stream.Stream;
/** /**
* The service provider that provides Minecraft online file downloads. * The service provider that provides Minecraft online file downloads.
* *
@ -39,6 +42,12 @@ public interface DownloadProvider {
*/ */
String injectURL(String baseURL); 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" * 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.ToStringBuilder;
import org.jackhuang.hmcl.util.versioning.VersionNumber; import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.util.List;
import java.util.Objects; import java.util.Objects;
/** /**
@ -34,7 +35,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
private final String libraryId; private final String libraryId;
private final String gameVersion; private final String gameVersion;
private final String selfVersion; private final String selfVersion;
private final String url; private final String[] url;
private final Type type; private final Type type;
/** /**
@ -44,8 +45,8 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
* @param selfVersion the version string of the remote version. * @param selfVersion the version string of the remote version.
* @param url the installer or universal jar URL. * @param url the installer or universal jar URL.
*/ */
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, String url) { public RemoteVersion(String libraryId, String gameVersion, String selfVersion, String... url) {
this(libraryId, gameVersion, selfVersion, url, Type.UNCATEGORIZED); 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 selfVersion the version string of the remote version.
* @param url the installer or universal jar URL. * @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.libraryId = Objects.requireNonNull(libraryId);
this.gameVersion = Objects.requireNonNull(gameVersion); this.gameVersion = Objects.requireNonNull(gameVersion);
this.selfVersion = Objects.requireNonNull(selfVersion); this.selfVersion = Objects.requireNonNull(selfVersion);
@ -75,7 +76,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
return selfVersion; return selfVersion;
} }
public String getUrl() { public String[] getUrl() {
return url; return url;
} }

View File

@ -31,6 +31,7 @@ import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -81,22 +82,28 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
for (ForgeVersion version : forgeVersions) { for (ForgeVersion version : forgeVersions) {
if (version == null) if (version == null)
continue; continue;
String jar = null; List<String> urls = new ArrayList<>();
for (ForgeVersion.File file : version.getFiles()) for (ForgeVersion.File file : version.getFiles())
if ("installer".equals(file.getCategory()) && "jar".equals(file.getFormat())) { 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("mcversion", version.getGameVersion()),
pair("version", version.getVersion()), pair("version", version.getVersion()),
pair("branch", version.getBranch()), pair("branch", version.getBranch()),
pair("category", file.getCategory()), pair("category", file.getCategory()),
pair("format", file.getFormat()) 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; continue;
versions.put(gameVersion, new ForgeRemoteVersion( versions.put(gameVersion, new ForgeRemoteVersion(
version.getGameVersion(), version.getVersion(), jar version.getGameVersion(), version.getVersion(), urls.toArray(new String[0])
)); ));
} }
} finally { } finally {

View File

@ -33,10 +33,8 @@ import java.io.IOException;
import java.nio.file.FileSystem; import java.nio.file.FileSystem;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection; import java.util.*;
import java.util.Collections; import java.util.stream.Collectors;
import java.util.Map;
import java.util.Optional;
import static org.jackhuang.hmcl.util.StringUtils.removePrefix; import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix; import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
@ -69,7 +67,11 @@ public final class ForgeInstallTask extends Task<Version> {
public void preExecute() throws Exception { public void preExecute() throws Exception {
installer = Files.createTempFile("forge-installer", ".jar"); 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()) .setCacheRepository(dependencyManager.getCacheRepository())
.setCaching(true); .setCaching(true);
} }

View File

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

View File

@ -38,7 +38,7 @@ public final class GameRemoteVersion extends RemoteVersion {
private final Date time; private final Date time;
public GameRemoteVersion(String gameVersion, String selfVersion, String url, ReleaseType type, 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.type = type;
this.time = time; this.time = time;
} }

View File

@ -25,9 +25,14 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.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 { public void execute() throws IOException {
RemoteVersion remoteVersion = gameVersionList.getVersion(gameVersion, gameVersion) RemoteVersion remoteVersion = gameVersionList.getVersion(gameVersion, gameVersion)
.orElseThrow(() -> new IOException("Cannot find specific version " + gameVersion + " in remote repository")); .orElseThrow(() -> new IOException("Cannot find specific version " + gameVersion + " in remote repository"));
String jsonURL = dependencyManager.getDownloadProvider().injectURL(remoteVersion.getUrl()); dependencies.add(new GetTask(
dependencies.add(new GetTask(NetworkUtils.toURL(jsonURL)).storeTo(this::setResult)); 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( Library library = new Library(
new Artifact("com.mumfrey", "liteloader", remote.getSelfVersion()), new Artifact("com.mumfrey", "liteloader", remote.getSelfVersion()),
"http://dl.liteloader.com/versions/", "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(), setResult(new Version(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId(),

View File

@ -72,7 +72,7 @@ public final class OptiFineBMCLVersionList extends VersionList<OptiFineRemoteVer
continue; continue;
String gameVersion = VersionNumber.normalize(element.getGameVersion()); 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.FileSystem;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.*;
import java.util.Collection; import java.util.stream.Collectors;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import static org.jackhuang.hmcl.util.Lang.getOrDefault; 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 Artifact("optifine", "OptiFine", mavenVersion, "installer"), null,
new LibrariesDownloadInfo(new LibraryDownloadInfo( new LibrariesDownloadInfo(new LibraryDownloadInfo(
"optifine/OptiFine/" + mavenVersion + "/OptiFine-" + mavenVersion + "-installer.jar", "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"); dest = Files.createTempFile("optifine-installer", ".jar");
if (installer == null) { 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()) .setCacheRepository(dependencyManager.getCacheRepository())
.setCaching(true)); .setCaching(true));
} else { } else {
@ -223,7 +222,7 @@ public final class OptiFineInstallTask extends Task<Version> {
throw new VersionMismatchException(mcVersion, gameVersion.get()); throw new VersionMismatchException(mcVersion, gameVersion.get());
return new OptiFineInstallTask(dependencyManager, version, 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; import java.util.function.Supplier;
public class OptiFineRemoteVersion extends RemoteVersion { public class OptiFineRemoteVersion extends RemoteVersion {
private final Supplier<String> url;
public OptiFineRemoteVersion(String gameVersion, String selfVersion, Supplier<String> url, boolean snapshot) { public OptiFineRemoteVersion(String gameVersion, String selfVersion, String url, boolean snapshot) {
super(LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId(), gameVersion, selfVersion, "", snapshot ? Type.SNAPSHOT : Type.RELEASE); super(LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId(), gameVersion, selfVersion, snapshot ? Type.SNAPSHOT : Type.RELEASE, url);
this.url = url;
}
@Override
public String getUrl() {
return url.get();
} }
@Override @Override

View File

@ -33,6 +33,9 @@ import java.net.URL;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.logging.Level; 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 File file;
private final IntegrityCheck integrityCheck; private final IntegrityCheck integrityCheck;
private final int retry; private final int retry;
private final EventManager<FailedEvent<URL>> onFailed = new EventManager<>();
private Path candidate; private Path candidate;
private boolean caching; private boolean caching;
private CacheRepository repository = CacheRepository.getInstance(); private CacheRepository repository = CacheRepository.getInstance();
@ -115,7 +117,7 @@ public class FileDownloadTask extends Task<Void> {
* @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) { public FileDownloadTask(URL url, File file, IntegrityCheck integrityCheck, int retry) {
this.url = url; this.urls = Collections.singletonList(url);
this.file = file; this.file = file;
this.integrityCheck = integrityCheck; this.integrityCheck = integrityCheck;
this.retry = retry; this.retry = retry;
@ -124,6 +126,25 @@ public class FileDownloadTask extends Task<Void> {
setExecutor(Schedulers.io()); 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() { private void closeFiles() {
if (rFile != null) if (rFile != null)
try { try {
@ -143,14 +164,6 @@ public class FileDownloadTask extends Task<Void> {
stream = null; stream = null;
} }
public EventManager<FailedEvent<URL>> getOnFailed() {
return onFailed;
}
public URL getUrl() {
return url;
}
public File getFile() { public File getFile() {
return file; return file;
} }
@ -172,8 +185,6 @@ public class FileDownloadTask extends Task<Void> {
@Override @Override
public void execute() throws Exception { public void execute() throws Exception {
URL currentURL = url;
boolean checkETag; boolean checkETag;
// Check cache // Check cache
if (integrityCheck != null && caching) { if (integrityCheck != null && caching) {
@ -182,7 +193,7 @@ public class FileDownloadTask extends Task<Void> {
if (cache.isPresent()) { if (cache.isPresent()) {
try { try {
FileUtils.copyFile(cache.get().toFile(), file); 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; return;
} catch (IOException e) { } catch (IOException e) {
Logging.LOG.log(Level.WARNING, "Failed to copy cache files", e); Logging.LOG.log(Level.WARNING, "Failed to copy cache files", e);
@ -192,15 +203,11 @@ public class FileDownloadTask extends Task<Void> {
checkETag = true; 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; Exception exception = null;
for (int repeat = 0; repeat < retry; repeat++) { for (int repeat = 0; repeat < retry; repeat++) {
if (repeat > 0) { URL url = urls.get(repeat % urls.size());
FailedEvent<URL> event = new FailedEvent<>(this, repeat, currentURL);
onFailed.fireEvent(event);
currentURL = event.getNewResult();
}
if (Thread.interrupted()) { if (Thread.interrupted()) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
break; break;
@ -226,7 +233,7 @@ public class FileDownloadTask extends Task<Void> {
repository.removeRemoteEntry(con); repository.removeRemoteEntry(con);
} }
} else if (con.getResponseCode() / 100 != 2) { } else if (con.getResponseCode() / 100 != 2) {
throw new ResponseCodeException(currentURL, con.getResponseCode()); throw new ResponseCodeException(url, con.getResponseCode());
} }
int contentLength = con.getContentLength(); int contentLength = con.getContentLength();
@ -316,16 +323,14 @@ public class FileDownloadTask extends Task<Void> {
if (temp != null) if (temp != null)
temp.toFile().delete(); temp.toFile().delete();
exception = e; exception = e;
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", repeat times: " + repeat + 1, e);
if (e instanceof ResponseCodeException && ((ResponseCodeException) e).getResponseCode() == 404)
break;
} finally { } finally {
closeFiles(); closeFiles();
} }
} }
if (exception != null) 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.net.URL;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import static java.nio.charset.StandardCharsets.UTF_8; 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> { public final class GetTask extends Task<String> {
private final URL url; private final List<URL> urls;
private final Charset charset; private final Charset charset;
private final int retry; private final int retry;
private CacheRepository repository = CacheRepository.getInstance(); private CacheRepository repository = CacheRepository.getInstance();
@ -54,7 +57,7 @@ public final class GetTask extends Task<String> {
} }
public GetTask(URL url, Charset charset, int retry) { public GetTask(URL url, Charset charset, int retry) {
this.url = url; this.urls = Collections.singletonList(url);
this.charset = charset; this.charset = charset;
this.retry = retry; this.retry = retry;
@ -62,6 +65,15 @@ public final class GetTask extends Task<String> {
setExecutor(Schedulers.io()); 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) { public GetTask setCacheRepository(CacheRepository repository) {
this.repository = repository; this.repository = repository;
return this; return this;
@ -72,8 +84,7 @@ public final class GetTask extends Task<String> {
Exception exception = null; Exception exception = null;
boolean checkETag = true; boolean checkETag = true;
for (int time = 0; time < retry; ++time) { for (int time = 0; time < retry; ++time) {
if (time > 0) URL url = urls.get(time % urls.size());
Logging.LOG.log(Level.WARNING, "Failed to download, repeat times: " + time);
try { try {
updateProgress(0); updateProgress(0);
HttpURLConnection conn = NetworkUtils.createConnection(url); HttpURLConnection conn = NetworkUtils.createConnection(url);
@ -122,10 +133,11 @@ public final class GetTask extends Task<String> {
return; return;
} catch (IOException ex) { } catch (IOException ex) {
exception = ex; exception = ex;
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", repeat times: " + time + 1, ex);
} }
} }
if (exception != null) if (exception != null)
throw new DownloadException(url, exception); throw new DownloadException(urls.get(0), exception);
} }
} }