Fixed logging spaces and tabs missing, unclickable version item, removing 'logging' element when installing forge and liteloader

This commit is contained in:
huangyuhui 2017-08-25 13:22:10 +08:00
parent e70226afba
commit 3b4359f7eb
16 changed files with 96 additions and 118 deletions

View File

@ -51,9 +51,9 @@ import com.sun.javafx.scene.layout.region.BorderStyleConverter.HIDDEN
*/
@Throws(IOException::class, JsonParseException::class)
fun readHMCLModpackManifest(f: File): Modpack {
val manifestJson = readTextFromZipFile(f, "modpack.json")
val manifestJson = f.readTextZipEntry("modpack.json")
val manifest = GSON.fromJson<Modpack>(manifestJson) ?: throw JsonParseException("`modpack.json` not found. $f is not a valid HMCL modpack.")
val gameJson = readTextFromZipFile(f, "minecraft/pack.json")
val gameJson = f.readTextZipEntry("minecraft/pack.json")
val game = GSON.fromJson<Version>(gameJson) ?: throw JsonParseException("`minecraft/pack.json` not found. $f iot a valid HMCL modpack.")
return if (game.jar == null)
if (manifest.gameVersion.isNullOrBlank()) throw JsonParseException("Cannot recognize the game version of modpack $f.")
@ -71,7 +71,7 @@ class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, privat
init {
check(!repository.hasVersion(name), { "Version $name already exists." })
val json = readTextFromZipFile(zipFile, "minecraft/pack.json")
val json = zipFile.readTextZipEntry("minecraft/pack.json")
var version = GSON.fromJson<Version>(json)!!
version = version.copy(jar = null)
dependents += dependency.gameBuilder().name(name).gameVersion(modpack.gameVersion!!).buildAsync()
@ -81,10 +81,7 @@ class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, privat
private val run = repository.getRunDirectory(name)
override fun execute() {
unzipSubDirectory(zipFile, run, "minecraft/", false)
val json = run.resolve("pack.json")
if (repository.getVersionJson(name) != json)
json.delete()
zipFile.uncompressTo(run, "minecraft/", callback = { it != "minecraft/pack.json" }, ignoreExistentFile = false)
}
}

View File

@ -171,7 +171,10 @@ object Settings {
else {
System.setProperty("http.proxyHost", proxyHost)
System.setProperty("http.proxyPort", proxyPort)
proxy = Proxy(proxyType, InetSocketAddress(host, port))
if (proxyType == Proxy.Type.DIRECT)
proxy = Proxy.NO_PROXY
else
proxy = Proxy(proxyType, InetSocketAddress(host, port))
val user = proxyUser
val pass = proxyPass

View File

@ -30,13 +30,11 @@ import javafx.scene.layout.StackPane
import javafx.scene.web.WebEngine
import javafx.scene.web.WebView
import javafx.stage.Stage
import netscape.javascript.JSObject
import org.jackhuang.hmcl.game.LauncherHelper
import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.util.Log4jLevel
import org.jackhuang.hmcl.util.inc
import org.jackhuang.hmcl.util.onChange
import org.jackhuang.hmcl.util.readFullyAsString
import org.jackhuang.hmcl.util.*
import org.w3c.dom.Document
import org.w3c.dom.Node
import java.util.concurrent.Callable
@ -59,7 +57,10 @@ class LogWindow : Stage() {
fun logLine(line: String, level: Log4jLevel) {
impl.body.appendChild(impl.engine.document.createElement("div").apply {
textContent = line
// a <pre> element to prevent multiple spaces and tabs being removed.
appendChild(impl.engine.document.createElement("pre").apply {
textContent = line
})
})
impl.engine.executeScript("checkNewLog(\"${level.name.toLowerCase()}\");scrollToBottom();")

View File

@ -72,7 +72,7 @@ class ModpackPage(private val controller: WizardController): StackPane(), Wizard
lblName.text = manifest!!.name
lblVersion.text = manifest!!.version
lblAuthor.text = manifest!!.author
txtModpackName.text = manifest!!.name + "-" + manifest!!.version
txtModpackName.text = manifest!!.name + (if (manifest!!.version.isNullOrBlank()) "" else ("-" + manifest!!.version))
} catch (e: Exception) {
// TODO
txtModpackName.text = i18n("modpack.task.install.error")

View File

@ -5,14 +5,13 @@
<?import javafx.scene.control.ScrollPane?>
<fx:root
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
styleClass="transparent" type="StackPane"
styleClass="transparent" type="StackPane" pickOnBounds="false"
xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">
<ScrollPane fitToHeight="true" fitToWidth="true" fx:id="scrollPane" hbarPolicy="NEVER">
<JFXMasonryPane fx:id="masonryPane" HSpacing="3" VSpacing="3" cellWidth="182" cellHeight="160">
</JFXMasonryPane>
</ScrollPane>
<VBox style="-fx-padding: 15;" spacing="15" pickOnBounds="false" alignment="BOTTOM_RIGHT">
<!--JFXSpinner fx:id="spinner" style="-fx-radius:10" styleClass="materialDesign-purple, first-spinner" /-->
<JFXButton prefWidth="40" prefHeight="40" buttonType="RAISED" fx:id="btnRefresh"
style="-fx-background-color:#5264AE;-fx-background-radius: 50px;">
<graphic>

View File

@ -10,9 +10,9 @@
<?import com.jfoenix.controls.JFXRadioButton?>
<fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
type="StackPane">
<VBox fx:id="content">
<StackPane fx:id="header" VBox.vgrow="ALWAYS">
type="StackPane" pickOnBounds="false">
<VBox fx:id="content" pickOnBounds="false">
<StackPane fx:id="header" VBox.vgrow="ALWAYS" pickOnBounds="false">
<BorderPane>
<top>
<HBox alignment="CENTER_RIGHT">
@ -26,7 +26,7 @@
</center>
</BorderPane>
</StackPane>
<StackPane fx:id="body" style="-fx-background-radius: 0 0 2 2; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;" minHeight="40">
<StackPane fx:id="body" style="-fx-background-radius: 0 0 2 2; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;" minHeight="40" pickOnBounds="false">
<BorderPane>
<left>
<HBox spacing="8">

View File

@ -9,7 +9,7 @@
overflow-x: hidden;
}
div {
div > pre {
font: ${FONT};
margin: 0px;
padding: 2px;

View File

@ -43,10 +43,10 @@ object BMCLAPIDownloadProvider : DownloadProvider() {
}
override fun injectURL(baseURL: String): String = baseURL
.replace("https://launchermeta.mojang.com", "http://bmclapi2.bangbang93.com")
.replace("https://launcher.mojang.com", "http://bmclapi2.bangbang93.com")
.replace("https://libraries.minecraft.net", "http://bmclapi2.bangbang93.com/libraries")
.replace("http://files.minecraftforge.net/maven", "http://bmclapi2.bangbang93.com/maven")
.replace("http://dl.liteloader.com/versions/versions.json", "http://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json")
.replace("http://dl.liteloader.com/versions", "http://bmclapi2.bangbang93.com/maven")
.replace("https://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com")
.replace("https://launcher.mojang.com", "https://bmclapi2.bangbang93.com")
.replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries")
.replace("http://files.minecraftforge.net/maven", "https://bmclapi2.bangbang93.com/maven")
.replace("http://dl.liteloader.com/versions/versions.json", "httsp://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json")
.replace("http://dl.liteloader.com/versions", "https://bmclapi2.bangbang93.com/maven")
}

View File

@ -46,8 +46,6 @@ object MojangDownloadProvider : DownloadProvider() {
override fun injectURL(baseURL: String): String {
if (baseURL.endsWith("net/minecraftforge/forge/json"))
return baseURL
else if (Locale.getDefault() == Locale.CHINA)
return baseURL.replace("http://files.minecraftforge.net/maven", "http://maven.aliyun.com/nexus/content/groups/public");
else
return baseURL.replace("http://files.minecraftforge.net/maven", "http://ftb.cursecdn.com/FTB2/maven")
}

View File

@ -73,7 +73,7 @@ class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager,
val versionProvider = SimpleVersionProvider()
versionProvider.addVersion(version)
result = installProfile.versionInfo!!.copy(inheritsFrom = version.id).resolve(versionProvider).copy(id = version.id)
result = installProfile.versionInfo!!.copy(inheritsFrom = version.id).resolve(versionProvider).copy(id = version.id, logging = emptyMap())
dependencies += GameLibrariesTask(dependencyManager, installProfile.versionInfo)
}

View File

@ -69,7 +69,8 @@ class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyMana
result = version.copy(
mainClass = "net.minecraft.launchwrapper.Launch",
minecraftArguments = version.minecraftArguments + " --tweakClass " + remote.tag.tweakClass,
libraries = merge(tempVersion.libraries, version.libraries)
libraries = merge(tempVersion.libraries, version.libraries),
logging = emptyMap()
)
dependencies += GameLibrariesTask(dependencyManager, tempVersion)
}

View File

@ -73,7 +73,7 @@ open class Library(
companion object LibrarySerializer : JsonDeserializer<Library>, JsonSerializer<Library> {
fun fromName(name: String, url: String? = null, downloads: LibrariesDownloadInfo? = null, extract: ExtractRules? = null, natives: Map<OS, String>? = null, rules: List<CompatibilityRule>? = null): Library {
val arr = name.split(":".toRegex(), 3)
val arr = name.split(":".toRegex(), 4)
if (arr.size != 3 && arr.size != 4)
throw IllegalArgumentException("Library name is malformed. Correct example: group:artifact:version.")
return Library(

View File

@ -214,11 +214,11 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
open fun decompressNatives() {
version.libraries.filter { it.isNative }.forEach { library ->
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
unzip(zip = repository.getLibraryFile(version, library),
repository.getLibraryFile(version, library).uncompressTo(
dest = native,
callback = if (library.extract == null) null
else library.extract!!::shouldExtract,
ignoreExistsFile = false)
ignoreExistentFile = false)
}
}
@ -230,10 +230,13 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
decompressNatives()
if (options.precalledCommand != null && options.precalledCommand.isNotBlank()) {
val process = Runtime.getRuntime().exec(options.precalledCommand)
ignoreException {
if (process.isAlive)
process.waitFor()
try {
val process = Runtime.getRuntime().exec(options.precalledCommand)
if (process.isAlive)
process.waitFor()
} catch (e: IOException) {
// TODO: alert precalledCommand is wrong.
// rethrow InterruptedException
}
}
@ -287,6 +290,14 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
}
private fun startMonitors(managedProcess: ManagedProcess, processListener: ProcessListener, isDaemon: Boolean = true) {
val enablesLoggingInfo = version.logging?.containsKey(DownloadType.CLIENT) ?: false
if (enablesLoggingInfo)
startMonitorsWithLoggingInfo(managedProcess, processListener, isDaemon)
else
startMonitorsWithoutLoggingInfo(managedProcess, processListener, isDaemon)
}
private fun startMonitorsWithLoggingInfo(managedProcess: ManagedProcess, processListener: ProcessListener, isDaemon: Boolean = true) {
processListener.setProcess(managedProcess)
val logHandler = Log4jHandler { line, level -> processListener.onLog(line, level); managedProcess.lines += line }.apply { start() }
managedProcess.relatedThreads += logHandler
@ -297,6 +308,15 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
managedProcess.relatedThreads += thread(name = "exit-waiter", isDaemon = isDaemon, block = ExitWaiter(managedProcess, listOf(stdout, stderr), { exitCode, exitType -> logHandler.onStopped(); processListener.onExit(exitCode, exitType) })::run)
}
private fun startMonitorsWithoutLoggingInfo(managedProcess: ManagedProcess, processListener: ProcessListener, isDaemon: Boolean = true) {
processListener.setProcess(managedProcess)
val stdout = thread(name = "stdout-pump", isDaemon = isDaemon, block = StreamPump(managedProcess.process.inputStream, { processListener.onLog(it + OS.LINE_SEPARATOR, Log4jLevel.guessLevel(it) ?: Log4jLevel.INFO); managedProcess.lines += it })::run)
managedProcess.relatedThreads += stdout
val stderr = thread(name = "stderr-pump", isDaemon = isDaemon, block = StreamPump(managedProcess.process.errorStream, { processListener.onLog(it + OS.LINE_SEPARATOR, Log4jLevel.ERROR); managedProcess.lines += it })::run)
managedProcess.relatedThreads += stderr
managedProcess.relatedThreads += thread(name = "exit-waiter", isDaemon = isDaemon, block = ExitWaiter(managedProcess, listOf(stdout, stderr), { exitCode, exitType -> processListener.onExit(exitCode, exitType) })::run)
}
companion object {
const val LAUNCH_ASYNC_ID = "process"
}

View File

@ -106,7 +106,7 @@ class CurseForgeModpackManifestFile (
*/
@Throws(IOException::class, JsonParseException::class)
fun readCurseForgeModpackManifest(f: File): Modpack {
val json = readTextFromZipFile(f, "manifest.json")
val json = f.readTextZipEntry("manifest.json")
val manifest = GSON.fromJson<CurseForgeModpackManifest>(json)
?: throw JsonParseException("`manifest.json` not found. Not a valid CurseForge modpack.")
return Modpack(
@ -114,7 +114,7 @@ fun readCurseForgeModpackManifest(f: File): Modpack {
version = manifest.version,
author = manifest.author,
gameVersion = manifest.minecraft.gameVersion,
description = readTextFromZipFileQuietly(f, "modlist.html") ?: "No description",
description = f.readTextZipEntryQuietly("modlist.html") ?: "No description",
manifest = manifest)
}
@ -147,7 +147,7 @@ class CurseForgeModpackInstallTask(private val dependencyManager: DefaultDepende
}
override fun execute() {
unzipSubDirectory(zipFile, run, manifest.overrides)
zipFile.uncompressTo(run, subDirectory = manifest.overrides)
var finished = 0
for (f in manifest.files) {

View File

@ -42,7 +42,7 @@ class InstancePatch @JvmOverloads constructor(
val libraries: List<Library> = emptyList()
)
class InstanceConfiguration(contentStream: InputStream) {
class InstanceConfiguration(defaultName: String, contentStream: InputStream) {
/**
* The instance's name
*/
@ -183,15 +183,17 @@ class InstanceConfiguration(contentStream: InputStream) {
showConsole = p.getProperty("ShowConsole") == "true"
showConsoleOnError = p.getProperty("ShowConsoleOnError") == "true"
wrapperCommand = p.getProperty("WrapperCommand")
name = p.getProperty("name")
notes = p.getProperty("notes")
name = defaultName
notes = p.getProperty("notes") ?: ""
}
}
fun readMMCModpackManifest(f: File): Modpack {
ZipFile(f).use { zipFile ->
val entry = zipFile.getEntry("instance.cfg") ?: throw IOException("`instance.cfg` not found, $f is not a valid MultiMC modpack.")
val cfg = InstanceConfiguration(zipFile.getInputStream(entry))
val firstEntry = zipFile.entries.nextElement()
val name = firstEntry.name.substringBefore("/")
val entry = zipFile.getEntry("$name/instance.cfg") ?: throw IOException("`instance.cfg` not found, $f is not a valid MultiMC modpack.")
val cfg = InstanceConfiguration(name, zipFile.getInputStream(entry))
return Modpack(
name = cfg.name,
version = "",
@ -217,12 +219,12 @@ class MMCModpackInstallTask(private val dependencyManager: DefaultDependencyMana
override fun execute() {
var version = repository.readVersionJson(name)!!
unzipSubDirectory(zipFile, run, "minecraft/", false)
zipFile.uncompressTo(run, subDirectory = "${manifest.name}/minecraft/", ignoreExistentFile = false, allowStoredEntriesWithDataDescriptor = true)
ZipFile(zipFile).use { zip ->
for (entry in zip.entries) {
// ensure that this entry is in folder 'patches' and is a json file.
if (!entry.isDirectory && entry.name.startsWith("patches/") && entry.name.endsWith(".json")) {
if (!entry.isDirectory && entry.name.startsWith("${manifest.name}/patches/") && entry.name.endsWith(".json")) {
val patch = GSON.fromJson<InstancePatch>(zip.getInputStream(entry).readFullyAsString())!!
val args = StringBuilder(version.minecraftArguments)
for (arg in patch.tweakers)

View File

@ -34,21 +34,22 @@ import java.io.IOException
*/
@JvmOverloads
@Throws(IOException::class)
fun zip(src: File, destZip: File, pathNameCallback: ((String, Boolean) -> String?)? = null) {
fun File.zipTo(destZip: File, pathNameCallback: ((String, Boolean) -> String?)? = null) {
ZipArchiveOutputStream(destZip.outputStream()).use { zos ->
val basePath: String
if (src.isDirectory)
basePath = src.path
if (this.isDirectory)
basePath = this.path
else
//直接压缩单个文件时,取父目录
basePath = src.parent
zipFile(src, basePath, zos, pathNameCallback)
//直接压缩单个文件时,取父目录
basePath = this.parent
zipFile(this, basePath, zos, pathNameCallback)
zos.closeArchiveEntry()
}
}
/**
* Zip file.
*
* @param src source directory to be compressed.
* @param basePath the file directory to be compressed, if [src] is a file, this is the parent directory of [src]
* @param zos the [ZipOutputStream] of dest zip file.
@ -92,69 +93,21 @@ private fun zipFile(src: File,
/**
* Decompress the given zip file to a directory.
* @param zip the source zip file.
* @param dest the dest directory.
* @param callback will be called for every entry in the zip file, returns false if you dont want this file unzipped.
* @throws IOException
*/
@JvmOverloads
@Throws(IOException::class)
fun unzip(zip: File, dest: File, callback: ((String) -> Boolean)? = null, ignoreExistsFile: Boolean = true) {
val buf = ByteArray(1024)
dest.mkdirs()
ZipArchiveInputStream(zip.inputStream()).use { zipFile ->
if (zip.exists()) {
val strPath = dest.absolutePath
while (true) {
val zipEnt = zipFile.nextEntry ?: break
var strtemp: String
var gbkPath = zipEnt.name
if (callback != null)
if (!callback.invoke(gbkPath))
continue
if (zipEnt.isDirectory) {
strtemp = strPath + File.separator + gbkPath
val dir = File(strtemp)
dir.mkdirs()
} else {
//读写文件
gbkPath = zipEnt.name
strtemp = strPath + File.separator + gbkPath
//建目录
val strsubdir = gbkPath
for (i in 0 until strsubdir.length)
if (strsubdir.substring(i, i + 1).equals("/", ignoreCase = true)) {
val temp = strPath + File.separator + strsubdir.substring(0, i)
val subdir = File(temp)
if (!subdir.exists())
subdir.mkdir()
}
if (ignoreExistsFile && File(strtemp).exists())
continue
File(strtemp).outputStream().use({ fos ->
zipFile.copyTo(fos, buf)
})
}
}
}
}
}
/**
* Decompress the subdirectory of given zip file.
* @param zip the source zip file.
*
* @param dest the dest directory.
* @param subDirectory the subdirectory of the zip file to be decompressed.
* @param callback will be called for every entry in the zip file, returns false if you dont want this file being uncompressed.
* @param ignoreExistentFile true if skip all existent files.
* @param allowStoredEntriesWithDataDescriptor whether the zip stream will try to read STORED entries that use a data descriptor
* @throws IOException
*/
@JvmOverloads
@Throws(IOException::class)
fun unzipSubDirectory(zip: File, dest: File, subDirectory: String, ignoreExistentFile: Boolean = true) {
fun File.uncompressTo(dest: File, subDirectory: String = "", callback: ((String) -> Boolean)? = null, ignoreExistentFile: Boolean = true, allowStoredEntriesWithDataDescriptor: Boolean = false) {
val buf = ByteArray(1024)
dest.mkdirs()
ZipArchiveInputStream(zip.inputStream()).use { zipFile ->
if (zip.exists()) {
ZipArchiveInputStream(this.inputStream(), null, true, allowStoredEntriesWithDataDescriptor).use { zipFile ->
if (this.exists()) {
val strPath = dest.absolutePath
while (true) {
val zipEnt = zipFile.nextEntry ?: break
@ -166,13 +119,18 @@ fun unzipSubDirectory(zip: File, dest: File, subDirectory: String, ignoreExisten
gbkPath = gbkPath.substring(subDirectory.length)
if (gbkPath.startsWith("/") || gbkPath.startsWith("\\")) gbkPath = gbkPath.substring(1)
strtemp = strPath + File.separator + gbkPath
if (callback != null)
if (!callback.invoke(gbkPath))
continue
if (zipEnt.isDirectory) {
val dir = File(strtemp)
dir.mkdirs()
} else {
//建目录
val strsubdir = gbkPath
for (i in 0..strsubdir.length - 1)
for (i in 0 until strsubdir.length)
if (strsubdir.substring(i, i + 1).equals("/", ignoreCase = true)) {
val temp = strPath + File.separator + strsubdir.substring(0, i)
val subdir = File(temp)
@ -194,14 +152,14 @@ fun unzipSubDirectory(zip: File, dest: File, subDirectory: String, ignoreExisten
* Read the text content of a file in zip.
*
* @param f the zip file
* @param location the location of the text in zip file, something like A/B/C/D.txt
* @param name the location of the text in zip file, something like A/B/C/D.txt
* @throws IOException if the file is not a valid zip file.
* @return the content of given file.
*/
@Throws(IOException::class)
fun readTextFromZipFile(f: File, location: String): String {
ZipFile(f).use { zipFile ->
val entry = zipFile.getEntry(location) ?: throw IOException("`$location` not found.")
fun File.readTextZipEntry(name: String): String {
ZipFile(this).use { zipFile ->
val entry = zipFile.getEntry(name) ?: throw IOException("`$name` not found.")
return zipFile.getInputStream(entry).readFullyAsString()
}
}
@ -213,10 +171,9 @@ fun readTextFromZipFile(f: File, location: String): String {
* @param location the location of the text in zip file, something like A/B/C/D.txt
* @return the content of given file.
*/
fun readTextFromZipFileQuietly(f: File, location: String): String? {
fun File.readTextZipEntryQuietly(location: String) =
try {
return readTextFromZipFile(f, location)
readTextZipEntry(location)
} catch (e: IOException) {
return null
}
}
null
}