Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e75ad64483 | ||
|
|
17d1af181d | ||
|
|
a72d27a431 | ||
|
|
4e9fc57aa4 | ||
|
|
494b27cc22 | ||
|
|
3606186975 | ||
|
|
41dbdfc016 | ||
|
|
655e5ca7e0 | ||
|
|
3067d323de | ||
|
|
1404a080ad | ||
|
|
cbb2a1b575 | ||
|
|
30b276ec1a | ||
|
|
13a24d5b00 |
No files matched your search
@@ -87,6 +87,8 @@ public final class Main extends Application {
|
||||
|
||||
public static void stopWithoutPlatform() {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (Controllers.getStage() == null)
|
||||
return;
|
||||
Controllers.getStage().close();
|
||||
|
||||
Logging.LOG.info("Shutting down executor services.");
|
||||
|
||||
@@ -62,7 +62,7 @@ public final class LauncherHelper {
|
||||
if (account == null)
|
||||
throw new IllegalStateException("No account");
|
||||
|
||||
Version version = repository.getVersion(selectedVersion);
|
||||
Version version = repository.getResolvedVersion(selectedVersion);
|
||||
VersionSetting setting = profile.getVersionSetting(selectedVersion);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
|
||||
@@ -530,8 +530,8 @@ public class Settings {
|
||||
}
|
||||
|
||||
private void onProfileChanged() {
|
||||
getSelectedProfile().getRepository().refreshVersionsAsync().start();
|
||||
EventBus.EVENT_BUS.fireEvent(new ProfileChangedEvent(SETTINGS, getSelectedProfile()));
|
||||
getSelectedProfile().getRepository().refreshVersionsAsync().start();
|
||||
}
|
||||
|
||||
private void profileNameChanged(ObservableValue<? extends String> observableValue, String oldValue, String newValue) {
|
||||
|
||||
@@ -487,6 +487,7 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
}
|
||||
|
||||
private void setContent(Node content, AnimationProducer animation) {
|
||||
isWizardPageNow = false;
|
||||
animationHandler.setContent(content, animation);
|
||||
|
||||
if (content instanceof Region) {
|
||||
@@ -494,11 +495,8 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
FXUtils.setOverflowHidden((Region) content, true);
|
||||
}
|
||||
|
||||
if (content instanceof Refreshable)
|
||||
refreshNavButton.setVisible(true);
|
||||
|
||||
if (content != mainPage)
|
||||
backNavButton.setVisible(true);
|
||||
refreshNavButton.setVisible(content instanceof Refreshable);
|
||||
backNavButton.setVisible(content != mainPage);
|
||||
|
||||
String prefix = category == null ? "" : category + " - ";
|
||||
|
||||
@@ -513,6 +511,7 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
|
||||
private String category;
|
||||
private Node nowPage;
|
||||
private boolean isWizardPageNow;
|
||||
|
||||
public Node getNowPage() {
|
||||
return nowPage;
|
||||
@@ -576,6 +575,7 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
contentPlaceHolder.getStyleClass().removeAll("gray-background", "white-background");
|
||||
contentPlaceHolder.getStyleClass().add("white-background");
|
||||
setContent(page, nav.getAnimation().getAnimationProducer());
|
||||
isWizardPageNow = true;
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -591,7 +591,7 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
|
||||
@FXML
|
||||
private void onBack() {
|
||||
if (wizardController.canPrev())
|
||||
if (isWizardPageNow && wizardController.canPrev())
|
||||
wizardController.onPrev(true);
|
||||
else
|
||||
onCloseNav();
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.JFXMasonryPane;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
@@ -33,7 +33,6 @@ import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.ProfileChangedEvent;
|
||||
import org.jackhuang.hmcl.event.ProfileLoadingEvent;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
|
||||
@@ -79,9 +78,11 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/main.fxml");
|
||||
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(() -> Platform.runLater(this::loadVersions));
|
||||
EventBus.EVENT_BUS.channel(ProfileLoadingEvent.class).register(this::onProfilesLoading);
|
||||
EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(this::onProfileChanged);
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> {
|
||||
if (event.getSource() == profile.getRepository())
|
||||
loadVersions((HMCLGameRepository) event.getSource());
|
||||
});
|
||||
EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(event -> this.profile = event.getProfile());
|
||||
|
||||
versionPopup = new JFXPopup(versionList);
|
||||
getChildren().remove(versionList);
|
||||
@@ -92,14 +93,15 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
FXUtils.installTooltip(btnRefresh, Main.i18n("button.refresh"));
|
||||
}
|
||||
|
||||
private Node buildNode(Profile profile, String version, String game) {
|
||||
private Node buildNode(HMCLGameRepository repository, Version version, String game) {
|
||||
String id = version.getId();
|
||||
VersionItem item = new VersionItem();
|
||||
item.setUpdate(profile.getRepository().isModpack(version));
|
||||
item.setUpdate(repository.isModpack(id));
|
||||
item.setGameVersion(game);
|
||||
item.setVersionName(version);
|
||||
item.setVersionName(id);
|
||||
|
||||
StringBuilder libraries = new StringBuilder();
|
||||
for (Library library : profile.getRepository().getVersion(version).getLibraries()) {
|
||||
for (Library library : version.getLibraries()) {
|
||||
if (library.getGroupId().equalsIgnoreCase("net.minecraftforge") && library.getArtifactId().equalsIgnoreCase("forge")) {
|
||||
libraries.append(Main.i18n("install.installer.forge")).append(": ").append(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)forge", "").replace(game, "").trim(), "-"), "-")).append("\n");
|
||||
}
|
||||
@@ -116,26 +118,26 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
if (Settings.INSTANCE.getSelectedAccount() == null)
|
||||
Controllers.dialog(Main.i18n("login.empty_username"));
|
||||
else
|
||||
LauncherHelper.INSTANCE.launch(version, null);
|
||||
LauncherHelper.INSTANCE.launch(id, null);
|
||||
});
|
||||
item.setOnScriptButtonClicked(e -> {
|
||||
if (Settings.INSTANCE.getSelectedAccount() == null)
|
||||
Controllers.dialog(Main.i18n("login.empty_username"));
|
||||
else {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setInitialDirectory(profile.getRepository().getRunDirectory(version));
|
||||
chooser.setInitialDirectory(repository.getRunDirectory(id));
|
||||
chooser.setTitle(Main.i18n("version.launch_script.save"));
|
||||
chooser.getExtensionFilters().add(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
|
||||
? new FileChooser.ExtensionFilter(Main.i18n("extension.bat"), "*.bat")
|
||||
: new FileChooser.ExtensionFilter(Main.i18n("extension.sh"), "*.sh"));
|
||||
File file = chooser.showSaveDialog(Controllers.getStage());
|
||||
if (file != null)
|
||||
LauncherHelper.INSTANCE.launch(version, file);
|
||||
LauncherHelper.INSTANCE.launch(id, file);
|
||||
}
|
||||
});
|
||||
item.setOnSettingsButtonClicked(e -> {
|
||||
Controllers.getDecorator().showPage(Controllers.getVersionPage());
|
||||
Controllers.getVersionPage().load(version, profile);
|
||||
Controllers.getVersionPage().load(id, profile);
|
||||
});
|
||||
item.setOnUpdateButtonClicked(event -> {
|
||||
FileChooser chooser = new FileChooser();
|
||||
@@ -145,7 +147,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
if (selectedFile != null) {
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(null);
|
||||
try {
|
||||
TaskExecutor executor = ModpackHelper.getUpdateTask(profile, selectedFile, version, ModpackHelper.readModpackConfiguration(profile.getRepository().getModpackConfiguration(version)))
|
||||
TaskExecutor executor = ModpackHelper.getUpdateTask(profile, selectedFile, id, ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(id)))
|
||||
.then(Task.of(Schedulers.javafx(), Controllers::closeDialog)).executor();
|
||||
pane.setExecutor(executor);
|
||||
pane.setTitle(Main.i18n("modpack.update"));
|
||||
@@ -162,41 +164,28 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
});
|
||||
item.setOnMouseClicked(event -> {
|
||||
if (event.getButton() == MouseButton.SECONDARY) {
|
||||
rightClickedVersion = version;
|
||||
rightClickedVersion = id;
|
||||
versionList.getSelectionModel().select(-1);
|
||||
versionPopup.show(item, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY());
|
||||
} else if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
||||
if (Settings.INSTANCE.getSelectedAccount() == null)
|
||||
Controllers.dialog(Main.i18n("login.empty_username"));
|
||||
else
|
||||
LauncherHelper.INSTANCE.launch(version, null);
|
||||
LauncherHelper.INSTANCE.launch(id, null);
|
||||
}
|
||||
});
|
||||
File iconFile = profile.getRepository().getVersionIcon(version);
|
||||
File iconFile = repository.getVersionIcon(id);
|
||||
if (iconFile.exists())
|
||||
item.setImage(new Image("file:" + iconFile.getAbsolutePath()));
|
||||
return item;
|
||||
}
|
||||
|
||||
public void onProfilesLoading() {
|
||||
// TODO: Profiles
|
||||
}
|
||||
|
||||
public void onProfileChanged(ProfileChangedEvent event) {
|
||||
Platform.runLater(() -> loadVersions(event.getProfile()));
|
||||
}
|
||||
|
||||
private void loadVersions() {
|
||||
loadVersions(Settings.INSTANCE.getSelectedProfile());
|
||||
}
|
||||
|
||||
private void loadVersions(Profile profile) {
|
||||
this.profile = profile;
|
||||
private void loadVersions(HMCLGameRepository repository) {
|
||||
List<Node> children = new LinkedList<>();
|
||||
for (Version version : profile.getRepository().getVersions()) {
|
||||
children.add(buildNode(profile, version.getId(), GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version.getId())).orElse("Unknown")));
|
||||
for (Version version : repository.getVersions()) {
|
||||
children.add(buildNode(repository, version, GameVersion.minecraftVersion(repository.getVersionJar(version.getId())).orElse("Unknown")));
|
||||
}
|
||||
FXUtils.resetChildren(masonryPane, children);
|
||||
JFXUtilities.runInFX(() -> FXUtils.resetChildren(masonryPane, children));
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -51,15 +51,16 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
*/
|
||||
public ProfilePage(Profile profile) {
|
||||
this.profile = profile;
|
||||
String profileDisplayName = Optional.ofNullable(profile).map(Profiles::getProfileDisplayName).orElse("");
|
||||
|
||||
title = new SimpleStringProperty(this, "title",
|
||||
profile == null ? Main.i18n("profile.new") : Main.i18n("profile") + " - " + profile.getName());
|
||||
profile == null ? Main.i18n("profile.new") : Main.i18n("profile") + " - " + profileDisplayName);
|
||||
location = new SimpleStringProperty(this, "location",
|
||||
Optional.ofNullable(profile).map(Profile::getGameDir).map(File::getAbsolutePath).orElse(""));
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/profile.fxml");
|
||||
|
||||
txtProfileName.setText(Optional.ofNullable(profile).map(Profiles::getProfileDisplayName).orElse(""));
|
||||
txtProfileName.setText(profileDisplayName);
|
||||
FXUtils.onChangeAndOperate(txtProfileName.textProperty(), it -> {
|
||||
btnSave.setDisable(!txtProfileName.validate() || StringUtils.isBlank(getLocation()));
|
||||
});
|
||||
|
||||
@@ -31,10 +31,7 @@ import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
|
||||
import org.jackhuang.hmcl.game.HMCLModpackExportTask;
|
||||
import org.jackhuang.hmcl.game.HMCLModpackInstallTask;
|
||||
import org.jackhuang.hmcl.mod.CurseCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.CurseInstallTask;
|
||||
import org.jackhuang.hmcl.mod.MinecraftInstanceTask;
|
||||
import org.jackhuang.hmcl.mod.MultiMCModpackInstallTask;
|
||||
import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
@@ -60,7 +57,7 @@ public final class TaskListPane extends StackPane {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReady(Task task) {
|
||||
public void onRunning(Task task) {
|
||||
if (!task.getSignificance().shouldShow())
|
||||
return;
|
||||
|
||||
@@ -76,6 +73,8 @@ public final class TaskListPane extends StackPane {
|
||||
task.setName(Main.i18n("install.installer.install", Main.i18n("install.installer.optifine")));
|
||||
} else if (task instanceof CurseCompletionTask) {
|
||||
task.setName(Main.i18n("modpack.type.curse.completion"));
|
||||
} else if (task instanceof ModpackInstallTask) {
|
||||
task.setName(Main.i18n("modpack.installing"));
|
||||
} else if (task instanceof CurseInstallTask) {
|
||||
task.setName(Main.i18n("modpack.install", Main.i18n("modpack.type.curse")));
|
||||
} else if (task instanceof MultiMCModpackInstallTask) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
@@ -31,6 +32,7 @@ import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.Main.i18n;
|
||||
|
||||
@@ -101,24 +103,28 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
|
||||
return Main.i18n("settings.tabs.installers");
|
||||
}
|
||||
|
||||
private String getVersion(String id) {
|
||||
return Optional.ofNullable(controller.getSettings().get(id)).map(it -> (RemoteVersion<?>) it).map(RemoteVersion::getSelfVersion).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNavigate(Map<String, Object> settings) {
|
||||
lblGameVersion.setText(Main.i18n("install.new_game.current_game_version") + ": " + provider.getGameVersion());
|
||||
btnForge.setDisable(provider.getForge() != null);
|
||||
if (provider.getForge() != null || controller.getSettings().containsKey("forge"))
|
||||
lblForge.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.forge")) + ": " + Lang.nonNull(provider.getForge(), controller.getSettings().get("forge")));
|
||||
lblForge.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.forge")) + ": " + Lang.nonNull(provider.getForge(), getVersion("forge")));
|
||||
else
|
||||
lblForge.setText(Main.i18n("install.installer.not_installed", Main.i18n("install.installer.forge")));
|
||||
|
||||
btnLiteLoader.setDisable(provider.getLiteLoader() != null);
|
||||
if (provider.getLiteLoader() != null || controller.getSettings().containsKey("liteloader"))
|
||||
lblLiteLoader.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.liteloader")) + ": " + Lang.nonNull(provider.getLiteLoader(), controller.getSettings().get("liteloader")));
|
||||
lblLiteLoader.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.liteloader")) + ": " + Lang.nonNull(provider.getLiteLoader(), getVersion("liteloader")));
|
||||
else
|
||||
lblLiteLoader.setText(Main.i18n("install.installer.not_installed", Main.i18n("install.installer.liteloader")));
|
||||
|
||||
btnOptiFine.setDisable(provider.getOptiFine() != null);
|
||||
if (provider.getOptiFine() != null || controller.getSettings().containsKey("optifine"))
|
||||
lblOptiFine.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.optifine")) + ": " + Lang.nonNull(provider.getOptiFine(), controller.getSettings().get("optifine")));
|
||||
lblOptiFine.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.optifine")) + ": " + Lang.nonNull(provider.getOptiFine(), getVersion("optifine")));
|
||||
else
|
||||
lblOptiFine.setText(Main.i18n("install.installer.not_installed", Main.i18n("install.installer.optifine")));
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
@@ -48,16 +49,16 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
GameBuilder builder = profile.getDependency().gameBuilder();
|
||||
|
||||
builder.name((String) settings.get("name"));
|
||||
builder.gameVersion((String) settings.get("game"));
|
||||
builder.gameVersion(((RemoteVersion<?>) settings.get("game")).getGameVersion());
|
||||
|
||||
if (settings.containsKey("forge"))
|
||||
builder.version("forge", (String) settings.get("forge"));
|
||||
builder.version((RemoteVersion<?>) settings.get("forge"));
|
||||
|
||||
if (settings.containsKey("liteloader"))
|
||||
builder.version("liteloader", (String) settings.get("liteloader"));
|
||||
builder.version((RemoteVersion<?>) settings.get("liteloader"));
|
||||
|
||||
if (settings.containsKey("optifine"))
|
||||
builder.version("optifine", (String) settings.get("optifine"));
|
||||
builder.version((RemoteVersion<?>) settings.get("optifine"));
|
||||
|
||||
return builder.buildAsync().finalized((a, b) -> profile.getRepository().refreshVersions());
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ public final class InstallTypePage extends StackPane implements WizardPage {
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/download/dltype.fxml");
|
||||
list.setOnMouseClicked(e -> {
|
||||
if (list.getSelectionModel().getSelectedIndex() < 0)
|
||||
return;
|
||||
controller.getSettings().put(INSTALL_TYPE, list.getSelectionModel().getSelectedIndex());
|
||||
controller.onNext();
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui.download;
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -85,13 +86,13 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
Task ret = Task.empty();
|
||||
|
||||
if (settings.containsKey("forge"))
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync(gameVersion, version, "forge", (String) settings.get("forge")));
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync(version, (RemoteVersion<?>) settings.get("forge")));
|
||||
|
||||
if (settings.containsKey("liteloader"))
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync(gameVersion, version, "liteloader", (String) settings.get("liteloader")));
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync(version, (RemoteVersion<?>) settings.get("liteloader")));
|
||||
|
||||
if (settings.containsKey("optifine"))
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync(gameVersion, version, "optifine", (String) settings.get("optifine")));
|
||||
ret = ret.then(profile.getDependency().installLibraryAsync(version, (RemoteVersion<?>) settings.get("optifine")));
|
||||
|
||||
return ret.then(profile.getRepository().refreshVersionsAsync());
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
@@ -33,6 +34,7 @@ import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class InstallersPage extends StackPane implements WizardPage {
|
||||
private final WizardController controller;
|
||||
@@ -76,7 +78,7 @@ public class InstallersPage extends StackPane implements WizardPage {
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/download/installers.fxml");
|
||||
|
||||
String gameVersion = (String) controller.getSettings().get("game");
|
||||
String gameVersion = ((RemoteVersion<?>) controller.getSettings().get("game")).getGameVersion();
|
||||
Validator hasVersion = new Validator(s -> !repository.hasVersion(s) && StringUtils.isNotBlank(s));
|
||||
hasVersion.setMessage(Main.i18n("install.new_game.already_exists"));
|
||||
txtName.getValidators().add(hasVersion);
|
||||
@@ -104,21 +106,25 @@ public class InstallersPage extends StackPane implements WizardPage {
|
||||
return Main.i18n("install.new_game");
|
||||
}
|
||||
|
||||
private String getVersion(String id) {
|
||||
return ((RemoteVersion<?>) controller.getSettings().get(id)).getSelfVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNavigate(Map<String, Object> settings) {
|
||||
lblGameVersion.setText(Main.i18n("install.new_game.current_game_version") + ": " + controller.getSettings().get("game"));
|
||||
lblGameVersion.setText(Main.i18n("install.new_game.current_game_version") + ": " + getVersion("game"));
|
||||
if (controller.getSettings().containsKey("forge"))
|
||||
lblForge.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.forge")) + ": " + controller.getSettings().get("forge"));
|
||||
lblForge.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.forge")) + ": " + getVersion("forge"));
|
||||
else
|
||||
lblForge.setText(Main.i18n("install.installer.not_installed", Main.i18n("install.installer.forge")));
|
||||
|
||||
if (controller.getSettings().containsKey("liteloader"))
|
||||
lblLiteLoader.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.liteloader")) + ": " + controller.getSettings().get("liteloader"));
|
||||
lblLiteLoader.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.liteloader")) + ": " + getVersion("liteloader"));
|
||||
else
|
||||
lblLiteLoader.setText(Main.i18n("install.installer.not_installed", Main.i18n("install.installer.liteloader")));
|
||||
|
||||
if (controller.getSettings().containsKey("optifine"))
|
||||
lblOptiFine.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.optifine")) + ": " + controller.getSettings().get("optifine"));
|
||||
lblOptiFine.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.optifine")) + ": " + getVersion("optifine"));
|
||||
else
|
||||
lblOptiFine.setText(Main.i18n("install.installer.not_installed", Main.i18n("install.installer.optifine")));
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class VersionsPage extends StackPane implements WizardPage, Refreshable {
|
||||
@@ -93,7 +94,9 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
chkOld.selectedProperty().addListener(listener);
|
||||
|
||||
list.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> {
|
||||
controller.getSettings().put(libraryId, newValue.getRemoteVersion().getSelfVersion());
|
||||
if (newValue == null)
|
||||
return;
|
||||
controller.getSettings().put(libraryId, newValue.getRemoteVersion());
|
||||
callback.run();
|
||||
});
|
||||
refresh();
|
||||
@@ -114,6 +117,7 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
}
|
||||
else return true;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.sorted()
|
||||
.map(VersionsPageItem::new).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersionTag;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@@ -47,7 +49,7 @@ public final class VersionsPageItem extends StackPane {
|
||||
private StackPane imageViewContainer;
|
||||
|
||||
public VersionsPageItem(RemoteVersion<?> remoteVersion) {
|
||||
this.remoteVersion = remoteVersion;
|
||||
this.remoteVersion = Objects.requireNonNull(remoteVersion);
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/download/versions-list-item.fxml");
|
||||
lblSelfVersion.setText(remoteVersion.getSelfVersion());
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<VBox style="-fx-padding: 8 8 0 8;">
|
||||
<Label fx:id="lblVersionName" style="-fx-font-size: 15;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
<Label fx:id="lblGameVersion" style="-fx-font-size: 11;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
<Label fx:id="lblLibraries" style="-fx-font-size: 11; -fx-text-fill: gray;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
<Label fx:id="lblLibraries" style="-fx-font-size: 10; -fx-text-fill: gray;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
</VBox>
|
||||
</StackPane>
|
||||
<StackPane fx:id="body" style="-fx-background-radius: 0 0 2 2; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;" minHeight="40" pickOnBounds="false">
|
||||
|
||||
@@ -18,9 +18,12 @@
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.download.forge.ForgeInstallTask;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.game.*;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.ParallelTask;
|
||||
@@ -80,25 +83,31 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
|
||||
@Override
|
||||
public Task installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion) {
|
||||
switch (libraryId) {
|
||||
case "forge":
|
||||
return new ForgeInstallTask(this, gameVersion, version, libraryVersion)
|
||||
.then(variables -> new LibrariesUniqueTask(variables.get("version")))
|
||||
.then(variables -> new MaintainTask(variables.get("version")))
|
||||
.then(variables -> new VersionJsonSaveTask(repository, variables.get("version")));
|
||||
case "liteloader":
|
||||
return new LiteLoaderInstallTask(this, gameVersion, version, libraryVersion)
|
||||
.then(variables -> new LibrariesUniqueTask(variables.get("version")))
|
||||
.then(variables -> new MaintainTask(variables.get("version")))
|
||||
.then(variables -> new VersionJsonSaveTask(repository, variables.get("version")));
|
||||
case "optifine":
|
||||
return new OptiFineInstallTask(this, gameVersion, version, libraryVersion)
|
||||
.then(variables -> new LibrariesUniqueTask(variables.get("version")))
|
||||
.then(variables -> new MaintainTask(variables.get("version")))
|
||||
.then(variables -> new VersionJsonSaveTask(repository, variables.get("version")));
|
||||
default:
|
||||
throw new IllegalArgumentException("Library id " + libraryId + " is unrecognized.");
|
||||
}
|
||||
VersionList<?> versionList = getVersionList(libraryId);
|
||||
return versionList.loadAsync(getDownloadProvider())
|
||||
.then(variables -> installLibraryAsync(version, versionList.getVersion(gameVersion, libraryVersion)
|
||||
.orElseThrow(() -> new IllegalStateException("Remote library " + libraryId + " has no version " + libraryVersion))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task installLibraryAsync(Version version, RemoteVersion<?> libraryVersion) {
|
||||
if (libraryVersion instanceof ForgeRemoteVersion)
|
||||
return new ForgeInstallTask(this, version, (ForgeRemoteVersion) libraryVersion)
|
||||
.then(variables -> new LibrariesUniqueTask(variables.get("version")))
|
||||
.then(variables -> new MaintainTask(variables.get("version")))
|
||||
.then(variables -> new VersionJsonSaveTask(repository, variables.get("version")));
|
||||
else if (libraryVersion instanceof LiteLoaderRemoteVersion)
|
||||
return new LiteLoaderInstallTask(this, version, (LiteLoaderRemoteVersion) libraryVersion)
|
||||
.then(variables -> new LibrariesUniqueTask(variables.get("version")))
|
||||
.then(variables -> new MaintainTask(variables.get("version")))
|
||||
.then(variables -> new VersionJsonSaveTask(repository, variables.get("version")));
|
||||
else if (libraryVersion instanceof OptiFineRemoteVersion)
|
||||
return new OptiFineInstallTask(this, version, (OptiFineRemoteVersion) libraryVersion)
|
||||
.then(variables -> new LibrariesUniqueTask(variables.get("version")))
|
||||
.then(variables -> new MaintainTask(variables.get("version")))
|
||||
.then(variables -> new VersionJsonSaveTask(repository, variables.get("version")));
|
||||
else
|
||||
throw new IllegalArgumentException("Remote library " + libraryVersion + " is unrecognized.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -52,7 +52,7 @@ public class DefaultGameBuilder extends GameBuilder {
|
||||
public Task buildAsync() {
|
||||
return new VersionJsonDownloadTask(gameVersion, dependencyManager).then(variables -> {
|
||||
Version version = Constants.GSON.fromJson(variables.<String>get(VersionJsonDownloadTask.ID), Version.class);
|
||||
version = version.setId(name).setJar(null).resolve(new SimpleVersionProvider());
|
||||
version = version.setId(name).setJar(null);
|
||||
variables.set("version", version);
|
||||
Task result = new ParallelTask(
|
||||
new GameAssetDownloadTask(dependencyManager, version),
|
||||
@@ -67,6 +67,10 @@ public class DefaultGameBuilder extends GameBuilder {
|
||||
result = result.then(libraryTaskHelper(gameVersion, "liteloader"));
|
||||
if (toolVersions.containsKey("optifine"))
|
||||
result = result.then(libraryTaskHelper(gameVersion, "optifine"));
|
||||
|
||||
for (RemoteVersion<?> remoteVersion : remoteVersions)
|
||||
result = result.then(var -> dependencyManager.installLibraryAsync(var.get("version"), remoteVersion));
|
||||
|
||||
return result;
|
||||
}).finalized((variables, isDependentsSucceeded) -> {
|
||||
if (!isDependentsSucceeded)
|
||||
|
||||
@@ -66,6 +66,16 @@ public interface DependencyManager {
|
||||
*/
|
||||
Task installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion);
|
||||
|
||||
/**
|
||||
* Install a library to a version.
|
||||
* **Note**: Installing a library may change the version.json.
|
||||
*
|
||||
* @param version the version.json.\
|
||||
* @param libraryVersion the remote version of being installed library.
|
||||
* @return the task to install the specific library.
|
||||
*/
|
||||
Task installLibraryAsync(Version version, RemoteVersion<?> libraryVersion);
|
||||
|
||||
/**
|
||||
* Get registered version list.
|
||||
*
|
||||
|
||||
@@ -19,9 +19,7 @@ package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The builder which provide a task to build Minecraft environment.
|
||||
@@ -33,6 +31,7 @@ public abstract class GameBuilder {
|
||||
protected String name = "";
|
||||
protected String gameVersion = "";
|
||||
protected final Map<String, String> toolVersions = new HashMap<>();
|
||||
protected final Set<RemoteVersion<?>> remoteVersions = new HashSet<>();
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
@@ -65,6 +64,11 @@ public abstract class GameBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public GameBuilder version(RemoteVersion<?> remoteVersion) {
|
||||
remoteVersions.add(remoteVersion);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the task that can build thw whole Minecraft environment
|
||||
*/
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
|
||||
import java.util.Objects;
|
||||
@@ -74,6 +75,15 @@ public class RemoteVersion<T> implements Comparable<RemoteVersion<T>> {
|
||||
return selfVersion.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("selfVersion", selfVersion)
|
||||
.append("gameVersion", gameVersion)
|
||||
.append("tag", tag)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(RemoteVersion<T> o) {
|
||||
// newer versions are smaller than older versions
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.SimpleMultimap;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* The remote version list.
|
||||
@@ -45,15 +46,36 @@ public abstract class VersionList<T> {
|
||||
return !versions.isEmpty();
|
||||
}
|
||||
|
||||
protected final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
|
||||
/**
|
||||
* @param downloadProvider DownloadProvider
|
||||
* @return the task to reload the remote version list.
|
||||
*/
|
||||
public abstract Task refreshAsync(DownloadProvider downloadProvider);
|
||||
|
||||
public Task loadAsync(DownloadProvider downloadProvider) {
|
||||
return Task.ofThen(variables -> {
|
||||
lock.readLock().lock();
|
||||
boolean loaded;
|
||||
|
||||
try {
|
||||
loaded = isLoaded();
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
return loaded ? null : refreshAsync(downloadProvider);
|
||||
});
|
||||
}
|
||||
|
||||
private Collection<RemoteVersion<T>> getVersionsImpl(String gameVersion) {
|
||||
Collection<RemoteVersion<T>> ans = versions.get(gameVersion);
|
||||
return ans.isEmpty() ? versions.values() : ans;
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
Collection<RemoteVersion<T>> ans = versions.get(gameVersion);
|
||||
return ans.isEmpty() ? versions.values() : ans;
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,10 +96,15 @@ public abstract class VersionList<T> {
|
||||
* @return the specific remote version, null if it is not found.
|
||||
*/
|
||||
public final Optional<RemoteVersion<T>> getVersion(String gameVersion, String remoteVersion) {
|
||||
RemoteVersion<T> result = null;
|
||||
for (RemoteVersion<T> it : versions.get(gameVersion))
|
||||
if (remoteVersion.equals(it.getSelfVersion()))
|
||||
result = it;
|
||||
return Optional.ofNullable(result);
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
RemoteVersion<T> result = null;
|
||||
for (RemoteVersion<T> it : versions.get(gameVersion))
|
||||
if (remoteVersion.equals(it.getSelfVersion()))
|
||||
result = it;
|
||||
return Optional.ofNullable(result);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,6 @@
|
||||
package org.jackhuang.hmcl.download.forge;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.SimpleVersionProvider;
|
||||
@@ -47,34 +45,22 @@ import java.util.zip.ZipFile;
|
||||
public final class ForgeInstallTask extends TaskResult<Version> {
|
||||
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final String gameVersion;
|
||||
private final Version version;
|
||||
private final String remoteVersion;
|
||||
private final VersionList<?> forgeVersionList;
|
||||
private final File installer = new File("forge-installer.jar").getAbsoluteFile();
|
||||
private RemoteVersion<?> remote;
|
||||
private ForgeRemoteVersion remote;
|
||||
private final List<Task> dependents = new LinkedList<>();
|
||||
private final List<Task> dependencies = new LinkedList<>();
|
||||
|
||||
private Task downloadFileTask() {
|
||||
remote = forgeVersionList.getVersion(gameVersion, remoteVersion)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Remote forge version " + gameVersion + ", " + remoteVersion + " not found"));
|
||||
return new FileDownloadTask(NetworkUtils.toURL(remote.getUrl()), installer);
|
||||
}
|
||||
|
||||
public ForgeInstallTask(DefaultDependencyManager dependencyManager, String gameVersion, Version version, String remoteVersion) {
|
||||
public ForgeInstallTask(DefaultDependencyManager dependencyManager, Version version, ForgeRemoteVersion remoteVersion) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.gameVersion = gameVersion;
|
||||
this.version = version;
|
||||
this.remoteVersion = remoteVersion;
|
||||
this.remote = remoteVersion;
|
||||
|
||||
forgeVersionList = dependencyManager.getVersionList("forge");
|
||||
|
||||
if (!forgeVersionList.isLoaded())
|
||||
dependents.add(forgeVersionList.refreshAsync(dependencyManager.getDownloadProvider())
|
||||
.then(s -> downloadFileTask()));
|
||||
else
|
||||
dependents.add(downloadFileTask());
|
||||
dependents.add(downloadFileTask());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* 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 {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.forge;
|
||||
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
|
||||
public class ForgeRemoteVersion extends RemoteVersion<Void> {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param gameVersion the Minecraft version that this remote version suits.
|
||||
* @param selfVersion the version string of the remote version.
|
||||
* @param url the installer or universal jar URL.
|
||||
*/
|
||||
public ForgeRemoteVersion(String gameVersion, String selfVersion, String url) {
|
||||
super(gameVersion, selfVersion, url, null);
|
||||
}
|
||||
}
|
||||
@@ -53,34 +53,40 @@ public final class ForgeVersionList extends VersionList<Void> {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
ForgeVersionRoot root = Constants.GSON.fromJson(task.getResult(), ForgeVersionRoot.class);
|
||||
if (root == null)
|
||||
return;
|
||||
versions.clear();
|
||||
lock.writeLock().lock();
|
||||
|
||||
for (Map.Entry<String, int[]> entry : root.getGameVersions().entrySet()) {
|
||||
Optional<String> gameVersion = VersionNumber.parseVersion(entry.getKey());
|
||||
if (!gameVersion.isPresent())
|
||||
continue;
|
||||
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 = downloadProvider.injectURL(root.getWebPath() + classifier + "/" + fileName);
|
||||
}
|
||||
try {
|
||||
ForgeVersionRoot root = Constants.GSON.fromJson(task.getResult(), ForgeVersionRoot.class);
|
||||
if (root == null)
|
||||
return;
|
||||
versions.clear();
|
||||
|
||||
if (jar == null)
|
||||
for (Map.Entry<String, int[]> entry : root.getGameVersions().entrySet()) {
|
||||
Optional<String> gameVersion = VersionNumber.parseVersion(entry.getKey());
|
||||
if (!gameVersion.isPresent())
|
||||
continue;
|
||||
versions.put(gameVersion.get(), new RemoteVersion<>(
|
||||
version.getGameVersion(), version.getVersion(), jar, null
|
||||
));
|
||||
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 = downloadProvider.injectURL(root.getWebPath() + classifier + "/" + fileName);
|
||||
}
|
||||
|
||||
if (jar == null)
|
||||
continue;
|
||||
versions.put(gameVersion.get(), new ForgeRemoteVersion(
|
||||
version.getGameVersion(), version.getVersion(), jar
|
||||
));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ public final class GameAssetDownloadTask extends Task {
|
||||
*/
|
||||
public GameAssetDownloadTask(AbstractDependencyManager dependencyManager, Version version) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.version = version.requireResolved();
|
||||
this.version = version;
|
||||
this.refreshTask = new GameAssetRefreshTask(dependencyManager, version);
|
||||
this.dependents.add(refreshTask);
|
||||
}
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ public final class GameAssetIndexDownloadTask extends Task {
|
||||
*/
|
||||
public GameAssetIndexDownloadTask(AbstractDependencyManager dependencyManager, Version version) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.version = version.requireResolved();
|
||||
this.version = version;
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ public final class GameAssetRefreshTask extends TaskResult<Collection<Pair<File,
|
||||
*/
|
||||
public GameAssetRefreshTask(AbstractDependencyManager dependencyManager, Version version) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.version = version.requireResolved();
|
||||
this.version = version;
|
||||
this.assetIndexInfo = version.getAssetIndex();
|
||||
this.assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId());
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ public final class GameLibrariesTask extends Task {
|
||||
*/
|
||||
public GameLibrariesTask(AbstractDependencyManager dependencyManager, Version version) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.version = version.requireResolved();
|
||||
this.version = version;
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
}
|
||||
|
||||
@@ -62,9 +62,7 @@ public final class GameLibrariesTask extends Task {
|
||||
version.getLibraries().stream().filter(Library::appliesToCurrentEnvironment).forEach(library -> {
|
||||
File file = dependencyManager.getGameRepository().getLibraryFile(version, library);
|
||||
if (!file.exists())
|
||||
dependencies.add(new FileDownloadTask(
|
||||
NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(library.getDownload().getUrl())),
|
||||
file, dependencyManager.getProxy(), library.getDownload().getSha1()));
|
||||
dependencies.add(new LibraryDownloadTask(dependencyManager, file, library));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ public final class GameLoggingDownloadTask extends Task {
|
||||
*/
|
||||
public GameLoggingDownloadTask(DependencyManager dependencyManager, Version version) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.version = version.requireResolved();
|
||||
this.version = version;
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,16 +50,22 @@ public final class GameVersionList extends VersionList<GameRemoteVersionTag> {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
versions.clear();
|
||||
lock.writeLock().lock();
|
||||
|
||||
GameRemoteVersions root = Constants.GSON.fromJson(task.getResult(), GameRemoteVersions.class);
|
||||
for (GameRemoteVersion remoteVersion : root.getVersions()) {
|
||||
versions.put(remoteVersion.getGameVersion(), new RemoteVersionGame(
|
||||
remoteVersion.getGameVersion(),
|
||||
remoteVersion.getGameVersion(),
|
||||
remoteVersion.getUrl(),
|
||||
new GameRemoteVersionTag(remoteVersion.getType(), remoteVersion.getReleaseTime()))
|
||||
);
|
||||
try {
|
||||
versions.clear();
|
||||
|
||||
GameRemoteVersions root = Constants.GSON.fromJson(task.getResult(), GameRemoteVersions.class);
|
||||
for (GameRemoteVersion remoteVersion : root.getVersions()) {
|
||||
versions.put(remoteVersion.getGameVersion(), new RemoteVersionGame(
|
||||
remoteVersion.getGameVersion(),
|
||||
remoteVersion.getGameVersion(),
|
||||
remoteVersion.getUrl(),
|
||||
new GameRemoteVersionTag(remoteVersion.getType(), remoteVersion.getReleaseTime()))
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.download.game;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.SimpleMultimap;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
|
||||
@@ -49,6 +50,7 @@ public class LibrariesUniqueTask extends TaskResult<Version> {
|
||||
for (Library library : libraries) {
|
||||
String id = library.getGroupId() + ":" + library.getArtifactId();
|
||||
VersionNumber number = VersionNumber.asVersion(library.getVersion());
|
||||
String serialized = Constants.GSON.toJson(library);
|
||||
|
||||
if (versionMap.containsKey(id)) {
|
||||
VersionNumber otherNumber = versionMap.get(id);
|
||||
@@ -56,8 +58,23 @@ public class LibrariesUniqueTask extends TaskResult<Version> {
|
||||
multimap.removeKey(id);
|
||||
versionMap.put(id, number);
|
||||
multimap.put(id, library);
|
||||
} else if (number.compareTo(otherNumber) == 0) {
|
||||
multimap.put(id, library);
|
||||
} else if (number.compareTo(otherNumber) == 0) { // same library id.
|
||||
boolean flag = false;
|
||||
// prevent from duplicated libraries
|
||||
for (Library otherLibrary : multimap.get(id))
|
||||
if (library.equals(otherLibrary)) {
|
||||
String otherSerialized = Constants.GSON.toJson(otherLibrary);
|
||||
// A trick, the library that has more information is better, which can be
|
||||
// considered whose serialized JSON text will be longer.
|
||||
if (serialized.length() <= otherSerialized.length()) {
|
||||
flag = true;
|
||||
break;
|
||||
} else {
|
||||
multimap.removeValue(id, otherLibrary);
|
||||
}
|
||||
}
|
||||
if (!flag)
|
||||
multimap.put(id, library);
|
||||
}
|
||||
} else {
|
||||
versionMap.put(id, number);
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
package org.jackhuang.hmcl.download.game;
|
||||
|
||||
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
|
||||
import org.jackhuang.hmcl.download.AbstractDependencyManager;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Scheduler;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarInputStream;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Pack200;
|
||||
|
||||
public final class LibraryDownloadTask extends Task {
|
||||
private final FileDownloadTask xzTask;
|
||||
private final FileDownloadTask task;
|
||||
private final File jar;
|
||||
private final File xzFile;
|
||||
private final Library library;
|
||||
|
||||
private boolean downloaded = false;
|
||||
|
||||
public LibraryDownloadTask(AbstractDependencyManager dependencyManager, File file, Library library) {
|
||||
if (library.is("net.minecraftforge", "forge"))
|
||||
library = library.setClassifier("universal");
|
||||
|
||||
this.library = library;
|
||||
|
||||
String url = dependencyManager.getDownloadProvider().injectURL(library.getDownload().getUrl());
|
||||
jar = file;
|
||||
|
||||
xzFile = new File(file.getAbsoluteFile().getParentFile(), file.getName() + ".pack.xz");
|
||||
|
||||
xzTask = new FileDownloadTask(NetworkUtils.toURL(url + ".pack.xz"),
|
||||
xzFile, dependencyManager.getProxy(), null, 1);
|
||||
xzTask.setSignificance(TaskSignificance.MINOR);
|
||||
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
|
||||
task = new FileDownloadTask(NetworkUtils.toURL(url),
|
||||
file, dependencyManager.getProxy(), library.getDownload().getSha1());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task> getDependents() {
|
||||
return library.getChecksums() != null ? Collections.singleton(xzTask) : Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelyingOnDependents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
if (isDependentsSucceeded() && library.getChecksums() != null) {
|
||||
unpackLibrary(jar, FileUtils.readBytes(xzFile));
|
||||
if (!checksumValid(jar, library.getChecksums()))
|
||||
throw new IOException("Checksum failed for " + library);
|
||||
|
||||
downloaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task> getDependencies() {
|
||||
return downloaded ? Collections.emptySet() : Collections.singleton(task);
|
||||
}
|
||||
|
||||
private static boolean checksumValid(File libPath, List<String> checksums) {
|
||||
try {
|
||||
if ((checksums == null) || (checksums.isEmpty())) {
|
||||
return true;
|
||||
}
|
||||
byte[] fileData = FileUtils.readBytes(libPath);
|
||||
boolean valid = checksums.contains(DigestUtils.sha1Hex(fileData));
|
||||
if ((!valid) && (libPath.getName().endsWith(".jar"))) {
|
||||
}
|
||||
return validateJar(fileData, checksums);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean validateJar(byte[] data, List<String> checksums) throws IOException {
|
||||
HashMap<String, String> files = new HashMap<>();
|
||||
String[] hashes = null;
|
||||
JarInputStream jar = new JarInputStream(new ByteArrayInputStream(data));
|
||||
JarEntry entry = jar.getNextJarEntry();
|
||||
while (entry != null) {
|
||||
byte[] eData = IOUtils.readFullyAsByteArray(jar);
|
||||
if (entry.getName().equals("checksums.sha1")) {
|
||||
hashes = new String(eData, Charset.forName("UTF-8")).split("\n");
|
||||
}
|
||||
if (!entry.isDirectory()) {
|
||||
files.put(entry.getName(), DigestUtils.sha1Hex(eData));
|
||||
}
|
||||
entry = jar.getNextJarEntry();
|
||||
}
|
||||
jar.close();
|
||||
if (hashes != null) {
|
||||
boolean passed = !checksums.contains(files.get("checksums.sha1"));
|
||||
if (passed) {
|
||||
for (String hash : hashes) {
|
||||
if ((!hash.trim().equals("")) && (hash.contains(" "))) {
|
||||
String[] e = hash.split(" ");
|
||||
String validChecksum = e[0];
|
||||
String target = hash.substring(validChecksum.length() + 1);
|
||||
String checksum = files.get(target);
|
||||
if ((!files.containsKey(target)) || (checksum == null)) {
|
||||
Logging.LOG.warning(" " + target + " : missing");
|
||||
passed = false;
|
||||
break;
|
||||
} else if (!checksum.equals(validChecksum)) {
|
||||
Logging.LOG.warning(" " + target + " : failed (" + checksum + ", " + validChecksum + ")");
|
||||
passed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return passed;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void unpackLibrary(File dest, byte[] src) throws IOException {
|
||||
if (dest.exists())
|
||||
if (!dest.delete())
|
||||
throw new IOException("Unable to delete file " + dest);
|
||||
|
||||
byte[] decompressed = IOUtils.readFullyAsByteArray(new XZCompressorInputStream(new ByteArrayInputStream(src)));
|
||||
|
||||
String end = new String(decompressed, decompressed.length - 4, 4);
|
||||
if (!end.equals("SIGN"))
|
||||
throw new IOException("Unpacking failed, signature missing " + end);
|
||||
|
||||
int x = decompressed.length;
|
||||
int len = decompressed[(x - 8)] & 0xFF | (decompressed[(x - 7)] & 0xFF) << 8 | (decompressed[(x - 6)] & 0xFF) << 16 | (decompressed[(x - 5)] & 0xFF) << 24;
|
||||
|
||||
File temp = FileUtils.createTempFile("minecraft", ".pack");
|
||||
|
||||
byte[] checksums = Arrays.copyOfRange(decompressed, decompressed.length - len - 8, decompressed.length - 8);
|
||||
|
||||
OutputStream out = new FileOutputStream(temp);
|
||||
out.write(decompressed, 0, decompressed.length - len - 8);
|
||||
out.close();
|
||||
|
||||
try (FileOutputStream jarBytes = new FileOutputStream(dest); JarOutputStream jos = new JarOutputStream(jarBytes)) {
|
||||
Pack200.newUnpacker().unpack(temp, jos);
|
||||
|
||||
JarEntry checksumsFile = new JarEntry("checksums.sha1");
|
||||
checksumsFile.setTime(0L);
|
||||
jos.putNextEntry(checksumsFile);
|
||||
jos.write(checksums);
|
||||
jos.closeEntry();
|
||||
}
|
||||
|
||||
temp.delete();
|
||||
}
|
||||
}
|
||||
@@ -40,11 +40,11 @@ public final class VersionJsonSaveTask extends Task {
|
||||
* Constructor.
|
||||
*
|
||||
* @param repository the game repository
|
||||
* @param version the **resolved** version
|
||||
* @param version the game version
|
||||
*/
|
||||
public VersionJsonSaveTask(DefaultGameRepository repository, Version version) {
|
||||
this.repository = repository;
|
||||
this.version = version.requireResolved();
|
||||
this.version = version;
|
||||
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
}
|
||||
|
||||
+3
-21
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.download.liteloader;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
|
||||
import org.jackhuang.hmcl.game.LibrariesDownloadInfo;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
@@ -41,32 +40,15 @@ import java.util.List;
|
||||
public final class LiteLoaderInstallTask extends TaskResult<Version> {
|
||||
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final String gameVersion;
|
||||
private final Version version;
|
||||
private final String remoteVersion;
|
||||
private final LiteLoaderVersionList liteLoaderVersionList;
|
||||
private RemoteVersion<LiteLoaderRemoteVersionTag> remote;
|
||||
private LiteLoaderRemoteVersion remote;
|
||||
private final List<Task> dependents = new LinkedList<>();
|
||||
private final List<Task> dependencies = new LinkedList<>();
|
||||
|
||||
private void doRemote() {
|
||||
remote = liteLoaderVersionList.getVersion(gameVersion, remoteVersion)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Remote LiteLoader version " + gameVersion + ", " + remoteVersion + " not found"));
|
||||
}
|
||||
|
||||
public LiteLoaderInstallTask(DefaultDependencyManager dependencyManager, String gameVersion, Version version, String remoteVersion) {
|
||||
public LiteLoaderInstallTask(DefaultDependencyManager dependencyManager, Version version, LiteLoaderRemoteVersion remoteVersion) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.gameVersion = gameVersion;
|
||||
this.version = version;
|
||||
this.remoteVersion = remoteVersion;
|
||||
|
||||
liteLoaderVersionList = (LiteLoaderVersionList) dependencyManager.getVersionList("liteloader");
|
||||
|
||||
if (!liteLoaderVersionList.isLoaded())
|
||||
dependents.add(liteLoaderVersionList.refreshAsync(dependencyManager.getDownloadProvider())
|
||||
.then(Task.of(this::doRemote)));
|
||||
else
|
||||
doRemote();
|
||||
this.remote = remoteVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* 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 {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.liteloader;
|
||||
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class LiteLoaderRemoteVersion extends RemoteVersion<LiteLoaderRemoteVersionTag> {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param gameVersion the Minecraft version that this remote version suits.
|
||||
* @param selfVersion the version string of the remote version.
|
||||
* @param url the installer or universal jar URL.
|
||||
*/
|
||||
LiteLoaderRemoteVersion(String gameVersion, String selfVersion, String url, String tweakClass, Collection<Library> libraries) {
|
||||
super(gameVersion, selfVersion, url, new LiteLoaderRemoteVersionTag(tweakClass, libraries));
|
||||
}
|
||||
}
|
||||
+18
-12
@@ -58,17 +58,23 @@ public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVer
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
LiteLoaderVersionsRoot root = Constants.GSON.fromJson(task.getResult(), LiteLoaderVersionsRoot.class);
|
||||
versions.clear();
|
||||
lock.writeLock().lock();
|
||||
|
||||
for (Map.Entry<String, LiteLoaderGameVersions> entry : root.getVersions().entrySet()) {
|
||||
String gameVersion = entry.getKey();
|
||||
LiteLoaderGameVersions liteLoader = entry.getValue();
|
||||
Optional<String> gg = VersionNumber.parseVersion(gameVersion);
|
||||
if (!gg.isPresent())
|
||||
continue;
|
||||
doBranch(gg.get(), gameVersion, liteLoader.getRepoitory(), liteLoader.getArtifacts(), false);
|
||||
doBranch(gg.get(), gameVersion, liteLoader.getRepoitory(), liteLoader.getSnapshots(), true);
|
||||
try {
|
||||
LiteLoaderVersionsRoot root = Constants.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();
|
||||
Optional<String> gg = VersionNumber.parseVersion(gameVersion);
|
||||
if (!gg.isPresent())
|
||||
continue;
|
||||
doBranch(gg.get(), gameVersion, liteLoader.getRepoitory(), liteLoader.getArtifacts(), false);
|
||||
doBranch(gg.get(), gameVersion, liteLoader.getRepoitory(), liteLoader.getSnapshots(), true);
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,9 +98,9 @@ public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVer
|
||||
}
|
||||
}
|
||||
|
||||
versions.put(key, new RemoteVersion<>(gameVersion,
|
||||
versions.put(key, new LiteLoaderRemoteVersion(gameVersion,
|
||||
version, downloadProvider.injectURL(url),
|
||||
new LiteLoaderRemoteVersionTag(v.getTweakClass(), v.getLibraries())
|
||||
v.getTweakClass(), v.getLibraries()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ public final class OptiFineBMCLVersionList extends VersionList<Void> {
|
||||
if (StringUtils.isBlank(element.getGameVersion()))
|
||||
continue;
|
||||
VersionNumber.parseVersion(element.getGameVersion())
|
||||
.ifPresent(gameVersion -> versions.put(gameVersion, new RemoteVersion<>(gameVersion, version, mirror, null)));
|
||||
.ifPresent(gameVersion -> versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, () -> mirror)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+5
-22
@@ -18,8 +18,6 @@
|
||||
package org.jackhuang.hmcl.download.optifine;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -39,32 +37,15 @@ import java.util.List;
|
||||
public final class OptiFineInstallTask extends TaskResult<Version> {
|
||||
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final String gameVersion;
|
||||
private final Version version;
|
||||
private final String remoteVersion;
|
||||
private final VersionList<?> optiFineVersionList;
|
||||
private RemoteVersion<?> remote;
|
||||
private OptiFineRemoteVersion remote;
|
||||
private final List<Task> dependents = new LinkedList<>();
|
||||
private final List<Task> dependencies = new LinkedList<>();
|
||||
|
||||
private void doRemote() {
|
||||
remote = optiFineVersionList.getVersion(gameVersion, remoteVersion)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Remote OptiFine version " + gameVersion + ", " + remoteVersion + " not found"));
|
||||
}
|
||||
|
||||
public OptiFineInstallTask(DefaultDependencyManager dependencyManager, String gameVersion, Version version, String remoteVersion) {
|
||||
public OptiFineInstallTask(DefaultDependencyManager dependencyManager, Version version, OptiFineRemoteVersion remoteVersion) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.gameVersion = gameVersion;
|
||||
this.version = version;
|
||||
this.remoteVersion = remoteVersion;
|
||||
|
||||
optiFineVersionList = dependencyManager.getVersionList("optifine");
|
||||
|
||||
if (!optiFineVersionList.isLoaded())
|
||||
dependents.add(optiFineVersionList.refreshAsync(dependencyManager.getDownloadProvider())
|
||||
.then(Task.of(this::doRemote)));
|
||||
else
|
||||
doRemote();
|
||||
this.remote = remoteVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,6 +70,8 @@ public final class OptiFineInstallTask extends TaskResult<Version> {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
String remoteVersion = remote.getSelfVersion();
|
||||
|
||||
Library library = new Library(
|
||||
"net.optifine", "optifine", remoteVersion, null, null,
|
||||
new LibrariesDownloadInfo(new LibraryDownloadInfo(
|
||||
|
||||
+2
-2
@@ -24,8 +24,8 @@ import java.util.function.Supplier;
|
||||
public class OptiFineRemoteVersion extends RemoteVersion<Void> {
|
||||
private final Supplier<String> url;
|
||||
|
||||
public OptiFineRemoteVersion(String gameVersion, String selfVersion, Supplier<String> url, Void tag) {
|
||||
super(gameVersion, selfVersion, "", tag);
|
||||
public OptiFineRemoteVersion(String gameVersion, String selfVersion, Supplier<String> url) {
|
||||
super(gameVersion, selfVersion, "", null);
|
||||
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
+38
-32
@@ -61,48 +61,54 @@ public final class OptiFineVersionList extends VersionList<Void> {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
versions.clear();
|
||||
lock.writeLock().lock();
|
||||
|
||||
String html = task.getResult().replace(" ", " ").replace(">", ">").replace("<", "<").replace("<br>", "<br />");
|
||||
try {
|
||||
versions.clear();
|
||||
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder db = factory.newDocumentBuilder();
|
||||
Document doc = db.parse(new ByteArrayInputStream(html.getBytes("UTF-8")));
|
||||
Element r = doc.getDocumentElement();
|
||||
NodeList tables = r.getElementsByTagName("table");
|
||||
for (int i = 0; i < tables.getLength(); i++) {
|
||||
Element e = (Element) tables.item(i);
|
||||
if ("downloadTable".equals(e.getAttribute("class"))) {
|
||||
NodeList tr = e.getElementsByTagName("tr");
|
||||
for (int k = 0; k < tr.getLength(); k++) {
|
||||
NodeList downloadLine = ((Element) tr.item(k)).getElementsByTagName("td");
|
||||
String url = null, version = null, gameVersion = null;
|
||||
for (int j = 0; j < downloadLine.getLength(); j++) {
|
||||
Element td = (Element) downloadLine.item(j);
|
||||
if (td.getAttribute("class") != null && td.getAttribute("class").startsWith("downloadLineMirror"))
|
||||
url = ((Element) td.getElementsByTagName("a").item(0)).getAttribute("href");
|
||||
if (td.getAttribute("class") != null && td.getAttribute("class").startsWith("downloadLineFile"))
|
||||
version = td.getTextContent();
|
||||
String html = task.getResult().replace(" ", " ").replace(">", ">").replace("<", "<").replace("<br>", "<br />");
|
||||
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder db = factory.newDocumentBuilder();
|
||||
Document doc = db.parse(new ByteArrayInputStream(html.getBytes("UTF-8")));
|
||||
Element r = doc.getDocumentElement();
|
||||
NodeList tables = r.getElementsByTagName("table");
|
||||
for (int i = 0; i < tables.getLength(); i++) {
|
||||
Element e = (Element) tables.item(i);
|
||||
if ("downloadTable".equals(e.getAttribute("class"))) {
|
||||
NodeList tr = e.getElementsByTagName("tr");
|
||||
for (int k = 0; k < tr.getLength(); k++) {
|
||||
NodeList downloadLine = ((Element) tr.item(k)).getElementsByTagName("td");
|
||||
String url = null, version = null, gameVersion = null;
|
||||
for (int j = 0; j < downloadLine.getLength(); j++) {
|
||||
Element td = (Element) downloadLine.item(j);
|
||||
if (td.getAttribute("class") != null && td.getAttribute("class").startsWith("downloadLineMirror"))
|
||||
url = ((Element) td.getElementsByTagName("a").item(0)).getAttribute("href");
|
||||
if (td.getAttribute("class") != null && td.getAttribute("class").startsWith("downloadLineFile"))
|
||||
version = td.getTextContent();
|
||||
}
|
||||
if (version == null || url == null)
|
||||
continue;
|
||||
|
||||
Matcher matcher = PATTERN.matcher(version);
|
||||
while (matcher.find())
|
||||
gameVersion = matcher.group(1);
|
||||
if (gameVersion == null)
|
||||
continue;
|
||||
|
||||
String finalURL = url;
|
||||
versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, Lang.hideException(() -> getLink(finalURL))));
|
||||
}
|
||||
if (version == null || url == null)
|
||||
continue;
|
||||
|
||||
Matcher matcher = PATTERN.matcher(version);
|
||||
while (matcher.find())
|
||||
gameVersion = matcher.group(1);
|
||||
if (gameVersion == null)
|
||||
continue;
|
||||
|
||||
String finalURL = url;
|
||||
versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, Lang.hideException(() -> getLink(finalURL)), null));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static String getLink(String url) throws IOException {
|
||||
private static String getLink(String url) throws IOException {
|
||||
String result = null;
|
||||
String content = NetworkUtils.doGet(NetworkUtils.toURL(url));
|
||||
Matcher m = LINK_PATTERN.matcher(content);
|
||||
|
||||
@@ -40,7 +40,7 @@ public class ClassicVersion extends Version {
|
||||
public ClassicLibrary(String name) {
|
||||
super("", "", "", null, null,
|
||||
new LibrariesDownloadInfo(new LibraryDownloadInfo("bin/" + name + ".jar"), null),
|
||||
false, null, null, null);
|
||||
false, null, null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ public class Library implements Comparable<Library> {
|
||||
private final boolean lateload;
|
||||
private final Map<OperatingSystem, String> natives;
|
||||
private final List<CompatibilityRule> rules;
|
||||
private final List<String> checksums;
|
||||
|
||||
private final String path;
|
||||
|
||||
@@ -60,10 +61,10 @@ public class Library implements Comparable<Library> {
|
||||
}
|
||||
|
||||
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads, boolean lateload) {
|
||||
this(groupId, artifactId, version, classifier, url, downloads, lateload, null, null, null);
|
||||
this(groupId, artifactId, version, classifier, url, downloads, lateload, null, null, null, null);
|
||||
}
|
||||
|
||||
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads, boolean lateload, ExtractRules extract, Map<OperatingSystem, String> natives, List<CompatibilityRule> rules) {
|
||||
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads, boolean lateload, List<String> checksums, ExtractRules extract, Map<OperatingSystem, String> natives, List<CompatibilityRule> rules) {
|
||||
this.groupId = groupId;
|
||||
this.artifactId = artifactId;
|
||||
this.version = version;
|
||||
@@ -80,6 +81,7 @@ public class Library implements Comparable<Library> {
|
||||
this.lateload = lateload;
|
||||
this.natives = natives;
|
||||
this.rules = rules;
|
||||
this.checksums = checksums;
|
||||
|
||||
LibraryDownloadInfo temp = null;
|
||||
if (downloads != null)
|
||||
@@ -141,6 +143,14 @@ public class Library implements Comparable<Library> {
|
||||
return lateload;
|
||||
}
|
||||
|
||||
public List<String> getChecksums() {
|
||||
return checksums;
|
||||
}
|
||||
|
||||
public boolean is(String groupId, String artifactId) {
|
||||
return this.groupId.equals(groupId) && this.artifactId.equals(artifactId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this).append("name", getName()).toString();
|
||||
@@ -168,16 +178,20 @@ public class Library implements Comparable<Library> {
|
||||
return Objects.hash(getName(), isNative());
|
||||
}
|
||||
|
||||
public static Library fromName(String name) {
|
||||
return fromName(name, null, null, null, null, null);
|
||||
public Library setClassifier(String classifier) {
|
||||
return new Library(groupId, artifactId, version, classifier, url, downloads, lateload, checksums, extract, natives, rules);
|
||||
}
|
||||
|
||||
public static Library fromName(String name, String url, LibrariesDownloadInfo downloads, ExtractRules extract, Map<OperatingSystem, String> natives, List<CompatibilityRule> rules) {
|
||||
public static Library fromName(String name) {
|
||||
return fromName(name, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public static Library fromName(String name, String url, LibrariesDownloadInfo downloads, List<String> checksums, ExtractRules extract, Map<OperatingSystem, String> natives, List<CompatibilityRule> rules) {
|
||||
String[] arr = name.split(":", 4);
|
||||
if (arr.length != 3 && arr.length != 4)
|
||||
throw new IllegalArgumentException("Library name is malformed. Correct example: group:artifact:version(:classifier).");
|
||||
|
||||
return new Library(arr[0].replace("\\", "/"), arr[1], arr[2], arr.length >= 4 ? arr[3] : null, url, downloads, false, extract, natives, rules);
|
||||
return new Library(arr[0].replace("\\", "/"), arr[1], arr[2], arr.length >= 4 ? arr[3] : null, url, downloads, false, checksums, extract, natives, rules);
|
||||
}
|
||||
|
||||
public static class Serializer implements JsonDeserializer<Library>, JsonSerializer<Library> {
|
||||
@@ -198,6 +212,8 @@ public class Library implements Comparable<Library> {
|
||||
jsonObject.get("name").getAsString(),
|
||||
jsonObject.has("url") ? jsonObject.get("url").getAsString() : null,
|
||||
context.deserialize(jsonObject.get("downloads"), LibrariesDownloadInfo.class),
|
||||
context.deserialize(jsonObject.get("checksums"), new TypeToken<List<String>>() {
|
||||
}.getType()),
|
||||
context.deserialize(jsonObject.get("extract"), ExtractRules.class),
|
||||
context.deserialize(jsonObject.get("natives"), new TypeToken<Map<OperatingSystem, String>>() {
|
||||
}.getType()),
|
||||
|
||||
@@ -143,13 +143,6 @@ public class Version implements Comparable<Version>, Validation {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Version requireResolved() {
|
||||
if (!resolved)
|
||||
throw new RuntimeException("Version not resolved");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve given version
|
||||
*/
|
||||
|
||||
@@ -71,7 +71,8 @@ final class Log4jHandler extends Thread {
|
||||
setName("log4j-handler");
|
||||
|
||||
try {
|
||||
reader.parse(new InputSource(inputStream));
|
||||
InputSource inputSource = new InputSource(inputStream);
|
||||
reader.parse(inputSource);
|
||||
} catch (InterruptedIOException e) {
|
||||
// Game has been interrupted.
|
||||
interrupted.set(true);
|
||||
@@ -108,7 +109,7 @@ final class Log4jHandler extends Thread {
|
||||
if (broken)
|
||||
System.out.println(line);
|
||||
|
||||
byte[] bytes = (line + OperatingSystem.LINE_SEPARATOR).getBytes(Charsets.UTF_8);
|
||||
byte[] bytes = (line + OperatingSystem.LINE_SEPARATOR).getBytes(Constants.SYSTEM_CHARSET);
|
||||
outputStream.write(bytes);
|
||||
outputStream.flush();
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -33,7 +33,6 @@ import java.util.List;
|
||||
final class CoupleTask<P extends Task> extends Task {
|
||||
|
||||
private final boolean relyingOnDependents;
|
||||
private final boolean failIfDependentsFail;
|
||||
private final Collection<Task> dependents;
|
||||
private final List<Task> dependencies = new LinkedList<>();
|
||||
private final ExceptionalFunction<AutoTypingMap<String>, Task, ?> succ;
|
||||
@@ -45,11 +44,10 @@ final class CoupleTask<P extends Task> extends Task {
|
||||
* @param succ a callback that returns the task runs after pred, succ will be executed asynchronously. You can do something that relies on the result of pred.
|
||||
* @param relyingOnDependents true if this task chain will be broken when task pred fails.
|
||||
*/
|
||||
public CoupleTask(P pred, ExceptionalFunction<AutoTypingMap<String>, Task, ?> succ, boolean relyingOnDependents, boolean failIfDependentsFail) {
|
||||
this.dependents = Collections.singleton(pred);
|
||||
public CoupleTask(P pred, ExceptionalFunction<AutoTypingMap<String>, Task, ?> succ, boolean relyingOnDependents) {
|
||||
this.dependents = pred == null ? Collections.emptySet() : Collections.singleton(pred);
|
||||
this.succ = succ;
|
||||
this.relyingOnDependents = relyingOnDependents;
|
||||
this.failIfDependentsFail = failIfDependentsFail;
|
||||
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
setName(succ.toString());
|
||||
@@ -61,9 +59,6 @@ final class CoupleTask<P extends Task> extends Task {
|
||||
Task task = succ.apply(getVariables());
|
||||
if (task != null)
|
||||
dependencies.add(task);
|
||||
|
||||
if (failIfDependentsFail && !isDependentsSucceeded())
|
||||
throw new SilentException();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -220,7 +220,6 @@ public class FileDownloadTask extends Task {
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
if (temp != null)
|
||||
temp.delete();
|
||||
Logging.LOG.log(Level.WARNING, "Unable to download file " + currentURL, e);
|
||||
exception = e;
|
||||
} finally {
|
||||
closeFiles();
|
||||
@@ -228,7 +227,7 @@ public class FileDownloadTask extends Task {
|
||||
}
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
throw new IOException("Unable to download file " + currentURL, exception);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,7 +36,10 @@ class SchedulerExecutorService extends Scheduler {
|
||||
|
||||
@Override
|
||||
public Future<?> schedule(ExceptionalRunnable<?> block) {
|
||||
return executorService.submit(block.toCallable());
|
||||
if (executorService.isShutdown() || executorService.isTerminated())
|
||||
return Schedulers.NONE.schedule(block);
|
||||
else
|
||||
return executorService.submit(block.toCallable());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
@@ -112,6 +114,8 @@ public final class Schedulers {
|
||||
return newThread();
|
||||
}
|
||||
|
||||
static final Scheduler NONE = new SchedulerImpl(Constants.emptyConsumer());
|
||||
|
||||
public static synchronized void shutdown() {
|
||||
if (CACHED_EXECUTOR != null)
|
||||
CACHED_EXECUTOR.shutdown();
|
||||
|
||||
@@ -133,6 +133,7 @@ public abstract class Task {
|
||||
|
||||
/**
|
||||
* The collection of sub-tasks that should execute **after** this task running.
|
||||
* Will not be executed if execution fails.
|
||||
*/
|
||||
public Collection<? extends Task> getDependencies() {
|
||||
return Collections.emptySet();
|
||||
@@ -243,7 +244,7 @@ public abstract class Task {
|
||||
}
|
||||
|
||||
public final Task then(ExceptionalFunction<AutoTypingMap<String>, Task, ?> b) {
|
||||
return new CoupleTask<>(this, b, true, false);
|
||||
return new CoupleTask<>(this, b, true);
|
||||
}
|
||||
|
||||
public final Task with(Task b) {
|
||||
@@ -251,7 +252,7 @@ public abstract class Task {
|
||||
}
|
||||
|
||||
public final <E extends Exception> Task with(ExceptionalFunction<AutoTypingMap<String>, Task, E> b) {
|
||||
return new CoupleTask<>(this, b, false, false);
|
||||
return new CoupleTask<>(this, b, false);
|
||||
}
|
||||
|
||||
public final Task finalized(FinalizedCallback b) {
|
||||
@@ -311,6 +312,10 @@ public abstract class Task {
|
||||
return of(ReflectionHelper.getCaller().toString(), scheduler, closure);
|
||||
}
|
||||
|
||||
public static Task ofThen(ExceptionalFunction<AutoTypingMap<String>, Task, ?> b) {
|
||||
return new CoupleTask<>(null, b, true);
|
||||
}
|
||||
|
||||
public static <V> TaskResult<V> ofResult(String id, Callable<V> callable) {
|
||||
return new TaskCallable<>(id, callable);
|
||||
}
|
||||
@@ -350,6 +355,7 @@ public abstract class Task {
|
||||
public enum TaskState {
|
||||
READY,
|
||||
RUNNING,
|
||||
EXECUTED,
|
||||
SUCCEEDED,
|
||||
FAILED
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ public final class TaskExecutor {
|
||||
return false;
|
||||
}
|
||||
|
||||
task.setState(Task.TaskState.RUNNING);
|
||||
task.setState(Task.TaskState.READY);
|
||||
|
||||
if (task.getSignificance().shouldLog())
|
||||
Logging.LOG.log(Level.FINE, "Executing task: {0}", task.getName());
|
||||
@@ -150,10 +150,18 @@ public final class TaskExecutor {
|
||||
|
||||
task.setVariables(variables);
|
||||
|
||||
task.setState(Task.TaskState.RUNNING);
|
||||
|
||||
taskListeners.forEach(it -> it.onRunning(task));
|
||||
try {
|
||||
task.getScheduler().schedule(task::execute).get();
|
||||
} catch (ExecutionException e) {
|
||||
throw (Exception) e.getCause();
|
||||
if (e.getCause() instanceof Exception)
|
||||
throw (Exception) e.getCause();
|
||||
else
|
||||
throw e;
|
||||
} finally {
|
||||
task.setState(Task.TaskState.EXECUTED);
|
||||
}
|
||||
|
||||
if (task instanceof TaskResult<?>) {
|
||||
@@ -169,22 +177,24 @@ public final class TaskExecutor {
|
||||
flag = true;
|
||||
if (task.getSignificance().shouldLog()) {
|
||||
Logging.LOG.log(Level.FINER, "Task finished: {0}", task.getName());
|
||||
|
||||
task.onDone().fireEvent(new TaskEvent(this, task, false));
|
||||
taskListeners.forEach(it -> it.onFinished(task));
|
||||
}
|
||||
|
||||
task.onDone().fireEvent(new TaskEvent(this, task, false));
|
||||
taskListeners.forEach(it -> it.onFinished(task));
|
||||
} catch (InterruptedException e) {
|
||||
if (task.getSignificance().shouldLog()) {
|
||||
Logging.LOG.log(Level.FINE, "Task aborted: " + task.getName());
|
||||
task.onDone().fireEvent(new TaskEvent(this, task, true));
|
||||
taskListeners.forEach(it -> it.onFailed(task, e));
|
||||
}
|
||||
task.onDone().fireEvent(new TaskEvent(this, task, true));
|
||||
taskListeners.forEach(it -> it.onFailed(task, e));
|
||||
} catch (SilentException | RejectedExecutionException e) {
|
||||
// do nothing
|
||||
} catch (Exception e) {
|
||||
lastException = e;
|
||||
variables.set(LAST_EXCEPTION_ID, e);
|
||||
Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e);
|
||||
if (task.getSignificance().shouldLog()) {
|
||||
Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e);
|
||||
}
|
||||
task.onDone().fireEvent(new TaskEvent(this, task, true));
|
||||
taskListeners.forEach(it -> it.onFailed(task, e));
|
||||
} finally {
|
||||
|
||||
@@ -31,6 +31,9 @@ public abstract class TaskListener implements EventListener {
|
||||
public void onReady(Task task) {
|
||||
}
|
||||
|
||||
public void onRunning(Task task) {
|
||||
}
|
||||
|
||||
public void onFinished(Task task) {
|
||||
}
|
||||
|
||||
|
||||
@@ -74,9 +74,15 @@ public final class SimpleMultimap<K, V> {
|
||||
return map.remove(key);
|
||||
}
|
||||
|
||||
public void removeValue(V value) {
|
||||
public boolean removeValue(V value) {
|
||||
boolean flag = false;
|
||||
for (Collection<V> c : map.values())
|
||||
c.remove(value);
|
||||
flag |= c.remove(value);
|
||||
return flag;
|
||||
}
|
||||
|
||||
public boolean removeValue(K key, V value) {
|
||||
return get(key).remove(value);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
|
||||
@@ -13,7 +13,7 @@ public class ToStringBuilder {
|
||||
if (!first)
|
||||
stringBuilder.append(", ");
|
||||
first = false;
|
||||
stringBuilder.append(name).append('=').append(content.toString());
|
||||
stringBuilder.append(name).append('=').append(content);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ allprojects {
|
||||
dependencies {
|
||||
compile "com.google.code.gson:gson:2.8.2"
|
||||
compile "org.apache.commons:commons-compress:1.15"
|
||||
compile "org.tukaani:xz:1.2"
|
||||
testCompile group: 'junit', name: 'junit', version: '4.12'
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user