This commit is contained in:
huangyuhui 2018-01-09 17:14:50 +08:00
parent 4b65d4da06
commit 8e6b56b2d6
19 changed files with 842 additions and 459 deletions

View File

@ -15,19 +15,31 @@
* 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.ui.construct
package org.jackhuang.hmcl.event;
import com.jfoenix.validation.base.ValidatorBase
import javafx.scene.control.TextInputControl
import org.jackhuang.hmcl.util.VersionNumber;
/**
* @param validator return true if the input string is valid.
*
* Result: Deny if do not upgrade HMCL.
*
* @author huangyuhui
*/
class Validator(val validator: (String) -> Boolean) : ValidatorBase() {
override fun eval() {
if (this.srcControl.get() is TextInputControl) {
val text = (srcControl.get() as TextInputControl).text
hasErrors.set(!validator(text))
}
public final class OutOfDateEvent extends Event {
private final VersionNumber version;
public OutOfDateEvent(Object source, VersionNumber version) {
super(source);
this.version = version;
}
public VersionNumber getVersion() {
return version;
}
@Override
public boolean hasResult() {
return true;
}
}

View File

@ -15,15 +15,21 @@
* 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.game
package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.auth.Account
import org.jackhuang.hmcl.auth.MultiCharacterSelector
import org.jackhuang.hmcl.auth.NoSelectedCharacterException
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.MultiCharacterSelector;
import org.jackhuang.hmcl.auth.NoSelectedCharacterException;
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
object HMCLMultiCharacterSelector : MultiCharacterSelector {
override fun select(account: Account, names: MutableList<GameProfile>): GameProfile {
return names.firstOrNull() ?: throw NoSelectedCharacterException(account)
import java.util.List;
/**
* @author huangyuhui
*/
public final class HMCLMultiCharacterSelector implements MultiCharacterSelector {
@Override
public GameProfile select(Account account, List<GameProfile> names) throws NoSelectedCharacterException {
return names.stream().findFirst().orElseThrow(() -> new NoSelectedCharacterException(account));
}
}

View File

@ -15,10 +15,24 @@
* 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.setting
package org.jackhuang.hmcl.setting;
enum class EnumGameDirectory {
/**
* Determines where game runs in and game files such as mods.
*
* @author huangyuhui
*/
public enum EnumGameDirectory {
/**
* .minecraft
*/
ROOT_FOLDER,
/**
* .minecraft/versions/&lt;version name&gt;
*/
VERSION_FOLDER,
/**
* user customized directory.
*/
CUSTOM
}

View File

@ -15,13 +15,13 @@
* 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.setting
package org.jackhuang.hmcl.setting;
/**
* The visibility of launcher.
* @author huangyuhui
*/
enum class LauncherVisibility {
public enum LauncherVisibility {
/**
* Close the launcher anyway when the game process created even if failed to

View File

@ -15,14 +15,14 @@
* 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.ui.animation
package org.jackhuang.hmcl.ui.animation;
import javafx.scene.Node
import javafx.scene.layout.Pane
import javafx.util.Duration
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import javafx.util.Duration;
interface AnimationHandler {
val snapshot: Node
val duration: Duration
val view: Pane
public interface AnimationHandler {
Node getSnapshot();
Duration getDuration();
Pane getView();
}

View File

@ -0,0 +1,162 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.construct;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.TextInputDialog;
import org.jackhuang.hmcl.MainKt;
import javax.swing.UIManager;
import java.util.Optional;
public final class MessageBox {
private MessageBox() {
}
private static final String TITLE = MainKt.i18n("message.info");
/**
* User Operation: Yes
*/
public static final int YES_OPTION = 0;
/**
* User Operation: No
*/
public static final int NO_OPTION = 1;
/**
* User Operation: Cancel
*/
public static final int CANCEL_OPTION = 2;
/**
* User Operation: OK
*/
public static final int OK_OPTION = 0;
/**
* User Operation: Closed Message Box
*/
public static final int CLOSED_OPTION = -1;
/**
* Buttons: Yes No
*/
public static final int YES_NO_OPTION = 10;
/**
* Buttons: Yes No Cancel
*/
public static final int YES_NO_CANCEL_OPTION = 11;
/**
* Buttons: OK Cancel
*/
public static final int OK_CANCEL_OPTION = 12;
/**
* Message Box Type: Error
*/
public static final int ERROR_MESSAGE = 0;
/**
* Message Box Type: Info
*/
public static final int INFORMATION_MESSAGE = 1;
/**
* Message Box Type: Warning
*/
public static final int WARNING_MESSAGE = 2;
/**
* Message Box Type: Question
*/
public static final int QUESTION_MESSAGE = 3;
/**
* Message Box Type: Plain
*/
public static final int PLAIN_MESSAGE = -1;
public static void show(String message) {
show(message, TITLE);
}
/**
* Show MsgBox with title and options
*
* @param message The Message
* @param title The title of MsgBox.
* @return user operation.
*/
public static void show(String message, String title) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle(title);
alert.setHeaderText(title);
alert.setContentText(message);
alert.showAndWait();
}
public static int confirm(String message, String title) {
return confirm(message, title, -1);
}
public static int confirm(String message, int option) {
return confirm(message, TITLE, option);
}
public static int confirm(String message, String title, int option) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle(title);
alert.setHeaderText(title);
alert.setContentText(message);
switch (option) {
case YES_NO_OPTION:
alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO);
break;
case YES_NO_CANCEL_OPTION:
alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO, ButtonType.CANCEL);
break;
case OK_CANCEL_OPTION:
alert.getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
break;
}
Optional<ButtonType> buttonType = alert.showAndWait();
if (!buttonType.isPresent()) return CLOSED_OPTION;
else if (buttonType.get() == ButtonType.OK) return OK_OPTION;
else if (buttonType.get() == ButtonType.YES) return YES_OPTION;
else if (buttonType.get() == ButtonType.NO) return NO_OPTION;
else if (buttonType.get() == ButtonType.CANCEL) return CANCEL_OPTION;
else throw new IllegalStateException("Unrecognized button type:" + buttonType.get());
}
public static Optional<String> input(String message) {
return input(message, UIManager.getString("OptionPane.inputDialogTitle"));
}
public static Optional<String> input(String message, String title) {
return input(message, title, "");
}
public static Optional<String> input(String message, String title, String initialValue) {
TextInputDialog dialog = new TextInputDialog(initialValue);
dialog.setTitle(title);
dialog.setHeaderText(message);
dialog.setContentText(message);
return dialog.showAndWait();
}
}

View File

@ -0,0 +1,62 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.construct;
import kotlin.jvm.Throws;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.Locale;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
public class UTF8Control extends ResourceBundle.Control {
@Override
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
throws IllegalAccessException, InstantiationException, IOException {
// The below is a copy of the default implementation.
String bundleName = toBundleName(baseName, locale);
String resourceName = toResourceName(bundleName, "properties");
ResourceBundle bundle = null;
InputStream stream = null;
if (reload) {
URL url = loader.getResource(resourceName);
if (url != null) {
URLConnection connection = url.openConnection();
if (connection != null) {
connection.setUseCaches(false);
stream = connection.getInputStream();
}
}
} else {
stream = loader.getResourceAsStream(resourceName);
}
if (stream != null) {
try {
// Only this line is changed to make it to read properties files as UTF-8.
bundle = new PropertyResourceBundle(new InputStreamReader(stream, "UTF-8"));
} finally {
stream.close();
}
}
return bundle;
}
}

View File

@ -0,0 +1,42 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.validation.base.ValidatorBase;
import javafx.scene.control.TextInputControl;
import java.util.function.Predicate;
public final class Validator extends ValidatorBase {
private final Predicate<String> validator;
/**
* @param validator return true if the input string is valid.
*/
public Validator(Predicate<String> validator) {
this.validator = validator;
}
@Override
protected void eval() {
if (this.srcControl.get() instanceof TextInputControl) {
String text = ((TextInputControl) srcControl.get()).getText();
hasErrors.set(!validator.test(text));
}
}
}

View File

@ -15,25 +15,36 @@
* 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.ui.wizard
package org.jackhuang.hmcl.ui.wizard;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
interface Navigation {
fun onStart()
fun onNext()
fun onPrev(cleanUp: Boolean)
fun canPrev(): Boolean
fun onFinish()
fun onEnd()
fun onCancel()
public interface Navigation {
enum class NavigationDirection(val animation: ContainerAnimations) {
void onStart();
void onNext();
void onPrev(boolean cleanUp);
boolean canPrev();
void onFinish();
void onEnd();
void onCancel();
enum NavigationDirection {
START(ContainerAnimations.NONE),
PREVIOUS(ContainerAnimations.SWIPE_RIGHT),
NEXT(ContainerAnimations.SWIPE_LEFT),
FINISH(ContainerAnimations.SWIPE_LEFT),
IN(ContainerAnimations.ZOOM_IN),
OUT(ContainerAnimations.ZOOM_OUT)
OUT(ContainerAnimations.ZOOM_OUT);
private final ContainerAnimations animation;
NavigationDirection(ContainerAnimations animation) {
this.animation = animation;
}
public ContainerAnimations getAnimation() {
return animation;
}
}
}

View File

@ -15,8 +15,8 @@
* 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.ui.wizard
package org.jackhuang.hmcl.ui.wizard;
interface Refreshable {
fun refresh()
public interface Refreshable {
void refresh();
}

View File

@ -0,0 +1,246 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.upgrade;
import com.google.gson.JsonSyntaxException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.Proxy;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.*;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Pack200;
import java.util.logging.Level;
import java.util.zip.GZIPInputStream;
import com.google.gson.reflect.TypeToken;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.MainKt;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.util.*;
/**
*
* @author huangyuhui
*/
public class AppDataUpgrader extends IUpgrader {
private boolean launchNewerVersion(List<String> args, File jar) throws IOException, PrivilegedActionException {
try (JarFile jarFile = new JarFile(jar)) {
String mainClass = jarFile.getManifest().getMainAttributes().getValue("Main-Class");
if (mainClass != null) {
ArrayList<String> al = new ArrayList<>(args);
al.add("--noupdate");
AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
new URLClassLoader(new URL[]{jar.toURI().toURL()},
URLClassLoader.getSystemClassLoader().getParent()).loadClass(mainClass)
.getMethod("main", String[].class).invoke(null, new Object[]{al.toArray(new String[0])});
return null;
});
return true;
}
}
return false;
}
@Override
public void parseArguments(VersionNumber nowVersion, List<String> args) {
File f = AppDataUpgraderPackGzTask.HMCL_VER_FILE;
if (!args.contains("--noupdate"))
try {
if (f.exists()) {
Map<String, String> m = Constants.GSON.fromJson(FileUtils.readText(f), new TypeToken<Map<String, String>>() {
}.getType());
String s = m.get("ver");
if (s != null && VersionNumber.asVersion(s).compareTo(nowVersion) > 0) {
String j = m.get("loc");
if (j != null) {
File jar = new File(j);
if (jar.exists() && launchNewerVersion(args, jar))
System.exit(0);
}
}
}
} catch (JsonSyntaxException ex) {
f.delete();
} catch (IOException | PrivilegedActionException t) {
Logging.LOG.log(Level.SEVERE, "Failed to execute newer version application", t);
}
}
@Override
public void download(UpdateChecker checker, VersionNumber ver) {
if (!(ver instanceof IntVersionNumber))
return;
IntVersionNumber version = (IntVersionNumber) ver;
checker.requestDownloadLink().then(Task.of(v -> {
Map<String, String> map = v.get(UpdateChecker.REQUEST_DOWNLOAD_LINK_ID);
if (MessageBox.confirm(MainKt.i18n("update.newest_version") + version.get(0) + "." + version.get(1) + "." + version.get(2) + "\n"
+ MainKt.i18n("update.should_open_link"),
MessageBox.YES_NO_OPTION) == MessageBox.YES_OPTION)
if (map != null && map.containsKey("jar") && !StringUtils.isBlank(map.get("jar")))
try {
String hash = null;
if (map.containsKey("jarsha1"))
hash = map.get("jarsha1");
if (TaskWindow.factory().append(new AppDataUpgraderJarTask(NetworkUtils.toURL(map.get("jar")), version.toString(), hash)).execute()) {
new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderJarTask.getSelf(version.toString()).getAbsolutePath())
.directory(new File("").getAbsoluteFile()).start();
System.exit(0);
}
} catch (IOException ex) {
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
}
else if (map != null && map.containsKey("pack") && !StringUtils.isBlank(map.get("pack")))
try {
String hash = null;
if (map.containsKey("packsha1"))
hash = map.get("packsha1");
if (TaskWindow.factory().append(new AppDataUpgraderPackGzTask(NetworkUtils.toURL(map.get("pack")), version.toString(), hash)).execute()) {
new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderPackGzTask.getSelf(version.toString()).getAbsolutePath())
.directory(new File("").getAbsoluteFile()).start();
System.exit(0);
}
} catch (IOException ex) {
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
}
else {
String url = URL_PUBLISH;
if (map != null)
if (map.containsKey(OperatingSystem.CURRENT_OS.getCheckedName()))
url = map.get(OperatingSystem.CURRENT_OS.getCheckedName());
else if (map.containsKey(OperatingSystem.UNKNOWN.getCheckedName()))
url = map.get(OperatingSystem.UNKNOWN.getCheckedName());
if (url == null)
url = URL_PUBLISH;
try {
java.awt.Desktop.getDesktop().browse(new URI(url));
} catch (URISyntaxException | IOException e) {
Logging.LOG.log(Level.SEVERE, "Failed to browse uri: " + url, e);
OperatingSystem.setClipboard(url);
MessageBox.show(MainKt.i18n("update.no_browser"));
}
}
})).start();
}
public static class AppDataUpgraderPackGzTask extends Task {
public static final File BASE_FOLDER = Main.getWorkingDirectory("hmcl");
public static final File HMCL_VER_FILE = new File(BASE_FOLDER, "hmclver.json");
public static File getSelf(String ver) {
return new File(BASE_FOLDER, "HMCL-" + ver + ".jar");
}
private final URL downloadLink;
private final String newestVersion, hash;
File tempFile;
public AppDataUpgraderPackGzTask(URL downloadLink, String newestVersion, String hash) throws IOException {
this.downloadLink = downloadLink;
this.newestVersion = newestVersion;
this.hash = hash;
tempFile = File.createTempFile("hmcl", ".pack.gz");
setName("Upgrade");
}
@Override
public Collection<Task> getDependents() {
return Arrays.asList(new FileDownloadTask(downloadLink, tempFile, Proxy.NO_PROXY, hash));
}
@Override
public void execute() throws Exception {
HashMap<String, String> json = new HashMap<>();
File f = getSelf(newestVersion);
if (!FileUtils.makeDirectory(f.getParentFile()))
throw new IOException("Failed to make directories: " + f.getParent());
for (int i = 0; f.exists(); i++)
f = new File(BASE_FOLDER, "HMCL-" + newestVersion + (i > 0 ? "-" + i : "") + ".jar");
if (!f.createNewFile())
throw new IOException("Failed to create new file: " + f);
try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(f))) {
Pack200.newUnpacker().unpack(new GZIPInputStream(new FileInputStream(tempFile)), jos);
}
json.put("ver", newestVersion);
json.put("loc", f.getAbsolutePath());
String result = Constants.GSON.toJson(json);
FileUtils.writeText(HMCL_VER_FILE, result);
}
}
public static class AppDataUpgraderJarTask extends Task {
public static final File BASE_FOLDER = Main.getWorkingDirectory("hmcl");
public static final File HMCL_VER_FILE = new File(BASE_FOLDER, "hmclver.json");
public static File getSelf(String ver) {
return new File(BASE_FOLDER, "HMCL-" + ver + ".jar");
}
private final URL downloadLink;
private final String newestVersion, hash;
File tempFile;
public AppDataUpgraderJarTask(URL downloadLink, String newestVersion, String hash) throws IOException {
this.downloadLink = downloadLink;
this.newestVersion = newestVersion;
this.hash = hash;
tempFile = File.createTempFile("hmcl", ".jar");
setName("Upgrade");
}
@Override
public Collection<Task> getDependents() {
return Collections.singleton(new FileDownloadTask(downloadLink, tempFile, Proxy.NO_PROXY, hash));
}
@Override
public void execute() throws Exception {
HashMap<String, String> json = new HashMap<>();
File f = getSelf(newestVersion);
FileUtils.copyFile(tempFile, f);
json.put("ver", newestVersion);
json.put("loc", f.getAbsolutePath());
String result = Constants.GSON.toJson(json);
FileUtils.writeText(HMCL_VER_FILE, result);
}
}
public static final String URL_PUBLISH = "http://www.mcbbs.net/thread-142335-1-1.html";
public static final String URL_CONTACT = "http://huangyuhui.duapp.com/hmcl.php";
}

