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.ui.FXUtils;
import java.util.List;
import java.util.Map;
import java.util.Optional;
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.util.Lang.mapOf;
@@ -57,10 +60,22 @@ public final class DownloadProviders {
});
}
/**
* Get current primary preferred download provider
*/
public static DownloadProvider getDownloadProvider() {
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() {
return downloadProviderProperty;
}

View File

@@ -162,7 +162,7 @@ public final class Profile implements Observable {
}
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) {

View File

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

View File

@@ -64,7 +64,7 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
@Override
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
DownloadProvider provider = profile.getDependency().getDownloadProvider();
DownloadProvider provider = profile.getDependency().getPrimaryDownloadProvider();
switch (step) {
case 0:
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
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
DownloadProvider provider = profile.getDependency().getDownloadProvider();
DownloadProvider provider = profile.getDependency().getPrimaryDownloadProvider();
switch (step) {
case 0:
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;
import java.util.List;
/**
*
* @author huangyuhui
*/
public abstract class AbstractDependencyManager implements DependencyManager {
public abstract DownloadProvider getDownloadProvider();
public abstract DownloadProvider getPrimaryDownloadProvider();
public abstract List<DownloadProvider> getPreferredDownloadProviders();
@Override
public abstract DefaultCacheRepository getCacheRepository();
@Override
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.nio.file.Path;
import java.util.List;
/**
* Note: This class has no state.
@@ -39,11 +40,13 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
private final DefaultGameRepository repository;
private final DownloadProvider downloadProvider;
private final List<DownloadProvider> preferredDownloadProviders;
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.downloadProvider = downloadProvider;
this.preferredDownloadProviders = preferredDownloadProviders;
this.cacheRepository = cacheRepository;
}
@@ -53,10 +56,15 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
}
@Override
public DownloadProvider getDownloadProvider() {
public DownloadProvider getPrimaryDownloadProvider() {
return downloadProvider;
}
@Override
public List<DownloadProvider> getPreferredDownloadProviders() {
return preferredDownloadProviders;
}
@Override
public DefaultCacheRepository getCacheRepository() {
return cacheRepository;

View File

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

View File

@@ -50,7 +50,7 @@ public final class FabricInstallTask extends Task<Version> {
this.version = version;
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());
}

View File

@@ -32,9 +32,11 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
/**
*
@@ -101,8 +103,12 @@ public final class GameAssetDownloadTask extends Task<Void> {
if (file.isFile())
dependencyManager.getCacheRepository().tryCacheFile(file.toPath(), CacheRepository.SHA1, assetObject.getHash());
else {
String url = dependencyManager.getDownloadProvider().getAssetBaseURL() + assetObject.getLocation();
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, new FileDownloadTask.IntegrityCheck("SHA-1", assetObject.getHash()));
List<URL> urls = dependencyManager.getPreferredDownloadProviders().stream()
.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());
dependencies.add(task
.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
// And Mojang will modify this file anytime. So assetIndex.hash might be outdated.
dependencies.add(new FileDownloadTask(
NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(assetIndexInfo.getUrl())),
NetworkUtils.toURL(dependencyManager.getPrimaryDownloadProvider().injectURL(assetIndexInfo.getUrl())),
assetIndexFile
).setCacheRepository(dependencyManager.getCacheRepository()));
}

View File

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

View File

@@ -68,7 +68,7 @@ public class LibraryDownloadTask extends Task<Void> {
this.library = library;
this.cacheRepository = dependencyManager.getCacheRepository();
url = dependencyManager.getDownloadProvider().injectURL(library.getDownload().getUrl());
url = dependencyManager.getPrimaryDownloadProvider().injectURL(library.getDownload().getUrl());
jar = file;
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)
.orElseThrow(() -> new IOException("Cannot find specific version " + gameVersion + " in remote repository"));
dependencies.add(new GetTask(
dependencyManager.getDownloadProvider().injectURLs(remoteVersion.getUrl())
dependencyManager.getPrimaryDownloadProvider().injectURLs(remoteVersion.getUrl())
.map(NetworkUtils::toURL).collect(Collectors.toList()),
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
*/
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.
*/
public FileDownloadTask(URL url, File file, IntegrityCheck integrityCheck, int retry) {
this.urls = Collections.singletonList(url);
this.file = file;
this.integrityCheck = integrityCheck;
this.retry = retry;
setName(file.getName());
setExecutor(Schedulers.io());
this(Collections.singletonList(url), file, integrityCheck, retry);
}
/**
@@ -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
*/
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())
throw new IllegalArgumentException("At least one URL is required");
this.urls = new ArrayList<>(urls);
this.file = file;
this.integrityCheck = integrityCheck;
this.retry = urls.size();
this.retry = retry;
setName(file.getName());
setExecutor(Schedulers.io());
@@ -208,8 +213,8 @@ public class FileDownloadTask extends Task<Void> {
Logging.LOG.log(Level.FINER, "Downloading " + urls.get(0) + " to " + file);
Exception exception = null;
for (int repeat = 0; repeat < retry; repeat++) {
URL url = urls.get(repeat % urls.size());
for (int repeat = 0; repeat < retry * urls.size(); repeat++) {
URL url = urls.get(repeat / urls.size());
if (isCancelled()) {
break;
}