mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-19 00:36:10 -04:00
Upgrader
This commit is contained in:
parent
8c61bc1a5e
commit
41337f66a8
@ -0,0 +1,224 @@
|
||||
/*
|
||||
* 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.util.OS
|
||||
import org.jackhuang.hmcl.i18n
|
||||
import org.jackhuang.hmcl.ui.alert
|
||||
import org.jackhuang.hmcl.util.VersionNumber
|
||||
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(OS.CURRENT_OS.checkedName))
|
||||
url = map.get(OS.CURRENT_OS.checkedName)!!
|
||||
else if (map.containsKey(OS.UNKNOWN.checkedName))
|
||||
url = map.get(OS.UNKNOWN.checkedName)!!
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().browse(URI(url))
|
||||
} catch (e: URISyntaxException) {
|
||||
LOG.log(Level.SEVERE, "Failed to browse uri: " + url, e)
|
||||
OS.setClipboard(url)
|
||||
} catch (e: IOException) {
|
||||
LOG.log(Level.SEVERE, "Failed to browse uri: " + url, e)
|
||||
OS.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")
|
||||
override val dependents = listOf(FileDownloadTask(downloadLink.toURL(), tempFile, expectedHash))
|
||||
|
||||
init {
|
||||
onDone += { event -> if (event.failed) tempFile.delete() }
|
||||
}
|
||||
|
||||
override fun execute() {
|
||||
val json = HashMap<String, String>()
|
||||
var f = getSelf(newestVersion)
|
||||
if (!f.parentFile.makeDirectory())
|
||||
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() {
|
||||
override var title = "Upgrade"
|
||||
set(value) {}
|
||||
private val tempFile = File.createTempFile("hmcl", ".jar")
|
||||
|
||||
init {
|
||||
onDone += { event -> if (event.failed) tempFile.delete() }
|
||||
}
|
||||
|
||||
override val dependents = listOf(FileDownloadTask(downloadLink.toURL(), tempFile, expectedHash))
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
49
HMCL/src/main/kotlin/org/jackhuang/hmcl/upgrade/IUpgrader.kt
Normal file
49
HMCL/src/main/kotlin/org/jackhuang/hmcl/upgrade/IUpgrader.kt
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.util.VersionNumber
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
interface IUpgrader {
|
||||
|
||||
/**
|
||||
* Paring arguments to decide on whether the upgrade is needed.
|
||||
*
|
||||
* @param nowVersion now launcher version
|
||||
* @param args Application CommandLine Arguments
|
||||
*/
|
||||
fun parseArguments(nowVersion: VersionNumber, args: Array<String>)
|
||||
|
||||
/**
|
||||
* Just download the new app.
|
||||
*
|
||||
* @param checker Should be VersionChecker
|
||||
* @param versionNumber the newest version
|
||||
*
|
||||
* @return should return true
|
||||
*/
|
||||
fun download(checker: UpdateChecker, versionNumber: VersionNumber)
|
||||
|
||||
companion object {
|
||||
val NOW_UPGRADER: IUpgrader = AppDataUpgrader()
|
||||
}
|
||||
}
|
110
HMCL/src/main/kotlin/org/jackhuang/hmcl/upgrade/UpdateChecker.kt
Normal file
110
HMCL/src/main/kotlin/org/jackhuang/hmcl/upgrade/UpdateChecker.kt
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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 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 val id = "update_checker.process"
|
||||
override fun execute() {
|
||||
if (newVersion == null) {
|
||||
versionString = ("http://huangyuhui.duapp.com/info.php?type=$type").toURL().doGet()
|
||||
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 val id = "update_checker.request_download_link"
|
||||
override fun execute() {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
if (download_link == null)
|
||||
try {
|
||||
download_link = GSON.fromJson(("http://huangyuhui.duapp.com/update_link.php?type=$type").toURL().doGet(), 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))
|
||||
}*/
|
||||
}
|
@ -132,6 +132,7 @@ abstract class Task {
|
||||
fun executor() = TaskExecutor(this)
|
||||
fun executor(taskListener: TaskListener) = TaskExecutor(this).apply { this.taskListener = taskListener }
|
||||
fun start() = executor().start()
|
||||
fun test() = executor().test()
|
||||
fun subscribe(subscriber: Task) = TaskExecutor(with(subscriber)).apply { start() }
|
||||
|
||||
fun subscribe(scheduler: Scheduler = Scheduler.DEFAULT, closure: (AutoTypingMap<String>) -> Unit) = subscribe(task(scheduler, closure))
|
||||
|
@ -47,6 +47,20 @@ class TaskExecutor(private val task: Task) {
|
||||
})
|
||||
}
|
||||
|
||||
@Throws(InterruptedException::class)
|
||||
fun test(): Boolean {
|
||||
var flag = true
|
||||
val future = Scheduler.NEW_THREAD.schedule {
|
||||
if (!executeTasks(listOf(task))) {
|
||||
taskListener?.onTerminate()
|
||||
flag = false
|
||||
}
|
||||
}
|
||||
workerQueue.add(future)
|
||||
future!!.get()
|
||||
return flag
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the subscription ant interrupt all tasks.
|
||||
*/
|
||||
|
@ -28,23 +28,23 @@ import java.util.*
|
||||
/**
|
||||
* Represents the operating system.
|
||||
*/
|
||||
enum class OS {
|
||||
enum class OS(val checkedName: String) {
|
||||
/**
|
||||
* Microsoft Windows.
|
||||
*/
|
||||
WINDOWS,
|
||||
WINDOWS("windows"),
|
||||
/**
|
||||
* Linux and Unix like OS, including Solaris.
|
||||
*/
|
||||
LINUX,
|
||||
LINUX("linux"),
|
||||
/**
|
||||
* Mac OS X.
|
||||
*/
|
||||
OSX,
|
||||
OSX("osx"),
|
||||
/**
|
||||
* Unknown operating system.
|
||||
*/
|
||||
UNKNOWN;
|
||||
UNKNOWN("universal");
|
||||
|
||||
companion object {
|
||||
/**
|
||||
|
@ -75,6 +75,8 @@ class StringVersionNumber internal constructor(val version: String): VersionNumb
|
||||
*/
|
||||
class IntVersionNumber internal constructor(val version: List<Int>): VersionNumber() {
|
||||
|
||||
operator fun get(index: Int) = version[index]
|
||||
|
||||
override fun compareTo(other: VersionNumber): Int {
|
||||
if (other !is IntVersionNumber) return 0
|
||||
val len = minOf(this.version.size, other.version.size)
|
||||
|
Loading…
x
Reference in New Issue
Block a user