Remove force update feature

This commit is contained in:
yushijinhun
2018-08-26 19:02:47 +08:00
parent 9a375fd0d1
commit b5f56eb2b5
8 changed files with 139 additions and 316 deletions

View File

@@ -0,0 +1,68 @@
/*
* 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.upgrade;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.jar.JarOutputStream;
import java.util.jar.Pack200;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.tukaani.xz.XZInputStream;
class HMCLDownloadTask extends FileDownloadTask {
private RemoteVersion.Type archiveFormat;
public HMCLDownloadTask(RemoteVersion version, Path target) {
super(NetworkUtils.toURL(version.getUrl()), target.toFile(), version.getIntegrityCheck());
archiveFormat = version.getType();
}
@Override
public void execute() throws Exception {
super.execute();
try {
Path target = getFile().toPath();
switch (archiveFormat) {
case JAR:
break;
case PACK_XZ:
byte[] raw = Files.readAllBytes(target);
try (InputStream in = new XZInputStream(new ByteArrayInputStream(raw));
JarOutputStream out = new JarOutputStream(Files.newOutputStream(target))) {
Pack200.newUnpacker().unpack(in, out);
}
break;
default:
throw new IllegalArgumentException("Unknown format: " + archiveFormat);
}
} catch (Throwable e) {
getFile().delete();
throw e;
}
}
}

View File

@@ -37,6 +37,7 @@ import java.util.zip.ZipFile;
import org.jackhuang.hmcl.util.DigestUtils; import org.jackhuang.hmcl.util.DigestUtils;
import org.jackhuang.hmcl.util.IOUtils; import org.jackhuang.hmcl.util.IOUtils;
import org.jackhuang.hmcl.util.JarUtils;
/** /**
* A class that checks the integrity of HMCL. * A class that checks the integrity of HMCL.
@@ -123,8 +124,7 @@ public final class IntegrityChecker {
} }
private static void verifySelf() throws IOException { private static void verifySelf() throws IOException {
Path self = LocalVersion.current().orElseThrow(() -> new IOException("Failed to find myself")) Path self = JarUtils.thisJar().orElseThrow(() -> new IOException("Failed to find current HMCL location"));
.getLocation();
requireVerifiedJar(self); requireVerifiedJar(self);
} }
} }

View File

@@ -1,153 +0,0 @@
/*
* 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.upgrade;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.JarUtils;
import org.tukaani.xz.XZInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.jar.JarOutputStream;
import java.util.jar.Pack200;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.Logging.LOG;
/**
* A class used to manage the local HMCL repository.
*
* @author yushijinhun
*/
final class LocalRepository {
private LocalRepository() {}
private static Path localStorage = Launcher.HMCL_DIRECTORY.toPath().resolve("hmcl.jar");
/**
* Gets the current stored executable in local repository.
*/
public static Optional<LocalVersion> getStored() {
if (!Files.isRegularFile(localStorage)) {
return Optional.empty();
}
return Optional.of(localStorage)
.flatMap(JarUtils::getImplementationVersion)
.map(version -> new LocalVersion(version, localStorage));
}
private static void writeToStorage(Path source, boolean checkHeaders) throws IOException {
IntegrityChecker.requireVerifiedJar(source);
Files.createDirectories(localStorage.getParent());
if (checkHeaders) {
ExecutableHeaderHelper.copyWithoutHeader(source, localStorage);
} else {
Files.copy(source, localStorage, StandardCopyOption.REPLACE_EXISTING);
}
}
/**
* Creates a task that downloads the given version to local repository.
*/
public static FileDownloadTask downloadFromRemote(RemoteVersion version) throws IOException {
Path downloaded = Files.createTempFile("hmcl-update-", null);
return new FileDownloadTask(new URL(version.getUrl()), downloaded.toFile(), version.getIntegrityCheck()) {
@Override
public void execute() throws Exception {
super.execute();
try {
switch (version.getType()) {
case JAR:
writeToStorage(downloaded, false);
break;
case PACK_XZ:
Path unpacked = Files.createTempFile("hmcl-update-unpack-", null);
try {
try (InputStream in = new XZInputStream(Files.newInputStream(downloaded));
JarOutputStream out = new JarOutputStream(Files.newOutputStream(unpacked))) {
Pack200.newUnpacker().unpack(in, out);
}
writeToStorage(unpacked, false);
} finally {
Files.deleteIfExists(unpacked);
}
break;
default:
throw new IllegalArgumentException("Unknown type: " + version.getType());
}
} finally {
Files.deleteIfExists(downloaded);
}
}
};
}
/**
* Copies the current HMCL executable to local repository.
*/
public static void downloadFromCurrent() {
Optional<LocalVersion> current = LocalVersion.current();
if (current.isPresent()) {
Path currentPath = current.get().getLocation();
if (!Files.isRegularFile(currentPath)) {
LOG.warning("Failed to download " + current.get() + ", it isn't a file");
return;
}
if (isSameAsLocalStorage(currentPath)) {
LOG.warning("Trying to download from self, ignored");
return;
}
LOG.info("Downloading " + current.get());
try {
writeToStorage(current.get().getLocation(), true);
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to download " + current.get(), e);
}
}
}
/**
* Writes the executable stored in local repository to the given location.
*/
public static void applyTo(Path target) throws IOException {
if (isSameAsLocalStorage(target)) {
throw new IOException("Cannot apply update to self");
}
LOG.info("Applying update to " + target);
IntegrityChecker.requireVerifiedJar(localStorage);
ExecutableHeaderHelper.copyWithHeader(localStorage, target);
}
private static boolean isSameAsLocalStorage(Path path) {
return path.toAbsolutePath().equals(localStorage.toAbsolutePath());
}
}

