feat: balanced download provider & show release date in download versions page & download page ui refactor.

This commit is contained in:
huanghongxun
2021-08-29 17:16:58 +08:00
parent a20331dfa8
commit 0210b9fd3d
28 changed files with 485 additions and 464 deletions

View File

@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -60,11 +60,11 @@ public final class DownloadProviders {
AdaptedDownloadProvider fileProvider = new AdaptedDownloadProvider();
fileProvider.setDownloadProviderCandidates(Arrays.asList(MCBBS, BMCLAPI, MOJANG));
// BalancedDownloadProvider balanced = new BalancedDownloadProvider();
BalancedDownloadProvider balanced = new BalancedDownloadProvider(Arrays.asList(MCBBS, BMCLAPI, MOJANG));
providersById = mapOf(
pair("official", new AutoDownloadProvider(MOJANG, fileProvider)),
pair("balanced", new AutoDownloadProvider(MCBBS, fileProvider)),
pair("balanced", new AutoDownloadProvider(balanced, fileProvider)),
pair("mirror", new AutoDownloadProvider(MCBBS, fileProvider)));
}

View File

@@ -37,7 +37,6 @@ import org.jackhuang.hmcl.download.forge.ForgeRemoteVersion;
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion;
import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionPane;
@@ -47,9 +46,12 @@ import org.jackhuang.hmcl.ui.construct.RipplerContainer;
import org.jackhuang.hmcl.ui.wizard.Refreshable;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardPage;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.i18n.Locales;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.stream.Collectors;
@@ -86,7 +88,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
private StackPane center;
private VersionList<?> versionList;
private TaskExecutor executor;
private CompletableFuture<?> executor;
public VersionsPage(WizardController controller, String title, String gameVersion, DownloadProvider downloadProvider, String libraryId, Runnable callback) {
this.title = title;
@@ -129,35 +131,55 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
setGraphic(pane);
content.setTitle(remoteVersion.getSelfVersion());
content.setSubtitle(remoteVersion.getGameVersion());
if (remoteVersion.getReleaseDate() != null) {
content.setSubtitle(Locales.DATE_TIME_FORMATTER.get().format(remoteVersion.getReleaseDate()));
} else {
content.setSubtitle("");
}
if (remoteVersion instanceof GameRemoteVersion) {
switch (remoteVersion.getVersionType()) {
case RELEASE:
content.setSubtitle(i18n("version.game.release"));
content.getTags().setAll(i18n("version.game.release"));
content.setImage(new Image("/assets/img/grass.png", 32, 32, false, true));
break;
case SNAPSHOT:
content.setSubtitle(i18n("version.game.snapshot"));
content.getTags().setAll(i18n("version.game.snapshot"));
content.setImage(new Image("/assets/img/command.png", 32, 32, false, true));
break;
default:
content.setSubtitle(i18n("version.game.old"));
content.getTags().setAll(i18n("version.game.old"));
content.setImage(new Image("/assets/img/craft_table.png", 32, 32, false, true));
break;
}
} else if (remoteVersion instanceof LiteLoaderRemoteVersion) {
content.setImage(new Image("/assets/img/chicken.png", 32, 32, false, true));
content.setSubtitle(remoteVersion.getGameVersion());
if (StringUtils.isNotBlank(content.getSubtitle())) {
content.getTags().setAll(remoteVersion.getGameVersion());
} else {
content.setSubtitle(remoteVersion.getGameVersion());
}
} else if (remoteVersion instanceof OptiFineRemoteVersion) {
content.setImage(new Image("/assets/img/command.png", 32, 32, false, true));
content.setSubtitle(remoteVersion.getGameVersion());
if (StringUtils.isNotBlank(content.getSubtitle())) {
content.getTags().setAll(remoteVersion.getGameVersion());
} else {
content.setSubtitle(remoteVersion.getGameVersion());
}
} else if (remoteVersion instanceof ForgeRemoteVersion) {
content.setImage(new Image("/assets/img/forge.png", 32, 32, false, true));
content.setSubtitle(remoteVersion.getGameVersion());
if (StringUtils.isNotBlank(content.getSubtitle())) {
content.getTags().setAll(remoteVersion.getGameVersion());
} else {
content.setSubtitle(remoteVersion.getGameVersion());
}
} else if (remoteVersion instanceof FabricRemoteVersion) {
content.setImage(new Image("/assets/img/fabric.png", 32, 32, false, true));
content.setSubtitle(remoteVersion.getGameVersion());
if (StringUtils.isNotBlank(content.getSubtitle())) {
content.getTags().setAll(remoteVersion.getGameVersion());
} else {
content.setSubtitle(remoteVersion.getGameVersion());
}
}
}
});
@@ -193,7 +215,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
public void refresh() {
VersionList<?> currentVersionList = versionList;
root.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
executor = currentVersionList.refreshAsync(gameVersion).whenComplete(exception -> {
executor = currentVersionList.refreshAsync(gameVersion).whenComplete((result, exception) -> {
if (exception == null) {
List<RemoteVersion> items = loadVersions();
@@ -222,7 +244,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
// https://github.com/huanghongxun/HMCL/issues/938
System.gc();
}).executor().start();
});
}
@Override
@@ -234,7 +256,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
public void cleanup(Map<String, Object> settings) {
settings.remove(libraryId);
if (executor != null)
executor.cancel();
executor.cancel(true);
}
@FXML

View File

@@ -29,10 +29,10 @@ import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
import org.jackhuang.hmcl.util.i18n.Locales;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import static org.jackhuang.hmcl.util.StringUtils.parseColorEscapes;
@@ -43,14 +43,13 @@ public class WorldListItem extends Control {
private final StringProperty subtitle = new SimpleStringProperty();
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
private final World world;
private final SimpleDateFormat simpleDateFormat;
public WorldListItem(World world) {
this.world = world;
this.simpleDateFormat = new SimpleDateFormat(i18n("world.time"));
title.set(parseColorEscapes(world.getWorldName()));
subtitle.set(i18n("world.description", world.getFileName(), simpleDateFormat.format(new Date(world.getLastPlayed())), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
subtitle.set(i18n("world.description", world.getFileName(), Locales.SIMPLE_DATE_FORMAT.get().format(new Date(world.getLastPlayed())), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
}
@Override

View File

@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,12 +21,18 @@ import com.google.gson.annotations.JsonAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Lazy;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class Locales {
private Locales() {
}
@@ -129,4 +135,7 @@ public final class Locales {
}
}
}
public static final Lazy<SimpleDateFormat> SIMPLE_DATE_FORMAT = new Lazy<>(() -> new SimpleDateFormat(i18n("world.time")));
public static final Lazy<DateTimeFormatter> DATE_TIME_FORMATTER = new Lazy<>(() -> DateTimeFormatter.ofPattern(i18n("world.time")).withZone(ZoneId.systemDefault()));
}

View File

@@ -204,7 +204,7 @@ public class MicrosoftService {
public Optional<MinecraftProfileResponse> getCompleteProfile(String authorization) throws AuthenticationException {
try {
return Optional.ofNullable(
HttpRequest.GET(NetworkUtils.toURL("https://api.minecraftservices.com/minecraft/profile"))
HttpRequest.GET("https://api.minecraftservices.com/minecraft/profile")
.authorization(authorization).getJson(MinecraftProfileResponse.class));
} catch (IOException e) {
throw new ServerDisconnectException(e);

View File

@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -120,7 +120,7 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
if (baseVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved");
VersionList<?> versionList = getVersionList(libraryId);
return versionList.loadAsync(gameVersion)
return Task.fromCompletableFuture(versionList.loadAsync(gameVersion))
.thenComposeAsync(() -> installLibraryAsync(baseVersion, versionList.getVersion(gameVersion, libraryVersion)
.orElseThrow(() -> new IOException("Remote library " + libraryId + " has no version " + libraryVersion))))
.withStage(String.format("hmcl.install.%s:%s", libraryId, libraryVersion));

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.time.Instant;
import java.util.List;
import java.util.Objects;
@@ -35,6 +36,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
private final String libraryId;
private final String gameVersion;
private final String selfVersion;
private final Instant releaseDate;
private final List<String> urls;
private final Type type;
@@ -45,8 +47,8 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
* @param selfVersion the version string of the remote version.
* @param urls the installer or universal jar original URL.
*/
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, List<String> urls) {
this(libraryId, gameVersion, selfVersion, Type.UNCATEGORIZED, urls);
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Instant releaseDate, List<String> urls) {
this(libraryId, gameVersion, selfVersion, releaseDate, Type.UNCATEGORIZED, urls);
}
/**
@@ -56,10 +58,11 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
* @param selfVersion the version string of the remote version.
* @param urls the installer or universal jar URL.
*/
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Type type, List<String> urls) {
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Instant releaseDate, Type type, List<String> urls) {
this.libraryId = Objects.requireNonNull(libraryId);
this.gameVersion = Objects.requireNonNull(gameVersion);
this.selfVersion = Objects.requireNonNull(selfVersion);
this.releaseDate = releaseDate;
this.urls = Objects.requireNonNull(urls);
this.type = Objects.requireNonNull(type);
}
@@ -76,6 +79,10 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
return selfVersion;
}
public Instant getReleaseDate() {
return releaseDate;
}
public List<String> getUrls() {
return urls;
}

View File

@@ -17,10 +17,10 @@
*/
package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.SimpleMultimap;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
@@ -61,42 +61,44 @@ public abstract class VersionList<T extends RemoteVersion> {
/**
* @return the task to reload the remote version list.
*/
public abstract Task<?> refreshAsync();
public abstract CompletableFuture<?> refreshAsync();
/**
* @param gameVersion the remote version depends on
* @return the task to reload the remote version list.
*/
public Task<?> refreshAsync(String gameVersion) {
public CompletableFuture<?> refreshAsync(String gameVersion) {
return refreshAsync();
}
public Task<?> loadAsync() {
return Task.composeAsync(() -> {
lock.readLock().lock();
boolean loaded;
public CompletableFuture<?> loadAsync() {
return CompletableFuture.completedFuture(null)
.thenComposeAsync(unused -> {
lock.readLock().lock();
boolean loaded;
try {
loaded = isLoaded();
} finally {
lock.readLock().unlock();
}
return loaded ? null : refreshAsync();
});
try {
loaded = isLoaded();
} finally {
lock.readLock().unlock();
}
return loaded ? CompletableFuture.completedFuture(null) : refreshAsync();
});
}
public Task<?> loadAsync(String gameVersion) {
return Task.composeAsync(() -> {
lock.readLock().lock();
boolean loaded;
public CompletableFuture<?> loadAsync(String gameVersion) {
return CompletableFuture.completedFuture(null)
.thenComposeAsync(unused -> {
lock.readLock().lock();
boolean loaded;
try {
loaded = isLoaded(gameVersion);
} finally {
lock.readLock().unlock();
}
return loaded ? null : refreshAsync(gameVersion);
});
try {
loaded = isLoaded(gameVersion);
} finally {
lock.readLock().unlock();
}
return loaded ? CompletableFuture.completedFuture(null) : refreshAsync(gameVersion);
});
}
protected Collection<T> getVersionsImpl(String gameVersion) {

View File

@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -34,7 +34,7 @@ public class FabricRemoteVersion extends RemoteVersion {
* @param urls the installer or universal jar original URL.
*/
FabricRemoteVersion(String gameVersion, String selfVersion, List<String> urls) {
super(LibraryAnalyzer.LibraryType.FABRIC.getPatchId(), gameVersion, selfVersion, urls);
super(LibraryAnalyzer.LibraryType.FABRIC.getPatchId(), gameVersion, selfVersion, null, urls);
}
@Override

View File

@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.download.fabric;
import com.google.gson.reflect.TypeToken;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jetbrains.annotations.Nullable;
@@ -29,8 +28,11 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.util.Lang.wrap;
public final class FabricVersionList extends VersionList<FabricRemoteVersion> {
private final DownloadProvider downloadProvider;
@@ -44,25 +46,22 @@ public final class FabricVersionList extends VersionList<FabricRemoteVersion> {
}
@Override
public Task<?> refreshAsync() {
return new Task<Void>() {
@Override
public void execute() throws IOException {
List<String> gameVersions = getGameVersions(GAME_META_URL);
List<String> loaderVersions = getGameVersions(LOADER_META_URL);
public CompletableFuture<?> refreshAsync() {
return CompletableFuture.runAsync(wrap(() -> {
List<String> gameVersions = getGameVersions(GAME_META_URL);
List<String> loaderVersions = getGameVersions(LOADER_META_URL);
lock.writeLock().lock();
lock.writeLock().lock();
try {
for (String gameVersion : gameVersions)
for (String loaderVersion : loaderVersions)
versions.put(gameVersion, new FabricRemoteVersion(gameVersion, loaderVersion,
Collections.singletonList(getLaunchMetaUrl(gameVersion, loaderVersion))));
} finally {
lock.writeLock().unlock();
}
try {
for (String gameVersion : gameVersions)
for (String loaderVersion : loaderVersions)
versions.put(gameVersion, new FabricRemoteVersion(gameVersion, loaderVersion,
Collections.singletonList(getLaunchMetaUrl(gameVersion, loaderVersion))));
} finally {
lock.writeLock().unlock();
}
};
}));
}
private static final String LOADER_META_URL = "https://meta.fabricmc.net/v2/versions/loader";

View File

@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,19 +20,26 @@ package org.jackhuang.hmcl.download.forge;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.gson.Validation;
import org.jackhuang.hmcl.util.io.HttpRequest;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Lang.wrap;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.Pair.pair;
public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion> {
@@ -51,64 +58,66 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
}
@Override
public Task<?> loadAsync() {
public CompletableFuture<?> loadAsync() {
throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list.");
}
@Override
public Task<?> refreshAsync() {
public CompletableFuture<?> refreshAsync() {
throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list.");
}
@Override
public Task<?> refreshAsync(String gameVersion) {
final GetTask task = new GetTask(NetworkUtils.toURL(apiRoot + "/forge/minecraft/" + gameVersion));
return new Task<Void>() {
@Override
public Collection<Task<?>> getDependents() {
return Collections.singleton(task);
}
public CompletableFuture<?> refreshAsync(String gameVersion) {
return CompletableFuture.completedFuture(null)
.thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/forge/minecraft/" + gameVersion).<List<ForgeVersion>>getJson(new TypeToken<List<ForgeVersion>>() {
}.getType())))
.thenAcceptAsync(forgeVersions -> {
lock.writeLock().lock();
@Override
public void execute() {
lock.writeLock().lock();
try {
versions.clear(gameVersion);
if (forgeVersions == null) return;
for (ForgeVersion version : forgeVersions) {
if (version == null)
continue;
List<String> urls = new ArrayList<>();
for (ForgeVersion.File file : version.getFiles())
if ("installer".equals(file.getCategory()) && "jar".equals(file.getFormat())) {
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("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" + classifier + "/" + fileName1);
urls.add("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" + classifier + "-" + gameVersion + "/" + fileName2);
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())
)));
}
try {
List<ForgeVersion> forgeVersions = JsonUtils.GSON.fromJson(task.getResult(), new TypeToken<List<ForgeVersion>>() {
}.getType());
versions.clear(gameVersion);
if (forgeVersions == null) return;
for (ForgeVersion version : forgeVersions) {
if (version == null)
continue;
List<String> urls = new ArrayList<>();
for (ForgeVersion.File file : version.getFiles())
if ("installer".equals(file.getCategory()) && "jar".equals(file.getFormat())) {
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("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" + classifier + "/" + fileName1);
urls.add("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" + classifier + "-" + gameVersion + "/" + fileName2);
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())
)));
if (urls.isEmpty())
continue;
Instant releaseDate = null;
if (version.getModified() != null) {
try {
releaseDate = Instant.parse(version.getModified());
} catch (DateTimeParseException e) {
LOG.log(Level.WARNING, "Failed to parse instant " + version.getModified(), e);
}
}
if (urls.isEmpty())
continue;
versions.put(gameVersion, new ForgeRemoteVersion(
version.getGameVersion(), version.getVersion(), urls));
versions.put(gameVersion, new ForgeRemoteVersion(
version.getGameVersion(), version.getVersion(), releaseDate, urls));
}
} finally {
lock.writeLock().unlock();
}
} finally {
lock.writeLock().unlock();
}
}
};
});
}
@Override
@@ -121,7 +130,9 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
public static final class ForgeVersion implements Validation {
private final String branch;
private final int build;
private final String mcversion;
private final String modified;
private final String version;
private final List<File> files;
@@ -130,12 +141,14 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
*/
@SuppressWarnings("unused")
public ForgeVersion() {
this(null, null, null, null);
this(null, 0, "", null, "", Collections.emptyList());
}
public ForgeVersion(String branch, String mcversion, String version, List<File> files) {
public ForgeVersion(String branch, int build, String mcversion, String modified, String version, List<File> files) {
this.branch = branch;
this.build = build;
this.mcversion = mcversion;
this.modified = modified;
this.version = version;
this.files = files;
}
@@ -145,11 +158,20 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
return branch;
}
public int getBuild() {
return build;
}
@NotNull
public String getGameVersion() {
return mcversion;
}
@Nullable
public String getModified() {
return modified;
}
@NotNull
public String getVersion() {
return version;

View File

@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,6 +23,7 @@ import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import java.time.Instant;
import java.util.List;
public class ForgeRemoteVersion extends RemoteVersion {
@@ -33,8 +34,8 @@ public class ForgeRemoteVersion extends RemoteVersion {
* @param selfVersion the version string of the remote version.
* @param url the installer or universal jar original URL.
*/
public ForgeRemoteVersion(String gameVersion, String selfVersion, List<String> url) {
super(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), gameVersion, selfVersion, url);
public ForgeRemoteVersion(String gameVersion, String selfVersion, Instant instant, List<String> url) {
super(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), gameVersion, selfVersion, instant, url);
}
@Override

View File

@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,16 +19,13 @@ package org.jackhuang.hmcl.download.forge;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.io.HttpRequest;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
*
@@ -47,53 +44,42 @@ public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
}
@Override
public Task<?> refreshAsync() {
final GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(FORGE_LIST)));
return new Task<Void>() {
public CompletableFuture<?> refreshAsync() {
return HttpRequest.GET(downloadProvider.injectURL(FORGE_LIST)).getJsonAsync(ForgeVersionRoot.class)
.thenAcceptAsync(root -> {
lock.writeLock().lock();
@Override
public Collection<Task<?>> getDependents() {
return Collections.singleton(task);
}
try {
if (root == null)
return;
versions.clear();
@Override
public void execute() {
lock.writeLock().lock();
for (Map.Entry<String, int[]> entry : root.getGameVersions().entrySet()) {
String gameVersion = VersionNumber.normalize(entry.getKey());
for (int v : entry.getValue()) {
ForgeVersion version = root.getNumber().get(v);
if (version == null)
continue;
String jar = null;
for (String[] file : version.getFiles())
if (file.length > 1 && "installer".equals(file[1])) {
String classifier = version.getGameVersion() + "-" + version.getVersion()
+ (StringUtils.isNotBlank(version.getBranch()) ? "-" + version.getBranch() : "");
String fileName = root.getArtifact() + "-" + classifier + "-" + file[1] + "." + file[0];
jar = root.getWebPath() + classifier + "/" + fileName;
}
try {
ForgeVersionRoot root = JsonUtils.GSON.fromJson(task.getResult(), ForgeVersionRoot.class);
if (root == null)
return;
versions.clear();
for (Map.Entry<String, int[]> entry : root.getGameVersions().entrySet()) {
String gameVersion = VersionNumber.normalize(entry.getKey());
for (int v : entry.getValue()) {
ForgeVersion version = root.getNumber().get(v);
if (version == null)
continue;
String jar = null;
for (String[] file : version.getFiles())
if (file.length > 1 && "installer".equals(file[1])) {
String classifier = version.getGameVersion() + "-" + version.getVersion()
+ (StringUtils.isNotBlank(version.getBranch()) ? "-" + version.getBranch() : "");
String fileName = root.getArtifact() + "-" + classifier + "-" + file[1] + "." + file[0];
jar = root.getWebPath() + classifier + "/" + fileName;
}
if (jar == null)
continue;
versions.put(gameVersion, new ForgeRemoteVersion(
version.getGameVersion(), version.getVersion(), Collections.singletonList(jar)
));
if (jar == null)
continue;
versions.put(gameVersion, new ForgeRemoteVersion(
version.getGameVersion(), version.getVersion(), null, Collections.singletonList(jar)
));
}
}
} finally {
lock.writeLock().unlock();
}
} finally {
lock.writeLock().unlock();
}
}
};
});
}
public static final String FORGE_LIST = "https://files.minecraftforge.net/maven/net/minecraftforge/forge/json";

View File

@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -25,7 +25,7 @@ import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Immutable;
import java.util.Date;
import java.time.Instant;
import java.util.List;
/**
@@ -36,16 +36,10 @@ import java.util.List;
public final class GameRemoteVersion extends RemoteVersion {
private final ReleaseType type;
private final Date time;
public GameRemoteVersion(String gameVersion, String selfVersion, List<String> url, ReleaseType type, Date time) {
super(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), gameVersion, selfVersion, getReleaseType(type), url);
public GameRemoteVersion(String gameVersion, String selfVersion, List<String> url, ReleaseType type, Instant releaseDate) {
super(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), gameVersion, selfVersion, releaseDate, getReleaseType(type), url);
this.type = type;
this.time = time;
}
public Date getTime() {
return time;
}
public ReleaseType getType() {
@@ -62,7 +56,7 @@ public final class GameRemoteVersion extends RemoteVersion {
if (!(o instanceof GameRemoteVersion))
return 0;
return ((GameRemoteVersion) o).getTime().compareTo(getTime());
return o.getReleaseDate().compareTo(getReleaseDate());
}
private static Type getReleaseType(ReleaseType type) {

View File

@@ -19,14 +19,11 @@ package org.jackhuang.hmcl.download.game;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.io.HttpRequest;
import java.util.Collection;
import java.util.Collections;
import static org.jackhuang.hmcl.util.gson.JsonUtils.GSON;
import java.util.concurrent.CompletableFuture;
/**
*
@@ -50,11 +47,9 @@ public final class GameVersionList extends VersionList<GameRemoteVersion> {
}
@Override
public Task<?> refreshAsync() {
return new GetTask(NetworkUtils.toURL(downloadProvider.getVersionListURL()))
.thenAcceptAsync(json -> {
GameRemoteVersions root = GSON.fromJson(json, GameRemoteVersions.class);
public CompletableFuture<?> refreshAsync() {
return HttpRequest.GET(downloadProvider.getVersionListURL()).getJsonAsync(GameRemoteVersions.class)
.thenAcceptAsync(root -> {
lock.writeLock().lock();
try {
versions.clear();
@@ -64,7 +59,7 @@ public final class GameVersionList extends VersionList<GameRemoteVersion> {
remoteVersion.getGameVersion(),
remoteVersion.getGameVersion(),
Collections.singletonList(remoteVersion.getUrl()),
remoteVersion.getType(), remoteVersion.getReleaseTime()));
remoteVersion.getType(), remoteVersion.getReleaseTime().toInstant()));
}
} finally {
lock.writeLock().unlock();

View File

@@ -44,7 +44,7 @@ public final class VersionJsonDownloadTask extends Task<String> {
this.dependencyManager = dependencyManager;
this.gameVersionList = dependencyManager.getVersionList("game");
dependents.add(gameVersionList.loadAsync());
dependents.add(Task.fromCompletableFuture(gameVersionList.loadAsync()));
setSignificance(TaskSignificance.MODERATE);
}

View File

@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,10 +19,7 @@ package org.jackhuang.hmcl.download.liteloader;
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.io.HttpRequest;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -30,9 +27,9 @@ import org.w3c.dom.Node;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
*
@@ -50,63 +47,54 @@ public final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemot
return false;
}
@Override
public Task<?> refreshAsync() {
GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(LITELOADER_LIST)));
return new Task<Void>() {
@Override
public Collection<Task<?>> getDependents() {
return Collections.singleton(task);
}
private void doBranch(String key, String gameVersion, LiteLoaderRepository repository, LiteLoaderBranch branch, boolean snapshot) {
if (branch == null || repository == null)
return;
@Override
public void execute() {
lock.writeLock().lock();
for (Map.Entry<String, LiteLoaderVersion> entry : branch.getLiteLoader().entrySet()) {
String branchName = entry.getKey();
LiteLoaderVersion v = entry.getValue();
if ("latest".equals(branchName))
continue;
String version = v.getVersion();
String url = "https://bmclapi2.bangbang93.com/liteloader/download?version=" + version;
if (snapshot) {
try {
LiteLoaderVersionsRoot root = JsonUtils.GSON.fromJson(task.getResult(), LiteLoaderVersionsRoot.class);
versions.clear();
for (Map.Entry<String, LiteLoaderGameVersions> entry : root.getVersions().entrySet()) {
String gameVersion = entry.getKey();
LiteLoaderGameVersions liteLoader = entry.getValue();
String gg = VersionNumber.normalize(gameVersion);
doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getArtifacts(), false);
doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getSnapshots(), true);
}
} finally {
lock.writeLock().unlock();
version = version.replace("SNAPSHOT", getLatestSnapshotVersion(repository.getUrl() + "com/mumfrey/liteloader/" + v.getVersion() + "/"));
url = repository.getUrl() + "com/mumfrey/liteloader/" + v.getVersion() + "/liteloader-" + version + "-release.jar";
} catch (Exception ignore) {
}
}
private void doBranch(String key, String gameVersion, LiteLoaderRepository repository, LiteLoaderBranch branch, boolean snapshot) {
if (branch == null || repository == null)
return;
versions.put(key, new LiteLoaderRemoteVersion(gameVersion,
version, Collections.singletonList(url),
v.getTweakClass(), v.getLibraries()
));
}
}
for (Map.Entry<String, LiteLoaderVersion> entry : branch.getLiteLoader().entrySet()) {
String branchName = entry.getKey();
LiteLoaderVersion v = entry.getValue();
if ("latest".equals(branchName))
continue;
@Override
public CompletableFuture<?> refreshAsync() {
return HttpRequest.GET(downloadProvider.injectURL(LITELOADER_LIST)).getJsonAsync(LiteLoaderVersionsRoot.class)
.thenAcceptAsync(root -> {
lock.writeLock().lock();
String version = v.getVersion();
String url = "https://bmclapi2.bangbang93.com/liteloader/download?version=" + version;
if (snapshot) {
try {
version = version.replace("SNAPSHOT", getLatestSnapshotVersion(repository.getUrl() + "com/mumfrey/liteloader/" + v.getVersion() + "/"));
url = repository.getUrl() + "com/mumfrey/liteloader/" + v.getVersion() + "/liteloader-" + version + "-release.jar";
} catch (Exception ignore) {
try {
versions.clear();
for (Map.Entry<String, LiteLoaderGameVersions> entry : root.getVersions().entrySet()) {
String gameVersion = entry.getKey();
LiteLoaderGameVersions liteLoader = entry.getValue();
String gg = VersionNumber.normalize(gameVersion);
doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getArtifacts(), false);
doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getSnapshots(), true);
}
} finally {
lock.writeLock().unlock();
}
versions.put(key, new LiteLoaderRemoteVersion(gameVersion,
version, Collections.singletonList(url),
v.getTweakClass(), v.getLibraries()
));
}
}
};
});
}
public static final String LITELOADER_LIST = "http://dl.liteloader.com/versions/versions.json";

View File

@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -38,7 +38,7 @@ public class LiteLoaderRemoteVersion extends RemoteVersion {
* @param urls the installer or universal jar original URL.
*/
LiteLoaderRemoteVersion(String gameVersion, String selfVersion, List<String> urls, String tweakClass, Collection<Library> libraries) {
super(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId(), gameVersion, selfVersion, urls);
super(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId(), gameVersion, selfVersion, null, urls);
this.tweakClass = tweakClass;
this.libraries = libraries;

View File

@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,10 +19,7 @@ package org.jackhuang.hmcl.download.liteloader;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.io.HttpRequest;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -30,9 +27,9 @@ import org.w3c.dom.Node;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
*
@@ -51,63 +48,54 @@ public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVer
return false;
}
@Override
public Task<?> refreshAsync() {
GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(LITELOADER_LIST)));
return new Task<Void>() {
@Override
public Collection<Task<?>> getDependents() {
return Collections.singleton(task);
}
private void doBranch(String key, String gameVersion, LiteLoaderRepository repository, LiteLoaderBranch branch, boolean snapshot) {
if (branch == null || repository == null)
return;
@Override
public void execute() {
lock.writeLock().lock();
for (Map.Entry<String, LiteLoaderVersion> entry : branch.getLiteLoader().entrySet()) {
String branchName = entry.getKey();
LiteLoaderVersion v = entry.getValue();
if ("latest".equals(branchName))
continue;
String version = v.getVersion();
String url = repository.getUrl() + "com/mumfrey/liteloader/" + gameVersion + "/" + v.getFile();
if (snapshot) {
try {
LiteLoaderVersionsRoot root = JsonUtils.GSON.fromJson(task.getResult(), LiteLoaderVersionsRoot.class);
versions.clear();
for (Map.Entry<String, LiteLoaderGameVersions> entry : root.getVersions().entrySet()) {
String gameVersion = entry.getKey();
LiteLoaderGameVersions liteLoader = entry.getValue();
String gg = VersionNumber.normalize(gameVersion);
doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getArtifacts(), false);
doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getSnapshots(), true);
}
} finally {
lock.writeLock().unlock();
version = version.replace("SNAPSHOT", getLatestSnapshotVersion(repository.getUrl() + "com/mumfrey/liteloader/" + v.getVersion() + "/"));
url = repository.getUrl() + "com/mumfrey/liteloader/" + v.getVersion() + "/liteloader-" + version + "-release.jar";
} catch (Exception ignore) {
}
}
private void doBranch(String key, String gameVersion, LiteLoaderRepository repository, LiteLoaderBranch branch, boolean snapshot) {
if (branch == null || repository == null)
return;
versions.put(key, new LiteLoaderRemoteVersion(gameVersion,
version, Collections.singletonList(url),
v.getTweakClass(), v.getLibraries()
));
}
}
for (Map.Entry<String, LiteLoaderVersion> entry : branch.getLiteLoader().entrySet()) {
String branchName = entry.getKey();
LiteLoaderVersion v = entry.getValue();
if ("latest".equals(branchName))
continue;
@Override
public CompletableFuture<?> refreshAsync() {
return HttpRequest.GET(downloadProvider.injectURL(LITELOADER_LIST)).getJsonAsync(LiteLoaderVersionsRoot.class)
.thenAcceptAsync(root -> {
lock.writeLock().lock();
String version = v.getVersion();
String url = repository.getUrl() + "com/mumfrey/liteloader/" + gameVersion + "/" + v.getFile();
if (snapshot) {
try {
version = version.replace("SNAPSHOT", getLatestSnapshotVersion(repository.getUrl() + "com/mumfrey/liteloader/" + v.getVersion() + "/"));
url = repository.getUrl() + "com/mumfrey/liteloader/" + v.getVersion() + "/liteloader-" + version + "-release.jar";
} catch (Exception ignore) {
try {
versions.clear();
for (Map.Entry<String, LiteLoaderGameVersions> entry : root.getVersions().entrySet()) {
String gameVersion = entry.getKey();
LiteLoaderGameVersions liteLoader = entry.getValue();
String gg = VersionNumber.normalize(gameVersion);
doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getArtifacts(), false);
doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getSnapshots(), true);
}
} finally {
lock.writeLock().unlock();
}
versions.put(key, new LiteLoaderRemoteVersion(gameVersion,
version, Collections.singletonList(url),
v.getTweakClass(), v.getLibraries()
));
}
}
};
});
}
public static final String LITELOADER_LIST = "http://dl.liteloader.com/versions/versions.json";

View File

@@ -19,14 +19,15 @@ package org.jackhuang.hmcl.download.optifine;
import com.google.gson.reflect.TypeToken;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.io.HttpRequest;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.util.*;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
/**
*
@@ -48,42 +49,33 @@ public final class OptiFineBMCLVersionList extends VersionList<OptiFineRemoteVer
}
@Override
public Task<?> refreshAsync() {
GetTask task = new GetTask(NetworkUtils.toURL(apiRoot + "/optifine/versionlist"));
return new Task<Void>() {
@Override
public Collection<Task<?>> getDependents() {
return Collections.singleton(task);
}
public CompletableFuture<?> refreshAsync() {
return HttpRequest.GET(apiRoot + "/optifine/versionlist").<List<OptiFineVersion>>getJsonAsync(new TypeToken<List<OptiFineVersion>>() {
}.getType())
.thenAcceptAsync(root -> {
lock.writeLock().lock();
@Override
public void execute() {
lock.writeLock().lock();
try {
versions.clear();
Set<String> duplicates = new HashSet<>();
for (OptiFineVersion element : root) {
String version = element.getType() + "_" + element.getPatch();
String mirror = "https://bmclapi2.bangbang93.com/optifine/" + element.getGameVersion() + "/" + element.getType() + "/" + element.getPatch();
if (!duplicates.add(mirror))
continue;
try {
versions.clear();
Set<String> duplicates = new HashSet<>();
List<OptiFineVersion> root = JsonUtils.GSON.fromJson(task.getResult(), new TypeToken<List<OptiFineVersion>>() {
}.getType());
for (OptiFineVersion element : root) {
String version = element.getType() + "_" + element.getPatch();
String mirror = "https://bmclapi2.bangbang93.com/optifine/" + element.getGameVersion() + "/" + element.getType() + "/" + element.getPatch();
if (!duplicates.add(mirror))
continue;
boolean isPre = element.getPatch() != null && (element.getPatch().startsWith("pre") || element.getPatch().startsWith("alpha"));
boolean isPre = element.getPatch() != null && (element.getPatch().startsWith("pre") || element.getPatch().startsWith("alpha"));
if (StringUtils.isBlank(element.getGameVersion()))
continue;
if (StringUtils.isBlank(element.getGameVersion()))
continue;
String gameVersion = VersionNumber.normalize(element.getGameVersion());
versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, Collections.singletonList(mirror), isPre));
String gameVersion = VersionNumber.normalize(element.getGameVersion());
versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, Collections.singletonList(mirror), isPre));
}
} finally {
lock.writeLock().unlock();
}
} finally {
lock.writeLock().unlock();
}
}
};
});
}
}

View File

@@ -28,7 +28,7 @@ import java.util.List;
public class OptiFineRemoteVersion extends RemoteVersion {
public OptiFineRemoteVersion(String gameVersion, String selfVersion, List<String> urls, boolean snapshot) {
super(LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId(), gameVersion, selfVersion, snapshot ? Type.SNAPSHOT : Type.RELEASE, urls);
super(LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId(), gameVersion, selfVersion, null, snapshot ? Type.SNAPSHOT : Type.RELEASE, urls);
}
@Override

View File

@@ -51,6 +51,8 @@ import java.util.stream.Stream;
import static org.jackhuang.hmcl.util.DigestUtils.digest;
import static org.jackhuang.hmcl.util.Hex.encodeHex;
import static org.jackhuang.hmcl.util.Lang.wrap;
import static org.jackhuang.hmcl.util.Lang.wrapConsumer;
public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
@@ -173,7 +175,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
manifest = remoteManifest.setFiles(newFiles);
return executor.all(tasks.stream().filter(Objects::nonNull).collect(Collectors.toList()));
})).thenAcceptAsync(wrap(unused1 -> {
})).thenAcceptAsync(wrapConsumer(unused1 -> {
File manifestFile = repository.getModpackConfiguration(version);
FileUtils.writeText(manifestFile, JsonUtils.GSON.toJson(
new ModpackConfiguration<>(manifest, this.configuration.getType(), this.manifest.getName(), this.manifest.getVersion(),

View File

@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,16 +20,15 @@ package org.jackhuang.hmcl.task;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
import java.util.*;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.*;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.Lang.rethrow;
import static org.jackhuang.hmcl.util.Lang.wrap;
/**
*
* @author huangyuhui
@@ -326,28 +325,6 @@ public final class AsyncTaskExecutor extends TaskExecutor {
return e;
}
private static void rethrow(Throwable e) {
if (e == null)
return;
if (e instanceof ExecutionException || e instanceof CompletionException) { // including UncheckedException and UncheckedThrowable
rethrow(e.getCause());
} else if (e instanceof RuntimeException) {
throw (RuntimeException) e;
} else {
throw new CompletionException(e);
}
}
private static Runnable wrap(ExceptionalRunnable<?> runnable) {
return () -> {
try {
runnable.run();
} catch (Exception e) {
rethrow(e);
}
};
}
private void checkCancellation() {
if (isCancelled()) {
throw new CancellationException("Cancelled by user");

View File

@@ -1,16 +1,25 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.task;
import org.jackhuang.hmcl.util.function.ExceptionalBiConsumer;
import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
public abstract class CompletableFutureTask<T> extends Task<T> {
@@ -20,59 +29,6 @@ public abstract class CompletableFutureTask<T> extends Task<T> {
public abstract CompletableFuture<T> getFuture(TaskCompletableFuture executor);
protected static Runnable wrap(ExceptionalRunnable<?> runnable) {
return () -> {
try {
runnable.run();
} catch (Exception e) {
rethrow(e);
}
};
}
protected static <T, R> Function<T, R> wrap(ExceptionalFunction<T, R, ?> fn) {
return t -> {
try {
return fn.apply(t);
} catch (Exception e) {
rethrow(e);
throw new InternalError("Unreachable code");
}
};
}
protected static <T> Consumer<T> wrap(ExceptionalConsumer<T, ?> fn) {
return t -> {
try {
fn.accept(t);
} catch (Exception e) {
rethrow(e);
}
};
}
protected static <T, E> BiConsumer<T, E> wrap(ExceptionalBiConsumer<T, E, ?> fn) {
return (t, e) -> {
try {
fn.accept(t, e);
} catch (Exception ex) {
rethrow(ex);
}
};
}
protected static void rethrow(Throwable e) {
if (e == null)
return;
if (e instanceof ExecutionException || e instanceof CompletionException) { // including UncheckedException and UncheckedThrowable
rethrow(e.getCause());
} else if (e instanceof RuntimeException) {
throw (RuntimeException) e;
} else {
throw new CompletionException(e);
}
}
protected static Throwable resolveException(Throwable e) {
if (e instanceof ExecutionException || e instanceof CompletionException)
return resolveException(e.getCause());

View File

@@ -36,6 +36,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -921,6 +922,15 @@ public abstract class Task<T> {
return task;
}
public static <T> Task<T> fromCompletableFuture(CompletableFuture<T> future) {
return new CompletableFutureTask<T>() {
@Override
public CompletableFuture<T> getFuture(TaskCompletableFuture executor) {
return future;
}
};
}
public enum TaskSignificance {
MAJOR,
MODERATE,

View File

@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,17 +17,12 @@
*/
package org.jackhuang.hmcl.util;
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
import org.jackhuang.hmcl.util.function.*;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.*;
/**
*
@@ -256,6 +251,70 @@ public final class Lang {
return t;
}
public static void rethrow(Throwable e) {
if (e == null)
return;
if (e instanceof ExecutionException || e instanceof CompletionException) { // including UncheckedException and UncheckedThrowable
rethrow(e.getCause());
} else if (e instanceof RuntimeException) {
throw (RuntimeException) e;
} else {
throw new CompletionException(e);
}
}
public static Runnable wrap(ExceptionalRunnable<?> runnable) {
return () -> {
try {
runnable.run();
} catch (Exception e) {
rethrow(e);
}
};
}
public static <T> Supplier<T> wrap(ExceptionalSupplier<T, ?> supplier) {
return () -> {
try {
return supplier.get();
} catch (Exception e) {
rethrow(e);
throw new InternalError("Unreachable code");
}
};
}
public static <T, R> Function<T, R> wrap(ExceptionalFunction<T, R, ?> fn) {
return t -> {
try {
return fn.apply(t);
} catch (Exception e) {
rethrow(e);
throw new InternalError("Unreachable code");
}
};
}
public static <T> Consumer<T> wrapConsumer(ExceptionalConsumer<T, ?> fn) {
return t -> {
try {
fn.accept(t);
} catch (Exception e) {
rethrow(e);
}
};
}
public static <T, E> BiConsumer<T, E> wrap(ExceptionalBiConsumer<T, E, ?> fn) {
return (t, e) -> {
try {
fn.accept(t, e);
} catch (Exception ex) {
rethrow(ex);
}
};
}
/**
* This is a useful function to prevent exceptions being eaten when using CompletableFuture.
* You can write:

View File

@@ -70,6 +70,11 @@ public final class SimpleMultimap<K, V> {
set.add(value);
}
public void putAll(K key, Collection<? extends V> value) {
Collection<V> set = get(key);
set.addAll(value);
}
public Collection<V> removeKey(K key) {
return map.remove(key);
}

View File

@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.util.io;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.function.ExceptionalBiConsumer;
import org.jackhuang.hmcl.util.gson.JsonUtils;
@@ -31,20 +32,22 @@ import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Lang.wrap;
import static org.jackhuang.hmcl.util.gson.JsonUtils.GSON;
import static org.jackhuang.hmcl.util.io.NetworkUtils.createHttpConnection;
import static org.jackhuang.hmcl.util.io.NetworkUtils.resolveConnection;
public abstract class HttpRequest {
protected final URL url;
protected final String url;
protected final String method;
protected final Map<String, String> headers = new HashMap<>();
protected ExceptionalBiConsumer<URL, Integer, IOException> responseCodeTester;
private HttpRequest(URL url, String method) {
private HttpRequest(String url, String method) {
this.url = url;
this.method = method;
}
@@ -64,6 +67,10 @@ public abstract class HttpRequest {
public abstract String getString() throws IOException;
public CompletableFuture<String> getStringAsync() {
return CompletableFuture.supplyAsync(wrap(this::getString), Schedulers.io());
}
public <T> T getJson(Class<T> typeOfT) throws IOException, JsonParseException {
return JsonUtils.fromNonNullJson(getString(), typeOfT);
}
@@ -72,13 +79,21 @@ public abstract class HttpRequest {
return JsonUtils.fromNonNullJson(getString(), type);
}
public <T> CompletableFuture<T> getJsonAsync(Class<T> typeOfT) {
return getStringAsync().thenApplyAsync(jsonString -> JsonUtils.fromNonNullJson(jsonString, typeOfT));
}
public <T> CompletableFuture<T> getJsonAsync(Type type) {
return getStringAsync().thenApplyAsync(jsonString -> JsonUtils.fromNonNullJson(jsonString, type));
}
public HttpRequest filter(ExceptionalBiConsumer<URL, Integer, IOException> responseCodeTester) {
this.responseCodeTester = responseCodeTester;
return this;
}
public HttpURLConnection createConnection() throws IOException {
HttpURLConnection con = createHttpConnection(url);
HttpURLConnection con = createHttpConnection(new URL(url));
con.setRequestMethod(method);
for (Map.Entry<String, String> entry : headers.entrySet()) {
con.setRequestProperty(entry.getKey(), entry.getValue());
@@ -87,7 +102,7 @@ public abstract class HttpRequest {
}
public static class HttpGetRequest extends HttpRequest {
public HttpGetRequest(URL url) {
public HttpGetRequest(String url) {
super(url, "GET");
}
@@ -101,7 +116,7 @@ public abstract class HttpRequest {
public static final class HttpPostRequest extends HttpRequest {
private byte[] bytes;
public HttpPostRequest(URL url) {
public HttpPostRequest(String url) {
super(url, "POST");
}
@@ -135,7 +150,7 @@ public abstract class HttpRequest {
con.setDoOutput(true);
if (responseCodeTester != null) {
responseCodeTester.accept(url, con.getResponseCode());
responseCodeTester.accept(new URL(url), con.getResponseCode());
}
try (OutputStream os = con.getOutputStream()) {
@@ -145,24 +160,17 @@ public abstract class HttpRequest {
}
}
public static HttpGetRequest GET(String url) throws MalformedURLException {
return GET(new URL(url));
}
@SafeVarargs
public static HttpGetRequest GET(String url, Pair<String, String>... query) throws MalformedURLException {
return GET(new URL(NetworkUtils.withQuery(url, mapOf(query))));
}
public static HttpGetRequest GET(URL url) {
public static HttpGetRequest GET(String url) {
return new HttpGetRequest(url);
}
public static HttpPostRequest POST(String url) throws MalformedURLException {
return POST(new URL(url));
@SafeVarargs
public static HttpGetRequest GET(String url, Pair<String, String>... query) {
return GET(NetworkUtils.withQuery(url, mapOf(query)));
}
public static HttpPostRequest POST(URL url) {
public static HttpPostRequest POST(String url) throws MalformedURLException {
return new HttpPostRequest(url);
}
}