This commit is contained in:
huangyuhui 2017-08-27 14:23:27 +08:00
parent 8c61bc1a5e
commit 41337f66a8
7 changed files with 405 additions and 5 deletions

View File

@ -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"
}
}

View 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()
}
}

View 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))
}*/
}

View File

@ -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))

View File

@ -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.
*/

View File

@ -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 {
/**

View File

@ -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)