diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/HMCLDownloadTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/HMCLDownloadTask.java new file mode 100644 index 000000000..73df477c8 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/HMCLDownloadTask.java @@ -0,0 +1,68 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2018 huangyuhui + * + * 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; + } + } + +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/IntegrityChecker.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/IntegrityChecker.java index e6184b575..72fb69817 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/IntegrityChecker.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/IntegrityChecker.java @@ -37,6 +37,7 @@ import java.util.zip.ZipFile; import org.jackhuang.hmcl.util.DigestUtils; import org.jackhuang.hmcl.util.IOUtils; +import org.jackhuang.hmcl.util.JarUtils; /** * A class that checks the integrity of HMCL. @@ -123,8 +124,7 @@ public final class IntegrityChecker { } private static void verifySelf() throws IOException { - Path self = LocalVersion.current().orElseThrow(() -> new IOException("Failed to find myself")) - .getLocation(); + Path self = JarUtils.thisJar().orElseThrow(() -> new IOException("Failed to find current HMCL location")); requireVerifiedJar(self); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/LocalRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/LocalRepository.java deleted file mode 100644 index 6bf59f2d9..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/LocalRepository.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2018 huangyuhui - * - * 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 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 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()); - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/LocalVersion.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/LocalVersion.java deleted file mode 100644 index ada1acff8..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/LocalVersion.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2018 huangyuhui - * - * 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 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 + "]"; - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java index 67f710adb..9960c6eb8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java @@ -18,10 +18,8 @@ package org.jackhuang.hmcl.upgrade; 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.Logging.LOG; -import static org.jackhuang.hmcl.util.VersionNumber.asVersion; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; 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.construct.DialogCloseEvent; import org.jackhuang.hmcl.ui.construct.MessageBox; +import org.jackhuang.hmcl.util.JarUtils; import org.jackhuang.hmcl.util.JavaVersion; import org.jackhuang.hmcl.util.StringUtils; @@ -58,10 +57,6 @@ public final class UpdateHandler { * @return whether to exit */ public static boolean processArguments(String[] args) { - if (!isIntVersionNumber(Metadata.VERSION)) { - return false; - } - if (isNestedApplication()) { // updated from old versions try { @@ -88,33 +83,69 @@ public final class UpdateHandler { return true; } - Optional local = LocalRepository.getStored(); - if (local.isPresent()) { - int difference = asVersion(local.get().getVersion()).compareTo(asVersion(Metadata.VERSION)); + return false; + } - if (difference < 0) { - LocalRepository.downloadFromCurrent(); + public static void updateFrom(RemoteVersion version) { + checkFxUserThread(); - } else if (difference > 0) { - Optional current = LocalVersion.current(); - if (current.isPresent() && IntegrityChecker.isSelfVerified()) { - try { - requestUpdate(local.get().getLocation(), current.get().getLocation()); - } catch (IOException e) { - LOG.log(Level.WARNING, "Failed to update from local repository", e); - return false; - } - return true; - } else { - return false; - } - } - - } else { - LocalRepository.downloadFromCurrent(); + Path downloaded; + try { + downloaded = Files.createTempFile("hmcl-update-", ".jar"); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to create temp file", e); + return; } - 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 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 { @@ -122,28 +153,6 @@ public final class UpdateHandler { startJava(updateTo, "--apply-to", self.toString()); } - private static void applyUpdate(Path target) throws IOException { - LocalRepository.applyTo(target); - - Optional newVersion = LocalRepository.getStored().map(LocalVersion::getVersion); - if (newVersion.isPresent()) { - Optional 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 { List commandline = new ArrayList<>(); commandline.add(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath()); @@ -159,49 +168,6 @@ public final class UpdateHandler { .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 current = LocalVersion.current(); - Optional 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 tryRename(Path path, String newVersion) { String filename = path.getFileName().toString(); Matcher matcher = Pattern.compile("^(?[hH][mM][cC][lL][.-])(?\\d+(?:\\.\\d+)*)(?\\.[^.]+)$").matcher(filename); @@ -214,6 +180,10 @@ public final class UpdateHandler { 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 === private static void performMigration() throws IOException { LOG.info("Migrating from old versions"); @@ -221,17 +191,7 @@ public final class UpdateHandler { Path location = getParentApplicationLocation() .orElseThrow(() -> new IOException("Failed to get parent application location")); - Optional local = LocalRepository.getStored(); - 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); + requestUpdate(getCurrentLocation(), location); } /** @@ -265,7 +225,7 @@ public final class UpdateHandler { } private static boolean isFirstLaunchAfterUpgrade() { - Optional currentPath = LocalVersion.current().map(LocalVersion::getLocation); + Optional currentPath = JarUtils.thisJar(); if (currentPath.isPresent()) { Path updated = Launcher.HMCL_DIRECTORY.toPath().resolve("HMCL-" + Metadata.VERSION + ".jar"); if (currentPath.get().toAbsolutePath().equals(updated.toAbsolutePath())) { diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 38ff16864..218847c1c 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -319,7 +319,7 @@ update.checking=Checking for updates update.failed=Failed to perform upgrade update.found=Update Available! 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.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 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 729fb2630..5e9d5828a 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -319,7 +319,7 @@ update.checking=正在檢查更新 update.failed=更新失敗 update.found=發現到更新 update.newest_version=最新版本為:%s -update.note=開發版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。選擇更新到開發版,將會把你電腦的所有 HMCL 更新至開發版 +update.note=開發版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。 update.latest=目前版本為最新版本 update.no_browser=無法打開瀏覽器,網址已經複製到剪貼簿了,您可以手動複製網址打開頁面 update.tooltip=更新 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index a30629db2..fc88f95a6 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -319,7 +319,7 @@ update.checking=正在检查更新 update.failed=更新失败 update.found=发现更新 update.newest_version=最新版本为:%s -update.note=开发版包含更多的功能以及错误修复,但也可能会包含其他的问题。选择更新到开发版导致你电脑中所有的 HMCL 更新至开发版 +update.note=开发版包含更多的功能以及错误修复,但也可能会包含其他的问题。 update.latest=当前版本为最新版本 update.no_browser=无法打开浏览器,网址已经复制到剪贴板了,您可以手动粘贴网址打开页面 update.tooltip=更新