add: retry other download providers when one failed.
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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, () -> {
|
||||||
|
|||||||
@@ -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)));
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user