Rework core/console updating, implement command args

This commit is contained in:
Him188 2020-05-06 14:40:53 +08:00
parent 0b1089c10d
commit fa8eb466e7
8 changed files with 272 additions and 141 deletions

View File

@ -11,10 +11,10 @@ import org.gradle.kotlin.dsl.DependencyHandlerScope
object Versions { object Versions {
object Mirai { object Mirai {
const val core = "0.39.1" const val core = "1.0-RC"
const val console = "0.4.11" const val console = "0.4.11"
const val consoleGraphical = "0.0.7" const val consoleGraphical = "0.0.7"
const val consoleWrapper = "0.2.0" const val consoleWrapper = "1.0.0"
} }
object Kotlin { object Kotlin {

View File

@ -28,13 +28,35 @@ dependencies {
api(kotlin("reflect", Versions.Kotlin.stdlib)) api(kotlin("reflect", Versions.Kotlin.stdlib))
api(kotlinx("coroutines-core", Versions.Kotlin.coroutines)) api(kotlinx("coroutines-core", Versions.Kotlin.coroutines))
api(kotlinx("coroutines-swing",Versions.Kotlin.coroutines)) api(kotlinx("coroutines-swing", Versions.Kotlin.coroutines))
api(ktor("client-cio", Versions.Kotlin.ktor)) api(ktor("client-cio", Versions.Kotlin.ktor))
api(ktor("client-core", Versions.Kotlin.ktor)) api(ktor("client-core", Versions.Kotlin.ktor))
api(ktor("network", Versions.Kotlin.ktor)) api(ktor("network", Versions.Kotlin.ktor))
api("com.github.ajalt:clikt:2.6.0")
testApi(kotlin("stdlib", Versions.Kotlin.stdlib))
testApi(kotlin("test-junit5"))
} }
version = Versions.Mirai.consoleWrapper version = Versions.Mirai.consoleWrapper
description = "Console with plugin support for mirai" description = "Console with plugin support for mirai"
val compileKotlin: org.jetbrains.kotlin.gradle.tasks.KotlinCompile by tasks
compileKotlin.kotlinOptions {
jvmTarget = "1.8"
}
val compileTestKotlin: org.jetbrains.kotlin.gradle.tasks.KotlinCompile by tasks
compileTestKotlin.kotlinOptions {
jvmTarget = "1.8"
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
tasks.withType(JavaCompile::class.java) {
options.encoding = "UTF8"
}

View File

@ -1,3 +1,11 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.wrapper package net.mamoe.mirai.console.wrapper
import io.ktor.client.request.get import io.ktor.client.request.get
@ -14,15 +22,15 @@ const val CONSOLE_GRAPHICAL = "Graphical"
internal object ConsoleUpdater { internal object ConsoleUpdater {
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
private object Links : HashMap<String, Map<String, String>>() { private object Links : HashMap<ConsoleType, Map<String, String>>() {
init { init {
put( put(
CONSOLE_PURE, mapOf( ConsoleType.Pure, mapOf(
"version" to "/net/mamoe/mirai-console/" "version" to "/net/mamoe/mirai-console/"
) )
) )
put( put(
CONSOLE_GRAPHICAL, mapOf( ConsoleType.Graphical, mapOf(
"version" to "/net/mamoe/mirai-console-graphical/" "version" to "/net/mamoe/mirai-console-graphical/"
) )
) )
@ -30,23 +38,26 @@ internal object ConsoleUpdater {
} }
var consoleType = CONSOLE_PURE var consoleType = ConsoleType.Pure
fun getFile(): File? { fun getFile(): File? {
contentPath.listFiles()?.forEach { file -> contentPath.listFiles()?.forEach { file ->
if (file != null && file.extension == "jar") { if (file != null && file.extension == "jar") {
if (file.name.contains("mirai-console")) { if (file.name.contains("mirai-console")) {
when (consoleType) { when (consoleType) {
CONSOLE_PURE -> { ConsoleType.Pure -> {
if(!file.name.contains("graphical")) { if (!file.name.contains("graphical")) {
return file return file
} }
} }
CONSOLE_GRAPHICAL -> { ConsoleType.Graphical -> {
if(file.name.contains("graphical")) { if (file.name.contains("graphical")) {
return file return file
} }
} }
else -> {
}
} }
} }
} }
@ -54,52 +65,35 @@ internal object ConsoleUpdater {
return null return null
} }
suspend fun versionCheck(type: String) { suspend fun versionCheck(type: ConsoleType, strategy: VersionUpdateStrategy) {
this.consoleType = type this.consoleType = type
println("Fetching Newest Console Version of $type") println("Fetching Newest Console Version of $type")
val newest = getNewestVersion() val current = CoreUpdater.getCurrentVersion()
val current = getCurrentVersion() if (current != "0.0.0" && strategy == VersionUpdateStrategy.KEEP) {
println("Local Console-$type Version: $current | Newest Console-$type Version: $newest") println("Stay on current version.")
return
}
val newest = getNewestVersion(
strategy,
Links[consoleType]!!["version"] ?: error("Unknown Console Type")
)
println("Local Console-$type Version: $current | Newest $strategy Console-$type Version: $newest")
if (current != newest) { if (current != newest) {
println("Updating Console-$type from V$current -> V$newest, this is a force update") println("Updating Console-$type from V$current -> V$newest")
this.getFile()?.delete() this.getFile()?.delete()
/** /**
MiraiDownloader.addTask( MiraiDownloader.addTask(
"https://raw.githubusercontent.com/mamoe/mirai-repo/master/shadow/${getProjectName()}/${getProjectName()}-$newest.jar",getContent("${getProjectName()}-$newest.jar") "https://raw.githubusercontent.com/mamoe/mirai-repo/master/shadow/${getProjectName()}/${getProjectName()}-$newest.jar",getContent("${getProjectName()}-$newest.jar")
) )
*/ */
MiraiDownloader.addTask( MiraiDownloader.addTask(
"https://pan.jasonczc.cn/?/mirai/${getProjectName()}/${getProjectName()}-$newest.mp4", getContent("${getProjectName()}-$newest.jar") "https://pan.jasonczc.cn/?/mirai/${getProjectName()}/${getProjectName()}-$newest.mp4",
getContent("${getProjectName()}-$newest.jar")
) )
} }
} }
private suspend fun getNewestVersion(): String {
try {
return """>([0-9])*\.([0-9])*\.([0-9])*/""".toRegex().findAll(
Http.get<String> {
url {
protocol = URLProtocol.HTTPS
host = "jcenter.bintray.com"
path(Links[consoleType]!!["version"] ?: error("Unknown Console Type"))
}
}
).asSequence()
.map { it.value.drop(1).dropLast(1) }
.maxBy {
it.split('.').foldRightIndexed(0) { index: Int, s: String, acc: Int ->
acc + 100.0.pow(2 - index).toInt() * (s.toIntOrNull() ?: 0)
}
}!!
} catch (e: Exception) {
println("Failed to fetch newest Console version, please seek for help")
e.printStackTrace()
println("Failed to fetch newest Console version, please seek for help")
exitProcess(1)
}
}
fun getCurrentVersion(): String { fun getCurrentVersion(): String {
val file = getFile() val file = getFile()
if (file != null) { if (file != null) {
@ -112,11 +106,70 @@ internal object ConsoleUpdater {
} }
private fun getProjectName(): String { private fun getProjectName(): String {
return if (consoleType == CONSOLE_PURE) { return if (consoleType == ConsoleType.Pure) {
"mirai-console" "mirai-console"
} else { } else {
"mirai-console-${consoleType.toLowerCase()}" "mirai-console-${consoleType.toString().toLowerCase()}"
} }
} }
}
suspend fun getNewestVersion(strategy: VersionUpdateStrategy, path: String): String {
try {
return Regex("""rel="nofollow">[0-9][0-9]*(\.[0-9]*)*.*/<""", RegexOption.IGNORE_CASE).findAll(
Http.get<String> {
url {
protocol = URLProtocol.HTTPS
host = "jcenter.bintray.com"
path(path)
}
})
.asSequence()
.map { it.value.substringAfter('>').substringBefore('/') }
.toList()
.let { list ->
if (list.filter { it.startsWith("1.") }.takeIf { it.isNotEmpty() }?.all { it.contains("-") } == true) {
// 只有 1.xxx-EA 版本, 那么也将他看作是正式版
list.filter { it.startsWith("1.") }
} else when (strategy) {
VersionUpdateStrategy.KEEP,
VersionUpdateStrategy.STABLE
-> {
list.filterNot { it.contains("-") } // e.g. "-EA"
}
VersionUpdateStrategy.EA -> {
list
}
}
}
.latestVersion()
} catch (e: Exception) {
println("Failed to fetch newest Console version, please seek for help")
e.printStackTrace()
println("Failed to fetch newest Console version, please seek for help")
exitProcess(1)
}
}
internal fun List<String>.latestVersion(): String {
return sortByVersion().first()
}
internal fun List<String>.sortByVersion(): List<String> {
return sortedByDescending { version ->
version.split('.').let {
if (it.size == 2) it + "0"
else it
}.reversed().foldIndexed(0.0) { index: Int, acc: Double, s: String ->
acc + 1000.0.pow(index) * s.convertPatchVersionToWeight()
}
}
}
internal fun String.convertPatchVersionToWeight(): Double {
return this.split('-').reversed().foldIndexed(0.0) { index: Int, acc: Double, s: String ->
acc + 10.0.pow(index) * (s.toIntOrNull()?.toDouble() ?: -0.5)
}
} }

View File

@ -11,11 +11,7 @@
package net.mamoe.mirai.console.wrapper package net.mamoe.mirai.console.wrapper
import io.ktor.client.request.get
import io.ktor.http.URLProtocol
import java.io.File import java.io.File
import kotlin.math.pow
import kotlin.system.exitProcess
internal object CoreUpdater { internal object CoreUpdater {
@ -29,13 +25,18 @@ internal object CoreUpdater {
} }
suspend fun versionCheck() { suspend fun versionCheck(strategy: VersionUpdateStrategy) {
println("Fetching Newest Core Version .. ") println("Fetching Newest Core Version .. ")
val newest = getNewestVersion()
val current = getCurrentVersion() val current = getCurrentVersion()
println("Local Core Version: $current | Newest Core Version: $newest") if (current != "0.0.0" && strategy == VersionUpdateStrategy.KEEP) {
println("Stay on current version.")
return
}
val newest = getNewestVersion(strategy, "net/mamoe/mirai-core-qqandroid/")
println("Local Core Version: $current | Newest $strategy Core Version: $newest")
if (current != newest) { if (current != newest) {
println("Updating shadowed-core from V$current -> V$newest, this is a force update") println("Updating shadowed-core from V$current -> V$newest")
this.getProtocolLib()?.delete() this.getProtocolLib()?.delete()
MiraiDownloader MiraiDownloader
.addTask( .addTask(
@ -47,33 +48,6 @@ internal object CoreUpdater {
} }
} }
/**
* 判断最新版本
* */
private suspend fun getNewestVersion(): String {
try {
return """>([0-9])*\.([0-9])*\.([0-9])*/""".toRegex().findAll(
Http.get<String> {
url {
protocol = URLProtocol.HTTPS
host = "jcenter.bintray.com"
path("net/mamoe/mirai-core-qqandroid/")
}
}).asSequence()
.map { it.value.drop(1).dropLast(1) }
.maxBy {
it.split('.').foldRightIndexed(0) { index: Int, s: String, acc: Int ->
acc + 100.0.pow(2 - index).toInt() * (s.toIntOrNull() ?: 0)
}
}!!
} catch (e: Exception) {
println("Failed to fetch newest Core version, please seek for help")
e.printStackTrace()
println("Failed to fetch newest Core version, please seek for help")
exitProcess(1)
}
}
/** /**
* 判断当前版本 * 判断当前版本
* 默认返回 "0.0.0" * 默认返回 "0.0.0"

View File

@ -1,3 +1,11 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("EXPERIMENTAL_API_USAGE") @file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.console.wrapper package net.mamoe.mirai.console.wrapper
@ -137,7 +145,6 @@ internal suspend fun ByteReadChannel.saveToContent(filepath: String) {
} }
internal fun getContent(filepath: String):File{ internal fun getContent(filepath: String):File{
return File(contentPath, filepath) return File(contentPath, filepath)
} }

View File

@ -1,3 +1,11 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.wrapper package net.mamoe.mirai.console.wrapper
import kotlinx.coroutines.* import kotlinx.coroutines.*
@ -146,6 +154,7 @@ class MiraiDownloaderProgressBarInUI(): MiraiDownloadProgressBar{
override fun ad(){ override fun ad(){
WrapperMain.uiLog("[Mirai国内镜像] 感谢崔Cloud慷慨提供更新服务器") WrapperMain.uiLog("[Mirai国内镜像] 感谢崔Cloud慷慨提供更新服务器")
} }
private val barLen = 20 private val barLen = 20
override fun update(rate: Float, message: String) { override fun update(rate: Float, message: String) {

View File

@ -10,11 +10,15 @@
package net.mamoe.mirai.console.wrapper package net.mamoe.mirai.console.wrapper
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.enum
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.awt.TextArea import java.awt.TextArea
import java.io.File import java.io.File
import java.net.URLClassLoader import java.net.URLClassLoader
import java.util.*
import java.util.jar.JarFile import java.util.jar.JarFile
import javax.swing.JFrame import javax.swing.JFrame
import javax.swing.JPanel import javax.swing.JPanel
@ -34,19 +38,43 @@ val extendedLibraries by lazy {
file.also { if (!it.exists()) it.mkdirs() } file.also { if (!it.exists()) it.mkdirs() }
} }
object WrapperMain { object WrapperCli : CliktCommand(name = "mirai-warpper") {
internal var uiBarOutput = StringBuilder() private val native by option(
private val uilog = StringBuilder() help = """
internal fun uiLog(any: Any?) { Start in GRAPHICAL mode without command line outputs
if (any != null) { ------------------------------------------
uilog.append(any) 以图形界面模式启动
} """.trimIndent(),
} envvar = "mirai.wrapper.native"
).flag("-n", default = false)
@JvmStatic private val update: VersionUpdateStrategy by option(
fun main(args: Array<String>) { help = """
gc() Strategy to automatic updates.
if (args.contains("native") || args.contains("-native")) { "KEEP" to stay on the current version;
"STABLE" to update to the latest stable versions;
"EA" to update to use the newest features but might not be stable.
------------------------------------------
版本升级策略. "KEEP" 为停留在当前版本; "STABLE" 为更新到最新稳定版; "EA" 为更新到最新预览版.
""".trimIndent(),
envvar = "mirai.wrapper.update"
).enum<VersionUpdateStrategy>().default(VersionUpdateStrategy.STABLE)
private val console: ConsoleType by option(
help = """
The type of the console to be started.
"GRAPHICAL" to use JavaFX graphical UI;
"TERMINAL" to use terminal UI for Unix;
"PURE" to use pure CLI.
------------------------------------------
UI 类型. "GRAPHICAL" JavaFX 图形界面; "TERMINAL" Unix 终端界面; "PURE" 为纯命令行.
""".trimIndent(),
envvar = "mirai.wrapper.console"
).enum<ConsoleType>().default(ConsoleType.Pure)
override fun run() {
if (native) {
val f = JFrame("Mirai-Console Version Check") val f = JFrame("Mirai-Console Version Check")
f.setSize(500, 200) f.setSize(500, 200)
f.setLocationRelativeTo(null) f.setLocationRelativeTo(null)
@ -60,29 +88,29 @@ object WrapperMain {
f.isVisible = true f.isVisible = true
uiLog("正在进行版本检查\n") WrapperMain.uiLog("正在进行版本检查\n")
val dic = System.getProperty("user.dir") val dic = System.getProperty("user.dir")
uiLog("工作目录: ${dic}\n") WrapperMain.uiLog("工作目录: ${dic}\n")
uiLog("扩展库目录: ${extendedLibraries}\n") WrapperMain.uiLog("扩展库目录: ${extendedLibraries}\n")
uiLog("若无法启动, 请尝试清除工作目录下/content/文件夹\n") WrapperMain.uiLog("若无法启动, 请尝试清除工作目录下/content/文件夹\n")
var uiOpen = true var uiOpen = true
GlobalScope.launch { GlobalScope.launch {
while (isActive && uiOpen) { while (isActive && uiOpen) {
delay(16)//60 fps delay(16)//60 fps
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
textArea.text = uilog.toString() + "\n" + uiBarOutput.toString() textArea.text = WrapperMain.uiLog.toString() + "\n" + WrapperMain.uiBarOutput.toString()
} }
} }
} }
runBlocking { runBlocking {
launch { launch {
CoreUpdater.versionCheck() CoreUpdater.versionCheck(update)
} }
launch { launch {
ConsoleUpdater.versionCheck(CONSOLE_GRAPHICAL) ConsoleUpdater.versionCheck(ConsoleType.Graphical, update)
} }
} }
uiLog("版本检查完成, 启动中\n") WrapperMain.uiLog("版本检查完成, 启动中\n")
runBlocking { runBlocking {
MiraiDownloader.downloadIfNeed(true) MiraiDownloader.downloadIfNeed(true)
@ -93,41 +121,61 @@ object WrapperMain {
f.isVisible = false f.isVisible = false
} }
start(CONSOLE_GRAPHICAL) WrapperMain.start(ConsoleType.Graphical)
} else { } else {
preStartInNonNative() WrapperMain.preStartInNonNative(console, update)
}
}
}
enum class ConsoleType {
Graphical,
Terminal,
Pure
}
enum class VersionUpdateStrategy {
KEEP,
STABLE,
EA
}
object WrapperMain {
internal var uiBarOutput = StringBuilder()
internal val uiLog = StringBuilder()
internal fun uiLog(any: Any?) {
if (any != null) {
uiLog.append(any)
} }
} }
@JvmStatic
fun main(args: Array<String>) {
gc()
WrapperCli.main(args)
}
private fun preStartInNonNative() {
internal fun preStartInNonNative(defaultType: ConsoleType, strategy: VersionUpdateStrategy) {
println("You are running Mirai-Console-Wrapper under " + System.getProperty("user.dir")) println("You are running Mirai-Console-Wrapper under " + System.getProperty("user.dir"))
println("All additional libraries are located at $extendedLibraries") println("All additional libraries are located at $extendedLibraries")
var type = WrapperProperties.determineConsoleType(WrapperProperties.content)
var type = ConsoleType.values().firstOrNull { it.name.equals(WrapperProperties.content, ignoreCase = true) }
if (type != null) { if (type != null) {
println("Starting Mirai Console $type, reset by clear /content/") println("Starting Mirai Console $type, reset by clear /content/")
} else { } else {
println("Please select Console Type") WrapperProperties.content = defaultType.toString()
println("请选择 Console 版本") type = defaultType
println("=> Pure : pure console")
println("=> Graphical : graphical UI except unix")
println("=> Terminal : [Not Supported Yet] console in unix")
val scanner = Scanner(System.`in`)
while (type == null) {
var input = scanner.next()
input = input.toUpperCase()[0] + input.toLowerCase().substring(1)
println("Selecting $input")
type = WrapperProperties.determineConsoleType(input)
}
WrapperProperties.content = type
} }
println("Starting version check...") println("Starting version check...")
runBlocking { runBlocking {
launch { launch {
CoreUpdater.versionCheck() CoreUpdater.versionCheck(strategy)
} }
launch { launch {
ConsoleUpdater.versionCheck(type) ConsoleUpdater.versionCheck(type, strategy)
} }
} }
@ -143,7 +191,7 @@ object WrapperMain {
start(type) start(type)
} }
private fun start(type: String) { internal fun start(type: ConsoleType) {
val loader = MiraiClassLoader( val loader = MiraiClassLoader(
CoreUpdater.getProtocolLib()!!, CoreUpdater.getProtocolLib()!!,
ConsoleUpdater.getFile()!!, ConsoleUpdater.getFile()!!,
@ -152,12 +200,12 @@ object WrapperMain {
loader.loadClass("net.mamoe.mirai.BotFactoryJvm") loader.loadClass("net.mamoe.mirai.BotFactoryJvm")
loader.loadClass( loader.loadClass(
when (type) { when (type) {
CONSOLE_PURE -> "net.mamoe.mirai.console.pure.MiraiConsolePureLoader" ConsoleType.Pure -> "net.mamoe.mirai.console.pure.MiraiConsolePureLoader"
CONSOLE_GRAPHICAL -> "net.mamoe.mirai.console.graphical.MiraiConsoleGraphicalLoader" ConsoleType.Graphical -> "net.mamoe.mirai.console.graphical.MiraiConsoleGraphicalLoader"
else -> return else -> return
} }
).getMethod("load", String::class.java, String::class.java) ).getMethod("load", String::class.java, String::class.java)
.invoke(null, CoreUpdater.getCurrentVersion(), ConsoleUpdater.getCurrentVersion()) .invoke(null, CoreUpdater.getCurrentVersion(), ConsoleUpdater.getCurrentVersion())
} }
@ -211,16 +259,6 @@ private object WrapperProperties {
var content var content
get() = contentFile.readText() get() = contentFile.readText()
set(value) = contentFile.writeText(value) set(value) = contentFile.writeText(value)
fun determineConsoleType(
type: String
): String? {
if (type == CONSOLE_PURE || type == CONSOLE_GRAPHICAL || type == CONSOLE_TERMINAL) {
return type
}
return null
}
} }
private fun gc() { private fun gc() {

View File

@ -0,0 +1,28 @@
package net.mamoe.mirai.console.wrapper
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
internal class ConsoleUpdaterKtTest {
@Test
fun testVersionCompare() {
assertEquals(
listOf(
"1.0.0",
"1.0-EA-2",
"1.0-EA",
"0.40.0"
),
listOf(
"1.0.0",
"0.40.0",
"1.0-EA",
"1.0-EA-2"
).sortByVersion()
)
}
}
fun main() {
ConsoleUpdaterKtTest().testVersionCompare()
}