add: retry other download providers when one failed.

This commit is contained in:
huanghongxun
2020-02-19 00:10:39 +08:00
parent bc4b41e8cd
commit ca577475fc
15 changed files with 65 additions and 27 deletions

View File

@@ -26,9 +26,12 @@ import org.jackhuang.hmcl.download.MojangDownloadProvider;
import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.FXUtils;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Lang.mapOf;
@@ -57,10 +60,22 @@ public final class DownloadProviders {
}); });
} }
/**
* Get current primary preferred download provider
*/
public static DownloadProvider getDownloadProvider() { public static DownloadProvider getDownloadProvider() {
return downloadProviderProperty.get(); return downloadProviderProperty.get();
} }
/**
* Preferred download providers have the primary one first, the official one next.
* @return the preferred download providers
*/
public static List<DownloadProvider> getPreferredDownloadProviders() {
DownloadProvider provider = getDownloadProvider();
return Stream.concat(Stream.of(provider), providersById.values().stream().filter(x -> x != provider)).collect(Collectors.toList());
}
public static ObservableObjectValue<DownloadProvider> downloadProviderProperty() { public static ObservableObjectValue<DownloadProvider> downloadProviderProperty() {
return downloadProviderProperty; return downloadProviderProperty;
} }

View File

@@ -162,7 +162,7 @@ public final class Profile implements Observable {
} }
public DefaultDependencyManager getDependency() { public DefaultDependencyManager getDependency() {
return new DefaultDependencyManager(repository, DownloadProviders.getDownloadProvider(), HMCLCacheRepository.REPOSITORY); return new DefaultDependencyManager(repository, DownloadProviders.getDownloadProvider(), DownloadProviders.getPreferredDownloadProviders(), HMCLCacheRepository.REPOSITORY);
} }
public VersionSetting getVersionSetting(String id) { public VersionSetting getVersionSetting(String id) {

View File

@@ -88,7 +88,7 @@ public final class InstallerWizardProvider implements WizardProvider {
@Override @Override
public Node createPage(WizardController controller, int step, Map<String, Object> settings) { public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
DownloadProvider provider = profile.getDependency().getDownloadProvider(); DownloadProvider provider = profile.getDependency().getPrimaryDownloadProvider();
switch (step) { switch (step) {
case 0: case 0:
return new AdditionalInstallersPage(this, controller, profile.getRepository(), provider); return new AdditionalInstallersPage(this, controller, profile.getRepository(), provider);

View File

@@ -64,7 +64,7 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
@Override @Override
public Node createPage(WizardController controller, int step, Map<String, Object> settings) { public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
DownloadProvider provider = profile.getDependency().getDownloadProvider(); DownloadProvider provider = profile.getDependency().getPrimaryDownloadProvider();
switch (step) { switch (step) {
case 0: case 0:
return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), gameVersion, provider, libraryId, () -> { return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), gameVersion, provider, libraryId, () -> {

View File

@@ -68,7 +68,7 @@ public final class VanillaInstallWizardProvider implements WizardProvider {
@Override @Override
public Node createPage(WizardController controller, int step, Map<String, Object> settings) { public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
DownloadProvider provider = profile.getDependency().getDownloadProvider(); DownloadProvider provider = profile.getDependency().getPrimaryDownloadProvider();
switch (step) { switch (step) {
case 0: case 0:
return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.game")), "", provider, "game", () -> controller.onNext(new InstallersPage(controller, profile.getRepository(), provider))); return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.game")), "", provider, "game", () -> controller.onNext(new InstallersPage(controller, profile.getRepository(), provider)));

View File

@@ -17,19 +17,23 @@
*/ */
package org.jackhuang.hmcl.download; package org.jackhuang.hmcl.download;
import java.util.List;
/** /**
* *
* @author huangyuhui * @author huangyuhui
*/ */
public abstract class AbstractDependencyManager implements DependencyManager { public abstract class AbstractDependencyManager implements DependencyManager {
public abstract DownloadProvider getDownloadProvider(); public abstract DownloadProvider getPrimaryDownloadProvider();
public abstract List<DownloadProvider> getPreferredDownloadProviders();
@Override @Override
public abstract DefaultCacheRepository getCacheRepository(); public abstract DefaultCacheRepository getCacheRepository();
@Override @Override
public VersionList<?> getVersionList(String id) { public VersionList<?> getVersionList(String id) {
return getDownloadProvider().getVersionListById(id); return getPrimaryDownloadProvider().getVersionListById(id);
} }
} }

View File

@@ -29,6 +29,7 @@ import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List;
/** /**
* Note: This class has no state. * Note: This class has no state.
@@ -39,11 +40,13 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
private final DefaultGameRepository repository; private final DefaultGameRepository repository;
private final DownloadProvider downloadProvider; private final DownloadProvider downloadProvider;
private final List<DownloadProvider> preferredDownloadProviders;
private final DefaultCacheRepository cacheRepository; private final DefaultCacheRepository cacheRepository;
public DefaultDependencyManager(DefaultGameRepository repository, DownloadProvider downloadProvider, DefaultCacheRepository cacheRepository) { public DefaultDependencyManager(DefaultGameRepository repository, DownloadProvider downloadProvider, List<DownloadProvider> preferredDownloadProviders, DefaultCacheRepository cacheRepository) {
this.repository = repository; this.repository = repository;
this.downloadProvider = downloadProvider; this.downloadProvider = downloadProvider;
this.preferredDownloadProviders = preferredDownloadProviders;
this.cacheRepository = cacheRepository; this.cacheRepository = cacheRepository;
} }
@@ -53,10 +56,15 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
} }
@Override @Override
public DownloadProvider getDownloadProvider() { public DownloadProvider getPrimaryDownloadProvider() {
return downloadProvider; return downloadProvider;
} }
@Override
public List<DownloadProvider> getPreferredDownloadProviders() {
return preferredDownloadProviders;
}
@Override @Override
public DefaultCacheRepository getCacheRepository() { public DefaultCacheRepository getCacheRepository() {
return cacheRepository; return cacheRepository;

View File

@@ -34,7 +34,7 @@ public class DefaultGameBuilder extends GameBuilder {
public DefaultGameBuilder(DefaultDependencyManager dependencyManager) { public DefaultGameBuilder(DefaultDependencyManager dependencyManager) {
this.dependencyManager = dependencyManager; this.dependencyManager = dependencyManager;
this.downloadProvider = dependencyManager.getDownloadProvider(); this.downloadProvider = dependencyManager.getPrimaryDownloadProvider();
} }
public DefaultDependencyManager getDependencyManager() { public DefaultDependencyManager getDependencyManager() {

View File

@@ -50,7 +50,7 @@ public final class FabricInstallTask extends Task<Version> {
this.version = version; this.version = version;
this.remote = remoteVersion; this.remote = remoteVersion;
launchMetaTask = new GetTask(NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(getLaunchMetaUrl(remote.getGameVersion(), remote.getSelfVersion())))) launchMetaTask = new GetTask(NetworkUtils.toURL(dependencyManager.getPrimaryDownloadProvider().injectURL(getLaunchMetaUrl(remote.getGameVersion(), remote.getSelfVersion()))))
.setCacheRepository(dependencyManager.getCacheRepository()); .setCacheRepository(dependencyManager.getCacheRepository());
} }

View File

@@ -32,9 +32,11 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URL;
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;
/** /**
* *
@@ -101,8 +103,12 @@ public final class GameAssetDownloadTask extends Task<Void> {
if (file.isFile()) if (file.isFile())
dependencyManager.getCacheRepository().tryCacheFile(file.toPath(), CacheRepository.SHA1, assetObject.getHash()); dependencyManager.getCacheRepository().tryCacheFile(file.toPath(), CacheRepository.SHA1, assetObject.getHash());
else { else {
String url = dependencyManager.getDownloadProvider().getAssetBaseURL() + assetObject.getLocation(); List<URL> urls = dependencyManager.getPreferredDownloadProviders().stream()
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, new FileDownloadTask.IntegrityCheck("SHA-1", assetObject.getHash())); .map(downloadProvider -> downloadProvider.getAssetBaseURL() + assetObject.getLocation())
.map(NetworkUtils::toURL)
.collect(Collectors.toList());
FileDownloadTask task = new FileDownloadTask(urls, file, new FileDownloadTask.IntegrityCheck("SHA-1", assetObject.getHash()));
task.setName(assetObject.getHash()); task.setName(assetObject.getHash());
dependencies.add(task dependencies.add(task
.setCacheRepository(dependencyManager.getCacheRepository()) .setCacheRepository(dependencyManager.getCacheRepository())

View File

@@ -65,7 +65,7 @@ public final class GameAssetIndexDownloadTask extends Task<Void> {
// We should not check the hash code of asset index file since this file is not consistent // 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. // And Mojang will modify this file anytime. So assetIndex.hash might be outdated.
dependencies.add(new FileDownloadTask( dependencies.add(new FileDownloadTask(
NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(assetIndexInfo.getUrl())), NetworkUtils.toURL(dependencyManager.getPrimaryDownloadProvider().injectURL(assetIndexInfo.getUrl())),
assetIndexFile assetIndexFile
).setCacheRepository(dependencyManager.getCacheRepository())); ).setCacheRepository(dependencyManager.getCacheRepository()));
} }

View File

@@ -58,7 +58,7 @@ public final class GameDownloadTask extends Task<Void> {
File jar = dependencyManager.getGameRepository().getVersionJar(version); File jar = dependencyManager.getGameRepository().getVersionJar(version);
FileDownloadTask task = new FileDownloadTask( FileDownloadTask task = new FileDownloadTask(
NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())), NetworkUtils.toURL(dependencyManager.getPrimaryDownloadProvider().injectURL(version.getDownloadInfo().getUrl())),
jar, jar,
IntegrityCheck.of(CacheRepository.SHA1, version.getDownloadInfo().getSha1())) IntegrityCheck.of(CacheRepository.SHA1, version.getDownloadInfo().getSha1()))
.setCaching(true) .setCaching(true)

View File

@@ -68,7 +68,7 @@ public class LibraryDownloadTask extends Task<Void> {
this.library = library; this.library = library;
this.cacheRepository = dependencyManager.getCacheRepository(); this.cacheRepository = dependencyManager.getCacheRepository();
url = dependencyManager.getDownloadProvider().injectURL(library.getDownload().getUrl()); url = dependencyManager.getPrimaryDownloadProvider().injectURL(library.getDownload().getUrl());
jar = file; jar = file;
xzFile = new File(file.getAbsoluteFile().getParentFile(), file.getName() + ".pack.xz"); xzFile = new File(file.getAbsoluteFile().getParentFile(), file.getName() + ".pack.xz");

View File

@@ -69,7 +69,7 @@ public final class VersionJsonDownloadTask extends Task<String> {
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"));
dependencies.add(new GetTask( dependencies.add(new GetTask(
dependencyManager.getDownloadProvider().injectURLs(remoteVersion.getUrl()) dependencyManager.getPrimaryDownloadProvider().injectURLs(remoteVersion.getUrl())
.map(NetworkUtils::toURL).collect(Collectors.toList()), .map(NetworkUtils::toURL).collect(Collectors.toList()),
UTF_8).storeTo(this::setResult)); UTF_8).storeTo(this::setResult));
} }

View File

@@ -109,7 +109,7 @@ public class FileDownloadTask extends Task<Void> {
* @param integrityCheck the integrity check to perform, null if no integrity check is to be performed * @param integrityCheck the integrity check to perform, null if no integrity check is to be performed
*/ */
public FileDownloadTask(URL url, File file, IntegrityCheck integrityCheck) { public FileDownloadTask(URL url, File file, IntegrityCheck integrityCheck) {
this(url, file, integrityCheck, 5); this(Collections.singletonList(url), file, integrityCheck);
} }
/** /**
@@ -119,13 +119,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.urls = Collections.singletonList(url); this(Collections.singletonList(url), file, integrityCheck, retry);
this.file = file;
this.integrityCheck = integrityCheck;
this.retry = retry;
setName(file.getName());
setExecutor(Schedulers.io());
} }
/** /**
@@ -135,13 +129,24 @@ public class FileDownloadTask extends Task<Void> {
* @param integrityCheck the integrity check to perform, null if no integrity check is to be performed * @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) { public FileDownloadTask(List<URL> urls, File file, IntegrityCheck integrityCheck) {
this(urls, file, integrityCheck, 3);
}
/**
* 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
* @param retry the times for retrying if downloading fails.
*/
public FileDownloadTask(List<URL> urls, File file, IntegrityCheck integrityCheck, int retry) {
if (urls == null || urls.isEmpty()) if (urls == null || urls.isEmpty())
throw new IllegalArgumentException("At least one URL is required"); throw new IllegalArgumentException("At least one URL is required");
this.urls = new ArrayList<>(urls); this.urls = new ArrayList<>(urls);
this.file = file; this.file = file;
this.integrityCheck = integrityCheck; this.integrityCheck = integrityCheck;
this.retry = urls.size(); this.retry = retry;
setName(file.getName()); setName(file.getName());
setExecutor(Schedulers.io()); setExecutor(Schedulers.io());
@@ -208,8 +213,8 @@ public class FileDownloadTask extends Task<Void> {
Logging.LOG.log(Level.FINER, "Downloading " + urls.get(0) + " 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 * urls.size(); repeat++) {
URL url = urls.get(repeat % urls.size()); URL url = urls.get(repeat / urls.size());
if (isCancelled()) { if (isCancelled()) {
break; break;
} }