View File

@@ -1,52 +0,0 @@
/*
* 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.upgrade;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.util.JarUtils;
import java.nio.file.Path;
import java.util.Optional;
class LocalVersion {
public static Optional<LocalVersion> current() {
return JarUtils.thisJar().map(path -> new LocalVersion(Metadata.VERSION, path));
}
private String version;
private Path location;
public LocalVersion(String version, Path location) {
this.version = version;
this.location = location;
}
public String getVersion() {
return version;
}
public Path getLocation() {
return location;
}
@Override
public String toString() {
return "[" + version + " at " + location + "]";
}
}

View File

@@ -18,10 +18,8 @@
package org.jackhuang.hmcl.upgrade; package org.jackhuang.hmcl.upgrade;
import static org.jackhuang.hmcl.ui.FXUtils.checkFxUserThread; import static org.jackhuang.hmcl.ui.FXUtils.checkFxUserThread;
import static org.jackhuang.hmcl.util.IntVersionNumber.isIntVersionNumber;
import static org.jackhuang.hmcl.util.Lang.thread; import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.VersionNumber.asVersion;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.io.IOException; import java.io.IOException;
@@ -45,6 +43,7 @@ import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.MessageBox; import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.util.JarUtils;
import org.jackhuang.hmcl.util.JavaVersion; import org.jackhuang.hmcl.util.JavaVersion;
import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.StringUtils;
@@ -58,10 +57,6 @@ public final class UpdateHandler {
* @return whether to exit * @return whether to exit
*/ */
public static boolean processArguments(String[] args) { public static boolean processArguments(String[] args) {
if (!isIntVersionNumber(Metadata.VERSION)) {
return false;
}
if (isNestedApplication()) { if (isNestedApplication()) {
// updated from old versions // updated from old versions
try { try {
@@ -88,33 +83,69 @@ public final class UpdateHandler {
return true; return true;
} }
Optional<LocalVersion> local = LocalRepository.getStored(); return false;
if (local.isPresent()) { }
int difference = asVersion(local.get().getVersion()).compareTo(asVersion(Metadata.VERSION));
if (difference < 0) { public static void updateFrom(RemoteVersion version) {
LocalRepository.downloadFromCurrent(); checkFxUserThread();
} else if (difference > 0) { Path downloaded;
Optional<LocalVersion> current = LocalVersion.current(); try {
if (current.isPresent() && IntegrityChecker.isSelfVerified()) { downloaded = Files.createTempFile("hmcl-update-", ".jar");
try { } catch (IOException e) {
requestUpdate(local.get().getLocation(), current.get().getLocation()); LOG.log(Level.WARNING, "Failed to create temp file", e);
} catch (IOException e) { return;
LOG.log(Level.WARNING, "Failed to update from local repository", e);
return false;
}
return true;
} else {
return false;
}
}
} else {
LocalRepository.downloadFromCurrent();
} }
return false; Task task = new HMCLDownloadTask(version, downloaded);
TaskExecutor executor = task.executor();
Region dialog = Controllers.taskDialog(executor, i18n("message.downloading"), "", null);
thread(() -> {
boolean success = executor.test();
Platform.runLater(() -> dialog.fireEvent(new DialogCloseEvent()));
if (success) {
try {
if (!IntegrityChecker.isSelfVerified()) {
throw new IOException("Current JAR is not verified");
}
requestUpdate(downloaded, getCurrentLocation());
System.exit(0);
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to update to " + version, e);
Platform.runLater(() -> Controllers.dialog(StringUtils.getStackTrace(e), i18n("update.failed"), MessageBox.ERROR_MESSAGE));
return;
}
} else {
Throwable e = task.getLastException();
LOG.log(Level.WARNING, "Failed to update to " + version, e);
Platform.runLater(() -> Controllers.dialog(e.toString(), i18n("update.failed"), MessageBox.ERROR_MESSAGE));
}
});
}
private static void applyUpdate(Path target) throws IOException {
LOG.info("Applying update to " + target);
Path self = getCurrentLocation();
IntegrityChecker.requireVerifiedJar(self);
ExecutableHeaderHelper.copyWithHeader(self, target);
Optional<Path> newFilename = tryRename(target, Metadata.VERSION);
if (newFilename.isPresent()) {
LOG.info("Move " + target + " to " + newFilename.get());
try {
Files.move(target, newFilename.get());
target = newFilename.get();
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to move target", e);
}
}
startJava(target);
} }
private static void requestUpdate(Path updateTo, Path self) throws IOException { private static void requestUpdate(Path updateTo, Path self) throws IOException {
@@ -122,28 +153,6 @@ public final class UpdateHandler {
startJava(updateTo, "--apply-to", self.toString()); startJava(updateTo, "--apply-to", self.toString());
} }
private static void applyUpdate(Path target) throws IOException {
LocalRepository.applyTo(target);
Optional<String> newVersion = LocalRepository.getStored().map(LocalVersion::getVersion);
if (newVersion.isPresent()) {
Optional<Path> newFilename = tryRename(target, newVersion.get());
if (newFilename.isPresent()) {
LOG.info("Move " + target + " to " + newFilename.get());
try {
Files.move(target, newFilename.get());
target = newFilename.get();
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to move target", e);
}
}
} else {
LOG.warning("Failed to find local repository");
}
startJava(target);
}
private static void startJava(Path jar, String... appArgs) throws IOException { private static void startJava(Path jar, String... appArgs) throws IOException {
List<String> commandline = new ArrayList<>(); List<String> commandline = new ArrayList<>();
commandline.add(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath()); commandline.add(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath());
@@ -159,49 +168,6 @@ public final class UpdateHandler {
.start(); .start();
} }
public static void updateFrom(RemoteVersion version) {
checkFxUserThread();
Task task;
try {
task = LocalRepository.downloadFromRemote(version);
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to create upgrade download task", e);
return;
}
TaskExecutor executor = task.executor();
Region dialog = Controllers.taskDialog(executor, i18n("message.downloading"), "", null);
thread(() -> {
boolean success = executor.test();
Platform.runLater(() -> dialog.fireEvent(new DialogCloseEvent()));
if (success) {
try {
Optional<LocalVersion> current = LocalVersion.current();
Optional<LocalVersion> stored = LocalRepository.getStored();
if (!current.isPresent()) {
throw new IOException("Failed to find current HMCL location");
}
if (!stored.isPresent()) {
throw new IOException("Failed to find local repository, this shouldn't happen");
}
if (!IntegrityChecker.isSelfVerified()) {
throw new IOException("Current JAR is not verified");
}
requestUpdate(stored.get().getLocation(), current.get().getLocation());
System.exit(0);
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to update to " + version, e);
Platform.runLater(() -> Controllers.dialog(StringUtils.getStackTrace(e), i18n("update.failed"), MessageBox.ERROR_MESSAGE));
return;
}
} else {
Throwable e = task.getLastException();
LOG.log(Level.WARNING, "Failed to update to " + version, e);
Platform.runLater(() -> Controllers.dialog(e.toString(), i18n("update.failed"), MessageBox.ERROR_MESSAGE));
}
});
}
private static Optional<Path> tryRename(Path path, String newVersion) { private static Optional<Path> tryRename(Path path, String newVersion) {
String filename = path.getFileName().toString(); String filename = path.getFileName().toString();
Matcher matcher = Pattern.compile("^(?<prefix>[hH][mM][cC][lL][.-])(?<version>\\d+(?:\\.\\d+)*)(?<suffix>\\.[^.]+)$").matcher(filename); Matcher matcher = Pattern.compile("^(?<prefix>[hH][mM][cC][lL][.-])(?<version>\\d+(?:\\.\\d+)*)(?<suffix>\\.[^.]+)$").matcher(filename);
@@ -214,6 +180,10 @@ public final class UpdateHandler {
return Optional.empty(); return Optional.empty();
} }
private static Path getCurrentLocation() throws IOException {
return JarUtils.thisJar().orElseThrow(() -> new IOException("Failed to find current HMCL location"));
}
// ==== support for old versions === // ==== support for old versions ===
private static void performMigration() throws IOException { private static void performMigration() throws IOException {
LOG.info("Migrating from old versions"); LOG.info("Migrating from old versions");
@@ -221,17 +191,7 @@ public final class UpdateHandler {
Path location = getParentApplicationLocation() Path location = getParentApplicationLocation()
.orElseThrow(() -> new IOException("Failed to get parent application location")); .orElseThrow(() -> new IOException("Failed to get parent application location"));
Optional<LocalVersion> local = LocalRepository.getStored(); requestUpdate(getCurrentLocation(), location);
if (!local.isPresent() ||
asVersion(local.get().getVersion()).compareTo(asVersion(Metadata.VERSION)) < 0) {
LocalRepository.downloadFromCurrent();
}
local = LocalRepository.getStored();
if (!local.isPresent()) {
throw new IOException("Failed to find local repository");
}
requestUpdate(local.get().getLocation(), location);
} }
/** /**
@@ -265,7 +225,7 @@ public final class UpdateHandler {
} }
private static boolean isFirstLaunchAfterUpgrade() { private static boolean isFirstLaunchAfterUpgrade() {
Optional<Path> currentPath = LocalVersion.current().map(LocalVersion::getLocation); Optional<Path> currentPath = JarUtils.thisJar();
if (currentPath.isPresent()) { if (currentPath.isPresent()) {
Path updated = Launcher.HMCL_DIRECTORY.toPath().resolve("HMCL-" + Metadata.VERSION + ".jar"); Path updated = Launcher.HMCL_DIRECTORY.toPath().resolve("HMCL-" + Metadata.VERSION + ".jar");
if (currentPath.get().toAbsolutePath().equals(updated.toAbsolutePath())) { if (currentPath.get().toAbsolutePath().equals(updated.toAbsolutePath())) {

View File

@@ -319,7 +319,7 @@ update.checking=Checking for updates
update.failed=Failed to perform upgrade update.failed=Failed to perform upgrade
update.found=Update Available! update.found=Update Available!
update.newest_version=Latest version: %s update.newest_version=Latest version: %s
update.note=Development version contains more functionality and bug fixes as well as more possible bugs. And this will affect all HMCL installations in your computer. update.note=Development version contains more functionality and bug fixes as well as more possible bugs.
update.latest=This is latest Version. update.latest=This is latest Version.
update.no_browser=Cannot open any browser. The link has been copied to the clipboard. Paste it to a browser address bar to update. update.no_browser=Cannot open any browser. The link has been copied to the clipboard. Paste it to a browser address bar to update.
update.tooltip=Update update.tooltip=Update

View File

@@ -319,7 +319,7 @@ update.checking=正在檢查更新
update.failed=更新失敗 update.failed=更新失敗
update.found=發現到更新 update.found=發現到更新
update.newest_version=最新版本為:%s update.newest_version=最新版本為:%s
update.note=開發版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。選擇更新到開發版,將會把你電腦的所有 HMCL 更新至開發版 update.note=開發版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。
update.latest=目前版本為最新版本 update.latest=目前版本為最新版本
update.no_browser=無法打開瀏覽器,網址已經複製到剪貼簿了,您可以手動複製網址打開頁面 update.no_browser=無法打開瀏覽器,網址已經複製到剪貼簿了,您可以手動複製網址打開頁面
update.tooltip=更新 update.tooltip=更新

View File

@@ -319,7 +319,7 @@ update.checking=正在检查更新
update.failed=更新失败 update.failed=更新失败
update.found=发现更新 update.found=发现更新
update.newest_version=最新版本为:%s update.newest_version=最新版本为:%s
update.note=开发版包含更多的功能以及错误修复,但也可能会包含其他的问题。选择更新到开发版导致你电脑中所有的 HMCL 更新至开发版 update.note=开发版包含更多的功能以及错误修复,但也可能会包含其他的问题。
update.latest=当前版本为最新版本 update.latest=当前版本为最新版本
update.no_browser=无法打开浏览器,网址已经复制到剪贴板了,您可以手动粘贴网址打开页面 update.no_browser=无法打开浏览器,网址已经复制到剪贴板了,您可以手动粘贴网址打开页面
update.tooltip=更新 update.tooltip=更新