View File

@ -15,15 +15,19 @@
* 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.upgrade
package org.jackhuang.hmcl.upgrade;
import org.jackhuang.hmcl.util.VersionNumber
import org.jackhuang.hmcl.util.VersionNumber;
import java.util.List;
/**
*
* @author huangyuhui
*/
interface IUpgrader {
public abstract class IUpgrader {
public static final IUpgrader NOW_UPGRADER = new AppDataUpgrader();
/**
* Paring arguments to decide on whether the upgrade is needed.
@ -31,19 +35,15 @@ interface IUpgrader {
* @param nowVersion now launcher version
* @param args Application CommandLine Arguments
*/
fun parseArguments(nowVersion: VersionNumber, args: Array<String>)
public abstract void parseArguments(VersionNumber nowVersion, List<String> args);
/**
* Just download the new app.
*
* @param checker Should be VersionChecker
* @param versionNumber the newest version
* @param version the newest version
*
* @return should return true
*/
fun download(checker: UpdateChecker, versionNumber: VersionNumber)
companion object {
val NOW_UPGRADER: IUpgrader = AppDataUpgrader()
}
public abstract void download(UpdateChecker checker, VersionNumber version);
}

View File

@ -0,0 +1,83 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.upgrade;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.util.Charsets;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.VersionNumber;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
/**
*
* @author huangyuhui
*/
public class NewFileUpgrader extends IUpgrader {
@Override
public void parseArguments(VersionNumber nowVersion, List<String> args) {
int i = args.indexOf("--removeOldLauncher");
if (i != -1 && i < args.size() - 1) {
File f = new File(args.get(i + 1));
if (f.exists())
f.deleteOnExit();
}
}
@Override
public void download(UpdateChecker checker, VersionNumber version) {
URL url = requestDownloadLink();
if (url == null) return;
File newf = new File(url.getFile());
if (TaskWindow.factory().append(new FileDownloadTask(url, newf)).execute()) {
try {
new ProcessBuilder(newf.getCanonicalPath(), "--removeOldLauncher", getRealPath())
.directory(new File("").getAbsoluteFile())
.start();
} catch (IOException ex) {
Logging.LOG.log(Level.SEVERE, "Failed to start new app", ex);
}
System.exit(0);
}
}
private static String getRealPath() {
String realPath = NewFileUpgrader.class.getClassLoader().getResource("").getFile();
File file = new File(realPath);
realPath = file.getAbsolutePath();
try {
realPath = java.net.URLDecoder.decode(realPath, Charsets.DEFAULT_CHARSET.name());
} catch (Exception e) {
e.printStackTrace();
}
return realPath;
}
private URL requestDownloadLink() {
return null;
}
}

View File

@ -0,0 +1,139 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.upgrade;
import com.google.gson.JsonSyntaxException;
import org.jackhuang.hmcl.MainKt;
import org.jackhuang.hmcl.event.Event;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.OutOfDateEvent;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.VersionNumber;
import java.io.IOException;
import java.util.Map;
import java.util.logging.Level;
/**
*
* @author huangyuhui
*/
public final class UpdateChecker {
private volatile boolean outOfDate = false;
private VersionNumber base;
private String versionString;
public String type;
private Map<String, String> download_link = null;
public UpdateChecker(VersionNumber base, String type) {
this.base = base;
this.type = type;
}
private VersionNumber value;
public boolean isOutOfDate() {
return outOfDate;
}
/**
* Download the version number synchronously. When you execute this method
* first, should leave "showMessage" false.
*
* @param showMessage If it is requested to warn the user that there is a
* new version.
*
* @return the process observable.
*/
public TaskResult<VersionNumber> process(final boolean showMessage) {
return new TaskResult<VersionNumber>() {
@Override
public void execute() throws Exception {
if (value == null) {
versionString = NetworkUtils.doGet(NetworkUtils.toURL("http://huangyuhui.duapp.com/info.php?type=" + type));
value = VersionNumber.asVersion(versionString);
}
if (value == null) {
Logging.LOG.warning("Failed to check update...");
if (showMessage)
MessageBox.show(MainKt.i18n("update.failed"));
} else if (base.compareTo(value) < 0)
outOfDate = true;
if (outOfDate)
setResult(value);
}
@Override
public String getId() {
return "update_checker.process";
}
};
}
/**
* Get the <b>cached</b> newest version number, use "process" method to
* download!
*
* @return the newest version number
*
* @see #process(boolean)
*/
public VersionNumber getNewVersion() {
return value;
}
/**
* Get the download links.
*
* @return a JSON, which contains the server response.
*/
public synchronized TaskResult<Map<String, String>> requestDownloadLink() {
return new TaskResult<Map<String, String>>() {
@Override
public void execute() throws Exception {
if (download_link == null)
try {
download_link = Constants.GSON.<Map<String, String>>fromJson(NetworkUtils.doGet(NetworkUtils.toURL("http://huangyuhui.duapp.com/update_link.php?type=" + type)), Map.class);
} catch (JsonSyntaxException | IOException e) {
Logging.LOG.log(Level.SEVERE, "Failed to get update link.", e);
}
setResult(download_link);
}
@Override
public String getId() {
return "update_checker.request_download_link";
}
};
}
public static final String REQUEST_DOWNLOAD_LINK_ID = "update_checker.request_download_link";
public void checkOutdate() {
if (outOfDate)
if (EventBus.EVENT_BUS.fireEvent(new OutOfDateEvent(this, getNewVersion())) != Event.Result.DENY) {
IUpgrader.NOW_UPGRADER.download(this, getNewVersion());
}
}
}

View File

@ -70,6 +70,7 @@ class Main : Application() {
launch(Main::class.java, *args)
}
@JvmStatic
fun getWorkingDirectory(folder: String): File {
val userhome = System.getProperty("user.home", ".")
return when (OperatingSystem.CURRENT_OS) {
@ -83,6 +84,7 @@ class Main : Application() {
}
}
@JvmStatic
fun getMinecraftDirectory(): File = getWorkingDirectory("minecraft")
fun stop() = runOnUiThread {

View File

@ -1,55 +0,0 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.construct
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
import java.util.*
object UTF8Control : ResourceBundle.Control() {
@Throws(IllegalAccessException::class, InstantiationException::class, IOException::class)
override fun newBundle(baseName: String, locale: Locale, format: String, loader: ClassLoader, reload: Boolean): ResourceBundle? {
// The below is a copy of the default implementation.
val bundleName = toBundleName(baseName, locale)
val resourceName = toResourceName(bundleName, "properties")
var bundle: ResourceBundle? = null
var stream: InputStream? = null
if (reload) {
val url = loader.getResource(resourceName)
if (url != null) {
val connection = url.openConnection()
if (connection != null) {
connection.useCaches = false
stream = connection.getInputStream()
}
}
} else {
stream = loader.getResourceAsStream(resourceName)
}
if (stream != null) {
try {
// Only this line is changed to make it to read properties files as UTF-8.
bundle = PropertyResourceBundle(InputStreamReader(stream, "UTF-8"))
} finally {
stream.close()
}
}
return bundle
}
}

View File

@ -1,227 +0,0 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.upgrade
import java.security.PrivilegedActionException
import java.io.IOException
import com.google.gson.JsonSyntaxException
import javafx.scene.control.Alert
import java.io.File
import java.net.URLClassLoader
import java.security.PrivilegedExceptionAction
import java.security.AccessController
import java.util.Arrays
import java.util.ArrayList
import java.util.jar.JarFile
import org.jackhuang.hmcl.Main
import java.util.HashMap
import org.jackhuang.hmcl.task.*
import java.util.logging.Level
import java.util.zip.GZIPInputStream
import java.util.jar.Pack200
import java.util.jar.JarOutputStream
import org.jackhuang.hmcl.util.*
import java.net.URISyntaxException
import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.ui.alert
import org.jackhuang.hmcl.util.Constants.GSON
import org.jackhuang.hmcl.util.Logging.LOG
import org.jackhuang.hmcl.util.VersionNumber
import java.net.Proxy
import java.net.URI
class AppDataUpgrader : IUpgrader {
@Throws(IOException::class, PrivilegedActionException::class)
private fun launchNewerVersion(args: Array<String>, jar: File): Boolean {
JarFile(jar).use { jarFile ->
val mainClass = jarFile.manifest.mainAttributes.getValue("Main-Class")
if (mainClass != null) {
val al = ArrayList(Arrays.asList(*args))
al.add("--noupdate")
AccessController.doPrivileged(PrivilegedExceptionAction<Void> {
URLClassLoader(arrayOf(jar.toURI().toURL()),
URLClassLoader.getSystemClassLoader().parent).loadClass(mainClass)
.getMethod("main", Array<String>::class.java).invoke(null, *arrayOf<Any>(al.toTypedArray()))
null
})
return true
}
}
return false
}
override fun parseArguments(nowVersion: VersionNumber, args: Array<String>) {
val f = AppDataUpgraderPackGzTask.HMCL_VER_FILE
if (!args.contains("--noupdate"))
try {
if (f.exists()) {
val m = GSON.fromJson(f.readText(), Map::class.java)
val s = m["ver"] as? String?
if (s != null && VersionNumber.asVersion(s.toString()) > nowVersion) {
val j = m["loc"] as? String?
if (j != null) {
val jar = File(j)
if (jar.exists() && launchNewerVersion(args, jar))
System.exit(0)
}
}
}
} catch (ex: JsonSyntaxException) {
f.delete()
} catch (t: IOException) {
LOG.log(Level.SEVERE, "Failed to execute newer version application", t)
} catch (t: PrivilegedActionException) {
LOG.log(Level.SEVERE, "Failed to execute newer version application", t)
}
}
override fun download(checker: UpdateChecker, versionNumber: VersionNumber) {
val version = versionNumber as IntVersionNumber
checker.requestDownloadLink().then {
val map: Map<String, String>? = it["update_checker.request_download_link"]
if (alert(Alert.AlertType.CONFIRMATION, "Alert", i18n("update.newest_version") + version[0] + "." + version[1] + "." + version[2] + "\n"
+ i18n("update.should_open_link")))
if (map != null && map.containsKey("jar") && map["jar"]!!.isNotBlank())
try {
var hash: String? = null
if (map.containsKey("jarsha1"))
hash = map.get("jarsha1")
if (AppDataUpgraderJarTask(map["jar"]!!, version.toString(), hash!!).test()) {
ProcessBuilder(JavaVersion.fromCurrentEnvironment().binary.absolutePath, "-jar", AppDataUpgraderJarTask.getSelf(version.toString()).absolutePath).directory(File("").absoluteFile).start()
System.exit(0)
}
} catch (ex: IOException) {
LOG.log(Level.SEVERE, "Failed to create upgrader", ex)
}
else if (map != null && map.containsKey("pack") && map["pack"]!!.isNotBlank())
try {
var hash: String? = null
if (map.containsKey("packsha1"))
hash = map["packsha1"]
if (AppDataUpgraderPackGzTask(map["pack"]!!, version.toString(), hash!!).test()) {
ProcessBuilder(JavaVersion.fromCurrentEnvironment().binary.absolutePath, "-jar", AppDataUpgraderPackGzTask.getSelf(version.toString()).absolutePath).directory(File("").absoluteFile).start()
System.exit(0)
}
} catch (ex: IOException) {
LOG.log(Level.SEVERE, "Failed to create upgrader", ex)
}
else {
var url = URL_PUBLISH
if (map != null)
if (map.containsKey(OperatingSystem.CURRENT_OS.checkedName))
url = map.get(OperatingSystem.CURRENT_OS.checkedName)!!
else if (map.containsKey(OperatingSystem.UNKNOWN.checkedName))
url = map.get(OperatingSystem.UNKNOWN.checkedName)!!
try {
java.awt.Desktop.getDesktop().browse(URI(url))
} catch (e: URISyntaxException) {
LOG.log(Level.SEVERE, "Failed to browse uri: " + url, e)
OperatingSystem.setClipboard(url)
} catch (e: IOException) {
LOG.log(Level.SEVERE, "Failed to browse uri: " + url, e)
OperatingSystem.setClipboard(url)
}
}
null
}.execute()
}
class AppDataUpgraderPackGzTask(downloadLink: String, private val newestVersion: String, private val expectedHash: String) : Task() {
private val tempFile: File = File.createTempFile("hmcl", ".pack.gz")
private val dependents = listOf(FileDownloadTask(downloadLink.toURL(), tempFile, Proxy.NO_PROXY, expectedHash))
override fun getDependents() = dependents
init {
onDone() += { event -> if (event.isFailed) tempFile.delete() }
}
override fun execute() {
val json = HashMap<String, String>()
var f = getSelf(newestVersion)
if (!FileUtils.makeDirectory(f.parentFile))
throw IOException("Failed to make directories: " + f.parent)
var i = 0
while (f.exists()) {
f = File(BASE_FOLDER, "HMCL-" + newestVersion + (if (i > 0) "-" + i else "") + ".jar")
i++
}
if (!f.createNewFile())
throw IOException("Failed to create new file: " + f)
JarOutputStream(f.outputStream()).use { jos -> Pack200.newUnpacker().unpack(GZIPInputStream(tempFile.inputStream()), jos) }
json.put("ver", newestVersion)
json.put("loc", f.absolutePath)
val result = GSON.toJson(json)
HMCL_VER_FILE.writeText(result)
}
val info: String
get() = "Upgrade"
companion object {
val BASE_FOLDER = Main.getWorkingDirectory("hmcl")
val HMCL_VER_FILE = File(BASE_FOLDER, "hmclver.json")
fun getSelf(ver: String): File {
return File(BASE_FOLDER, "HMCL-$ver.jar")
}
}
}
class AppDataUpgraderJarTask(downloadLink: String, private val newestVersion: String, expectedHash: String) : Task() {
private val tempFile = File.createTempFile("hmcl", ".jar")
init {
name = "Upgrade"
onDone() += { event -> if (event.isFailed) tempFile.delete() }
}
private val dependents = listOf(FileDownloadTask(downloadLink.toURL(), tempFile, Proxy.NO_PROXY, expectedHash))
override fun getDependents() = dependents
override fun execute() {
val json = HashMap<String, String>()
val f = getSelf(newestVersion)
tempFile.copyTo(f)
json.put("ver", newestVersion)
json.put("loc", f.absolutePath)
val result = GSON.toJson(json)
HMCL_VER_FILE.writeText(result)
}
companion object {
val BASE_FOLDER = Main.getWorkingDirectory("hmcl")
val HMCL_VER_FILE = File(BASE_FOLDER, "hmclver.json")
fun getSelf(ver: String): File {
return File(BASE_FOLDER, "HMCL-$ver.jar")
}
}
}
companion object {
const val URL_PUBLISH = "http://www.mcbbs.net/thread-142335-1-1.html"
}
}

View File

@ -1,112 +0,0 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.upgrade
import java.io.IOException
import com.google.gson.JsonSyntaxException
import org.jackhuang.hmcl.task.TaskResult
import org.jackhuang.hmcl.util.*
import org.jackhuang.hmcl.util.Constants.GSON
import org.jackhuang.hmcl.util.Logging.LOG
import java.util.logging.Level
/**
*
* @author huangyuhui
*/
class UpdateChecker(var base: VersionNumber, var type: String) {
@Volatile
var isOutOfDate = false
private set
var versionString: String? = null
private var download_link: Map<String, String>? = null
/**
* Get the <b>cached</b> newest version number, use "process" method to
* download!
*
* @return the newest version number
*
* @see process
*/
var newVersion: VersionNumber? = null
internal set
/**
* Download the version number synchronously. When you execute this method
* first, should leave "showMessage" false.
*
* @param showMessage If it is requested to warn the user that there is a
* new version.
*
* @return the process observable.
*/
fun process(showMessage: Boolean): TaskResult<VersionNumber> {
return object : TaskResult<VersionNumber>() {
override fun getId() = "update_checker.process"
override fun execute() {
if (newVersion == null) {
versionString = NetworkUtils.doGet("http://huangyuhui.duapp.com/info.php?type=$type".toURL())
newVersion = VersionNumber.asVersion(versionString!!)
}
if (newVersion == null) {
LOG.warning("Failed to check update...")
} else if (base < newVersion!!)
isOutOfDate = true
if (isOutOfDate)
result = newVersion
}
}
}
/**
* Get the download links.
*
* @return a JSON, which contains the server response.
*/
@Synchronized
fun requestDownloadLink(): TaskResult<Map<String, String>> {
return object : TaskResult<Map<String, String>>() {
override fun getId() = "update_checker.request_download_link"
override fun execute() {
@Suppress("UNCHECKED_CAST")
if (download_link == null)
try {
download_link = GSON.fromJson(NetworkUtils.doGet("http://huangyuhui.duapp.com/update_link.php?type=$type".toURL()), Map::class.java) as Map<String, String>
} catch (e: JsonSyntaxException) {
LOG.log(Level.WARNING, "Failed to get update link.", e)
} catch (e: IOException) {
LOG.log(Level.WARNING, "Failed to get update link.", e)
}
result = download_link
}
}
}
/*
val upgrade: EventHandler<SimpleEvent<VersionNumber>> = EventHandler()
fun checkOutdate() {
if (isOutOfDate)
if (EVENT_BUS.fireChannelResulted(OutOfDateEvent(this, newVersion)))
upgrade.fire(SimpleEvent(this, newVersion))
}*/
}

View File

@ -108,14 +108,14 @@ public abstract class Task {
* The collection of sub-tasks that should execute **before** this task running.
*/
public Collection<Task> getDependents() {
return Collections.EMPTY_SET;
return Collections.emptySet();
}
/**
* The collection of sub-tasks that should execute **after** this task running.
*/
public Collection<Task> getDependencies() {
return Collections.EMPTY_SET;
return Collections.emptySet();
}
public EventManager<TaskEvent> onDone() {
@ -180,8 +180,6 @@ public abstract class Task {
progressProperty.unbind();
}
;
public final TaskExecutor executor() {
return new TaskExecutor(this);
}