Compare commits

..
13 Commits
56 changed files with 516 additions and 212 deletions

No files matched your search

@@ -85,7 +85,7 @@ public class HMCLModpackExportTask extends TaskResult<ZipEngine> {
return null;
});
Version mv = repository.getVersion(version).resolve(repository);
Version mv = repository.getResolvedVersion(version);
String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version))
.orElseThrow(() -> new IllegalStateException("Cannot parse the version of " + version));
zip.putTextFile(Constants.GSON.toJson(mv.setJar(gameVersion)), "minecraft/pack.json"); // Making "jar" to gameVersion is to be compatible with old HMCL.
@@ -25,6 +25,7 @@ import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.ServerDisconnectException;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.MaintainTask;
import org.jackhuang.hmcl.launch.*;
import org.jackhuang.hmcl.mod.CurseCompletionTask;
import org.jackhuang.hmcl.setting.LauncherVisibility;
@@ -61,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(() -> {
@@ -76,7 +77,7 @@ public final class LauncherHelper {
Profile profile = Settings.INSTANCE.getSelectedProfile();
GameRepository repository = profile.getRepository();
DefaultDependencyManager dependencyManager = profile.getDependency();
Version version = repository.getVersion(selectedVersion);
Version version = repository.getResolvedVersion(selectedVersion);
Account account = Settings.INSTANCE.getSelectedAccount();
VersionSetting setting = profile.getVersionSetting(selectedVersion);
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
@@ -155,9 +156,7 @@ public final class LauncherHelper {
@Override
public void onStop(boolean success, TaskExecutor executor) {
if (success) {
Controllers.closeDialog();
} else {
if (!success) {
Platform.runLater(() -> {
if (executor.getLastException() != null)
Controllers.dialog(I18nException.getStackTrace(executor.getLastException()),
@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.setting;
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
import org.jackhuang.hmcl.download.CurseCDNDownloadProvider;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.MojangDownloadProvider;
import org.jackhuang.hmcl.util.Lang;
@@ -28,9 +29,9 @@ import java.util.List;
public final class DownloadProviders {
private DownloadProviders() {}
public static final List<DownloadProvider> DOWNLOAD_PROVIDERS = Lang.immutableListOf(MojangDownloadProvider.INSTANCE, BMCLAPIDownloadProvider.INSTANCE);
public static final List<DownloadProvider> DOWNLOAD_PROVIDERS = Lang.immutableListOf(new MojangDownloadProvider(Settings.INSTANCE.getLocale() == Locales.ZH_CN), BMCLAPIDownloadProvider.INSTANCE, CurseCDNDownloadProvider.INSTANCE);
public static DownloadProvider getDownloadProvider(int index) {
return Lang.get(DOWNLOAD_PROVIDERS, index).orElse(MojangDownloadProvider.INSTANCE);
return Lang.get(DOWNLOAD_PROVIDERS, index).orElse(DOWNLOAD_PROVIDERS.get(0));
}
}
@@ -27,9 +27,7 @@ import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.MojangDownloadProvider;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.ProfileChangedEvent;
import org.jackhuang.hmcl.event.ProfileLoadingEvent;
@@ -272,6 +270,10 @@ public class Settings {
SETTINGS.setLogLines(logLines);
}
/****************************************
* AUTHLIB INJECTORS *
****************************************/
public Set<String> getAuthlibInjectorServerURLs() {
return SETTINGS.getAuthlibInjectorServerURLs();
}
@@ -307,24 +309,19 @@ public class Settings {
}
}
/****************************************
* DOWNLOAD PROVIDERS *
****************************************/
public DownloadProvider getDownloadProvider() {
switch (SETTINGS.getDownloadType()) {
case 0:
return MojangDownloadProvider.INSTANCE;
case 1:
return BMCLAPIDownloadProvider.INSTANCE;
default:
return MojangDownloadProvider.INSTANCE;
}
return DownloadProviders.getDownloadProvider(SETTINGS.getDownloadType());
}
public void setDownloadProvider(DownloadProvider downloadProvider) {
if (downloadProvider == MojangDownloadProvider.INSTANCE)
SETTINGS.setDownloadType(0);
else if (downloadProvider == BMCLAPIDownloadProvider.INSTANCE)
SETTINGS.setDownloadType(1);
else
int index = DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(downloadProvider);
if (index == -1)
throw new IllegalArgumentException("Unknown download provider: " + downloadProvider);
SETTINGS.setDownloadType(index);
}
/****************************************
@@ -533,8 +530,8 @@ public class Settings {
}
private void onProfileChanged() {
getSelectedProfile().getRepository().refreshVersionsAsync().start();
EventBus.EVENT_BUS.fireEvent(new ProfileChangedEvent(SETTINGS, getSelectedProfile()));
getSelectedProfile().getRepository().refreshVersionsAsync().subscribe(() ->
EventBus.EVENT_BUS.fireEvent(new ProfileChangedEvent(SETTINGS, getSelectedProfile())));
}
private void profileNameChanged(ObservableValue<? extends String> observableValue, String oldValue, String newValue) {
@@ -17,6 +17,8 @@
*/
package org.jackhuang.hmcl.setting;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.scene.paint.Color;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.IOUtils;
@@ -57,12 +59,22 @@ public class Theme {
return name.startsWith("#");
}
public boolean isLight() {
return Color.web(color).grayscale().getRed() >= 0.5;
}
public Color getForegroundColor() {
return isLight() ? Color.BLACK : Color.WHITE;
}
public String[] getStylesheets() {
String name = isCustom() ? BLUE.getName() : this.name;
String css = Theme.class.getResource("/assets/css/" + name + ".css").toExternalForm();
try {
File temp = File.createTempFile("hmcl", ".css");
FileUtils.writeText(temp, IOUtils.readFullyAsString(Theme.class.getResourceAsStream("/assets/css/custom.css")).replace("%base-color%", color));
FileUtils.writeText(temp, IOUtils.readFullyAsString(Theme.class.getResourceAsStream("/assets/css/custom.css"))
.replace("%base-color%", color)
.replace("%font-color%", getColorDisplayName(getForegroundColor())));
css = temp.toURI().toString();
} catch (IOException e) {
Logging.LOG.log(Level.SEVERE, "Unable to create theme stylesheet", e);
@@ -104,5 +116,15 @@ public class Theme {
return c != null ? String.format("#%02x%02x%02x", Math.round(c.getRed() * 255.0D), Math.round(c.getGreen() * 255.0D), Math.round(c.getBlue() * 255.0D)).toUpperCase() : null;
}
public static ObjectBinding<Color> foregroundFillBinding() {
return Bindings.createObjectBinding(() -> Settings.INSTANCE.getTheme().getForegroundColor(), Settings.INSTANCE.themeProperty());
}
public static ObjectBinding<Color> blackFillBinding() {
return Bindings.createObjectBinding(() -> Color.BLACK);
}
public static ObjectBinding<Color> whiteFillBinding() {
return Bindings.createObjectBinding(() -> Color.WHITE);
}
}
@@ -41,6 +41,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.game.AccountHelper;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
@@ -73,8 +74,8 @@ public final class AccountItem extends StackPane {
setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -0.5, 1.0));
chkSelected.setToggleGroup(toggleGroup);
btnDelete.setGraphic(SVG.delete("black", 15, 15));
btnRefresh.setGraphic(SVG.refresh("black", 15, 15));
btnDelete.setGraphic(SVG.delete(Theme.blackFillBinding(), 15, 15));
btnRefresh.setGraphic(SVG.refresh(Theme.blackFillBinding(), 15, 15));
// create image view
icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 16, header.boundsInParentProperty(), icon.heightProperty()));
@@ -24,6 +24,7 @@ import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerInfo;
import org.jackhuang.hmcl.setting.Theme;
import java.util.function.Consumer;
@@ -48,7 +49,7 @@ public final class AuthlibInjectorServerItem extends BorderPane {
right.setOnMouseClicked(e -> deleteCallback.accept(this));
right.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(right, Pos.CENTER);
right.setGraphic(SVG.close("black", 15, 15));
right.setGraphic(SVG.close(Theme.blackFillBinding(), 15, 15));
setRight(right);
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
@@ -48,6 +48,7 @@ import javafx.stage.StageStyle;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
@@ -150,6 +151,15 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
btnMin.setGraphic(minus);
btnMax.setGraphic(resizeMax);
close.fillProperty().bind(Theme.foregroundFillBinding());
minus.fillProperty().bind(Theme.foregroundFillBinding());
resizeMax.fillProperty().bind(Theme.foregroundFillBinding());
resizeMin.fillProperty().bind(Theme.foregroundFillBinding());
refreshNavButton.setGraphic(SVG.refresh(Theme.foregroundFillBinding(), 15, 15));
closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), 15, 15));
backNavButton.setGraphic(SVG.back(Theme.foregroundFillBinding(), 15, 15));
lblTitle.setText(title);
buttonsContainer.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
@@ -53,7 +53,7 @@ public class InstallerController {
public void loadVersion(Profile profile, String versionId) {
this.profile = profile;
this.versionId = versionId;
this.version = profile.getRepository().getVersion(versionId).resolve(profile.getRepository());
this.version = profile.getRepository().getResolvedVersion(versionId);
contentPane.getChildren().clear();
forge = liteLoader = optiFine = null;
@@ -62,7 +62,7 @@ public class InstallerController {
Consumer<InstallerItem> removeAction = x -> {
LinkedList<Library> newList = new LinkedList<>(version.getLibraries());
newList.remove(library);
new MaintainTask(profile.getRepository(), version.setLibraries(newList))
new MaintainTask(version.setLibraries(newList))
.then(variables -> new VersionJsonSaveTask(profile.getRepository(), variables.get(MaintainTask.ID)))
.with(profile.getRepository().refreshVersionsAsync())
.with(Task.of(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)))
@@ -39,6 +39,7 @@ import org.jackhuang.hmcl.mod.UnsupportedModpackException;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
@@ -65,14 +66,14 @@ public final class LeftPaneController {
accountItem.setOnSettingsButtonClicked(() -> Controllers.navigate(new AccountsPage()));
}))
.startCategory(Main.i18n("launcher").toUpperCase())
.add(Lang.apply(new IconedItem(SVG.gear("black", 20, 20), Main.i18n("settings.launcher")), iconedItem -> {
.add(Lang.apply(new IconedItem(SVG.gear(Theme.blackFillBinding(), 20, 20), Main.i18n("settings.launcher")), iconedItem -> {
iconedItem.prefWidthProperty().bind(leftPane.widthProperty());
iconedItem.setOnMouseClicked(e -> Controllers.navigate(Controllers.getSettingsPage()));
}))
.add(new ClassTitle(Lang.apply(new BorderPane(), borderPane -> {
borderPane.setLeft(Lang.apply(new VBox(), vBox -> vBox.getChildren().setAll(new Text(Main.i18n("profile.title").toUpperCase()))));
JFXButton addProfileButton = new JFXButton();
addProfileButton.setGraphic(SVG.plus("black", 10, 10));
addProfileButton.setGraphic(SVG.plus(Theme.blackFillBinding(), 10, 10));
addProfileButton.getStyleClass().add("toggle-icon-tiny");
addProfileButton.setOnMouseClicked(e ->
Controllers.getDecorator().showPage(new ProfilePage(null)));
@@ -80,8 +80,6 @@ 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);
versionPopup = new JFXPopup(versionList);
getChildren().remove(versionList);
@@ -178,14 +176,6 @@ public final class MainPage extends StackPane implements DecoratorPage {
return item;
}
public void onProfilesLoading() {
// TODO: Profiles
}
public void onProfileChanged(ProfileChangedEvent event) {
Platform.runLater(() -> loadVersions(event.getProfile()));
}
private void loadVersions() {
loadVersions(Settings.INSTANCE.getSelectedProfile());
}
@@ -193,8 +183,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
private void loadVersions(Profile profile) {
this.profile = profile;
List<Node> children = new LinkedList<>();
List<Version> versions = new LinkedList<>(profile.getRepository().getVersions());
for (Version version : versions) {
for (Version version : profile.getRepository().getVersions()) {
children.add(buildNode(profile, version.getId(), GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version.getId())).orElse("Unknown")));
}
FXUtils.resetChildren(masonryPane, children);
@@ -24,6 +24,7 @@ import javafx.geometry.Pos;
import javafx.scene.layout.BorderPane;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.mod.ModInfo;
import org.jackhuang.hmcl.setting.Theme;
import java.util.function.Consumer;
@@ -43,7 +44,7 @@ public final class ModItem extends BorderPane {
btnRemove.setOnMouseClicked(e -> deleteCallback.accept(this));
btnRemove.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnRemove, Pos.CENTER);
btnRemove.setGraphic(SVG.close("black", 15, 15));
btnRemove.setGraphic(SVG.close(Theme.blackFillBinding(), 15, 15));
setRight(btnRemove);
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
@@ -17,19 +17,21 @@
*/
package org.jackhuang.hmcl.ui;
import javafx.beans.binding.ObjectBinding;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Paint;
import javafx.scene.shape.SVGPath;
public final class SVG {
private SVG() {
}
private static Node createSVGPath(String d, String fill, double width, double height) {
private static Node createSVGPath(String d, ObjectBinding<? extends Paint> fill, double width, double height) {
SVGPath path = new SVGPath();
path.getStyleClass().add("svg");
path.setContent(d);
path.setStyle("-fx-fill: " + fill + ";");
path.fillProperty().bind(fill);
Group svg = new Group(path);
double scale = Math.min(width / svg.getBoundsInParent().getWidth(), height / svg.getBoundsInParent().getHeight());
@@ -41,87 +43,87 @@ public final class SVG {
// default fill: white, width: 20, height 20
public static Node gear(String fill, double width, double height) {
public static Node gear(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z", fill, width, height);
}
public static Node back(String fill, double width, double height) {
public static Node back(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z", fill, width, height);
}
public static Node close(String fill, double width, double height) {
public static Node close(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z", fill, width, height);
}
public static Node dotsVertical(String fill, double width, double height) {
public static Node dotsVertical(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z", fill, width, height);
}
public static Node delete(String fill, double width, double height) {
public static Node delete(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z", fill, width, height);
}
public static Node accountEdit(String fill, double width, double height) {
public static Node accountEdit(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M21.7,13.35L20.7,14.35L18.65,12.3L19.65,11.3C19.86,11.09 20.21,11.09 20.42,11.3L21.7,12.58C21.91,12.79 21.91,13.14 21.7,13.35M12,18.94L18.06,12.88L20.11,14.93L14.06,21H12V18.94M12,14C7.58,14 4,15.79 4,18V20H10V18.11L14,14.11C13.34,14.03 12.67,14 12,14M12,4A4,4 0 0,0 8,8A4,4 0 0,0 12,12A4,4 0 0,0 16,8A4,4 0 0,0 12,4Z", fill, width, height);
}
public static Node expand(String fill, double width, double height) {
public static Node expand(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z", fill, width, height);
}
public static Node collapse(String fill, double width, double height) {
public static Node collapse(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z", fill, width, height);
}
public static Node navigate(String fill, double width, double height) {
public static Node navigate(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z", fill, width, height);
}
public static Node launch(String fill, double width, double height) {
public static Node launch(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M1008 6.286q18.857 13.714 15.429 36.571l-146.286 877.714q-2.857 16.571-18.286 25.714-8 4.571-17.714 4.571-6.286 0-13.714-2.857l-258.857-105.714-138.286 168.571q-10.286 13.143-28 13.143-7.429 0-12.571-2.286-10.857-4-17.429-13.429t-6.571-20.857v-199.429l493.714-605.143-610.857 528.571-225.714-92.571q-21.143-8-22.857-31.429-1.143-22.857 18.286-33.714l950.857-548.571q8.571-5.143 18.286-5.14311.429 0 20.571 6.286z", fill, width, height);
}
public static Node script(String fill, double width, double height) {
public static Node script(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M14,20A2,2 0 0,0 16,18V5H9A1,1 0 0,0 8,6V16H5V5A3,3 0 0,1 8,2H19A3,3 0 0,1 22,5V6H18V18L18,19A3,3 0 0,1 15,22H5A3,3 0 0,1 2,19V18H12A2,2 0 0,0 14,20Z", fill, width, height);
}
public static Node pencil(String fill, double width, double height) {
public static Node pencil(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M20.71,4.04C21.1,3.65 21.1,3 20.71,2.63L18.37,0.29C18,-0.1 17.35,-0.1 16.96,0.29L15,2.25L18.75,6M17.75,7L14,3.25L4,13.25V17H7.75L17.75,7Z", fill, width, height);
}
public static Node refresh(String fill, double width, double height) {
public static Node refresh(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z", fill, width, height);
}
public static Node folderOpen(String fill, double width, double height) {
public static Node folderOpen(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M19,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H19A2,2 0 0,1 21,8H21L4,8V18L6.14,10H23.21L20.93,18.5C20.7,19.37 19.92,20 19,20Z", fill, width, height);
}
public static Node update(String fill, double width, double height) {
public static Node update(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M21,10.12H14.22L16.96,7.3C14.23,4.6 9.81,4.5 7.08,7.2C4.35,9.91 4.35,14.28 7.08,17C9.81,19.7 14.23,19.7 16.96,17C18.32,15.65 19,14.08 19,12.1H21C21,14.08 20.12,16.65 18.36,18.39C14.85,21.87 9.15,21.87 5.64,18.39C2.14,14.92 2.11,9.28 5.62,5.81C9.13,2.34 14.76,2.34 18.27,5.81L21,3V10.12M12.5,8V12.25L16,14.33L15.28,15.54L11,13V8H12.5Z", fill, width, height);
}
public static Node closeCircle(String fill, double width, double height) {
public static Node closeCircle(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z", fill, width, height);
}
public static Node checkCircle(String fill, double width, double height) {
public static Node checkCircle(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z", fill, width, height);
}
public static Node infoCircle(String fill, double width, double height) {
public static Node infoCircle(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M13,9H11V7H13M13,17H11V11H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z", fill, width, height);
}
public static Node helpCircle(String fill, double width, double height) {
public static Node helpCircle(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z", fill, width, height);
}
public static Node alert(String fill, double width, double height) {
public static Node alert(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z", fill, width, height);
}
public static Node plus(String fill, double width, double height) {
public static Node plus(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z", fill, width, height);
}
@@ -18,6 +18,8 @@
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.*;
import com.jfoenix.effects.JFXDepthManager;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
@@ -41,7 +43,7 @@ import org.jackhuang.hmcl.util.Lang;
import java.net.Proxy;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
public final class SettingsPage extends StackPane implements DecoratorPage {
private final StringProperty title = new SimpleStringProperty(this, "title", Main.i18n("settings.launcher"));
@@ -77,7 +79,7 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
@FXML
private MultiFileItem backgroundItem;
@FXML
private MultiColorItem themeItem;
private StackPane themeColorPickerContainer;
@FXML
private JFXRadioButton chkNoProxy;
@FXML
@@ -193,38 +195,17 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
Settings.INSTANCE.setBackgroundImageType((EnumBackgroundImage) newValue.getUserData()));
// theme
themeItem.loadChildren(Arrays.asList(
themeItem.createChildren(Main.i18n("color.blue"), Theme.BLUE),
themeItem.createChildren(Main.i18n("color.dark_blue"), Theme.DARK_BLUE),
themeItem.createChildren(Main.i18n("color.green"), Theme.GREEN),
themeItem.createChildren(Main.i18n("color.orange"), Theme.ORANGE),
themeItem.createChildren(Main.i18n("color.purple"), Theme.PURPLE),
themeItem.createChildren(Main.i18n("color.red"), Theme.RED)
));
if (Settings.INSTANCE.getTheme().isCustom())
themeItem.setColor(Color.web(Settings.INSTANCE.getTheme().getColor()));
themeItem.setToggleSelectedListener(newValue -> {
if (newValue.getUserData() != null) {
Settings.INSTANCE.setTheme((Theme) newValue.getUserData());
themeItem.setOnColorPickerChanged(null);
} else {
themeItem.setOnColorPickerChanged(color ->
Settings.INSTANCE.setTheme(Theme.custom(Theme.getColorDisplayName(color))));
}
});
themeItem.getGroup().getToggles().stream().filter(it -> Settings.INSTANCE.getTheme() == it.getUserData() || Settings.INSTANCE.getTheme().isCustom() && themeItem.isCustomToggle(it)).findFirst().ifPresent(it -> it.setSelected(true));
Settings.INSTANCE.themeProperty().setChangedListenerAndOperate(it -> {
if (it.getName().startsWith("#") || it.getName().startsWith("0x"))
themeItem.setSubtitle(it.getName());
else
themeItem.setSubtitle(Main.i18n("color." + it.getName().toLowerCase()));
Controllers.getScene().getStylesheets().setAll(it.getStylesheets());
JFXColorPicker picker = new JFXColorPicker(Color.web(Settings.INSTANCE.getTheme().getColor()), null);
picker.setCustomColorText(Main.i18n("color.custom"));
picker.setRecentColorsText(Main.i18n("color.recent"));
picker.getCustomColors().setAll(Arrays.stream(Theme.VALUES).map(Theme::getColor).map(Color::web).collect(Collectors.toList()));
picker.setOnAction(e -> {
Theme theme = Theme.custom(Theme.getColorDisplayName(picker.getValue()));
Settings.INSTANCE.setTheme(theme);
Controllers.getScene().getStylesheets().setAll(theme.getStylesheets());
});
themeColorPickerContainer.getChildren().setAll(picker);
Platform.runLater(() -> JFXDepthManager.setDepth(picker, 0));
}
private void initBackgroundItemSubtitle() {
@@ -33,6 +33,7 @@ import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.setting.Theme;
import java.util.Optional;
@@ -67,10 +68,10 @@ public final class VersionItem extends StackPane {
public VersionItem() {
FXUtils.loadFXML(this, "/assets/fxml/version-item.fxml");
setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -1.0, 1.0));
btnSettings.setGraphic(SVG.gear("black", 15, 15));
btnUpdate.setGraphic(SVG.update("black", 15, 15));
btnLaunch.setGraphic(SVG.launch("black", 15, 15));
btnScript.setGraphic(SVG.script("black", 15, 15));
btnSettings.setGraphic(SVG.gear(Theme.blackFillBinding(), 15, 15));
btnUpdate.setGraphic(SVG.update(Theme.blackFillBinding(), 15, 15));
btnLaunch.setGraphic(SVG.launch(Theme.blackFillBinding(), 15, 15));
btnScript.setGraphic(SVG.script(Theme.blackFillBinding(), 15, 15));
FXUtils.installTooltip(btnSettings, Main.i18n("version.settings"));
FXUtils.installTooltip(btnUpdate, Main.i18n("version.update"));
@@ -161,7 +161,7 @@ public final class VersionPage extends StackPane implements DecoratorPage {
deleteVersion(profile, version);
break;
case 2: // redownload asset index
new GameAssetIndexDownloadTask(profile.getDependency(), profile.getRepository().getVersion(version).resolve(profile.getRepository())).start();
new GameAssetIndexDownloadTask(profile.getDependency(), profile.getRepository().getResolvedVersion(version)).start();
break;
case 3: // delete libraries
FileUtils.deleteDirectoryQuietly(new File(profile.getRepository().getBaseDirectory(), "libraries"));
@@ -31,6 +31,7 @@ import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
@@ -77,7 +78,7 @@ public class ComponentListCell extends StackPane {
StackPane groupNode = new StackPane();
groupNode.getStyleClass().add("options-list-item-header");
Node expandIcon = SVG.expand("black", 10, 10);
Node expandIcon = SVG.expand(Theme.blackFillBinding(), 10, 10);
JFXButton expandButton = new JFXButton();
expandButton.setGraphic(expandIcon);
expandButton.getStyleClass().add("options-list-item-expand-button");
@@ -27,6 +27,7 @@ import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.DirectoryChooser;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
@@ -50,7 +51,7 @@ public class FileItem extends BorderPane {
setLeft(left);
JFXButton right = new JFXButton();
right.setGraphic(SVG.pencil("black", 15, 15));
right.setGraphic(SVG.pencil(Theme.blackFillBinding(), 15, 15));
right.getStyleClass().add("toggle-icon4");
right.setOnMouseClicked(e -> onExplore());
FXUtils.installTooltip(right, Main.i18n("button.edit"));
@@ -16,6 +16,7 @@ import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
@@ -36,7 +37,7 @@ public final class ImagePickerItem extends BorderPane {
imageView.setPreserveRatio(true);
selectButton = new JFXButton();
selectButton.setGraphic(SVG.pencil("black", 15, 15));
selectButton.setGraphic(SVG.pencil(Theme.blackFillBinding(), 15, 15));
selectButton.onMouseClickedProperty().bind(onSelectButtonClicked);
selectButton.getStyleClass().add("toggle-icon4");
@@ -24,6 +24,7 @@ import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
@@ -62,19 +63,19 @@ public final class MessageDialogPane extends StackPane {
switch (type) {
case MessageBox.INFORMATION_MESSAGE:
graphic.setGraphic(SVG.infoCircle("black", 40, 40));
graphic.setGraphic(SVG.infoCircle(Theme.blackFillBinding(), 40, 40));
break;
case MessageBox.ERROR_MESSAGE:
graphic.setGraphic(SVG.closeCircle("black", 40, 40));
graphic.setGraphic(SVG.closeCircle(Theme.blackFillBinding(), 40, 40));
break;
case MessageBox.FINE_MESSAGE:
graphic.setGraphic(SVG.checkCircle("black", 40, 40));
graphic.setGraphic(SVG.checkCircle(Theme.blackFillBinding(), 40, 40));
break;
case MessageBox.WARNING_MESSAGE:
graphic.setGraphic(SVG.alert("black", 40, 40));
graphic.setGraphic(SVG.alert(Theme.blackFillBinding(), 40, 40));
break;
case MessageBox.QUESTION_MESSAGE:
graphic.setGraphic(SVG.helpCircle("black", 40, 40));
graphic.setGraphic(SVG.helpCircle(Theme.blackFillBinding(), 40, 40));
break;
default:
throw new IllegalArgumentException("Unrecognized message box message type " + type);
@@ -39,6 +39,7 @@ import javafx.scene.layout.VBox;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
@@ -69,7 +70,7 @@ public class MultiFileItem extends ComponentList {
BorderPane.setAlignment(txtCustom, Pos.CENTER_RIGHT);
btnSelect.setGraphic(SVG.folderOpen("black", 15, 15));
btnSelect.setGraphic(SVG.folderOpen(Theme.blackFillBinding(), 15, 15));
btnSelect.setOnMouseClicked(e -> {
if (isDirectory()) {
DirectoryChooser chooser = new DirectoryChooser();
@@ -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) {
@@ -1,5 +1,5 @@
.root {
-fx-base-color: %base-color%;
-fx-base-check-color: derive(-fx-base-color, 30%);
-fx-base-text-fill: white;
-fx-base-text-fill: %font-color%;
}
+5 -4
View File
@@ -4,7 +4,8 @@
*/
.root {
-fx-font-family: "微软雅黑", "Microsoft YaHei", "Roboto";
-fx-base-font-family: "微软雅黑", "Microsoft YaHei", "Roboto";
-fx-font-family: -fx-base-font-family;
}
.disabled Label {
@@ -129,7 +130,7 @@
.jfx-layout-heading {
-fx-font-size: 20.0px;
-fx-font-weight: BOLD;
-fx-font-family: -fx-base-font-family;
-fx-alignment: center-left;
-fx-padding: 5.0 0.0 5.0 0.0;
}
@@ -152,12 +153,12 @@
.dialog-accept {
-fx-text-fill: #03A9F4;
-fx-font-weight: BOLD;
-fx-font-family: -fx-base-font-family;
-fx-padding: 0.7em 0.8em;
}
.dialog-cancel {
-fx-font-weight: BOLD;
-fx-font-family: -fx-base-font-family;
-fx-padding: 0.7em 0.8em;
}
@@ -68,7 +68,7 @@
<BorderPane minWidth="200" maxWidth="200" fx:id="titleWrapper">
<center>
<Label fx:id="lblTitle" BorderPane.alignment="CENTER" mouseTransparent="true"
style="-fx-background-color: transparent; -fx-text-fill: white; -fx-font-size: 15px;">
style="-fx-background-color: transparent; -fx-text-fill: -fx-base-text-fill; -fx-font-size: 15px;">
<BorderPane.margin>
<Insets left="3" />
</BorderPane.margin>
@@ -84,32 +84,20 @@
<left>
<HBox fx:id="navLeft" alignment="CENTER_LEFT" style="-fx-padding: 0 5 0 5;">
<JFXButton fx:id="backNavButton" onMouseClicked="#onBack"
styleClass="jfx-decorator-button" ripplerFill="white">
<graphic>
<fx:include source="/assets/svg/arrow-left.fxml"/>
</graphic>
</JFXButton>
styleClass="jfx-decorator-button" ripplerFill="white" />
<JFXButton fx:id="closeNavButton" onMouseClicked="#onCloseNav"
styleClass="jfx-decorator-button" ripplerFill="white">
<graphic>
<fx:include source="/assets/svg/close.fxml"/>
</graphic>
</JFXButton>
styleClass="jfx-decorator-button" ripplerFill="white" />
</HBox>
</left>
<center>
<VBox alignment="CENTER_LEFT"> <!-- don't know why label always be centered when using HBox -->
<Label fx:id="titleLabel" style="-fx-text-fill:WHITE; -fx-font-size: 15;"/>
<Label fx:id="titleLabel" style="-fx-text-fill: -fx-base-text-fill; -fx-font-size: 15;"/>
</VBox>
</center>
<right>
<HBox fx:id="navRight" alignment="CENTER_LEFT">
<JFXButton fx:id="refreshNavButton" onMouseClicked="#onRefresh"
styleClass="jfx-decorator-button" ripplerFill="white">
<graphic>
<fx:include source="/assets/svg/refresh.fxml"/>
</graphic>
</JFXButton>
styleClass="jfx-decorator-button" ripplerFill="white" />
<Rectangle height="${navBar.height}" width="1" fill="gray"/>
</HBox>
</right>
@@ -108,7 +108,14 @@
</VBox>
</ComponentList>
<MultiColorItem fx:id="themeItem" title="%settings.launcher.theme" hasSubtitle="true" />
<BorderPane>
<left>
<Label text="%settings.launcher.theme" BorderPane.alignment="CENTER_LEFT" />
</left>
<right>
<StackPane fx:id="themeColorPickerContainer" minHeight="30" />
</right>
</BorderPane>
<VBox spacing="5">
<BorderPane><left><Label text="%settings.launcher.log_font" BorderPane.alignment="CENTER_LEFT" /></left><right><HBox spacing="3"><FontComboBox fontSize="12" enableStyle="false" fx:id="cboFont" /><JFXTextField fx:id="txtFontSize" maxWidth="50" minWidth="50" /></HBox></right></BorderPane>
@@ -89,6 +89,8 @@ color.green=Green
color.orange=Orange
color.purple=Purple
color.red=Red
color.recent=Recommended
color.custom=Custom Color
crash.NoClassDefFound=Please check "HMCL" software is complete.
crash.advice.ClassNotFoundException=Minecraft or mods are incomplete. Retry if there are some libraries that have not downloaded or update your game and mods! Or you can try Game Settings -> Manage (Version) -> Delete library files to solve the problem.
@@ -89,6 +89,8 @@ color.green=绿色
color.orange=橙色
color.purple=紫色
color.red=红色
color.recent=推荐
color.custom=自定义颜色
crash.NoClassDefFound=请确认HMCL本体是否完整
crash.advice.ClassNotFoundException=Minecraft不完整或Mod冲突,如果有未能下载的文件请下载成功后重试或是客户端损坏请重试请重新制作客户端或下载整合包解决问题,另可尝试游戏设置->(版本)管理->删除库文件解决问题
@@ -0,0 +1,31 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 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;
public class CurseCDNDownloadProvider extends MojangDownloadProvider {
public static final CurseCDNDownloadProvider INSTANCE = new CurseCDNDownloadProvider();
protected CurseCDNDownloadProvider() {
super(false);
}
@Override
public String injectURL(String baseURL) {
return baseURL == null ? null : baseURL.replace("http://files.minecraftforge.net/maven", "http://ftb.cursecdn.com/FTB2/maven");
}
}
@@ -84,17 +84,17 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
case "forge":
return new ForgeInstallTask(this, gameVersion, version, libraryVersion)
.then(variables -> new LibrariesUniqueTask(variables.get("version")))
.then(variables -> new MaintainTask(repository, 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(repository, 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(repository, 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.");
@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.download.game.*;
import org.jackhuang.hmcl.game.SimpleVersionProvider;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.ParallelTask;
import org.jackhuang.hmcl.task.Task;
@@ -28,16 +28,14 @@ import java.util.List;
public class MaintainTask extends TaskResult<Version> {
private final GameRepository repository;
private final Version version;
private final String id;
public MaintainTask(GameRepository repository, Version version) {
this(repository, version, ID);
public MaintainTask(Version version) {
this(version, ID);
}
public MaintainTask(GameRepository repository, Version version, String id) {
this.repository = repository;
public MaintainTask(Version version, String id) {
this.version = version;
this.id = id;
}
@@ -26,11 +26,12 @@ import org.jackhuang.hmcl.download.optifine.OptiFineVersionList;
* @see <a href="http://wiki.vg">http://wiki,vg</a>
* @author huangyuhui
*/
public final class MojangDownloadProvider implements DownloadProvider {
public class MojangDownloadProvider implements DownloadProvider {
public static final MojangDownloadProvider INSTANCE = new MojangDownloadProvider();
private boolean isChina;
private MojangDownloadProvider() {
public MojangDownloadProvider(boolean isChina) {
this.isChina = isChina;
}
@Override
@@ -76,9 +77,16 @@ public final class MojangDownloadProvider implements DownloadProvider {
@Override
public String injectURL(String baseURL) {
if (baseURL.contains("net/minecraftforge/forge"))
if (baseURL == null)
return null;
//else if (baseURL.contains("scala-swing") || baseURL.contains("scala-xml") || baseURL.contains("scala-parser-combinators"))
// return baseURL.replace("http://files.minecraftforge.net/maven", "http://ftb.cursecdn.com/FTB2/maven");
/*else if (baseURL.contains("typesafe") || baseURL.contains("scala"))
if (isChina)
return baseURL.replace("http://files.minecraftforge.net/maven", "http://maven.aliyun.com/nexus/content/groups/public");
else
return baseURL.replace("http://files.minecraftforge.net/maven", "http://repo1.maven.org/maven2");
*/else
return baseURL;
else
return baseURL.replace("http://files.minecraftforge.net/maven", "http://ftb.cursecdn.com/FTB2/maven");
}
}
@@ -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));
});
}
@@ -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,7 +40,7 @@ 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;
@@ -29,7 +29,7 @@ import java.util.Date;
public class ClassicVersion extends Version {
public ClassicVersion() {
super("Classic", "${auth_player_name} ${auth_session} --workDir ${game_directory}",
super(true, "Classic", "${auth_player_name} ${auth_session} --workDir ${game_directory}",
null, "net.minecraft.client.Minecraft", null, null, null, null,
Arrays.asList(new ClassicLibrary("lwjgl"), new ClassicLibrary("jinput"), new ClassicLibrary("lwjgl_util")),
null, null, null, ReleaseType.UNKNOWN, new Date(), new Date(), 0);
@@ -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);
}
}
@@ -63,7 +63,7 @@ public final class CompatibilityRule {
}
public static boolean appliesToCurrentEnvironment(Collection<CompatibilityRule> rules, Map<String, Boolean> features) {
if (rules == null)
if (rules == null || rules.isEmpty())
return true;
Action action = Action.DISALLOW;
@@ -38,7 +38,7 @@ import java.util.logging.Level;
public class DefaultGameRepository implements GameRepository {
private File baseDirectory;
protected final Map<String, Version> versions = new TreeMap<>();
protected Map<String, Version> versions;
protected boolean loaded = false;
public DefaultGameRepository(File baseDirectory) {
@@ -143,6 +143,8 @@ public class DefaultGameRepository implements GameRepository {
}
public boolean removeVersionFromDisk(String id) {
if (!versions.containsKey(id))
return true;
File file = getVersionRoot(id);
if (!file.exists())
return true;
@@ -151,7 +153,7 @@ public class DefaultGameRepository implements GameRepository {
}
protected void refreshVersionsImpl() {
versions.clear();
Map<String, Version> versions = new TreeMap<>();
if (ClassicVersion.hasClassicVersion(getBaseDirectory())) {
Version version = new ClassicVersion();
@@ -172,7 +174,10 @@ public class DefaultGameRepository implements GameRepository {
if (!json.exists()) {
List<File> jsons = FileUtils.listFilesByExtension(dir, "json");
if (jsons.size() == 1)
jsons.get(0).renameTo(json);
if (!jsons.get(0).renameTo(json)) {
Logging.LOG.warning("Cannot rename json file " + jsons.get(0) + " to " + json + ", ignoring version " + id);
continue;
}
}
Version version;
@@ -208,13 +213,15 @@ public class DefaultGameRepository implements GameRepository {
try {
Version resolved = version.resolve(provider);
if (EventBus.EVENT_BUS.fireEvent(new LoadedOneVersionEvent(this, resolved)) != Event.Result.DENY)
if (resolved.appliesToCurrentEnvironment() &&
EventBus.EVENT_BUS.fireEvent(new LoadedOneVersionEvent(this, resolved)) != Event.Result.DENY)
versions.put(version.getId(), version);
} catch (VersionNotFoundException e) {
Logging.LOG.log(Level.WARNING, "Ignoring version {0} because it inherits from a nonexistent version.", version.getId());
}
}
this.versions = versions;
loaded = true;
}
@@ -52,6 +52,10 @@ public interface GameRepository extends VersionProvider {
@Override
Version getVersion(String id) throws VersionNotFoundException;
default Version getResolvedVersion(String id) throws VersionNotFoundException {
return getVersion(id).resolve(this);
}
/**
* How many version are there?
*/
@@ -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()),
@@ -47,7 +47,10 @@ public class Version implements Comparable<Version>, Validation {
private final Date releaseTime;
private final int minimumLauncherVersion;
public Version(String id, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, List<Library> libraries, List<CompatibilityRule> compatibilityRules, Map<DownloadType, DownloadInfo> downloads, Map<DownloadType, LoggingInfo> logging, ReleaseType type, Date time, Date releaseTime, int minimumLauncherVersion) {
private transient final boolean resolved;
public Version(boolean resolved, String id, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, List<Library> libraries, List<CompatibilityRule> compatibilityRules, Map<DownloadType, DownloadInfo> downloads, Map<DownloadType, LoggingInfo> logging, ReleaseType type, Date time, Date releaseTime, int minimumLauncherVersion) {
this.resolved = resolved;
this.id = id;
this.minecraftArguments = minecraftArguments;
this.arguments = arguments;
@@ -144,7 +147,7 @@ public class Version implements Comparable<Version>, Validation {
* Resolve given version
*/
public Version resolve(VersionProvider provider) throws VersionNotFoundException {
return resolve(provider, new HashSet<>());
return resolve(provider, new HashSet<>()).setResolved();
}
protected Version resolve(VersionProvider provider, Set<String> resolvedSoFar) throws VersionNotFoundException {
@@ -159,7 +162,9 @@ public class Version implements Comparable<Version>, Validation {
// It is supposed to auto install an version in getVersion.
Version parent = provider.getVersion(inheritsFrom).resolve(provider, resolvedSoFar);
return new Version(id,
return new Version(
true,
id,
minecraftArguments == null ? parent.minecraftArguments : minecraftArguments,
Arguments.merge(parent.arguments, arguments),
mainClass == null ? parent.mainClass : mainClass,
@@ -167,7 +172,7 @@ public class Version implements Comparable<Version>, Validation {
jar == null ? parent.jar : jar,
assetIndex == null ? parent.assetIndex : assetIndex,
assets == null ? parent.assets : assets,
Lang.merge(parent.libraries, this.libraries),
Lang.merge(this.libraries, parent.libraries),
Lang.merge(parent.compatibilityRules, this.compatibilityRules),
downloads == null ? parent.downloads : downloads,
logging == null ? parent.logging : logging,
@@ -177,36 +182,40 @@ public class Version implements Comparable<Version>, Validation {
Math.max(minimumLauncherVersion, parent.minimumLauncherVersion));
}
private Version setResolved() {
return new Version(true, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
}
public Version setId(String id) {
return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
}
public Version setMinecraftArguments(String minecraftArguments) {
return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
}
public Version setArguments(Arguments arguments) {
return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
}
public Version setMainClass(String mainClass) {
return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
}
public Version setInheritsFrom(String inheritsFrom) {
return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
}
public Version setJar(String jar) {
return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
}
public Version setLibraries(List<Library> libraries) {
return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
}
public Version setLogging(Map<DownloadType, LoggingInfo> logging) {
return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
}
@Override
@@ -56,7 +56,7 @@ public abstract class Launcher {
this.listener = listener;
this.daemon = daemon;
version = repository.getVersion(versionId).resolve(repository);
version = repository.getResolvedVersion(versionId);
}
/**
@@ -56,6 +56,7 @@ final class Log4jHandler extends Thread {
private final PipedInputStream inputStream = Lang.invoke(() -> new PipedInputStream(outputStream));
private final AtomicBoolean interrupted = new AtomicBoolean(false);
private final List<String> logs = new LinkedList<>();
private boolean broken = false;
public Log4jHandler(BiConsumer<String, Log4jLevel> callback) {
this.callback = callback;
@@ -95,20 +96,28 @@ final class Log4jHandler extends Thread {
public Future<?> newLine(String log) {
return Schedulers.computation().schedule(() -> {
try {
String line = (log + OperatingSystem.LINE_SEPARATOR)
String line = log
.replace("<![CDATA[", "")
.replace("]]>", "")
.replace("log4j:Event", "log4j_Event")
.replace("<log4j:Message>", "<log4j_Message><![CDATA[")
.replace("</log4j:Message>", "]]></log4j_Message>")
.replace("log4j:Throwable", "log4j_Throwable");
.replace("log4j:", "log4j_")
.replace("<log4j_Message>", "<log4j_Message><![CDATA[")
.replace("</log4j_Message>", "]]></log4j_Message>")
.replace("<log4j_Throwable>", "<log4j_Throwable><![CDATA[")
.replace("</log4j_Throwable>", "]]></log4j_Throwable>");
logs.add(line);
byte[] bytes = line.getBytes(Charsets.UTF_8);
if (broken)
System.out.println(line);
byte[] bytes = (line + OperatingSystem.LINE_SEPARATOR).getBytes(Charsets.UTF_8);
outputStream.write(bytes);
outputStream.flush();
} catch (IOException e) {
// Ignoring IOException, including read end dead.
Logging.LOG.log(Level.WARNING, "An error occurred when writing console lines", e);
if (!broken) {
Logging.LOG.log(Level.WARNING, "An error occurred when writing console lines", e);
logs.forEach(System.out::println);
broken = true;
}
}
});
}
@@ -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();
@@ -350,6 +351,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;
}
+1
View File
@@ -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'
}
+1 -1
View File
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.5.1-bin.zip
BIN
View File
Binary file not shown.