Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9981423927 | ||
|
|
0e5250dfb3 | ||
|
|
2dc956b357 | ||
|
|
b8bd0a08e0 | ||
|
|
fb31eebcf2 | ||
|
|
3c8689a3f1 | ||
|
|
2957d66dd6 | ||
|
|
6f6efe0874 | ||
|
|
355fc04069 | ||
|
|
b4738f383b | ||
|
|
b12711969c | ||
|
|
94727353c4 | ||
|
|
5eb7449b74 |
No files matched your search
@@ -140,12 +140,12 @@ public final class AccountHelper {
|
||||
private static void downloadSkin(YggdrasilAccount account, GameProfile profile, boolean refresh) throws Exception {
|
||||
account.clearCache();
|
||||
|
||||
Optional<Texture> texture = account.getSkin(profile);
|
||||
if (!texture.isPresent()) return;
|
||||
String url = texture.get().getUrl();
|
||||
File file = getSkinFile(profile.getId());
|
||||
if (!refresh && file.exists())
|
||||
return;
|
||||
Optional<Texture> texture = account.getSkin(profile);
|
||||
if (!texture.isPresent()) return;
|
||||
String url = texture.get().getUrl();
|
||||
new FileDownloadTask(NetworkUtils.toURL(url), file).run();
|
||||
}
|
||||
|
||||
@@ -153,12 +153,12 @@ public final class AccountHelper {
|
||||
account.clearCache();
|
||||
|
||||
if (account.getCharacter() == null) return;
|
||||
Optional<Texture> texture = account.getSkin();
|
||||
if (!texture.isPresent()) return;
|
||||
String url = texture.get().getUrl();
|
||||
File file = getSkinFile(account.getUUID());
|
||||
if (!refresh && file.exists())
|
||||
return;
|
||||
Optional<Texture> texture = account.getSkin();
|
||||
if (!texture.isPresent()) return;
|
||||
String url = texture.get().getUrl();
|
||||
new FileDownloadTask(NetworkUtils.toURL(url), file).run();
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
@@ -216,7 +217,10 @@ public final class LauncherHelper {
|
||||
if (ex != null) {
|
||||
String message;
|
||||
if (ex instanceof CurseCompletionException) {
|
||||
message = i18n("modpack.type.curse.error");
|
||||
if (ex.getCause() instanceof FileNotFoundException)
|
||||
message = i18n("modpack.type.curse.not_found");
|
||||
else
|
||||
message = i18n("modpack.type.curse.error");
|
||||
} else if (ex instanceof PermissionException) {
|
||||
message = i18n("launch.failed.executable_permission");
|
||||
} else if (ex instanceof ProcessCreationException) {
|
||||
@@ -343,6 +347,7 @@ public final class LauncherHelper {
|
||||
}
|
||||
}
|
||||
|
||||
// 32-bit JVM cannot make use of too much memory.
|
||||
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.BIT_32 &&
|
||||
setting.getMaxMemory() > 1.5 * 1024) {
|
||||
// 1.5 * 1024 is an inaccurate number.
|
||||
@@ -350,12 +355,24 @@ public final class LauncherHelper {
|
||||
Controllers.dialog(i18n("launch.advice.too_large_memory_for_32bit"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
|
||||
// Cannot allocate too much memory exceeding free space.
|
||||
if (!flag && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
|
||||
Controllers.dialog(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
// Forge 2760 and above will crash game with LiteLoader.
|
||||
if (!flag) {
|
||||
boolean hasForge2760 = version.getLibraries().stream().filter(it -> it.is("net.minecraftforge", "forge"))
|
||||
.anyMatch(it -> VersionNumber.VERSION_COMPARATOR.compare(it.getVersion(), "1.12.2-14.23.5.2760") >= 0);
|
||||
boolean hasLiteLoader = version.getLibraries().stream().anyMatch(it -> it.is("com.mumfrey", "liteloader"));
|
||||
if (hasForge2760 && hasLiteLoader && gameVersion.compareTo(VersionNumber.asVersion("1.12.2")) == 0) {
|
||||
Controllers.dialog(i18n("launch.advice.forge2760_liteloader"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!flag)
|
||||
onAccept.run();
|
||||
}
|
||||
@@ -454,6 +471,31 @@ public final class LauncherHelper {
|
||||
});
|
||||
}
|
||||
|
||||
private void finishLaunch() {
|
||||
switch (launcherVisibility) {
|
||||
case HIDE_AND_REOPEN:
|
||||
Platform.runLater(() -> {
|
||||
Controllers.getStage().hide();
|
||||
emitStatus(LoadingState.DONE);
|
||||
});
|
||||
break;
|
||||
case CLOSE:
|
||||
// Never come to here.
|
||||
break;
|
||||
case KEEP:
|
||||
Platform.runLater(() -> {
|
||||
emitStatus(LoadingState.DONE);
|
||||
});
|
||||
break;
|
||||
case HIDE:
|
||||
Platform.runLater(() -> {
|
||||
Controllers.getStage().close();
|
||||
emitStatus(LoadingState.DONE);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onLog(String log, Log4jLevel level) {
|
||||
String newLog = log;
|
||||
@@ -483,28 +525,7 @@ public final class LauncherHelper {
|
||||
|
||||
if (!lwjgl && (log.contains("LWJGL Version: ") || !detectWindow)) {
|
||||
lwjgl = true;
|
||||
switch (launcherVisibility) {
|
||||
case HIDE_AND_REOPEN:
|
||||
Platform.runLater(() -> {
|
||||
Controllers.getStage().hide();
|
||||
emitStatus(LoadingState.DONE);
|
||||
});
|
||||
break;
|
||||
case CLOSE:
|
||||
// Never come to here.
|
||||
break;
|
||||
case KEEP:
|
||||
Platform.runLater(() -> {
|
||||
emitStatus(LoadingState.DONE);
|
||||
});
|
||||
break;
|
||||
case HIDE:
|
||||
Platform.runLater(() -> {
|
||||
Controllers.getStage().close();
|
||||
emitStatus(LoadingState.DONE);
|
||||
});
|
||||
break;
|
||||
}
|
||||
finishLaunch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,6 +534,9 @@ public final class LauncherHelper {
|
||||
if (exitType == ExitType.INTERRUPTED)
|
||||
return;
|
||||
|
||||
// Game crashed before opening the game window.
|
||||
if (!lwjgl) finishLaunch();
|
||||
|
||||
if (exitType != ExitType.NORMAL && logWindow == null)
|
||||
Platform.runLater(() -> {
|
||||
logWindow = new LogWindow();
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
@@ -146,15 +147,17 @@ public final class Profile implements Observable {
|
||||
}
|
||||
|
||||
private void checkSelectedVersion() {
|
||||
if (!repository.isLoaded()) return;
|
||||
String newValue = selectedVersion.get();
|
||||
if (!repository.hasVersion(newValue)) {
|
||||
Optional<String> version = repository.getVersions().stream().findFirst().map(Version::getId);
|
||||
if (version.isPresent())
|
||||
selectedVersion.setValue(version.get());
|
||||
else if (newValue != null)
|
||||
selectedVersion.setValue(null);
|
||||
}
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (!repository.isLoaded()) return;
|
||||
String newValue = selectedVersion.get();
|
||||
if (!repository.hasVersion(newValue)) {
|
||||
Optional<String> version = repository.getVersions().stream().findFirst().map(Version::getId);
|
||||
if (version.isPresent())
|
||||
selectedVersion.setValue(version.get());
|
||||
else if (newValue != null)
|
||||
selectedVersion.setValue(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public HMCLGameRepository getRepository() {
|
||||
|
||||
@@ -30,6 +30,7 @@ import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
@@ -42,16 +43,24 @@ import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.versions.GameItem;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.MultiStepBinding;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -87,7 +96,8 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
btnLaunch.setClip(new Rectangle(-100, -100, 310, 200));
|
||||
btnMenu.setClip(new Rectangle(211, -100, 100, 200));
|
||||
menu.setMaxHeight(365);
|
||||
menu.setMinWidth(545);
|
||||
menu.setMaxWidth(545);
|
||||
menu.setAlwaysShowingVBar(true);
|
||||
|
||||
updatePane.visibleProperty().bind(UpdateChecker.outdatedProperty());
|
||||
closeUpdateButton.setGraphic(SVG.close(Theme.whiteFillBinding(), 10, 10));
|
||||
@@ -126,6 +136,29 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
});
|
||||
|
||||
Profiles.registerVersionsListener(this::loadVersions);
|
||||
|
||||
setOnDragOver(event -> {
|
||||
if (event.getGestureSource() != this && event.getDragboard().hasFiles()) {
|
||||
if (event.getDragboard().getFiles().stream().anyMatch(it -> "zip".equals(FileUtils.getExtension(it))))
|
||||
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
|
||||
}
|
||||
event.consume();
|
||||
});
|
||||
|
||||
setOnDragDropped(event -> {
|
||||
List<File> files = event.getDragboard().getFiles();
|
||||
if (files != null) {
|
||||
List<File> modpacks = files.stream()
|
||||
.filter(it -> "zip".equals(FileUtils.getExtension(it)))
|
||||
.collect(Collectors.toList());
|
||||
if (!modpacks.isEmpty()) {
|
||||
File modpack = modpacks.get(0);
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(modpack), i18n("install.modpack"));
|
||||
event.setDropCompleted(true);
|
||||
}
|
||||
}
|
||||
event.consume();
|
||||
});
|
||||
}
|
||||
|
||||
private void loadVersions(Profile profile) {
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.When;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Node;
|
||||
@@ -31,6 +34,7 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
public class PopupMenu extends Control {
|
||||
|
||||
private final ObservableList<Node> content = FXCollections.observableArrayList();
|
||||
private final BooleanProperty alwaysShowingVBar = new SimpleBooleanProperty();
|
||||
|
||||
public PopupMenu() {
|
||||
}
|
||||
@@ -39,6 +43,18 @@ public class PopupMenu extends Control {
|
||||
return content;
|
||||
}
|
||||
|
||||
public boolean isAlwaysShowingVBar() {
|
||||
return alwaysShowingVBar.get();
|
||||
}
|
||||
|
||||
public BooleanProperty alwaysShowingVBarProperty() {
|
||||
return alwaysShowingVBar;
|
||||
}
|
||||
|
||||
public void setAlwaysShowingVBar(boolean alwaysShowingVBar) {
|
||||
this.alwaysShowingVBar.set(alwaysShowingVBar);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new PopupMenuSkin();
|
||||
@@ -52,6 +68,9 @@ public class PopupMenu extends Control {
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
scrollPane.setFitToHeight(true);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.vbarPolicyProperty().bind(new When(alwaysShowingVBar)
|
||||
.then(ScrollPane.ScrollBarPolicy.ALWAYS)
|
||||
.otherwise(ScrollPane.ScrollBarPolicy.AS_NEEDED));
|
||||
|
||||
VBox content = new VBox();
|
||||
content.getStyleClass().add("menu");
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
|
||||
public class DecoratorControl extends Control {
|
||||
public class Decorator extends Control {
|
||||
private final ListProperty<Node> drawer = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final ListProperty<Node> content = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final ListProperty<Node> container = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
@@ -49,7 +49,7 @@ public class DecoratorControl extends Control {
|
||||
private final Stage primaryStage;
|
||||
private StackPane drawerWrapper;
|
||||
|
||||
public DecoratorControl(Stage primaryStage) {
|
||||
public Decorator(Stage primaryStage) {
|
||||
this.primaryStage = primaryStage;
|
||||
|
||||
primaryStage.initStyle(StageStyle.UNDECORATED);
|
||||
@@ -69,7 +69,7 @@ import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
public class DecoratorController {
|
||||
private static final String PROPERTY_DIALOG_CLOSE_HANDLER = DecoratorController.class.getName() + ".dialog.closeListener";
|
||||
|
||||
private final DecoratorControl decorator;
|
||||
private final Decorator decorator;
|
||||
private final ImageView welcomeView;
|
||||
private final Navigator navigator;
|
||||
private final Node mainPage;
|
||||
@@ -80,7 +80,7 @@ public class DecoratorController {
|
||||
public DecoratorController(Stage stage, Node mainPage) {
|
||||
this.mainPage = mainPage;
|
||||
|
||||
decorator = new DecoratorControl(stage);
|
||||
decorator = new Decorator(stage);
|
||||
decorator.titleProperty().set(Metadata.TITLE);
|
||||
decorator.setOnCloseButtonAction(Launcher::stopApplication);
|
||||
|
||||
@@ -114,7 +114,7 @@ public class DecoratorController {
|
||||
setupAuthlibInjectorDnD();
|
||||
}
|
||||
|
||||
public DecoratorControl getDecorator() {
|
||||
public Decorator getDecorator() {
|
||||
return decorator;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
public class DecoratorSkin extends SkinBase<DecoratorControl> {
|
||||
public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
private static final SVGGlyph minus = Lang.apply(new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE),
|
||||
glyph -> { glyph.setSize(12, 2); glyph.setTranslateY(4); });
|
||||
|
||||
@@ -62,14 +62,14 @@ public class DecoratorSkin extends SkinBase<DecoratorControl> {
|
||||
*
|
||||
* @param control The control for which this Skin should attach to.
|
||||
*/
|
||||
public DecoratorSkin(DecoratorControl control) {
|
||||
public DecoratorSkin(Decorator control) {
|
||||
super(control);
|
||||
|
||||
primaryStage = control.getPrimaryStage();
|
||||
|
||||
minus.fillProperty().bind(Theme.foregroundFillBinding());
|
||||
|
||||
DecoratorControl skinnable = getSkinnable();
|
||||
Decorator skinnable = getSkinnable();
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.getStyleClass().setAll("jfx-decorator", "resize-border");
|
||||
@@ -146,7 +146,7 @@ public class DecoratorSkin extends SkinBase<DecoratorControl> {
|
||||
titleContainer.setOnMouseMoved(this::onMouseMoved);
|
||||
titleContainer.setPickOnBounds(false);
|
||||
titleContainer.setMinHeight(40);
|
||||
titleContainer.getStyleClass().setAll("jfx-tool-bar");
|
||||
titleContainer.getStyleClass().addAll("jfx-tool-bar", "window-title-bar");
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> allowMove = true);
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED, e -> {
|
||||
if (!isDragging) allowMove = false;
|
||||
@@ -169,9 +169,9 @@ public class DecoratorSkin extends SkinBase<DecoratorControl> {
|
||||
titleWrapper.setCenter(lblTitle);
|
||||
|
||||
Rectangle separator = new Rectangle();
|
||||
separator.getStyleClass().add("separator");
|
||||
separator.heightProperty().bind(titleWrapper.heightProperty());
|
||||
separator.setWidth(1);
|
||||
separator.setFill(Color.GRAY);
|
||||
titleWrapper.setRight(separator);
|
||||
}
|
||||
titleContainer.setLeft(titleWrapper);
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class ModpackInstallWizardProvider implements WizardProvider {
|
||||
private Profile profile;
|
||||
private final File file;
|
||||
|
||||
public ModpackInstallWizardProvider() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public ModpackInstallWizardProvider(File modpackFile) {
|
||||
this.file = modpackFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Map<String, Object> settings) {
|
||||
profile = Profiles.getSelectedProfile();
|
||||
|
||||
if (file != null)
|
||||
settings.put(ModpackPage.MODPACK_FILE, file);
|
||||
settings.put(PROFILE, profile);
|
||||
}
|
||||
|
||||
private Task finishModpackInstallingAsync(Map<String, Object> settings) {
|
||||
if (!settings.containsKey(ModpackPage.MODPACK_FILE))
|
||||
return null;
|
||||
|
||||
File selected = tryCast(settings.get(ModpackPage.MODPACK_FILE), File.class).orElse(null);
|
||||
Modpack modpack = tryCast(settings.get(ModpackPage.MODPACK_MANIFEST), Modpack.class).orElse(null);
|
||||
String name = tryCast(settings.get(ModpackPage.MODPACK_NAME), String.class).orElse(null);
|
||||
if (selected == null || modpack == null || name == null) return null;
|
||||
|
||||
return ModpackHelper.getInstallTask(profile, selected, name, modpack)
|
||||
.then(Task.of(Schedulers.javafx(), () -> profile.setSelectedVersion(name)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_message", i18n("install.failed"));
|
||||
|
||||
return finishModpackInstallingAsync(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
|
||||
switch (step) {
|
||||
case 0:
|
||||
return new ModpackPage(controller);
|
||||
default:
|
||||
throw new IllegalStateException("error step " + step + ", settings: " + settings + ", pages: " + controller.getPages());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static final String PROFILE = "PROFILE";
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.WebStage;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
@@ -39,7 +40,9 @@ import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class ModpackPage extends StackPane implements WizardPage {
|
||||
@@ -75,31 +78,41 @@ public final class ModpackPage extends StackPane implements WizardPage {
|
||||
|
||||
Profile profile = (Profile) controller.getSettings().get("PROFILE");
|
||||
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("modpack.choose"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile == null) Platform.runLater(controller::onEnd);
|
||||
else {
|
||||
File selectedFile;
|
||||
|
||||
Optional<File> filePath = tryCast(controller.getSettings().get(MODPACK_FILE), File.class);
|
||||
if (filePath.isPresent()) {
|
||||
selectedFile = filePath.get();
|
||||
} else {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("modpack.choose"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
|
||||
selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile == null) {
|
||||
Platform.runLater(controller::onEnd);
|
||||
return;
|
||||
}
|
||||
|
||||
controller.getSettings().put(MODPACK_FILE, selectedFile);
|
||||
}
|
||||
|
||||
try {
|
||||
manifest = ModpackHelper.readModpackManifest(selectedFile);
|
||||
controller.getSettings().put(MODPACK_MANIFEST, manifest);
|
||||
lblName.setText(manifest.getName());
|
||||
lblVersion.setText(manifest.getVersion());
|
||||
lblAuthor.setText(manifest.getAuthor());
|
||||
txtModpackName.setText(manifest.getName() + (StringUtils.isBlank(manifest.getVersion()) ? "" : "-" + manifest.getVersion()));
|
||||
|
||||
lblModpackLocation.setText(selectedFile.getAbsolutePath());
|
||||
txtModpackName.getValidators().addAll(
|
||||
new Validator(i18n("install.new_game.already_exists"), str -> !profile.getRepository().hasVersion(str) && StringUtils.isNotBlank(str)),
|
||||
new Validator(i18n("version.forbidden_name"), str -> !profile.getRepository().forbidsVersion(str))
|
||||
);
|
||||
txtModpackName.textProperty().addListener(e -> btnInstall.setDisable(!txtModpackName.validate()));
|
||||
|
||||
try {
|
||||
manifest = ModpackHelper.readModpackManifest(selectedFile);
|
||||
controller.getSettings().put(MODPACK_MANIFEST, manifest);
|
||||
lblName.setText(manifest.getName());
|
||||
lblVersion.setText(manifest.getVersion());
|
||||
lblAuthor.setText(manifest.getAuthor());
|
||||
txtModpackName.setText(manifest.getName() + (StringUtils.isBlank(manifest.getVersion()) ? "" : "-" + manifest.getVersion()));
|
||||
} catch (UnsupportedModpackException e) {
|
||||
txtModpackName.setText(i18n("modpack.task.install.error"));
|
||||
btnInstall.setDisable(true);
|
||||
}
|
||||
} catch (UnsupportedModpackException e) {
|
||||
Controllers.dialog(i18n("modpack.task.install.error"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
Platform.runLater(controller::onEnd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-34
@@ -21,8 +21,6 @@ import javafx.scene.Node;
|
||||
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;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
@@ -30,18 +28,14 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class DownloadWizardProvider implements WizardProvider {
|
||||
public final class VanillaInstallWizardProvider implements WizardProvider {
|
||||
private Profile profile;
|
||||
private final int type;
|
||||
|
||||
public DownloadWizardProvider(int type) {
|
||||
this.type = type;
|
||||
public VanillaInstallWizardProvider() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,29 +64,12 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
.then(Task.of(Schedulers.javafx(), () -> profile.setSelectedVersion(name)));
|
||||
}
|
||||
|
||||
private Task finishModpackInstallingAsync(Map<String, Object> settings) {
|
||||
if (!settings.containsKey(ModpackPage.MODPACK_FILE))
|
||||
return null;
|
||||
|
||||
File selected = tryCast(settings.get(ModpackPage.MODPACK_FILE), File.class).orElse(null);
|
||||
Modpack modpack = tryCast(settings.get(ModpackPage.MODPACK_MANIFEST), Modpack.class).orElse(null);
|
||||
String name = tryCast(settings.get(ModpackPage.MODPACK_NAME), String.class).orElse(null);
|
||||
if (selected == null || modpack == null || name == null) return null;
|
||||
|
||||
return ModpackHelper.getInstallTask(profile, selected, name, modpack)
|
||||
.then(Task.of(Schedulers.javafx(), () -> profile.setSelectedVersion(name)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_message", i18n("install.failed"));
|
||||
|
||||
switch (type) {
|
||||
case 0: return finishVersionDownloadingAsync(settings);
|
||||
case 1: return finishModpackInstallingAsync(settings);
|
||||
default: return null;
|
||||
}
|
||||
return finishVersionDownloadingAsync(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -100,14 +77,7 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
DownloadProvider provider = profile.getDependency().getDownloadProvider();
|
||||
switch (step) {
|
||||
case 0:
|
||||
switch (type) {
|
||||
case 0:
|
||||
return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.game")), "", provider, "game", () -> controller.onNext(new InstallersPage(controller, profile.getRepository(), provider)));
|
||||
case 1:
|
||||
return new ModpackPage(controller);
|
||||
default:
|
||||
throw new IllegalStateException("Error step " + step + ", subStep " + type + ", settings: " + settings + ", pages: " + controller.getPages());
|
||||
}
|
||||
return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.game")), "", provider, "game", () -> controller.onNext(new InstallersPage(controller, profile.getRepository(), provider)));
|
||||
default:
|
||||
throw new IllegalStateException("error step " + step + ", settings: " + settings + ", pages: " + controller.getPages());
|
||||
}
|
||||
@@ -33,7 +33,8 @@ import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.download.VanillaInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
@@ -99,11 +100,11 @@ public class GameList extends Control implements DecoratorPage {
|
||||
}
|
||||
|
||||
public void addNewGame() {
|
||||
Controllers.getDecorator().startWizard(new DownloadWizardProvider(0), i18n("install.new_game"));
|
||||
Controllers.getDecorator().startWizard(new VanillaInstallWizardProvider(), i18n("install.new_game"));
|
||||
}
|
||||
|
||||
public void importModpack() {
|
||||
Controllers.getDecorator().startWizard(new DownloadWizardProvider(1), i18n("install.modpack"));
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(), i18n("install.modpack"));
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
|
||||
@@ -28,7 +28,9 @@ import javafx.scene.control.Tab;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
|
||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||
@@ -62,6 +64,8 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
@FXML
|
||||
private JFXButton btnExport;
|
||||
@FXML
|
||||
private JFXButton btnTestGame;
|
||||
@FXML
|
||||
private StackPane rootPane;
|
||||
@FXML
|
||||
private StackPane contentPane;
|
||||
@@ -102,6 +106,9 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
FXUtils.installTooltip(btnManagementMenu, i18n("settings.game.management"));
|
||||
FXUtils.installTooltip(btnExport, i18n("modpack.export"));
|
||||
|
||||
btnTestGame.setGraphic(SVG.launch(Theme.whiteFillBinding(), 20, 20));
|
||||
FXUtils.installTooltip(btnTestGame, i18n("version.launch.test"));
|
||||
|
||||
setEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
|
||||
}
|
||||
|
||||
@@ -134,6 +141,11 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
load(this.version, this.profile);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onTestGame() {
|
||||
Versions.testGame(profile, version);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onBrowseMenu() {
|
||||
browsePopup.show(btnBrowseMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, btnBrowseMenu.getHeight());
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.root {
|
||||
-fx-base-color: #5c6bc0;
|
||||
-fx-base-darker-color: derive(-fx-base-color, -10%);
|
||||
-fx-base-check-color: derive(-fx-base-color, 30%);
|
||||
-fx-base-text-fill: white;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
.root {
|
||||
-fx-base-color: %base-color%;
|
||||
-fx-base-darker-color: derive(-fx-base-color, -10%);
|
||||
-fx-base-check-color: derive(-fx-base-color, 30%);
|
||||
-fx-base-text-fill: %font-color%;
|
||||
}
|
||||
@@ -104,6 +104,14 @@
|
||||
-jfx-subtitle-fill: gray;
|
||||
}
|
||||
|
||||
.window-title-bar .separator {
|
||||
-fx-fill: -fx-base-darker-color;
|
||||
}
|
||||
|
||||
.darker-fill {
|
||||
-fx-fill: -fx-base-darker-color;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* JFX Tab Pane *
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</VBox>
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
<Rectangle fx:id="separator" translateX="95" height="57" fill="gray" width="1" mouseTransparent="true"/>
|
||||
<Rectangle fx:id="separator" translateX="95" height="57" styleClass="darker-fill" width="1" mouseTransparent="true"/>
|
||||
<JFXButton prefWidth="230" prefHeight="55" buttonType="RAISED" styleClass="jfx-button-raised"
|
||||
style="-fx-font-size: 15;" onMouseClicked="#onMenu" fx:id="btnMenu" />
|
||||
</StackPane>
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
</JFXTabPane>
|
||||
|
||||
<HBox alignment="TOP_RIGHT" style="-fx-padding: 4;" spacing="3" pickOnBounds="false">
|
||||
<JFXButton fx:id="btnTestGame" maxHeight="40.0" minHeight="40.0" onMouseClicked="#onTestGame"
|
||||
styleClass="toggle-icon3" />
|
||||
<JFXButton fx:id="btnBrowseMenu" maxHeight="40.0" minHeight="40.0" onMouseClicked="#onBrowseMenu"
|
||||
styleClass="toggle-icon3">
|
||||
<graphic>
|
||||
|
||||
@@ -140,6 +140,7 @@ lang=English
|
||||
lang.default=Use system language
|
||||
|
||||
launch.advice.different_platform=Your OS is 64-Bit but your Java is 32-Bit. The 64-Bit Java is recommended.
|
||||
launch.advice.forge2760_liteloader=Forge 2760 and above is not compatible with LiteLoader, please consider downgrade Forge to 2759 or earlier.
|
||||
launch.advice.java8_1_13=Minecraft 1.13 and later versions can only run on Java 8 or later.
|
||||
launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 earlier than 1.8.0_51. You are supposed to install the latest version of Java 8.
|
||||
launch.advice.java9=You cannot launch Minecraft until game version is higher than 1.13 with Java 9 or later Java versions.
|
||||
@@ -217,11 +218,12 @@ modpack.name=Modpack Name
|
||||
modpack.not_a_valid_name=Not a valid modpack name
|
||||
modpack.scan=Scanning this modpack
|
||||
modpack.task.install=Import Modpack
|
||||
modpack.task.install.error=Failed to install the modpack. Maybe the files is incorrect, or a management issue occurred.
|
||||
modpack.task.install.error=This modpack file cannot be recognized. Only Curse, MultiMC modpacks are supported.
|
||||
modpack.task.install.will=Install the modpack:
|
||||
modpack.type.curse=Curse
|
||||
modpack.type.curse.completion=Install relative files to Curse modpack
|
||||
modpack.type.curse.error=Unable to complete this Curse modpack. Please retry.
|
||||
modpack.type.curse.not_found=Some of required resources are deleted and cannot be downloaded. Please consider the latest version or other modpacks.
|
||||
modpack.type.hmcl=HMCL
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.unsupported=Unsupported modpack, only HMCL, MultiMC, Curse modpacks are supported.
|
||||
|
||||
@@ -140,6 +140,7 @@ lang=正體中文
|
||||
lang.default=跟隨系統語言
|
||||
|
||||
launch.advice.different_platform=您的系統為 64 位元,但是 Java 是 32 位元,建議您安裝 64 位 Java。
|
||||
launch.advice.forge2760_liteloader=Forge 2760 與 LiteLoader 不相容。請刪除 LiteLoader 或者更換 Forge 至 2759 或更舊的版本。
|
||||
launch.advice.java8_1_13=Minecraft 1.13 只支援 Java 8 或更高版本,請使用 Java 8 或最新版本。
|
||||
launch.advice.java8_51_1_13=低於 1.8.0_51 的 Java 版本可能會導致 Minecraft 1.13 崩潰。建議您到 https://java.com 安裝最新版的 Java 8。
|
||||
launch.advice.java9=低於 (包含) 1.13 的有安裝 Mod Minecraft 版本 不支援 Java 9 或更高版本,請使用 Java 8。
|
||||
@@ -217,11 +218,12 @@ modpack.name=整合包名稱
|
||||
modpack.not_a_valid_name=整合包名稱無效
|
||||
modpack.scan=解析整合包
|
||||
modpack.task.install=匯入整合包
|
||||
modpack.task.install.error=安裝失敗,可能是整合包格式錯誤或操作檔案失敗。
|
||||
modpack.task.install.error=無法識別該整合包,目前僅支持導入 Curse、MultiMC、HMCL 整合包。
|
||||
modpack.task.install.will=將會安裝整合包:
|
||||
modpack.type.curse=Curse
|
||||
modpack.type.curse.completion=下載 Curse 整合包相關檔案
|
||||
modpack.type.curse.error=無法完成 Curse 整合包的下載,請多次重試或設定代理
|
||||
modpack.type.curse.not_found=部分必需檔案已經從網路中被刪除並且再也無法下載,請嘗試該整合包的最新版本或者安裝其他整合包。
|
||||
modpack.type.hmcl=HMCL
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.unsupported=不支援該整合包。僅 HMCL、MultiMC、Curse 整合包受支援。
|
||||
|
||||
@@ -140,6 +140,7 @@ lang=简体中文
|
||||
lang.default=跟随系统语言
|
||||
|
||||
launch.advice.different_platform=您的系统是 64 位,但是 Java 是 32 位的,建议您安装 64 位 Java。
|
||||
launch.advice.forge2760_liteloader=Forge 2760 与 LiteLoader 不兼容。请删除 LiteLoader 或者更换 Forge 至 2759 或更旧的版本。
|
||||
launch.advice.java8_1_13=Minecraft 1.13 只支持 Java 8 或更高版本,请使用 Java 8 或最新版本。
|
||||
launch.advice.java8_51_1_13=低于 1.8.0_51 的 Java 版本可能会导致 Minecraft 1.13 崩溃。建议您到 https://java.com 安装最新版的 Java 8。
|
||||
launch.advice.java9=低于 1.13 的有安装 Mod 的 Minecraft 版本不支持 Java 9 或更高版本,请使用 Java 8。
|
||||
@@ -217,11 +218,12 @@ modpack.name=整合包名称
|
||||
modpack.not_a_valid_name=不是一个有效的整合包名称
|
||||
modpack.scan=解析整合包
|
||||
modpack.task.install=导入整合包
|
||||
modpack.task.install.error=安装失败,可能是整合包格式不正确或操作文件失败。
|
||||
modpack.task.install.error=无法识别该整合包,目前仅支持导入 Curse、MultiMC、HMCL 整合包。
|
||||
modpack.task.install.will=将会安装整合包:
|
||||
modpack.type.curse=Curse
|
||||
modpack.type.curse.completion=下载 Curse 整合包相关文件
|
||||
modpack.type.curse.error=未能完成 Curse 整合包的下载,请多次重试或设置代理
|
||||
modpack.type.curse.not_found=部分必需文件已经在网络中被删除并且再也无法下载,请尝试该整合包的最新版本或者安装其他整合包。
|
||||
modpack.type.hmcl=HMCL
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.unsupported=该整合包不被支持。仅 HMCL、MultiMC、Curse 整合包受支持。
|
||||
|
||||
@@ -35,9 +35,12 @@ public class LibraryDownloadTask extends Task {
|
||||
protected final Library library;
|
||||
protected final String url;
|
||||
protected boolean xz;
|
||||
private final Library originalLibrary;
|
||||
private boolean cached = false;
|
||||
|
||||
public LibraryDownloadTask(AbstractDependencyManager dependencyManager, File file, Library library) {
|
||||
this.originalLibrary = library;
|
||||
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
|
||||
if (library.is("net.minecraftforge", "forge"))
|
||||
@@ -86,7 +89,7 @@ public class LibraryDownloadTask extends Task {
|
||||
|
||||
@Override
|
||||
public void preExecute() throws Exception {
|
||||
Optional<Path> libPath = cacheRepository.getLibrary(library);
|
||||
Optional<Path> libPath = cacheRepository.getLibrary(originalLibrary);
|
||||
if (libPath.isPresent()) {
|
||||
try {
|
||||
FileUtils.copyFile(libPath.get().toFile(), jar);
|
||||
|
||||
@@ -27,10 +27,12 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
@@ -103,6 +105,7 @@ public final class CurseCompletionTask extends Task {
|
||||
|
||||
AtomicBoolean flag = new AtomicBoolean(true);
|
||||
AtomicInteger finished = new AtomicInteger(0);
|
||||
AtomicBoolean notFound = new AtomicBoolean(false);
|
||||
|
||||
// Because in China, Curse is too difficult to visit,
|
||||
// if failed, ignore it and retry next time.
|
||||
@@ -113,6 +116,9 @@ public final class CurseCompletionTask extends Task {
|
||||
if (StringUtils.isBlank(file.getFileName())) {
|
||||
try {
|
||||
return file.withFileName(NetworkUtils.detectFileName(file.getUrl()));
|
||||
} catch (FileNotFoundException e) {
|
||||
notFound.set(true);
|
||||
return file;
|
||||
} catch (IOException ioe) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to fetch the file name of URL: " + file.getUrl(), ioe);
|
||||
flag.set(false);
|
||||
@@ -132,9 +138,13 @@ public final class CurseCompletionTask extends Task {
|
||||
}
|
||||
|
||||
// Let this task fail if the curse manifest has not been completed.
|
||||
if (!flag.get())
|
||||
// But continue other downloads.
|
||||
if (!flag.get() || notFound.get())
|
||||
dependencies.add(Task.of(() -> {
|
||||
throw new CurseCompletionException();
|
||||
if (notFound.get())
|
||||
throw new CurseCompletionException(new FileNotFoundException());
|
||||
else
|
||||
throw new CurseCompletionException();
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -218,9 +218,9 @@ public class FileDownloadTask extends Task {
|
||||
|
||||
HttpURLConnection con = NetworkUtils.createConnection(url);
|
||||
if (checkETag) repository.injectConnection(con);
|
||||
con.connect();
|
||||
con = NetworkUtils.resolveConnection(con);
|
||||
|
||||
if (con.getResponseCode() == 304) {
|
||||
if (con.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
// Handle cache
|
||||
Path cache = repository.getCachedRemoteFile(con);
|
||||
FileUtils.copyFile(cache.toFile(), file);
|
||||
|
||||
@@ -34,6 +34,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -45,6 +46,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public class CacheRepository {
|
||||
private Path commonDirectory;
|
||||
@@ -69,7 +71,7 @@ public class CacheRepository {
|
||||
} else
|
||||
index = new HashMap<>();
|
||||
} catch (IOException | JsonParseException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to read index file", e);
|
||||
LOG.log(Level.WARNING, "Unable to read index file", e);
|
||||
index = new HashMap<>();
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
@@ -189,7 +191,7 @@ public class CacheRepository {
|
||||
Lock writeLock = lock.writeLock();
|
||||
writeLock.lock();
|
||||
try {
|
||||
index.put(url, eTagItem);
|
||||
index.compute(eTagItem.url, updateEntity(eTagItem));
|
||||
saveETagIndex();
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
@@ -208,13 +210,31 @@ public class CacheRepository {
|
||||
Lock writeLock = lock.writeLock();
|
||||
writeLock.lock();
|
||||
try {
|
||||
index.put(url, eTagItem);
|
||||
index.compute(eTagItem.url, updateEntity(eTagItem));
|
||||
saveETagIndex();
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private BiFunction<String, ETagItem, ETagItem> updateEntity(ETagItem newItem) {
|
||||
return (key, oldItem) -> {
|
||||
if (oldItem == null) {
|
||||
return newItem;
|
||||
} else if (oldItem.compareTo(newItem) < 0) {
|
||||
Path cached = getFile(SHA1, oldItem.hash);
|
||||
try {
|
||||
Files.deleteIfExists(cached);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Cannot delete old file");
|
||||
}
|
||||
return newItem;
|
||||
} else {
|
||||
return oldItem;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private final Map<String, ETagItem> joinETagIndexes(Collection<ETagItem>... indexes) {
|
||||
Map<String, ETagItem> eTags = new ConcurrentHashMap<>();
|
||||
@@ -223,12 +243,7 @@ public class CacheRepository {
|
||||
.reduce(Stream.empty(), Stream::concat);
|
||||
|
||||
stream.forEach(eTag -> {
|
||||
eTags.compute(eTag.url, (key, oldValue) -> {
|
||||
if (oldValue == null || oldValue.compareTo(eTag) < 0)
|
||||
return eTag;
|
||||
else
|
||||
return oldValue;
|
||||
});
|
||||
eTags.compute(eTag.url, updateEntity(eTag));
|
||||
});
|
||||
|
||||
return eTags;
|
||||
|
||||
@@ -19,6 +19,8 @@ package org.jackhuang.hmcl.util.io;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
@@ -57,13 +59,76 @@ public final class NetworkUtils {
|
||||
|
||||
public static HttpURLConnection createConnection(URL url) throws IOException {
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setDoInput(true);
|
||||
connection.setUseCaches(false);
|
||||
connection.setConnectTimeout(15000);
|
||||
connection.setReadTimeout(15000);
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see <a href="https://github.com/curl/curl/blob/3f7b1bb89f92c13e69ee51b710ac54f775aab320/lib/transfer.c#L1427-L1461">Curl</a>
|
||||
* @param location
|
||||
* @return
|
||||
*/
|
||||
public static String encodeLocation(String location) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean left = true;
|
||||
for (char ch : location.toCharArray()) {
|
||||
switch (ch) {
|
||||
case ' ':
|
||||
if (left) sb.append("%20");
|
||||
else sb.append('+');
|
||||
break;
|
||||
case '?':
|
||||
left = false;
|
||||
default:
|
||||
if (ch >= 0x80)
|
||||
sb.append(encodeURL(Character.toString(ch)));
|
||||
else
|
||||
sb.append(ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method aims to solve problem when "Location" in stupid server's response is not encoded.
|
||||
* @see <a href="https://github.com/curl/curl/issues/473">Issue with libcurl</a>
|
||||
* @param conn
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static HttpURLConnection resolveConnection(HttpURLConnection conn) throws IOException {
|
||||
int redirect = 0;
|
||||
while (true) {
|
||||
|
||||
conn.setUseCaches(false);
|
||||
conn.setConnectTimeout(15000);
|
||||
conn.setReadTimeout(15000);
|
||||
conn.setInstanceFollowRedirects(false);
|
||||
Map<String, List<String>> properties = conn.getRequestProperties();
|
||||
int code = conn.getResponseCode();
|
||||
if (code >= 300 && code <= 307 && code != 306 && code != 304) {
|
||||
String newURL = conn.getHeaderField("Location");
|
||||
conn.disconnect();
|
||||
|
||||
if (redirect > 20) {
|
||||
throw new IOException("Too much redirects");
|
||||
}
|
||||
|
||||
HttpURLConnection redirected = (HttpURLConnection) new URL(conn.getURL(), encodeLocation(newURL)).openConnection();
|
||||
properties.forEach((key, value) -> value.forEach(element -> redirected.addRequestProperty(key, element)));
|
||||
conn = redirected;
|
||||
++redirect;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
public static String doGet(URL url) throws IOException {
|
||||
return IOUtils.readFullyAsString(createConnection(url).getInputStream());
|
||||
}
|
||||
@@ -110,10 +175,12 @@ public final class NetworkUtils {
|
||||
}
|
||||
|
||||
public static String detectFileName(URL url) throws IOException {
|
||||
HttpURLConnection conn = createConnection(url);
|
||||
conn.connect();
|
||||
if (conn.getResponseCode() / 100 != 2)
|
||||
throw new IOException("Response code " + conn.getResponseCode());
|
||||
HttpURLConnection conn = resolveConnection(createConnection(url));
|
||||
int code = conn.getResponseCode();
|
||||
if (code == 404)
|
||||
throw new FileNotFoundException();
|
||||
if (code / 100 != 2)
|
||||
throw new IOException(url + ": response code " + conn.getResponseCode());
|
||||
|
||||
return detectFileName(conn);
|
||||
}
|
||||
|
||||
@@ -332,4 +332,6 @@ public class VersionNumber implements Comparable<VersionNumber> {
|
||||
public int hashCode() {
|
||||
return canonical.hashCode();
|
||||
}
|
||||
|
||||
public static Comparator<String> VERSION_COMPARATOR = Comparator.comparing(VersionNumber::asVersion);
|
||||
}
|
||||
Reference in new issue
Block a user