This commit is contained in:
huangyuhui
2017-08-22 14:31:35 +08:00
parent b40b0ec47c
commit 0cc7d621bc
108 changed files with 752 additions and 371 deletions

View File

@@ -18,7 +18,7 @@
package org.jackhuang.hmcl package org.jackhuang.hmcl
import org.jackhuang.hmcl.setting.Profile import org.jackhuang.hmcl.setting.Profile
import java.util.EventObject import java.util.*
/** /**
* This event gets fired when the selected profile changed. * This event gets fired when the selected profile changed.

View File

@@ -20,7 +20,6 @@ package org.jackhuang.hmcl
import javafx.application.Application import javafx.application.Application
import javafx.application.Platform import javafx.application.Platform
import javafx.stage.Stage import javafx.stage.Stage
import javafx.stage.StageStyle
import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.task.Scheduler import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.ui.Controllers import org.jackhuang.hmcl.ui.Controllers
@@ -29,7 +28,6 @@ import org.jackhuang.hmcl.util.DEFAULT_USER_AGENT
import org.jackhuang.hmcl.util.LOG import org.jackhuang.hmcl.util.LOG
import org.jackhuang.hmcl.util.OS import org.jackhuang.hmcl.util.OS
import java.io.File import java.io.File
import java.util.concurrent.Callable
import java.util.logging.Level import java.util.logging.Level
fun i18n(key: String): String { fun i18n(key: String): String {

View File

@@ -23,7 +23,6 @@ import org.jackhuang.hmcl.setting.EnumGameDirectory
import org.jackhuang.hmcl.setting.Profile import org.jackhuang.hmcl.setting.Profile
import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.setting.VersionSetting import org.jackhuang.hmcl.setting.VersionSetting
import org.jackhuang.hmcl.util.GSON
import org.jackhuang.hmcl.util.LOG import org.jackhuang.hmcl.util.LOG
import org.jackhuang.hmcl.util.fromJson import org.jackhuang.hmcl.util.fromJson
import java.io.File import java.io.File

View File

@@ -29,15 +29,15 @@ import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.setting.VersionSetting import org.jackhuang.hmcl.setting.VersionSetting
import org.jackhuang.hmcl.task.* import org.jackhuang.hmcl.task.*
import org.jackhuang.hmcl.ui.* import org.jackhuang.hmcl.ui.*
import org.jackhuang.hmcl.util.JavaProcess
import org.jackhuang.hmcl.util.Log4jLevel import org.jackhuang.hmcl.util.Log4jLevel
import org.jackhuang.hmcl.util.ManagedProcess
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
object LauncherHelper { object LauncherHelper {
private val launchingStepsPane = LaunchingStepsPane() private val launchingStepsPane = LaunchingStepsPane()
val PROCESS = ConcurrentLinkedQueue<JavaProcess>() val PROCESS = ConcurrentLinkedQueue<ManagedProcess>()
fun launch() { fun launch() {
val profile = Settings.selectedProfile val profile = Settings.selectedProfile
@@ -119,11 +119,11 @@ object LauncherHelper {
authInfo.username to "<player>" authInfo.username to "<player>"
) )
private val launcherVisibility = setting.launcherVisibility private val launcherVisibility = setting.launcherVisibility
private lateinit var process: JavaProcess private lateinit var process: ManagedProcess
private var lwjgl = false private var lwjgl = false
private var logWindow: LogWindow? = null private var logWindow: LogWindow? = null
private val logs = LinkedList<Pair<String, Log4jLevel>>() private val logs = LinkedList<Pair<String, Log4jLevel>>()
override fun setProcess(process: JavaProcess) { override fun setProcess(process: ManagedProcess) {
this.process = process this.process = process
if (setting.showLogs) { if (setting.showLogs) {

View File

@@ -20,8 +20,7 @@ package org.jackhuang.hmcl.setting
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import org.jackhuang.hmcl.Main import org.jackhuang.hmcl.Main
import org.jackhuang.hmcl.util.JavaVersion import org.jackhuang.hmcl.util.JavaVersion
import java.io.File import java.util.*
import java.util.TreeMap
class Config { class Config {
@SerializedName("last") @SerializedName("last")

View File

@@ -22,9 +22,10 @@ import javafx.beans.InvalidationListener
import org.jackhuang.hmcl.download.DefaultDependencyManager import org.jackhuang.hmcl.download.DefaultDependencyManager
import org.jackhuang.hmcl.game.HMCLGameRepository import org.jackhuang.hmcl.game.HMCLGameRepository
import org.jackhuang.hmcl.mod.ModManager import org.jackhuang.hmcl.mod.ModManager
import org.jackhuang.hmcl.util.* import org.jackhuang.hmcl.util.ImmediateObjectProperty
import org.jackhuang.hmcl.util.property.ImmediateObjectProperty import org.jackhuang.hmcl.util.ImmediateStringProperty
import org.jackhuang.hmcl.util.property.ImmediateStringProperty import org.jackhuang.hmcl.util.getValue
import org.jackhuang.hmcl.util.setValue
import java.io.File import java.io.File
import java.lang.reflect.Type import java.lang.reflect.Type

View File

@@ -20,26 +20,23 @@ package org.jackhuang.hmcl.setting
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import javafx.beans.InvalidationListener import javafx.beans.InvalidationListener
import javafx.scene.text.Font import javafx.scene.text.Font
import java.io.IOException
import org.jackhuang.hmcl.Main import org.jackhuang.hmcl.Main
import org.jackhuang.hmcl.ProfileChangedEvent
import org.jackhuang.hmcl.ProfileLoadingEvent
import org.jackhuang.hmcl.auth.Account
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
import org.jackhuang.hmcl.download.DownloadProvider import org.jackhuang.hmcl.download.DownloadProvider
import org.jackhuang.hmcl.download.MojangDownloadProvider import org.jackhuang.hmcl.download.MojangDownloadProvider
import org.jackhuang.hmcl.util.LOG
import java.io.File
import java.util.logging.Level
import org.jackhuang.hmcl.ProfileLoadingEvent
import org.jackhuang.hmcl.ProfileChangedEvent
import org.jackhuang.hmcl.auth.Account
import org.jackhuang.hmcl.util.*
import org.jackhuang.hmcl.event.EVENT_BUS import org.jackhuang.hmcl.event.EVENT_BUS
import org.jackhuang.hmcl.util.property.ImmediateObjectProperty import org.jackhuang.hmcl.util.*
import org.jackhuang.hmcl.util.property.ImmediateStringProperty import java.io.File
import java.io.IOException
import java.net.Authenticator import java.net.Authenticator
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.net.PasswordAuthentication import java.net.PasswordAuthentication
import java.net.Proxy import java.net.Proxy
import java.util.* import java.util.*
import java.util.logging.Level
object Settings { object Settings {
val GSON = GsonBuilder() val GSON = GsonBuilder()

View File

@@ -22,7 +22,6 @@ import javafx.beans.InvalidationListener
import org.jackhuang.hmcl.Main import org.jackhuang.hmcl.Main
import org.jackhuang.hmcl.game.LaunchOptions import org.jackhuang.hmcl.game.LaunchOptions
import org.jackhuang.hmcl.util.* import org.jackhuang.hmcl.util.*
import org.jackhuang.hmcl.util.property.*
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.lang.reflect.Type import java.lang.reflect.Type

View File

@@ -19,15 +19,14 @@ package org.jackhuang.hmcl.ui
import com.jfoenix.controls.* import com.jfoenix.controls.*
import javafx.application.Platform import javafx.application.Platform
import javafx.fxml.FXML
import javafx.scene.Node
import javafx.scene.control.ScrollPane
import javafx.scene.layout.StackPane
import javafx.beans.property.SimpleStringProperty import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty import javafx.beans.property.StringProperty
import javafx.beans.value.ChangeListener import javafx.fxml.FXML
import javafx.scene.Node
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.scene.control.ScrollPane
import javafx.scene.control.ToggleGroup import javafx.scene.control.ToggleGroup
import javafx.scene.layout.StackPane
import org.jackhuang.hmcl.auth.Account import org.jackhuang.hmcl.auth.Account
import org.jackhuang.hmcl.auth.OfflineAccount import org.jackhuang.hmcl.auth.OfflineAccount
import org.jackhuang.hmcl.auth.yggdrasil.InvalidCredentialsException import org.jackhuang.hmcl.auth.yggdrasil.InvalidCredentialsException
@@ -37,6 +36,7 @@ import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.task.Scheduler import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.taskResult import org.jackhuang.hmcl.task.taskResult
import org.jackhuang.hmcl.ui.wizard.DecoratorPage import org.jackhuang.hmcl.ui.wizard.DecoratorPage
import org.jackhuang.hmcl.util.onChange
class AccountsPage() : StackPane(), DecoratorPage { class AccountsPage() : StackPane(), DecoratorPage {
override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Accounts") override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Accounts")
@@ -50,14 +50,6 @@ class AccountsPage() : StackPane(), DecoratorPage {
@FXML lateinit var cboType: JFXComboBox<String> @FXML lateinit var cboType: JFXComboBox<String>
@FXML lateinit var progressBar: JFXProgressBar @FXML lateinit var progressBar: JFXProgressBar
val listener = ChangeListener<Account?> { _, _, newValue ->
masonryPane.children.forEach {
if (it is AccountItem) {
it.chkSelected.isSelected = newValue?.username == it.lblUser.text
}
}
}
init { init {
loadFXML("/assets/fxml/account.fxml") loadFXML("/assets/fxml/account.fxml")
children.remove(dialog) children.remove(dialog)
@@ -75,8 +67,8 @@ class AccountsPage() : StackPane(), DecoratorPage {
} }
txtPassword.validate() txtPassword.validate()
cboType.selectionModel.selectedIndexProperty().addListener { _, _, newValue -> cboType.selectionModel.selectedIndexProperty().onChange {
val visible = newValue != 0 val visible = it != 0
txtPassword.isVisible = visible txtPassword.isVisible = visible
} }
cboType.selectionModel.select(0) cboType.selectionModel.select(0)
@@ -84,7 +76,13 @@ class AccountsPage() : StackPane(), DecoratorPage {
txtPassword.setOnAction { onCreationAccept() } txtPassword.setOnAction { onCreationAccept() }
txtUsername.setOnAction { onCreationAccept() } txtUsername.setOnAction { onCreationAccept() }
Settings.selectedAccountProperty.addListener(listener) Settings.selectedAccountProperty.setChangedListener { account ->
masonryPane.children.forEach { node ->
if (node is AccountItem) {
node.chkSelected.isSelected = account?.username == node.lblUser.text
}
}
}
loadAccounts() loadAccounts()
@@ -92,10 +90,6 @@ class AccountsPage() : StackPane(), DecoratorPage {
addNewAccount() addNewAccount()
} }
override fun onClose() {
Settings.selectedAccountProperty.removeListener(listener)
}
fun loadAccounts() { fun loadAccounts() {
val children = mutableListOf<Node>() val children = mutableListOf<Node>()
var i = 0 var i = 0
@@ -103,9 +97,9 @@ class AccountsPage() : StackPane(), DecoratorPage {
for ((_, account) in Settings.getAccounts()) { for ((_, account) in Settings.getAccounts()) {
children += buildNode(++i, account, group) children += buildNode(++i, account, group)
} }
group.selectedToggleProperty().addListener { _, _, newValue -> group.selectedToggleProperty().onChange {
if (newValue != null) if (it != null)
Settings.selectedAccount = newValue.properties["account"] as Account Settings.selectedAccount = it.properties["account"] as Account
} }
masonryPane.resetChildren(children) masonryPane.resetChildren(children)
Platform.runLater { Platform.runLater {

View File

@@ -23,7 +23,7 @@ import com.jfoenix.controls.JFXDrawer
import com.jfoenix.controls.JFXHamburger import com.jfoenix.controls.JFXHamburger
import com.jfoenix.effects.JFXDepthManager import com.jfoenix.effects.JFXDepthManager
import com.jfoenix.svg.SVGGlyph import com.jfoenix.svg.SVGGlyph
import javafx.animation.* import javafx.animation.Timeline
import javafx.application.Platform import javafx.application.Platform
import javafx.beans.property.BooleanProperty import javafx.beans.property.BooleanProperty
import javafx.beans.property.ObjectProperty import javafx.beans.property.ObjectProperty
@@ -36,7 +36,6 @@ import javafx.geometry.Insets
import javafx.scene.Cursor import javafx.scene.Cursor
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.scene.control.ScrollPane
import javafx.scene.control.Tooltip import javafx.scene.control.Tooltip
import javafx.scene.input.MouseEvent import javafx.scene.input.MouseEvent
import javafx.scene.layout.* import javafx.scene.layout.*
@@ -49,7 +48,8 @@ import org.jackhuang.hmcl.ui.animation.AnimationProducer
import org.jackhuang.hmcl.ui.animation.ContainerAnimations import org.jackhuang.hmcl.ui.animation.ContainerAnimations
import org.jackhuang.hmcl.ui.animation.TransitionHandler import org.jackhuang.hmcl.ui.animation.TransitionHandler
import org.jackhuang.hmcl.ui.wizard.* import org.jackhuang.hmcl.ui.wizard.*
import org.jackhuang.hmcl.util.* import org.jackhuang.hmcl.util.getValue
import org.jackhuang.hmcl.util.setValue
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue

View File

@@ -18,13 +18,11 @@
package org.jackhuang.hmcl.ui package org.jackhuang.hmcl.ui
import com.jfoenix.controls.JFXComboBox import com.jfoenix.controls.JFXComboBox
import javafx.scene.layout.* import javafx.scene.layout.VBox
import org.jackhuang.hmcl.auth.Account
import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.ui.construct.IconedItem import org.jackhuang.hmcl.ui.construct.IconedItem
import org.jackhuang.hmcl.ui.construct.RipplerContainer import org.jackhuang.hmcl.ui.construct.RipplerContainer
import javax.swing.event.ChangeListener
class LeftPaneController(private val leftPane: AdvancedListBox) { class LeftPaneController(private val leftPane: AdvancedListBox) {
val versionsPane = VBox() val versionsPane = VBox()

View File

@@ -35,6 +35,7 @@ import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.util.Log4jLevel import org.jackhuang.hmcl.util.Log4jLevel
import org.jackhuang.hmcl.util.inc import org.jackhuang.hmcl.util.inc
import org.jackhuang.hmcl.util.onChange
import org.jackhuang.hmcl.util.readFullyAsString import org.jackhuang.hmcl.util.readFullyAsString
import org.w3c.dom.Document import org.w3c.dom.Document
import org.w3c.dom.Node import org.w3c.dom.Node
@@ -90,8 +91,8 @@ class LogWindow : Stage() {
engine = webView.engine engine = webView.engine
engine.loadContent(javaClass.getResourceAsStream("/assets/log-window-content.html").readFullyAsString().replace("\${FONT}", "${Settings.font.size}px \"${Settings.font.family}\"")) engine.loadContent(javaClass.getResourceAsStream("/assets/log-window-content.html").readFullyAsString().replace("\${FONT}", "${Settings.font.size}px \"${Settings.font.family}\""))
engine.loadWorker.stateProperty().addListener { _, _, newValue -> engine.loadWorker.stateProperty().onChange {
if (newValue == Worker.State.SUCCEEDED) { if (it == Worker.State.SUCCEEDED) {
document = engine.document document = engine.document
body = document.getElementsByTagName("body").item(0) body = document.getElementsByTagName("body").item(0)
engine.executeScript("limitedLogs=${Settings.logLines};") engine.executeScript("limitedLogs=${Settings.logLines};")
@@ -105,8 +106,8 @@ class LogWindow : Stage() {
flag = true flag = true
} }
} }
cboLines.selectionModel.selectedItemProperty().addListener { _, _, newValue -> cboLines.selectionModel.selectedItemProperty().onChange {
Settings.logLines = newValue.toInt() Settings.logLines = it?.toInt() ?: 100
engine.executeScript("limitedLogs=${Settings.logLines};") engine.executeScript("limitedLogs=${Settings.logLines};")
} }
if (!flag) { if (!flag) {

View File

@@ -38,6 +38,7 @@ import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.ui.construct.RipplerContainer import org.jackhuang.hmcl.ui.construct.RipplerContainer
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider import org.jackhuang.hmcl.ui.download.DownloadWizardProvider
import org.jackhuang.hmcl.ui.wizard.DecoratorPage import org.jackhuang.hmcl.ui.wizard.DecoratorPage
import org.jackhuang.hmcl.util.onChange
/** /**
* @see /assets/fxml/main.fxml * @see /assets/fxml/main.fxml
@@ -104,9 +105,9 @@ class MainPage : StackPane(), DecoratorPage {
profile.repository.getVersions().forEach { version -> profile.repository.getVersions().forEach { version ->
children += buildNode(++i, profile, version.id, minecraftVersion(profile.repository.getVersionJar(version.id)) ?: "Unknown", group) children += buildNode(++i, profile, version.id, minecraftVersion(profile.repository.getVersionJar(version.id)) ?: "Unknown", group)
} }
group.selectedToggleProperty().addListener { _, _, newValue -> group.selectedToggleProperty().onChange {
if (newValue != null) if (it != null)
profile.selectedVersion = newValue.properties["version"] as String profile.selectedVersion = it.properties["version"] as String
} }
masonryPane.resetChildren(children) masonryPane.resetChildren(children)
} }

View File

@@ -28,6 +28,7 @@ import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.mod.ModManager import org.jackhuang.hmcl.mod.ModManager
import org.jackhuang.hmcl.task.Scheduler import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.task import org.jackhuang.hmcl.task.task
import org.jackhuang.hmcl.util.onChange
class ModController { class ModController {
@FXML lateinit var scrollPane: ScrollPane @FXML lateinit var scrollPane: ScrollPane
@@ -71,8 +72,8 @@ class ModController {
JFXDepthManager.setDepth(this, 1) JFXDepthManager.setDepth(this, 1)
style += "-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;" style += "-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;"
modInfo.activeProperty.addListener { _, _, newValue -> modInfo.activeProperty.onChange {
if (newValue) if (it)
styleClass -= "disabled" styleClass -= "disabled"
else else
styleClass += "disabled" styleClass += "disabled"

View File

@@ -22,6 +22,7 @@ import javafx.fxml.FXML
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.scene.layout.BorderPane import javafx.scene.layout.BorderPane
import org.jackhuang.hmcl.mod.ModInfo import org.jackhuang.hmcl.mod.ModInfo
import org.jackhuang.hmcl.util.onChange
class ModItem(info: ModInfo, private val deleteCallback: (ModItem) -> Unit) : BorderPane() { class ModItem(info: ModInfo, private val deleteCallback: (ModItem) -> Unit) : BorderPane() {
@FXML lateinit var lblModFileName: Label @FXML lateinit var lblModFileName: Label
@@ -34,8 +35,8 @@ class ModItem(info: ModInfo, private val deleteCallback: (ModItem) -> Unit) : Bo
lblModFileName.text = info.fileName lblModFileName.text = info.fileName
lblModAuthor.text = "${info.name}, Version: ${info.version}, Game Version: ${info.mcversion}, Authors: ${info.authors}" lblModAuthor.text = "${info.name}, Version: ${info.version}, Game Version: ${info.mcversion}, Authors: ${info.authors}"
chkEnabled.isSelected = info.isActive chkEnabled.isSelected = info.isActive
chkEnabled.selectedProperty().addListener { _, _, newValue -> chkEnabled.selectedProperty().onChange {
info.activeProperty.set(newValue) info.activeProperty.set(it)
} }
} }

View File

@@ -21,7 +21,6 @@ import javafx.fxml.FXMLLoader
import javafx.scene.Group import javafx.scene.Group
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.shape.SVGPath import javafx.scene.shape.SVGPath
import kotlin.coroutines.experimental.EmptyCoroutineContext.plus
object SVG { object SVG {
val svgNames = setOf("gear") val svgNames = setOf("gear")

View File

@@ -35,6 +35,7 @@ import org.jackhuang.hmcl.ui.construct.FileItem
import org.jackhuang.hmcl.ui.construct.FontComboBox import org.jackhuang.hmcl.ui.construct.FontComboBox
import org.jackhuang.hmcl.ui.construct.Validator import org.jackhuang.hmcl.ui.construct.Validator
import org.jackhuang.hmcl.ui.wizard.DecoratorPage import org.jackhuang.hmcl.ui.wizard.DecoratorPage
import org.jackhuang.hmcl.util.onChange
class SettingsPage : StackPane(), DecoratorPage { class SettingsPage : StackPane(), DecoratorPage {
override val titleProperty: StringProperty = SimpleStringProperty(this, "title", i18n("launcher.title.launcher")) override val titleProperty: StringProperty = SimpleStringProperty(this, "title", i18n("launcher.title.launcher"))
@@ -59,41 +60,33 @@ class SettingsPage : StackPane(), DecoratorPage {
cboDownloadSource.limitWidth(400.0) cboDownloadSource.limitWidth(400.0)
txtProxyHost.text = Settings.proxyHost txtProxyHost.text = Settings.proxyHost
txtProxyHost.textProperty().addListener { _, _, newValue -> txtProxyHost.textProperty().onChange { Settings.proxyHost = it }
Settings.proxyHost = newValue
}
txtProxyPort.text = Settings.proxyPort txtProxyPort.text = Settings.proxyPort
txtProxyPort.textProperty().addListener { _, _, newValue -> txtProxyPort.textProperty().onChange { Settings.proxyPort = it }
Settings.proxyPort = newValue
}
txtProxyUsername.text = Settings.proxyUser txtProxyUsername.text = Settings.proxyUser
txtProxyUsername.textProperty().addListener { _, _, newValue -> txtProxyUsername.textProperty().onChange { Settings.proxyUser = it }
Settings.proxyUser = newValue
}
txtProxyPassword.text = Settings.proxyPass txtProxyPassword.text = Settings.proxyPass
txtProxyPassword.textProperty().addListener { _, _, newValue -> txtProxyPassword.textProperty().onChange { Settings.proxyPass = it }
Settings.proxyPass = newValue
}
cboDownloadSource.selectionModel.select(DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(Settings.downloadProvider)) cboDownloadSource.selectionModel.select(DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(Settings.downloadProvider))
cboDownloadSource.selectionModel.selectedIndexProperty().addListener { _, _, newValue -> cboDownloadSource.selectionModel.selectedIndexProperty().onChange {
Settings.downloadProvider = DownloadProviders.getDownloadProvider(newValue.toInt()) Settings.downloadProvider = DownloadProviders.getDownloadProvider(it)
} }
cboFont.selectionModel.select(Settings.font.family) cboFont.selectionModel.select(Settings.font.family)
cboFont.valueProperty().addListener { _, _, newValue -> cboFont.valueProperty().onChange {
val font = Font.font(newValue, Settings.font.size) val font = Font.font(it, Settings.font.size)
Settings.font = font Settings.font = font
lblDisplay.style = "-fx-font: ${Settings.font.size} \"${font.family}\";" lblDisplay.style = "-fx-font: ${Settings.font.size} \"${font.family}\";"
} }
txtFontSize.text = Settings.font.size.toString() txtFontSize.text = Settings.font.size.toString()
txtFontSize.validators += Validator { it.toDoubleOrNull() != null } txtFontSize.validators += Validator { it.toDoubleOrNull() != null }
txtFontSize.textProperty().addListener { _, _, newValue -> txtFontSize.textProperty().onChange {
if (txtFontSize.validate()) { if (txtFontSize.validate()) {
val font = Font.font(Settings.font.family, newValue.toDouble()) val font = Font.font(Settings.font.family, it!!.toDouble())
Settings.font = font Settings.font = font
lblDisplay.style = "-fx-font: ${font.size} \"${Settings.font.family}\";" lblDisplay.style = "-fx-font: ${font.size} \"${Settings.font.family}\";"
} }
@@ -106,13 +99,13 @@ class SettingsPage : StackPane(), DecoratorPage {
} }
cboLanguage.items = list cboLanguage.items = list
cboLanguage.selectionModel.select(Locales.LOCALES.indexOf(Settings.locale)) cboLanguage.selectionModel.select(Locales.LOCALES.indexOf(Settings.locale))
cboLanguage.selectionModel.selectedIndexProperty().addListener { _, _, newValue -> cboLanguage.selectionModel.selectedIndexProperty().onChange {
Settings.locale = Locales.getLocale(newValue.toInt()) Settings.locale = Locales.getLocale(it)
} }
cboProxyType.selectionModel.select(Proxies.PROXIES.indexOf(Settings.proxyType)) cboProxyType.selectionModel.select(Proxies.PROXIES.indexOf(Settings.proxyType))
cboProxyType.selectionModel.selectedIndexProperty().addListener { _, _, newValue -> cboProxyType.selectionModel.selectedIndexProperty().onChange {
Settings.proxyType = Proxies.getProxyType(newValue.toInt()) Settings.proxyType = Proxies.getProxyType(it)
} }
fileCommonLocation.setProperty(Settings.commonPathProperty) fileCommonLocation.setProperty(Settings.commonPathProperty)

View File

@@ -18,9 +18,7 @@
package org.jackhuang.hmcl.ui package org.jackhuang.hmcl.ui
import com.jfoenix.controls.JFXButton import com.jfoenix.controls.JFXButton
import com.jfoenix.controls.JFXCheckBox
import com.jfoenix.controls.JFXRadioButton import com.jfoenix.controls.JFXRadioButton
import com.jfoenix.effects.JFXDepthManager
import javafx.beans.binding.Bindings import javafx.beans.binding.Bindings
import javafx.fxml.FXML import javafx.fxml.FXML
import javafx.scene.control.Label import javafx.scene.control.Label

View File

@@ -20,10 +20,7 @@ package org.jackhuang.hmcl.ui
import javafx.fxml.FXML import javafx.fxml.FXML
import javafx.scene.control.Button import javafx.scene.control.Button
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.scene.control.Tooltip
import javafx.scene.layout.BorderPane
import javafx.scene.layout.StackPane import javafx.scene.layout.StackPane
import org.jackhuang.hmcl.setting.VersionSetting
class VersionListItem(val versionName: String, val gameVersion: String) : StackPane() { class VersionListItem(val versionName: String, val gameVersion: String) : StackPane() {

View File

@@ -17,13 +17,15 @@
*/ */
package org.jackhuang.hmcl.ui package org.jackhuang.hmcl.ui
import com.jfoenix.controls.* import com.jfoenix.controls.JFXButton
import com.jfoenix.controls.JFXListView
import com.jfoenix.controls.JFXPopup
import javafx.beans.property.SimpleStringProperty import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty import javafx.beans.property.StringProperty
import javafx.fxml.FXML import javafx.fxml.FXML
import javafx.scene.control.Alert import javafx.scene.control.Alert
import javafx.scene.layout.* import javafx.scene.layout.StackPane
import org.jackhuang.hmcl.download.GameAssetIndexDownloadTask import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask
import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.setting.Profile import org.jackhuang.hmcl.setting.Profile
import org.jackhuang.hmcl.ui.wizard.DecoratorPage import org.jackhuang.hmcl.ui.wizard.DecoratorPage

View File

@@ -19,15 +19,15 @@ package org.jackhuang.hmcl.ui
import com.jfoenix.controls.* import com.jfoenix.controls.*
import javafx.beans.InvalidationListener import javafx.beans.InvalidationListener
import javafx.beans.binding.Bindings
import javafx.beans.value.ChangeListener import javafx.beans.value.ChangeListener
import javafx.fxml.FXML import javafx.fxml.FXML
import javafx.geometry.Pos
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.scene.control.ScrollPane import javafx.scene.control.ScrollPane
import javafx.scene.control.Toggle import javafx.scene.control.Toggle
import javafx.scene.control.ToggleGroup import javafx.scene.control.ToggleGroup
import javafx.scene.layout.* import javafx.scene.layout.BorderPane
import javafx.scene.layout.Pane
import javafx.scene.layout.VBox
import javafx.stage.DirectoryChooser import javafx.stage.DirectoryChooser
import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.setting.VersionSetting import org.jackhuang.hmcl.setting.VersionSetting

View File

@@ -26,9 +26,7 @@ import javafx.scene.SnapshotParameters
import javafx.scene.image.ImageView import javafx.scene.image.ImageView
import javafx.scene.image.WritableImage import javafx.scene.image.WritableImage
import javafx.scene.layout.StackPane import javafx.scene.layout.StackPane
import javafx.scene.shape.Rectangle
import javafx.util.Duration import javafx.util.Duration
import org.jackhuang.hmcl.ui.setOverflowHidden
import org.jackhuang.hmcl.ui.takeSnapshot import org.jackhuang.hmcl.ui.takeSnapshot
/** /**

View File

@@ -19,7 +19,6 @@ package org.jackhuang.hmcl.ui.construct
import javafx.beans.DefaultProperty import javafx.beans.DefaultProperty
import javafx.beans.property.SimpleIntegerProperty import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty import javafx.beans.property.SimpleStringProperty
import javafx.collections.FXCollections import javafx.collections.FXCollections
import javafx.collections.ListChangeListener import javafx.collections.ListChangeListener
@@ -27,7 +26,10 @@ import javafx.collections.ObservableList
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.layout.StackPane import javafx.scene.layout.StackPane
import javafx.scene.layout.VBox import javafx.scene.layout.VBox
import org.jackhuang.hmcl.util.* import org.jackhuang.hmcl.util.getValue
import org.jackhuang.hmcl.util.setValue
import kotlin.collections.plusAssign
import kotlin.collections.set
@DefaultProperty("content") @DefaultProperty("content")
class ComponentList: StackPane() { class ComponentList: StackPane() {

View File

@@ -18,14 +18,14 @@
package org.jackhuang.hmcl.ui.construct package org.jackhuang.hmcl.ui.construct
import com.jfoenix.controls.JFXButton import com.jfoenix.controls.JFXButton
import javafx.animation.* import javafx.animation.Animation
import javafx.animation.KeyFrame
import javafx.animation.KeyValue
import javafx.animation.Timeline
import javafx.beans.property.SimpleBooleanProperty import javafx.beans.property.SimpleBooleanProperty
import javafx.geometry.Insets
import javafx.geometry.Pos import javafx.geometry.Pos
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.scene.layout.HBox
import javafx.scene.layout.Priority
import javafx.scene.layout.StackPane import javafx.scene.layout.StackPane
import javafx.scene.layout.VBox import javafx.scene.layout.VBox
import javafx.scene.shape.Rectangle import javafx.scene.shape.Rectangle
@@ -33,7 +33,9 @@ import javafx.util.Duration
import org.jackhuang.hmcl.ui.SINE import org.jackhuang.hmcl.ui.SINE
import org.jackhuang.hmcl.ui.SVG import org.jackhuang.hmcl.ui.SVG
import org.jackhuang.hmcl.ui.limitHeight import org.jackhuang.hmcl.ui.limitHeight
import org.jackhuang.hmcl.util.* import org.jackhuang.hmcl.util.getValue
import org.jackhuang.hmcl.util.onChange
import org.jackhuang.hmcl.util.setValue
class ComponentListCell(private val content: Node) : StackPane() { class ComponentListCell(private val content: Node) : StackPane() {
@@ -146,8 +148,8 @@ class ComponentListCell(private val content: Node) : StackPane() {
expandAnimation?.play() expandAnimation?.play()
} }
expandedProperty.addListener { _, _, newValue -> expandedProperty.onChange {
if (newValue) { if (it) {
expandIcon.rotate = 180.0 expandIcon.rotate = 180.0
} else { } else {
expandIcon.rotate = 0.0 expandIcon.rotate = 0.0

View File

@@ -27,7 +27,8 @@ import javafx.scene.layout.VBox
import javafx.stage.DirectoryChooser import javafx.stage.DirectoryChooser
import org.jackhuang.hmcl.ui.Controllers import org.jackhuang.hmcl.ui.Controllers
import org.jackhuang.hmcl.ui.SVG import org.jackhuang.hmcl.ui.SVG
import org.jackhuang.hmcl.util.* import org.jackhuang.hmcl.util.getValue
import org.jackhuang.hmcl.util.setValue
class FileItem : BorderPane() { class FileItem : BorderPane() {
val nameProperty = SimpleStringProperty(this, "name") val nameProperty = SimpleStringProperty(this, "name")

View File

@@ -23,12 +23,13 @@ import javafx.collections.FXCollections
import javafx.scene.control.ListCell import javafx.scene.control.ListCell
import javafx.scene.text.Font import javafx.scene.text.Font
import javafx.util.Callback import javafx.util.Callback
import org.jackhuang.hmcl.util.onChange
class FontComboBox(@NamedArg("fontSize") fontSize: Double = 12.0, @NamedArg("enableStyle") enableStyle: Boolean = false) : JFXComboBox<String>(FXCollections.observableArrayList(Font.getFamilies())) { class FontComboBox(@NamedArg("fontSize") fontSize: Double = 12.0, @NamedArg("enableStyle") enableStyle: Boolean = false) : JFXComboBox<String>(FXCollections.observableArrayList(Font.getFamilies())) {
init { init {
valueProperty().addListener { _, _, newValue -> valueProperty().onChange {
if (enableStyle) if (enableStyle)
style = "-fx-font-family: \"$newValue\";" style = "-fx-font-family: \"$it\";"
} }
cellFactory = Callback { cellFactory = Callback {
object : ListCell<String>() { object : ListCell<String>() {

View File

@@ -26,11 +26,15 @@ import javafx.beans.property.SimpleBooleanProperty
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
import javafx.geometry.Insets import javafx.geometry.Insets
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.layout.* import javafx.scene.layout.Background
import javafx.scene.layout.BackgroundFill
import javafx.scene.layout.CornerRadii
import javafx.scene.layout.StackPane
import javafx.scene.paint.Color import javafx.scene.paint.Color
import javafx.scene.paint.Paint import javafx.scene.paint.Paint
import javafx.scene.shape.Rectangle import javafx.scene.shape.Rectangle
import org.jackhuang.hmcl.util.getValue import org.jackhuang.hmcl.util.getValue
import org.jackhuang.hmcl.util.onChange
import org.jackhuang.hmcl.util.setValue import org.jackhuang.hmcl.util.setValue
import java.util.concurrent.Callable import java.util.concurrent.Callable
@@ -92,8 +96,8 @@ open class RipplerContainer(@NamedArg("container") container: Node): StackPane()
} }
} }
focusedProperty().addListener { _, _, newVal -> focusedProperty().onChange {
if (newVal) { if (it) {
if (!isPressed) { if (!isPressed) {
this.buttonRippler.showOverlay() this.buttonRippler.showOverlay()
} }
@@ -125,7 +129,7 @@ open class RipplerContainer(@NamedArg("container") container: Node): StackPane()
return@Callable background return@Callable background
} }
}, backgroundProperty())) }, backgroundProperty()))
ripplerFillProperty.addListener { _, _, newVal -> this.buttonRippler.ripplerFill = newVal } ripplerFillProperty.onChange { this.buttonRippler.ripplerFill = it }
if (background == null || this.isJavaDefaultBackground(background)) { if (background == null || this.isJavaDefaultBackground(background)) {
background = Background(BackgroundFill(Color.TRANSPARENT, this.defaultRadii, null)) background = Background(BackgroundFill(Color.TRANSPARENT, this.defaultRadii, null))
} }

View File

@@ -17,12 +17,10 @@
*/ */
package org.jackhuang.hmcl.ui.construct package org.jackhuang.hmcl.ui.construct
import java.io.InputStreamReader
import java.util.PropertyResourceBundle
import java.util.ResourceBundle
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.util.Locale import java.io.InputStreamReader
import java.util.*
object UTF8Control : ResourceBundle.Control() { object UTF8Control : ResourceBundle.Control() {
@Throws(IllegalAccessException::class, InstantiationException::class, IOException::class) @Throws(IllegalAccessException::class, InstantiationException::class, IOException::class)

View File

@@ -26,6 +26,7 @@ import org.jackhuang.hmcl.download.DownloadProvider
import org.jackhuang.hmcl.ui.loadFXML import org.jackhuang.hmcl.ui.loadFXML
import org.jackhuang.hmcl.ui.wizard.WizardController import org.jackhuang.hmcl.ui.wizard.WizardController
import org.jackhuang.hmcl.ui.wizard.WizardPage import org.jackhuang.hmcl.ui.wizard.WizardPage
import org.jackhuang.hmcl.util.onChange
class InstallersPage(private val controller: WizardController, private val downloadProvider: DownloadProvider): StackPane(), WizardPage { class InstallersPage(private val controller: WizardController, private val downloadProvider: DownloadProvider): StackPane(), WizardPage {
@@ -42,9 +43,9 @@ class InstallersPage(private val controller: WizardController, private val downl
val gameVersion = controller.settings["game"] as String val gameVersion = controller.settings["game"] as String
txtName.text = gameVersion txtName.text = gameVersion
list.selectionModel.selectedIndexProperty().addListener { _, _, newValue -> list.selectionModel.selectedIndexProperty().onChange {
controller.settings[INSTALLER_TYPE] = newValue controller.settings[INSTALLER_TYPE] = it
controller.onNext(when (newValue){ controller.onNext(when (it){
0 -> VersionsPage(controller, gameVersion, downloadProvider, "forge") { controller.onPrev(false) } 0 -> VersionsPage(controller, gameVersion, downloadProvider, "forge") { controller.onPrev(false) }
1 -> VersionsPage(controller, gameVersion, downloadProvider, "liteloader") { controller.onPrev(false) } 1 -> VersionsPage(controller, gameVersion, downloadProvider, "liteloader") { controller.onPrev(false) }
2 -> VersionsPage(controller, gameVersion, downloadProvider, "optifine") { controller.onPrev(false) } 2 -> VersionsPage(controller, gameVersion, downloadProvider, "optifine") { controller.onPrev(false) }

View File

@@ -28,7 +28,6 @@ import javafx.stage.FileChooser
import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.mod.readCurseForgeModpackManifest import org.jackhuang.hmcl.mod.readCurseForgeModpackManifest
import org.jackhuang.hmcl.setting.Profile import org.jackhuang.hmcl.setting.Profile
import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.ui.Controllers import org.jackhuang.hmcl.ui.Controllers
import org.jackhuang.hmcl.ui.construct.Validator import org.jackhuang.hmcl.ui.construct.Validator
import org.jackhuang.hmcl.ui.loadFXML import org.jackhuang.hmcl.ui.loadFXML

View File

@@ -21,8 +21,8 @@ import com.jfoenix.controls.JFXListView
import com.jfoenix.controls.JFXSpinner import com.jfoenix.controls.JFXSpinner
import javafx.fxml.FXML import javafx.fxml.FXML
import javafx.scene.layout.StackPane import javafx.scene.layout.StackPane
import org.jackhuang.hmcl.download.RemoteVersion
import org.jackhuang.hmcl.download.DownloadProvider import org.jackhuang.hmcl.download.DownloadProvider
import org.jackhuang.hmcl.download.RemoteVersion
import org.jackhuang.hmcl.task.Scheduler import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.TaskExecutor import org.jackhuang.hmcl.task.TaskExecutor
import org.jackhuang.hmcl.ui.animation.ContainerAnimations import org.jackhuang.hmcl.ui.animation.ContainerAnimations
@@ -31,6 +31,7 @@ import org.jackhuang.hmcl.ui.loadFXML
import org.jackhuang.hmcl.ui.wizard.Refreshable import org.jackhuang.hmcl.ui.wizard.Refreshable
import org.jackhuang.hmcl.ui.wizard.WizardController import org.jackhuang.hmcl.ui.wizard.WizardController
import org.jackhuang.hmcl.ui.wizard.WizardPage import org.jackhuang.hmcl.ui.wizard.WizardPage
import org.jackhuang.hmcl.util.onChange
class VersionsPage(private val controller: WizardController, private val gameVersion: String, private val downloadProvider: DownloadProvider, private val libraryId: String, private val callback: () -> Unit): StackPane(), WizardPage, Refreshable { class VersionsPage(private val controller: WizardController, private val gameVersion: String, private val downloadProvider: DownloadProvider, private val libraryId: String, private val callback: () -> Unit): StackPane(), WizardPage, Refreshable {
@@ -44,8 +45,8 @@ class VersionsPage(private val controller: WizardController, private val gameVer
init { init {
loadFXML("/assets/fxml/download/versions.fxml") loadFXML("/assets/fxml/download/versions.fxml")
children.setAll(spinner) children.setAll(spinner)
list.selectionModel.selectedItemProperty().addListener { _, _, newValue -> list.selectionModel.selectedItemProperty().onChange {
controller.settings[libraryId] = newValue.remoteVersion.selfVersion controller.settings[libraryId] = it!!.remoteVersion.selfVersion
callback() callback()
} }
refresh() refresh()

View File

@@ -19,7 +19,6 @@ package org.jackhuang.hmcl.ui.download
import javafx.fxml.FXML import javafx.fxml.FXML
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.scene.layout.BorderPane
import javafx.scene.layout.StackPane import javafx.scene.layout.StackPane
import org.jackhuang.hmcl.download.RemoteVersion import org.jackhuang.hmcl.download.RemoteVersion
import org.jackhuang.hmcl.ui.loadFXML import org.jackhuang.hmcl.ui.loadFXML

View File

@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.ui.wizard
import com.jfoenix.concurrency.JFXUtilities import com.jfoenix.concurrency.JFXUtilities
import com.jfoenix.controls.JFXProgressBar import com.jfoenix.controls.JFXProgressBar
import javafx.application.Platform import javafx.application.Platform
import javafx.scene.Node
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.scene.layout.StackPane import javafx.scene.layout.StackPane
import javafx.scene.layout.VBox import javafx.scene.layout.VBox

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.*?> <?import com.jfoenix.controls.*?>
<?import com.jfoenix.transitions.hamburger.HamburgerBackArrowBasicTransition?>
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
@@ -60,7 +59,11 @@
<BorderPane minWidth="200" maxWidth="200" fx:id="titleWrapper"> <BorderPane minWidth="200" maxWidth="200" fx:id="titleWrapper">
<center> <center>
<Label fx:id="lblTitle" BorderPane.alignment="CENTER" mouseTransparent="true" <Label fx:id="lblTitle" BorderPane.alignment="CENTER" mouseTransparent="true"
style="-fx-background-color: transparent; -fx-text-fill: white; -fx-font-size: 15px;"/> style="-fx-background-color: transparent; -fx-text-fill: white; -fx-font-size: 15px;">
<BorderPane.margin>
<Insets left="3" />
</BorderPane.margin>
</Label>
</center> </center>
<right> <right>
<Rectangle height="${navBar.height}" width="1" fill="gray"/> <Rectangle height="${navBar.height}" width="1" fill="gray"/>

View File

@@ -19,9 +19,7 @@ package org.jackhuang.hmcl.auth.yggdrasil
import com.google.gson.* import com.google.gson.*
import java.lang.reflect.Type import java.lang.reflect.Type
import com.google.gson.JsonObject import java.util.*
import java.util.UUID
import com.google.gson.JsonParseException
data class GameProfile( data class GameProfile(
val id: UUID? = null, val id: UUID? = null,

View File

@@ -20,13 +20,6 @@ package org.jackhuang.hmcl.auth.yggdrasil
import com.google.gson.* import com.google.gson.*
import java.lang.reflect.Type import java.lang.reflect.Type
import java.util.* import java.util.*
import com.google.gson.JsonObject
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import kotlin.collections.HashMap
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
class PropertyMap: HashMap<String, Property>() { class PropertyMap: HashMap<String, Property>() {

View File

@@ -21,9 +21,6 @@ import com.google.gson.GsonBuilder
import com.google.gson.JsonParseException import com.google.gson.JsonParseException
import org.jackhuang.hmcl.auth.* import org.jackhuang.hmcl.auth.*
import org.jackhuang.hmcl.util.* import org.jackhuang.hmcl.util.*
import org.jackhuang.hmcl.util.doGet
import org.jackhuang.hmcl.util.doPost
import org.jackhuang.hmcl.util.toURL
import java.io.IOException import java.io.IOException
import java.net.Proxy import java.net.Proxy
import java.net.URL import java.net.URL

View File

@@ -17,11 +17,11 @@
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download
import org.jackhuang.hmcl.game.* import org.jackhuang.hmcl.game.GameRepository
import java.net.Proxy import java.net.Proxy
abstract class AbstractDependencyManager(repository: GameRepository, proxy: Proxy) abstract class AbstractDependencyManager
: DependencyManager(repository, proxy) { : DependencyManager {
abstract val downloadProvider: DownloadProvider abstract val downloadProvider: DownloadProvider
fun getVersions(id: String, selfVersion: String) = fun getVersions(id: String, selfVersion: String) =

View File

@@ -17,6 +17,14 @@
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download
import org.jackhuang.hmcl.download.forge.ForgeVersionList
import org.jackhuang.hmcl.download.game.GameVersionList
import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList
import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList
/**
* @see {@link http://bmclapi2.bangbang93.com}
*/
object BMCLAPIDownloadProvider : DownloadProvider() { object BMCLAPIDownloadProvider : DownloadProvider() {
override val libraryBaseURL: String = "http://bmclapi2.bangbang93.com/libraries/" override val libraryBaseURL: String = "http://bmclapi2.bangbang93.com/libraries/"
override val versionListURL: String = "http://bmclapi2.bangbang93.com/mc/game/version_manifest.json" override val versionListURL: String = "http://bmclapi2.bangbang93.com/mc/game/version_manifest.json"

View File

@@ -17,6 +17,13 @@
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download
import org.jackhuang.hmcl.download.forge.ForgeInstallTask
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask
import org.jackhuang.hmcl.download.game.GameLibrariesTask
import org.jackhuang.hmcl.download.game.GameLoggingDownloadTask
import org.jackhuang.hmcl.download.game.VersionJSONSaveTask
import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask
import org.jackhuang.hmcl.game.DefaultGameRepository import org.jackhuang.hmcl.game.DefaultGameRepository
import org.jackhuang.hmcl.game.Version import org.jackhuang.hmcl.game.Version
import org.jackhuang.hmcl.task.ParallelTask import org.jackhuang.hmcl.task.ParallelTask
@@ -27,8 +34,8 @@ import java.net.Proxy
/** /**
* This class has no state. * This class has no state.
*/ */
class DefaultDependencyManager(override val repository: DefaultGameRepository, override var downloadProvider: DownloadProvider, proxy: Proxy = Proxy.NO_PROXY) class DefaultDependencyManager(override val repository: DefaultGameRepository, override var downloadProvider: DownloadProvider, override val proxy: Proxy = Proxy.NO_PROXY)
: AbstractDependencyManager(repository, proxy) { : AbstractDependencyManager() {
override fun gameBuilder(): GameBuilder = DefaultGameBuilder(this) override fun gameBuilder(): GameBuilder = DefaultGameBuilder(this)

View File

@@ -17,9 +17,16 @@
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download
import org.jackhuang.hmcl.game.* import org.jackhuang.hmcl.download.game.GameAssetDownloadTask
import org.jackhuang.hmcl.download.game.GameLibrariesTask
import org.jackhuang.hmcl.download.game.GameLoggingDownloadTask
import org.jackhuang.hmcl.download.game.VersionJSONSaveTask
import org.jackhuang.hmcl.game.Version
import org.jackhuang.hmcl.task.* import org.jackhuang.hmcl.task.*
import org.jackhuang.hmcl.util.* import org.jackhuang.hmcl.util.AutoTypingMap
import org.jackhuang.hmcl.util.GSON
import org.jackhuang.hmcl.util.fromJson
import org.jackhuang.hmcl.util.toURL
import java.util.* import java.util.*
class DefaultGameBuilder(val dependencyManager: DefaultDependencyManager): GameBuilder() { class DefaultGameBuilder(val dependencyManager: DefaultDependencyManager): GameBuilder() {

View File

@@ -22,27 +22,51 @@ import org.jackhuang.hmcl.game.Version
import org.jackhuang.hmcl.task.Task import org.jackhuang.hmcl.task.Task
import java.net.Proxy import java.net.Proxy
abstract class DependencyManager(open val repository: GameRepository, open val proxy: Proxy) { /**
* Do everything that will connect to Internet.
* Downloading Minecraft files.
*/
interface DependencyManager {
/**
* The relied game repository.
*/
val repository: GameRepository
/**
* The proxy that all network operations should go through.
*/
val proxy: Proxy
/** /**
* Check if the game is complete. * Check if the game is complete.
* Check libraries, assets, logging files and so on. * Check libraries, assets, logging files and so on.
* *
* @return * @return the task to check game completion.
*/ */
abstract fun checkGameCompletionAsync(version: Version): Task fun checkGameCompletionAsync(version: Version): Task
/** /**
* The builder to build a brand new game then libraries such as Forge, LiteLoader and OptiFine. * The builder to build a brand new game then libraries such as Forge, LiteLoader and OptiFine.
*/ */
abstract fun gameBuilder(): GameBuilder fun gameBuilder(): GameBuilder
abstract fun installLibraryAsync(gameVersion: String, version: Version, libraryId: String, libraryVersion: String): Task /**
* Install a library to a version.
* **Note**: Installing a library may change the version.json.
*
* @param gameVersion the Minecraft version that the library relies on.
* @param version the version.json.
* @param libraryId the type of being installed library. i.e. "forge", "liteloader", "optifine"
* @param libraryVersion the version of being installed library.
* @return the task to install the specific library.
*/
fun installLibraryAsync(gameVersion: String, version: Version, libraryId: String, libraryVersion: String): Task
/** /**
* Get registered version list. * Get registered version list.
*
* @param id the id of version list. i.e. game, forge, liteloader, optifine * @param id the id of version list. i.e. game, forge, liteloader, optifine
* @throws IllegalArgumentException if the version list of specific id is not found. * @throws IllegalArgumentException if the version list of specific id is not found.
*/ */
abstract fun getVersionList(id: String): VersionList<*> fun getVersionList(id: String): VersionList<*>
} }

View File

@@ -17,14 +17,31 @@
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download
import org.jackhuang.hmcl.download.VersionList /**
* The service provider that provides Minecraft online file downloads.
*/
abstract class DownloadProvider { abstract class DownloadProvider {
abstract val libraryBaseURL: String abstract val libraryBaseURL: String
abstract val versionListURL: String abstract val versionListURL: String
abstract val versionBaseURL: String abstract val versionBaseURL: String
abstract val assetIndexBaseURL: String abstract val assetIndexBaseURL: String
abstract val assetBaseURL: String abstract val assetBaseURL: String
/**
* Inject into original URL provided by Mojang and Forge.
*
* Since there are many provided URLs that are written in JSONs and are unmodifiable,
* this method provides a way to change them.
*
* @param baseURL original URL provided by Mojang and Forge.
* @return the URL that is equivalent to [baseURL], but belongs to your own service provider.
*/
abstract fun injectURL(baseURL: String): String abstract fun injectURL(baseURL: String): String
/**
* the specific version list that this download provider provides. i.e. "forge", "liteloader", "game", "optifine"
* @param id the id of specific version list that this download provider provides. i.e. "forge", "liteloader", "game", "optifine"
* @return the version list
*/
abstract fun getVersionListById(id: String): VersionList<*> abstract fun getVersionListById(id: String): VersionList<*>
} }

View File

@@ -19,12 +19,20 @@ package org.jackhuang.hmcl.download
import org.jackhuang.hmcl.task.Task import org.jackhuang.hmcl.task.Task
/**
* The builder which provide a task to build Minecraft environment.
*
* @author huangyuhui
*/
abstract class GameBuilder { abstract class GameBuilder {
var name: String = "" var name: String = ""
protected var gameVersion: String = "" protected var gameVersion: String = ""
protected var toolVersions = HashMap<String, String>() protected var toolVersions = HashMap<String, String>()
/**
* The new game version name, for .minecraft/<version name>.
* @param name the name of new game version.
*/
fun name(name: String): GameBuilder { fun name(name: String): GameBuilder {
this.name = name this.name = name
return this return this
@@ -35,10 +43,17 @@ abstract class GameBuilder {
return this return this
} }
/**
* @param id the core library id. i.e. "forge", "liteloader", "optifine"
* @param version the version of the core library. For documents, you can first try [VersionList.versions]
*/
fun version(id: String, version: String): GameBuilder { fun version(id: String, version: String): GameBuilder {
toolVersions[id] = version toolVersions[id] = version
return this return this
} }
/**
* @return the task that can build thw whole Minecraft environment
*/
abstract fun buildAsync(): Task abstract fun buildAsync(): Task
} }

View File

@@ -17,8 +17,15 @@
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download
import org.jackhuang.hmcl.download.forge.ForgeVersionList
import org.jackhuang.hmcl.download.game.GameVersionList
import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList
import org.jackhuang.hmcl.download.optifine.OptiFineVersionList
import java.util.* import java.util.*
/**
* @see {@link http://wiki.vg}
*/
object MojangDownloadProvider : DownloadProvider() { object MojangDownloadProvider : DownloadProvider() {
override val libraryBaseURL: String = "https://libraries.minecraft.net/" override val libraryBaseURL: String = "https://libraries.minecraft.net/"
override val versionBaseURL: String = "http://s3.amazonaws.com/Minecraft.Download/versions/" override val versionBaseURL: String = "http://s3.amazonaws.com/Minecraft.Download/versions/"
@@ -37,15 +44,6 @@ object MojangDownloadProvider : DownloadProvider() {
} }
override fun injectURL(baseURL: String): String { override fun injectURL(baseURL: String): String {
/**if (baseURL.contains("scala-swing") || baseURL.contains("scala-xml") || baseURL.contains("scala-parser-combinators"))
return baseURL.replace("http://files.minecraftforge.net/maven", "http://ftb.cursecdn.com/FTB2/maven/");
else if (baseURL.contains("typesafe") || baseURL.contains("scala"))
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://repo1.maven.org/maven2");
else
return baseURL; */
if (baseURL.endsWith("net/minecraftforge/forge/json")) if (baseURL.endsWith("net/minecraftforge/forge/json"))
return baseURL return baseURL
else if (Locale.getDefault() == Locale.CHINA) else if (Locale.getDefault() == Locale.CHINA)

View File

@@ -19,14 +19,18 @@ package org.jackhuang.hmcl.download
import org.jackhuang.hmcl.util.VersionNumber import org.jackhuang.hmcl.util.VersionNumber
import java.util.* import java.util.*
import kotlin.Comparator
/**
* The remote version.
*
* @param gameVersion the Minecraft version that this remote version suits.
* @param selfVersion the version string of the remote version.
* @param url the installer or universal jar URL.
* @param tag some necessary information for Installer Task.
*/
data class RemoteVersion<T> ( data class RemoteVersion<T> (
val gameVersion: String, val gameVersion: String,
val selfVersion: String, val selfVersion: String,
/**
* The file of remote version, may be an installer or an universal jar.
*/
val url: String, val url: String,
val tag: T val tag: T
): Comparable<RemoteVersion<T>> { ): Comparable<RemoteVersion<T>> {
@@ -39,6 +43,7 @@ data class RemoteVersion<T> (
} }
override fun compareTo(other: RemoteVersion<T>): Int { override fun compareTo(other: RemoteVersion<T>): Int {
// newer versions are smaller than older versions
return -selfVersion.compareTo(other.selfVersion) return -selfVersion.compareTo(other.selfVersion)
} }

View File

@@ -22,20 +22,37 @@ import org.jackhuang.hmcl.util.SimpleMultimap
import java.util.* import java.util.*
import kotlin.collections.HashMap import kotlin.collections.HashMap
/**
* The remote version list.
* @param T The type of RemoteVersion<T>, the type of tags.
*/
abstract class VersionList<T> { abstract class VersionList<T> {
@JvmField /**
* the remote version list.
* key: game version.
* values: corresponding remote versions.
*/
protected val versions = SimpleMultimap<String, RemoteVersion<T>>(::HashMap, ::TreeSet) protected val versions = SimpleMultimap<String, RemoteVersion<T>>(::HashMap, ::TreeSet)
/**
* True if the version list has been loaded.
*/
val loaded = versions.isNotEmpty val loaded = versions.isNotEmpty
/**
* @param downloadProvider DownloadProvider
* @return the task to reload the remote version list.
*/
abstract fun refreshAsync(downloadProvider: DownloadProvider): Task abstract fun refreshAsync(downloadProvider: DownloadProvider): Task
protected open fun getVersionsImpl(gameVersion: String): Collection<RemoteVersion<T>> { private fun getVersionsImpl(gameVersion: String): Collection<RemoteVersion<T>> {
val ans = versions[gameVersion] val ans = versions[gameVersion]
return if (ans.isEmpty()) versions.values else ans return if (ans.isEmpty()) versions.values else ans
} }
/** /**
* Get the remote versions that specifics Minecraft version.
*
* @param gameVersion the Minecraft version that remote versions belong to * @param gameVersion the Minecraft version that remote versions belong to
* @return the collection of specific remote versions * @return the collection of specific remote versions
*/ */
@@ -43,6 +60,13 @@ abstract class VersionList<T> {
return Collections.unmodifiableCollection(getVersionsImpl(gameVersion)) return Collections.unmodifiableCollection(getVersionsImpl(gameVersion))
} }
/**
* Get the specific remote version.
*
* @param gameVersion the Minecraft version that remote versions belong to
* @param remoteVersion the version of the remote version.
* @return the specific remote version, null if it is not found.
*/
fun getVersion(gameVersion: String, remoteVersion: String): RemoteVersion<T>? { fun getVersion(gameVersion: String, remoteVersion: String): RemoteVersion<T>? {
var result : RemoteVersion<T>? = null var result : RemoteVersion<T>? = null
versions[gameVersion].forEach { versions[gameVersion].forEach {

View File

@@ -15,8 +15,11 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}. * along with this program. If not, see {http://www.gnu.org/licenses/}.
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download.forge
import org.jackhuang.hmcl.download.DefaultDependencyManager
import org.jackhuang.hmcl.download.RemoteVersion
import org.jackhuang.hmcl.download.game.GameLibrariesTask
import org.jackhuang.hmcl.game.Library import org.jackhuang.hmcl.game.Library
import org.jackhuang.hmcl.game.SimpleVersionProvider import org.jackhuang.hmcl.game.SimpleVersionProvider
import org.jackhuang.hmcl.game.Version import org.jackhuang.hmcl.game.Version
@@ -30,9 +33,9 @@ import java.io.IOException
import java.util.zip.ZipFile import java.util.zip.ZipFile
class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager, class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager,
private val gameVersion: String, private val gameVersion: String,
private val version: Version, private val version: Version,
private val remoteVersion: String) : TaskResult<Version>() { private val remoteVersion: String) : TaskResult<Version>() {
private val forgeVersionList = dependencyManager.getVersionList("forge") private val forgeVersionList = dependencyManager.getVersionList("forge")
private val installer: File = File("forge-installer.jar").absoluteFile private val installer: File = File("forge-installer.jar").absoluteFile
lateinit var remote: RemoteVersion<*> lateinit var remote: RemoteVersion<*>

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}. * along with this program. If not, see {http://www.gnu.org/licenses/}.
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download.forge
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import org.jackhuang.hmcl.game.Version import org.jackhuang.hmcl.game.Version

View File

@@ -15,8 +15,11 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}. * along with this program. If not, see {http://www.gnu.org/licenses/}.
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download.forge
import org.jackhuang.hmcl.download.DownloadProvider
import org.jackhuang.hmcl.download.RemoteVersion
import org.jackhuang.hmcl.download.VersionList
import org.jackhuang.hmcl.task.GetTask import org.jackhuang.hmcl.task.GetTask
import org.jackhuang.hmcl.task.Task import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.util.* import org.jackhuang.hmcl.util.*

View File

@@ -15,8 +15,11 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}. * along with this program. If not, see {http://www.gnu.org/licenses/}.
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download.game
import org.jackhuang.hmcl.download.AbstractDependencyManager
import org.jackhuang.hmcl.download.DefaultDependencyManager
import org.jackhuang.hmcl.download.DependencyManager
import org.jackhuang.hmcl.game.AssetIndex import org.jackhuang.hmcl.game.AssetIndex
import org.jackhuang.hmcl.game.AssetObject import org.jackhuang.hmcl.game.AssetObject
import org.jackhuang.hmcl.game.DownloadType import org.jackhuang.hmcl.game.DownloadType
@@ -30,13 +33,12 @@ import java.io.IOException
import java.util.* import java.util.*
import java.util.logging.Level import java.util.logging.Level
/** /**
* This task is to download game libraries. * This task is to download game libraries.
* This task should be executed last(especially after game downloading, Forge, LiteLoader and OptiFine install task) * This task should be executed last(especially after game downloading, Forge, LiteLoader and OptiFine install task)
* @param resolvedVersion the <b>resolved</b> version * @param resolvedVersion the <b>resolved</b> version
*/ */
class GameLibrariesTask(private val dependencyManager: DefaultDependencyManager, private val resolvedVersion: Version): Task() { class GameLibrariesTask(private val dependencyManager: AbstractDependencyManager, private val resolvedVersion: Version): Task() {
override val dependencies = LinkedList<Task>() override val dependencies = LinkedList<Task>()
override fun execute() { override fun execute() {
for (library in resolvedVersion.libraries) for (library in resolvedVersion.libraries)
@@ -49,7 +51,12 @@ class GameLibrariesTask(private val dependencyManager: DefaultDependencyManager,
} }
class GameLoggingDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() { /**
* This task is to download log4j configuration file provided in minecraft.json.
* @param dependencyManager the dependency manager that can provides proxy settings and [GameRepository]
* @param version the **resolved** version
*/
class GameLoggingDownloadTask(private val dependencyManager: DependencyManager, private val version: Version) : Task() {
override val dependencies = LinkedList<Task>() override val dependencies = LinkedList<Task>()
override fun execute() { override fun execute() {
val logging = version.logging?.get(DownloadType.CLIENT) ?: return val logging = version.logging?.get(DownloadType.CLIENT) ?: return
@@ -59,6 +66,11 @@ class GameLoggingDownloadTask(private val dependencyManager: DefaultDependencyMa
} }
} }
/**
* This task is to download asset index file provided in minecraft.json.
* @param dependencyManager the dependency manager that can provides proxy settings and [GameRepository]
* @param version the **resolved** version
*/
class GameAssetIndexDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() { class GameAssetIndexDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() {
override val dependencies = LinkedList<Task>() override val dependencies = LinkedList<Task>()
override fun execute() { override fun execute() {
@@ -72,6 +84,11 @@ class GameAssetIndexDownloadTask(private val dependencyManager: DefaultDependenc
} }
} }
/**
* This task is to extract all asset objects described in asset index json.
* @param dependencyManager the dependency manager that can provides proxy settings and [GameRepository]
* @param version the **resolved** version
*/
class GameAssetRefreshTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : TaskResult<Collection<Pair<File, AssetObject>>>() { class GameAssetRefreshTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : TaskResult<Collection<Pair<File, AssetObject>>>() {
private val assetIndexTask = GameAssetIndexDownloadTask(dependencyManager, version) private val assetIndexTask = GameAssetIndexDownloadTask(dependencyManager, version)
private val assetIndexInfo = version.actualAssetIndex private val assetIndexInfo = version.actualAssetIndex
@@ -100,6 +117,11 @@ class GameAssetRefreshTask(private val dependencyManager: DefaultDependencyManag
} }
} }
/**
* This task is to download all asset objects provided in asset index json.
* @param dependencyManager the dependency manager that can provides proxy settings and [GameRepository]
* @param version the **resolved** version
*/
class GameAssetDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() { class GameAssetDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() {
private val refreshTask = GameAssetRefreshTask(dependencyManager, version) private val refreshTask = GameAssetRefreshTask(dependencyManager, version)
override val dependents = listOf(refreshTask) override val dependents = listOf(refreshTask)
@@ -140,6 +162,11 @@ class GameAssetDownloadTask(private val dependencyManager: DefaultDependencyMana
} }
} }
/**
* This task is to save the version json.
* @param dependencyManager the dependency manager that can provides proxy settings and [GameRepository]
* @param version the **resolved** version
*/
class VersionJSONSaveTask(private val dependencyManager: DefaultDependencyManager, private val version: Version): Task() { class VersionJSONSaveTask(private val dependencyManager: DefaultDependencyManager, private val version: Version): Task() {
override fun execute() { override fun execute() {
val json = dependencyManager.repository.getVersionJson(version.id).absoluteFile val json = dependencyManager.repository.getVersionJson(version.id).absoluteFile

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}. * along with this program. If not, see {http://www.gnu.org/licenses/}.
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download.game
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import org.jackhuang.hmcl.game.ReleaseType import org.jackhuang.hmcl.game.ReleaseType

View File

@@ -15,13 +15,16 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}. * along with this program. If not, see {http://www.gnu.org/licenses/}.
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download.game
import org.jackhuang.hmcl.download.DownloadProvider
import org.jackhuang.hmcl.download.RemoteVersion
import org.jackhuang.hmcl.download.VersionList
import org.jackhuang.hmcl.task.GetTask
import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.util.GSON import org.jackhuang.hmcl.util.GSON
import org.jackhuang.hmcl.util.asVersion import org.jackhuang.hmcl.util.asVersion
import org.jackhuang.hmcl.util.fromJson import org.jackhuang.hmcl.util.fromJson
import org.jackhuang.hmcl.task.GetTask
import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.util.toURL import org.jackhuang.hmcl.util.toURL

View File

@@ -15,8 +15,11 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}. * along with this program. If not, see {http://www.gnu.org/licenses/}.
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download.liteloader
import org.jackhuang.hmcl.download.DefaultDependencyManager
import org.jackhuang.hmcl.download.RemoteVersion
import org.jackhuang.hmcl.download.game.GameLibrariesTask
import org.jackhuang.hmcl.game.LibrariesDownloadInfo import org.jackhuang.hmcl.game.LibrariesDownloadInfo
import org.jackhuang.hmcl.game.Library import org.jackhuang.hmcl.game.Library
import org.jackhuang.hmcl.game.LibraryDownloadInfo import org.jackhuang.hmcl.game.LibraryDownloadInfo
@@ -27,12 +30,12 @@ import org.jackhuang.hmcl.task.then
import org.jackhuang.hmcl.util.merge import org.jackhuang.hmcl.util.merge
/** /**
* LiteLoader must be installed after Forge. * Note: LiteLoader must be installed after Forge.
*/ */
class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyManager, class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyManager,
private val gameVersion: String, private val gameVersion: String,
private val version: Version, private val version: Version,
private val remoteVersion: String): TaskResult<Version>() { private val remoteVersion: String): TaskResult<Version>() {
private val liteLoaderVersionList = dependencyManager.getVersionList("liteloader") as LiteLoaderVersionList private val liteLoaderVersionList = dependencyManager.getVersionList("liteloader") as LiteLoaderVersionList
lateinit var remote: RemoteVersion<LiteLoaderRemoteVersionTag> lateinit var remote: RemoteVersion<LiteLoaderRemoteVersionTag>
override val dependents = mutableListOf<Task>() override val dependents = mutableListOf<Task>()

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}. * along with this program. If not, see {http://www.gnu.org/licenses/}.
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download.liteloader
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import org.jackhuang.hmcl.game.Library import org.jackhuang.hmcl.game.Library

View File

@@ -15,13 +15,16 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}. * along with this program. If not, see {http://www.gnu.org/licenses/}.
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download.liteloader
import org.jackhuang.hmcl.download.DownloadProvider
import org.jackhuang.hmcl.download.RemoteVersion
import org.jackhuang.hmcl.download.VersionList
import org.jackhuang.hmcl.task.GetTask
import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.util.GSON import org.jackhuang.hmcl.util.GSON
import org.jackhuang.hmcl.util.asVersion import org.jackhuang.hmcl.util.asVersion
import org.jackhuang.hmcl.util.fromJson import org.jackhuang.hmcl.util.fromJson
import org.jackhuang.hmcl.task.GetTask
import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.util.toURL import org.jackhuang.hmcl.util.toURL
object LiteLoaderVersionList : VersionList<LiteLoaderRemoteVersionTag>() { object LiteLoaderVersionList : VersionList<LiteLoaderRemoteVersionTag>() {

View File

@@ -15,15 +15,17 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}. * along with this program. If not, see {http://www.gnu.org/licenses/}.
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download.optifine
import org.jackhuang.hmcl.download.DownloadProvider
import org.jackhuang.hmcl.download.RemoteVersion
import org.jackhuang.hmcl.download.VersionList
import org.jackhuang.hmcl.task.GetTask import org.jackhuang.hmcl.task.GetTask
import org.jackhuang.hmcl.task.Task import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.util.GSON import org.jackhuang.hmcl.util.GSON
import org.jackhuang.hmcl.util.asVersion import org.jackhuang.hmcl.util.asVersion
import org.jackhuang.hmcl.util.toURL import org.jackhuang.hmcl.util.toURL
import org.jackhuang.hmcl.util.typeOf import org.jackhuang.hmcl.util.typeOf
import java.util.TreeSet
object OptiFineBMCLVersionList : VersionList<Unit>() { object OptiFineBMCLVersionList : VersionList<Unit>() {
override fun refreshAsync(downloadProvider: DownloadProvider): Task { override fun refreshAsync(downloadProvider: DownloadProvider): Task {

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}. * along with this program. If not, see {http://www.gnu.org/licenses/}.
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download.optifine
object Opt object Opt

View File

@@ -15,8 +15,11 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}. * along with this program. If not, see {http://www.gnu.org/licenses/}.
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download.optifine
import org.jackhuang.hmcl.download.DefaultDependencyManager
import org.jackhuang.hmcl.download.RemoteVersion
import org.jackhuang.hmcl.download.game.GameLibrariesTask
import org.jackhuang.hmcl.game.LibrariesDownloadInfo import org.jackhuang.hmcl.game.LibrariesDownloadInfo
import org.jackhuang.hmcl.game.Library import org.jackhuang.hmcl.game.Library
import org.jackhuang.hmcl.game.LibraryDownloadInfo import org.jackhuang.hmcl.game.LibraryDownloadInfo
@@ -26,10 +29,13 @@ import org.jackhuang.hmcl.task.TaskResult
import org.jackhuang.hmcl.task.then import org.jackhuang.hmcl.task.then
import org.jackhuang.hmcl.util.merge import org.jackhuang.hmcl.util.merge
/**
* **Note**: OptiFine should be installed in the end.
*/
class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManager, class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManager,
private val gameVersion: String, private val gameVersion: String,
private val version: Version, private val version: Version,
private val remoteVersion: String): TaskResult<Version>() { private val remoteVersion: String): TaskResult<Version>() {
private val optiFineVersionList = dependencyManager.getVersionList("optifine") private val optiFineVersionList = dependencyManager.getVersionList("optifine")
lateinit var remote: RemoteVersion<*> lateinit var remote: RemoteVersion<*>
override val dependents = mutableListOf<Task>() override val dependents = mutableListOf<Task>()

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}. * along with this program. If not, see {http://www.gnu.org/licenses/}.
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download.optifine
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName

View File

@@ -15,15 +15,18 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}. * along with this program. If not, see {http://www.gnu.org/licenses/}.
*/ */
package org.jackhuang.hmcl.download package org.jackhuang.hmcl.download.optifine
import org.jackhuang.hmcl.download.DownloadProvider
import org.jackhuang.hmcl.download.RemoteVersion
import org.jackhuang.hmcl.download.VersionList
import org.jackhuang.hmcl.task.GetTask import org.jackhuang.hmcl.task.GetTask
import org.jackhuang.hmcl.task.Task import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.util.toURL import org.jackhuang.hmcl.util.toURL
import org.w3c.dom.Element import org.w3c.dom.Element
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import javax.xml.parsers.DocumentBuilderFactory
import java.util.regex.Pattern import java.util.regex.Pattern
import javax.xml.parsers.DocumentBuilderFactory
object OptiFineVersionList : VersionList<Unit>() { object OptiFineVersionList : VersionList<Unit>() {
private val pattern = Pattern.compile("OptiFine (.*?) ") private val pattern = Pattern.compile("OptiFine (.*?) ")

View File

@@ -17,8 +17,8 @@
*/ */
package org.jackhuang.hmcl.event package org.jackhuang.hmcl.event
import org.jackhuang.hmcl.util.JavaProcess import org.jackhuang.hmcl.util.ManagedProcess
import java.util.EventObject import java.util.*
/** /**
* This event gets fired when loading versions in a .minecraft folder. * This event gets fired when loading versions in a .minecraft folder.
@@ -59,7 +59,7 @@ class LoadedOneVersionEvent(source: Any, val version: String) : EventObject(sour
* @param JavaProcess The process that exited abnormally. * @param JavaProcess The process that exited abnormally.
* @author huangyuhui * @author huangyuhui
*/ */
class JavaProcessExitedAbnormallyEvent(source: Any, val value: JavaProcess) : EventObject(source) class JavaProcessExitedAbnormallyEvent(source: Any, val value: ManagedProcess) : EventObject(source)
/** /**
* This event gets fired when minecraft process exited successfully and the exit code is 0. * This event gets fired when minecraft process exited successfully and the exit code is 0.
@@ -69,7 +69,7 @@ class JavaProcessExitedAbnormallyEvent(source: Any, val value: JavaProcess) : Ev
* @param JavaProcess minecraft process * @param JavaProcess minecraft process
* @author huangyuhui * @author huangyuhui
*/ */
class JavaProcessStoppedEvent(source: Any, val value: JavaProcess) : EventObject(source) class JavaProcessStoppedEvent(source: Any, val value: ManagedProcess) : EventObject(source)
/** /**
* This event gets fired when we launch the JVM and it got crashed. * This event gets fired when we launch the JVM and it got crashed.
@@ -79,4 +79,4 @@ class JavaProcessStoppedEvent(source: Any, val value: JavaProcess) : EventObject
* @param JavaProcess the crashed process. * @param JavaProcess the crashed process.
* @author huangyuhui * @author huangyuhui
*/ */
class JVMLaunchFailedEvent(source: Any, val value: JavaProcess) : EventObject(source) class JVMLaunchFailedEvent(source: Any, val value: ManagedProcess) : EventObject(source)

View File

@@ -18,7 +18,6 @@
package org.jackhuang.hmcl.game package org.jackhuang.hmcl.game
import jdk.nashorn.internal.ir.annotations.Immutable import jdk.nashorn.internal.ir.annotations.Immutable
import java.net.URL
@Immutable @Immutable
class AssetIndexInfo( class AssetIndexInfo(

View File

@@ -20,6 +20,9 @@ package org.jackhuang.hmcl.game
import org.jackhuang.hmcl.util.Immutable import org.jackhuang.hmcl.util.Immutable
import java.io.File import java.io.File
/**
* The Minecraft version for 1.5.x and earlier.
*/
@Immutable @Immutable
class ClassicVersion : Version( class ClassicVersion : Version(
mainClass = "net.minecraft.client.Minecraft", mainClass = "net.minecraft.client.Minecraft",
@@ -41,6 +44,9 @@ class ClassicVersion : Version(
) )
companion object { companion object {
/**
* Check if this directory is an old style Minecraft repository.
*/
fun hasClassicVersion(baseDirectory: File): Boolean { fun hasClassicVersion(baseDirectory: File): Boolean {
val file = File(baseDirectory, "bin") val file = File(baseDirectory, "bin")
if (!file.exists()) return false if (!file.exists()) return false

View File

@@ -18,7 +18,10 @@
package org.jackhuang.hmcl.game package org.jackhuang.hmcl.game
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import org.jackhuang.hmcl.event.* import org.jackhuang.hmcl.event.EVENT_BUS
import org.jackhuang.hmcl.event.LoadedOneVersionEvent
import org.jackhuang.hmcl.event.RefreshedVersionsEvent
import org.jackhuang.hmcl.event.RefreshingVersionsEvent
import org.jackhuang.hmcl.util.GSON import org.jackhuang.hmcl.util.GSON
import org.jackhuang.hmcl.util.LOG import org.jackhuang.hmcl.util.LOG
import org.jackhuang.hmcl.util.fromJson import org.jackhuang.hmcl.util.fromJson
@@ -28,6 +31,9 @@ import java.io.IOException
import java.util.* import java.util.*
import java.util.logging.Level import java.util.logging.Level
/**
* An implementation of classic Minecraft game repository.
*/
open class DefaultGameRepository(var baseDirectory: File): GameRepository { open class DefaultGameRepository(var baseDirectory: File): GameRepository {
protected val versions: MutableMap<String, Version> = TreeMap<String, Version>() protected val versions: MutableMap<String, Version> = TreeMap<String, Version>()
@@ -44,6 +50,11 @@ open class DefaultGameRepository(var baseDirectory: File): GameRepository {
val id = v.jar ?: v.id val id = v.jar ?: v.id
return getVersionRoot(id).resolve("$id.jar") return getVersionRoot(id).resolve("$id.jar")
} }
/**
* {@inheritsDoc}
* @return something like ".minecraft/versions/<version name>/<version name>-natives"
*/
override fun getNativeDirectory(id: String) = File(getVersionRoot(id), "$id-natives") override fun getNativeDirectory(id: String) = File(getVersionRoot(id), "$id-natives")
override fun getVersionRoot(id: String) = File(baseDirectory, "versions/$id") override fun getVersionRoot(id: String) = File(baseDirectory, "versions/$id")
open fun getVersionJson(id: String) = File(getVersionRoot(id), "$id.json") open fun getVersionJson(id: String) = File(getVersionRoot(id), "$id.json")
@@ -83,7 +94,6 @@ open class DefaultGameRepository(var baseDirectory: File): GameRepository {
} }
protected open fun refreshVersionsImpl() { protected open fun refreshVersionsImpl() {
versions.clear() versions.clear()
if (ClassicVersion.hasClassicVersion(baseDirectory)) { if (ClassicVersion.hasClassicVersion(baseDirectory)) {
@@ -167,7 +177,7 @@ open class DefaultGameRepository(var baseDirectory: File): GameRepository {
return assetDir.resolve("objects/${obj.location}") return assetDir.resolve("objects/${obj.location}")
} }
open fun getIndexFile(version: String, assetId: String): File = override fun getIndexFile(version: String, assetId: String): File =
getAssetDirectory(version, assetId).resolve("indexes/$assetId.json") getAssetDirectory(version, assetId).resolve("indexes/$assetId.json")
override fun getLoggingObject(version: String, assetId: String, loggingInfo: LoggingInfo): File = override fun getLoggingObject(version: String, assetId: String, loggingInfo: LoggingInfo): File =
@@ -176,9 +186,8 @@ open class DefaultGameRepository(var baseDirectory: File): GameRepository {
@Throws(IOException::class, JsonSyntaxException::class) @Throws(IOException::class, JsonSyntaxException::class)
protected open fun reconstructAssets(version: String, assetId: String): File { protected open fun reconstructAssets(version: String, assetId: String): File {
val assetsDir = getAssetDirectory(version, assetId) val assetsDir = getAssetDirectory(version, assetId)
val assetVersion = assetId val indexFile: File = getIndexFile(version, assetId)
val indexFile: File = getIndexFile(version, assetVersion) val virtualRoot = assetsDir.resolve("virtual").resolve(assetId)
val virtualRoot = assetsDir.resolve("virtual").resolve(assetVersion)
if (!indexFile.isFile) { if (!indexFile.isFile) {
return assetsDir return assetsDir

View File

@@ -18,13 +18,15 @@
package org.jackhuang.hmcl.game package org.jackhuang.hmcl.game
import java.io.File import java.io.File
import java.io.IOException
/** /**
* Supports operations on versioning. * Supports operations on versioning.
* *
* Note that game repository will not do any tasks that connect to Internet. * Note that game repository will not do any tasks that connect to Internet, if do, see [org.jackhuang.hmcl.download.DependencyManager]
*/ */
interface GameRepository : VersionProvider { interface GameRepository : VersionProvider {
/** /**
* Does the version of id exist? * Does the version of id exist?
* @param id the id of version * @param id the id of version
@@ -126,7 +128,8 @@ interface GameRepository : VersionProvider {
* Will reconstruct assets or do some blocking tasks if necessary. * Will reconstruct assets or do some blocking tasks if necessary.
* You'd better create a new thread to invoke this method. * You'd better create a new thread to invoke this method.
* *
* @param assetId the asset id, [AssetIndexInfo.id] [Version.assets] * @param version the id of specific version that is relevant to [assetId]
* @param assetId the asset id, you can find it in [AssetIndexInfo.id] [Version.actualAssetIndex.id]
* @throws java.io.IOException if I/O operation fails. * @throws java.io.IOException if I/O operation fails.
* @return the actual asset directory * @return the actual asset directory
*/ */
@@ -134,24 +137,31 @@ interface GameRepository : VersionProvider {
/** /**
* Get the asset directory according to the asset id. * Get the asset directory according to the asset id.
*
* @param version the id of specific version that is relevant to [assetId]
* @param assetId the asset id, you can find it in [AssetIndexInfo.id] [Version.actualAssetIndex.id]
* @return the asset directory
*/ */
fun getAssetDirectory(version: String, assetId: String): File fun getAssetDirectory(version: String, assetId: String): File
/** /**
* Get the file that given asset object refers to * Get the file that given asset object refers to
* *
* @param assetId the asset id, [AssetIndexInfo.id] [Version.assets] * @param version the id of specific version that is relevant to [assetId]
* @param name the asset object name, [AssetIndex.objects.key] * @param assetId the asset id, you can find it in [AssetIndexInfo.id] [Version.actualAssetIndex.id]
* @param name the asset object name, you can find it in [AssetIndex.objects.keys]
* @throws java.io.IOException if I/O operation fails. * @throws java.io.IOException if I/O operation fails.
* @return the file that given asset object refers to * @return the file that given asset object refers to
*/ */
@Throws(IOException::class)
fun getAssetObject(version: String, assetId: String, name: String): File fun getAssetObject(version: String, assetId: String, name: String): File
/** /**
* Get the file that given asset object refers to * Get the file that given asset object refers to
* *
* @param assetId the asset id, [AssetIndexInfo.id] [Version.assets] * @param version the id of specific version that is relevant to [assetId]
* @param obj the asset object, [AssetIndex.objects] * @param assetId the asset id, you can find it in [AssetIndexInfo.id] [Version.actualAssetIndex.id]
* @param obj the asset object, you can find it in [AssetIndex.objects]
* @return the file that given asset object refers to * @return the file that given asset object refers to
*/ */
fun getAssetObject(version: String, assetId: String, obj: AssetObject): File fun getAssetObject(version: String, assetId: String, obj: AssetObject): File
@@ -159,17 +169,28 @@ interface GameRepository : VersionProvider {
/** /**
* Get asset index that assetId represents * Get asset index that assetId represents
* *
* @param assetId the asset id, [AssetIndexInfo.id] [Version.assets] * @param version the id of specific version that is relevant to [assetId]
* @param assetId the asset id, you can find it in [AssetIndexInfo.id] [Version.actualAssetIndex.id]
* @return the asset index * @return the asset index
*/ */
fun getAssetIndex(version: String, assetId: String): AssetIndex fun getAssetIndex(version: String, assetId: String): AssetIndex
/**
* Get the asset_index.json which includes asset objects information.
*
* @param version the id of specific version that is relevant to [assetId]
* @param assetId the asset id, you can find it in [AssetIndexInfo.id] [Version.actualAssetIndex.id]
*/
fun getIndexFile(version: String, assetId: String): File
/** /**
* Get logging object * Get logging object
* *
* @param assetId the asset id, [AssetIndexInfo.id] [Version.assets] * @param version the id of specific version that is relevant to [assetId]
* @param assetId the asset id, you can find it in [AssetIndexInfo.id] [Version.assets]
* @param loggingInfo the logging info * @param loggingInfo the logging info
* @return the file that loggingInfo refers to * @return the file that loggingInfo refers to
*/ */
fun getLoggingObject(version: String, assetId: String, loggingInfo: LoggingInfo): File fun getLoggingObject(version: String, assetId: String, loggingInfo: LoggingInfo): File
} }

View File

@@ -19,10 +19,10 @@ package org.jackhuang.hmcl.game
import org.jackhuang.hmcl.util.closeQuietly import org.jackhuang.hmcl.util.closeQuietly
import org.jackhuang.hmcl.util.readFullyAsByteArray import org.jackhuang.hmcl.util.readFullyAsByteArray
import java.io.File
import java.io.IOException import java.io.IOException
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipFile import java.util.zip.ZipFile
import java.io.File
private fun lessThan32(b: ByteArray, startIndex: Int): Int { private fun lessThan32(b: ByteArray, startIndex: Int): Int {
for (i in startIndex until b.size) for (i in startIndex until b.size)

View File

@@ -17,7 +17,6 @@
*/ */
package org.jackhuang.hmcl.game package org.jackhuang.hmcl.game
import com.google.gson.JsonSyntaxException
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import org.jackhuang.hmcl.util.Immutable import org.jackhuang.hmcl.util.Immutable
import org.jackhuang.hmcl.util.Validation import org.jackhuang.hmcl.util.Validation

View File

@@ -23,6 +23,7 @@ package org.jackhuang.hmcl.game
* @see Version.resolve * @see Version.resolve
*/ */
interface VersionProvider { interface VersionProvider {
/** /**
* Does the version of id exist? * Does the version of id exist?
* @param id the id of version * @param id the id of version
@@ -36,4 +37,5 @@ interface VersionProvider {
* @return the version you want * @return the version you want
*/ */
fun getVersion(id: String): Version fun getVersion(id: String): Version
} }

View File

@@ -18,14 +18,16 @@
package org.jackhuang.hmcl.launch package org.jackhuang.hmcl.launch
import org.jackhuang.hmcl.auth.AuthInfo import org.jackhuang.hmcl.auth.AuthInfo
import org.jackhuang.hmcl.game.* import org.jackhuang.hmcl.game.DownloadType
import org.jackhuang.hmcl.game.GameException
import org.jackhuang.hmcl.game.GameRepository
import org.jackhuang.hmcl.game.LaunchOptions
import org.jackhuang.hmcl.task.TaskResult import org.jackhuang.hmcl.task.TaskResult
import org.jackhuang.hmcl.util.* import org.jackhuang.hmcl.util.*
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.* import java.util.*
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.coroutines.experimental.EmptyCoroutineContext.plus
/** /**
* @param versionId The version to be launched. * @param versionId The version to be launched.
@@ -220,7 +222,7 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
} }
} }
override fun launch(): JavaProcess { override fun launch(): ManagedProcess {
// To guarantee that when failed to generate code, we will not call precalled command // To guarantee that when failed to generate code, we will not call precalled command
val builder = ProcessBuilder(rawCommandLine) val builder = ProcessBuilder(rawCommandLine)
@@ -237,7 +239,7 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
builder.directory(repository.getRunDirectory(version.id)) builder.directory(repository.getRunDirectory(version.id))
.environment().put("APPDATA", options.gameDir.absoluteFile.parent) .environment().put("APPDATA", options.gameDir.absoluteFile.parent)
val p = JavaProcess(builder.start(), rawCommandLine) val p = ManagedProcess(builder.start(), rawCommandLine)
if (listener == null) if (listener == null)
startMonitors(p) startMonitors(p)
else else
@@ -245,8 +247,8 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
return p return p
} }
fun launchAsync(): TaskResult<JavaProcess> { fun launchAsync(): TaskResult<ManagedProcess> {
return object : TaskResult<JavaProcess>() { return object : TaskResult<ManagedProcess>() {
override val id = LAUNCH_ASYNC_ID override val id = LAUNCH_ASYNC_ID
override fun execute() { override fun execute() {
result = launch() result = launch()
@@ -279,20 +281,20 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
return scriptFile return scriptFile
} }
private fun startMonitors(javaProcess: JavaProcess) { private fun startMonitors(managedProcess: ManagedProcess) {
javaProcess.relatedThreads += thread(name = "stdout-pump", isDaemon = true, block = StreamPump(javaProcess.process.inputStream)::run) managedProcess.relatedThreads += thread(name = "stdout-pump", isDaemon = true, block = StreamPump(managedProcess.process.inputStream)::run)
javaProcess.relatedThreads += thread(name = "stderr-pump", isDaemon = true, block = StreamPump(javaProcess.process.errorStream)::run) managedProcess.relatedThreads += thread(name = "stderr-pump", isDaemon = true, block = StreamPump(managedProcess.process.errorStream)::run)
} }
private fun startMonitors(javaProcess: JavaProcess, processListener: ProcessListener, isDaemon: Boolean = true) { private fun startMonitors(managedProcess: ManagedProcess, processListener: ProcessListener, isDaemon: Boolean = true) {
processListener.setProcess(javaProcess) processListener.setProcess(managedProcess)
val logHandler = Log4jHandler { line, level -> processListener.onLog(line, level); javaProcess.lines += line }.apply { start() } val logHandler = Log4jHandler { line, level -> processListener.onLog(line, level); managedProcess.lines += line }.apply { start() }
javaProcess.relatedThreads += logHandler managedProcess.relatedThreads += logHandler
val stdout = thread(name = "stdout-pump", isDaemon = isDaemon, block = StreamPump(javaProcess.process.inputStream, { logHandler.newLine(it) } )::run) val stdout = thread(name = "stdout-pump", isDaemon = isDaemon, block = StreamPump(managedProcess.process.inputStream, { logHandler.newLine(it) } )::run)
javaProcess.relatedThreads += stdout managedProcess.relatedThreads += stdout
val stderr = thread(name = "stderr-pump", isDaemon = isDaemon, block = StreamPump(javaProcess.process.errorStream, { processListener.onLog(it + OS.LINE_SEPARATOR, Log4jLevel.ERROR); javaProcess.lines += it })::run) 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)
javaProcess.relatedThreads += stderr managedProcess.relatedThreads += stderr
javaProcess.relatedThreads += thread(name = "exit-waiter", isDaemon = isDaemon, block = ExitWaiter(javaProcess, listOf(stdout, stderr), { exitCode, exitType -> logHandler.onStopped(); processListener.onExit(exitCode, exitType) })::run) managedProcess.relatedThreads += thread(name = "exit-waiter", isDaemon = isDaemon, block = ExitWaiter(managedProcess, listOf(stdout, stderr), { exitCode, exitType -> logHandler.onStopped(); processListener.onExit(exitCode, exitType) })::run)
} }
companion object { companion object {

View File

@@ -21,16 +21,15 @@ import org.jackhuang.hmcl.event.EVENT_BUS
import org.jackhuang.hmcl.event.JVMLaunchFailedEvent import org.jackhuang.hmcl.event.JVMLaunchFailedEvent
import org.jackhuang.hmcl.event.JavaProcessExitedAbnormallyEvent import org.jackhuang.hmcl.event.JavaProcessExitedAbnormallyEvent
import org.jackhuang.hmcl.event.JavaProcessStoppedEvent import org.jackhuang.hmcl.event.JavaProcessStoppedEvent
import org.jackhuang.hmcl.util.JavaProcess import org.jackhuang.hmcl.util.ManagedProcess
import org.jackhuang.hmcl.util.containsOne import org.jackhuang.hmcl.util.containsOne
import org.jackhuang.hmcl.util.guessLogLineError import org.jackhuang.hmcl.util.guessLogLineError
import java.util.*
/** /**
* @param process the process to wait for * @param process the process to wait for
* @param watcher the callback that will be called after process stops. * @param watcher the callback that will be called after process stops.
*/ */
internal class ExitWaiter(val process: JavaProcess, val joins: Collection<Thread>, val watcher: (Int, ProcessListener.ExitType) -> Unit) : Runnable { internal class ExitWaiter(val process: ManagedProcess, val joins: Collection<Thread>, val watcher: (Int, ProcessListener.ExitType) -> Unit) : Runnable {
override fun run() { override fun run() {
try { try {
process.process.waitFor() process.process.waitFor()

View File

@@ -21,7 +21,7 @@ import org.jackhuang.hmcl.auth.AuthInfo
import org.jackhuang.hmcl.game.GameRepository import org.jackhuang.hmcl.game.GameRepository
import org.jackhuang.hmcl.game.LaunchOptions import org.jackhuang.hmcl.game.LaunchOptions
import org.jackhuang.hmcl.game.Version import org.jackhuang.hmcl.game.Version
import org.jackhuang.hmcl.util.JavaProcess import org.jackhuang.hmcl.util.ManagedProcess
import java.io.File import java.io.File
abstract class Launcher( abstract class Launcher(
@@ -34,7 +34,7 @@ abstract class Launcher(
val version: Version = repository.getVersion(versionId).resolve(repository) val version: Version = repository.getVersion(versionId).resolve(repository)
abstract val rawCommandLine: List<String> abstract val rawCommandLine: List<String>
abstract fun launch(): JavaProcess abstract fun launch(): ManagedProcess
/** /**
* @param file The file path without extension * @param file The file path without extension

View File

@@ -33,6 +33,7 @@ import java.util.concurrent.atomic.AtomicBoolean
/** /**
* This class is to parse log4j classic XML layout logging, since only vanilla Minecraft will enable this layout. * This class is to parse log4j classic XML layout logging, since only vanilla Minecraft will enable this layout.
* Also supports plain logs.
*/ */
internal class Log4jHandler(val callback: (String, Log4jLevel) -> Unit) : Thread() { internal class Log4jHandler(val callback: (String, Log4jLevel) -> Unit) : Thread() {
val reader = XMLReaderFactory.createXMLReader().apply { val reader = XMLReaderFactory.createXMLReader().apply {
@@ -53,6 +54,9 @@ internal class Log4jHandler(val callback: (String, Log4jLevel) -> Unit) : Thread
} }
} }
/**
* Should be called to stop [Log4jHandler] manually.
*/
fun onStopped() { fun onStopped() {
if (interrupted.get()) if (interrupted.get())
return return
@@ -66,7 +70,7 @@ internal class Log4jHandler(val callback: (String, Log4jLevel) -> Unit) : Thread
} }
/** /**
* Should be called in [ProcessListener.onErrorLog] and [ProcessListener.onLog] manually. * New XML line.
*/ */
fun newLine(content: String) = fun newLine(content: String) =
Scheduler.COMPUTATION.schedule { Scheduler.COMPUTATION.schedule {
@@ -102,6 +106,7 @@ internal class Log4jHandler(val callback: (String, Log4jLevel) -> Unit) : Thread
l = Log4jLevel.ERROR l = Log4jLevel.ERROR
} }
"log4j_Message" -> readingMessage = true "log4j_Message" -> readingMessage = true
"log4j_Throwable" -> {}
} }
} }

View File

@@ -17,15 +17,15 @@
*/ */
package org.jackhuang.hmcl.launch package org.jackhuang.hmcl.launch
import org.jackhuang.hmcl.util.JavaProcess
import org.jackhuang.hmcl.util.Log4jLevel import org.jackhuang.hmcl.util.Log4jLevel
import org.jackhuang.hmcl.util.ManagedProcess
interface ProcessListener { interface ProcessListener {
/** /**
* When a game launched, this method will be called to get the new process. * When a game launched, this method will be called to get the new process.
* You should not override this method when your ProcessListener is shared with all processes. * You should not override this method when your ProcessListener is shared with all processes.
*/ */
fun setProcess(process: JavaProcess) {} fun setProcess(process: ManagedProcess) {}
/** /**
* Called when receiving a log from stdout/stderr. * Called when receiving a log from stdout/stderr.

View File

@@ -23,6 +23,12 @@ import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.util.logging.Level import java.util.logging.Level
/**
* Pump the given input stream.
* @param inputStream the input stream to pump
* @param callback receives each line
*
*/
internal class StreamPump @JvmOverloads constructor( internal class StreamPump @JvmOverloads constructor(
private val inputStream: InputStream, private val inputStream: InputStream,
private val callback: (String) -> Unit = {} private val callback: (String) -> Unit = {}

View File

@@ -99,14 +99,27 @@ class CurseForgeModpackManifestFile (
val url: URL get() = "https://minecraft.curseforge.com/projects/$projectID/files/$fileID/download".toURL() val url: URL get() = "https://minecraft.curseforge.com/projects/$projectID/files/$fileID/download".toURL()
} }
/**
* @param f the CurseForge modpack file.
* @return the manifest.
*/
fun readCurseForgeModpackManifest(f: File): CurseForgeModpackManifest { fun readCurseForgeModpackManifest(f: File): CurseForgeModpackManifest {
ZipFile(f).use { zipFile -> ZipFile(f).use { zipFile ->
val entry = zipFile.getEntry("manifest.json") ?: throw IOException("Manifest.json not found. Not a valid CurseForge modpack.") val entry = zipFile.getEntry("manifest.json") ?: throw IOException("`manifest.json` not found. Not a valid CurseForge modpack.")
val json = zipFile.getInputStream(entry).readFullyAsString() val json = zipFile.getInputStream(entry).readFullyAsString()
return GSON.fromJson<CurseForgeModpackManifest>(json) ?: throw JsonParseException("Manifest.json not found. Not a valid CurseForge modpack.") return GSON.fromJson<CurseForgeModpackManifest>(json) ?: throw JsonParseException("`manifest.json` not found. Not a valid CurseForge modpack.")
} }
} }
/**
* Install a downloaded CurseForge modpack.
*
* @param dependencyManager the dependency manager.
* @param zipFile the CurseForge modpack file.
* @param manifest The manifest content of given CurseForge modpack.
* @param name the new version name
* @see readCurseForgeModpackManifest
*/
class CurseForgeModpackInstallTask(private val dependencyManager: DefaultDependencyManager, private val zipFile: File, private val manifest: CurseForgeModpackManifest, private val name: String): Task() { class CurseForgeModpackInstallTask(private val dependencyManager: DefaultDependencyManager, private val zipFile: File, private val manifest: CurseForgeModpackManifest, private val name: String): Task() {
val repository = dependencyManager.repository val repository = dependencyManager.repository
init { init {
@@ -136,7 +149,8 @@ class CurseForgeModpackInstallTask(private val dependencyManager: DefaultDepende
f.fileName = f.url.detectFileName(dependencyManager.proxy) f.fileName = f.url.detectFileName(dependencyManager.proxy)
dependencies += FileDownloadTask(f.url, run.resolve("mods").resolve(f.fileName), proxy = dependencyManager.proxy) dependencies += FileDownloadTask(f.url, run.resolve("mods").resolve(f.fileName), proxy = dependencyManager.proxy)
} catch (e: IOException) { } catch (e: IOException) {
// ignore it and retry next time. // Because in China, the CurseForge is too difficult to visit.
// So if failed, ignore it and retry next time.
} }
++finished ++finished
updateProgress(1.0 * finished / manifest.files.size) updateProgress(1.0 * finished / manifest.files.size)
@@ -146,6 +160,12 @@ class CurseForgeModpackInstallTask(private val dependencyManager: DefaultDepende
} }
} }
/**
* Complete the CurseForge version.
*
* @param dependencyManager the dependency manager.
* @param version the existent and physical version.
*/
class CurseForgeModpackCompletionTask(dependencyManager: DependencyManager, version: String): Task() { class CurseForgeModpackCompletionTask(dependencyManager: DependencyManager, version: String): Task() {
val repository = dependencyManager.repository val repository = dependencyManager.repository
val run = repository.getRunDirectory(version) val run = repository.getRunDirectory(version)
@@ -161,6 +181,8 @@ class CurseForgeModpackCompletionTask(dependencyManager: DependencyManager, vers
else { else {
manifest = GSON.fromJson<CurseForgeModpackManifest>(repository.getVersionRoot(version).resolve("manifest.json").readText())!! manifest = GSON.fromJson<CurseForgeModpackManifest>(repository.getVersionRoot(version).resolve("manifest.json").readText())!!
// Because in China, the CurseForge is too difficult to visit.
// So caching the file name is necessary.
for (f in manifest!!.files) { for (f in manifest!!.files) {
if (f.fileName.isBlank()) if (f.fileName.isBlank())
dependents += task { f.fileName = f.url.detectFileName(proxy) } dependents += task { f.fileName = f.url.detectFileName(proxy) }

View File

@@ -26,7 +26,7 @@ import org.jackhuang.hmcl.util.typeOf
import java.io.File import java.io.File
import java.util.zip.ZipFile import java.util.zip.ZipFile
class ForgeModMetadata( class ForgeModMetadata @JvmOverloads internal constructor(
@SerializedName("modid") @SerializedName("modid")
val modId: String = "", val modId: String = "",
val name: String = "", val name: String = "",
@@ -42,6 +42,9 @@ class ForgeModMetadata(
) { ) {
companion object { companion object {
/**
* Read Forge mod ModInfo.
*/
fun fromFile(modFile: File): ModInfo { fun fromFile(modFile: File): ModInfo {
ZipFile(modFile).use { ZipFile(modFile).use {
val entry = it.getEntry("mcmod.info") ?: throw JsonParseException("File $modFile is not a Forge mod.") val entry = it.getEntry("mcmod.info") ?: throw JsonParseException("File $modFile is not a Forge mod.")

View File

@@ -18,11 +18,13 @@
package org.jackhuang.hmcl.mod package org.jackhuang.hmcl.mod
import com.google.gson.JsonParseException import com.google.gson.JsonParseException
import org.jackhuang.hmcl.util.* import org.jackhuang.hmcl.util.GSON
import org.jackhuang.hmcl.util.fromJson
import org.jackhuang.hmcl.util.readFullyAsString
import java.io.File import java.io.File
import java.util.zip.ZipFile import java.util.zip.ZipFile
class LiteModMetadata ( class LiteModMetadata @JvmOverloads internal constructor(
val name: String = "", val name: String = "",
val version: String = "", val version: String = "",
val mcversion: String = "", val mcversion: String = "",
@@ -37,6 +39,9 @@ class LiteModMetadata (
) { ) {
companion object { companion object {
/**
* Read LiteLoader mod ModInfo.
*/
fun fromFile(modFile: File): ModInfo { fun fromFile(modFile: File): ModInfo {
ZipFile(modFile).use { ZipFile(modFile).use {
val entry = it.getEntry("litemod.json") val entry = it.getEntry("litemod.json")

View File

@@ -17,9 +17,9 @@
*/ */
package org.jackhuang.hmcl.mod package org.jackhuang.hmcl.mod
import com.google.gson.JsonParseException import org.jackhuang.hmcl.util.ImmediateBooleanProperty
import org.jackhuang.hmcl.util.property.ImmediateBooleanProperty import org.jackhuang.hmcl.util.getValue
import org.jackhuang.hmcl.util.* import org.jackhuang.hmcl.util.setValue
import java.io.File import java.io.File
class ModInfo ( class ModInfo (

View File

@@ -17,8 +17,6 @@
*/ */
package org.jackhuang.hmcl.mod package org.jackhuang.hmcl.mod
import org.jackhuang.hmcl.download.DefaultDependencyManager
import org.jackhuang.hmcl.task.Task
import java.io.File import java.io.File
class Modpack(val file: File) { class Modpack(val file: File) {

View File

@@ -19,6 +19,13 @@ package org.jackhuang.hmcl.task
import org.jackhuang.hmcl.util.AutoTypingMap import org.jackhuang.hmcl.util.AutoTypingMap
/**
* A task that combines two tasks and make sure [pred] runs before [succ].
*
* @param pred the task that runs before [succ]
* @param succ a callback that returns the task runs after [pred], [succ] will be executed asynchronously. You can do something that relies on the result of [pred].
* @param reliant true if this task chain will be broken when task [pred] fails.
*/
internal class CoupleTask<P: Task>(pred: P, private val succ: (AutoTypingMap<String>) -> Task?, override val reliant: Boolean) : Task() { internal class CoupleTask<P: Task>(pred: P, private val succ: (AutoTypingMap<String>) -> Task?, override val reliant: Boolean) : Task() {
override val hidden: Boolean = true override val hidden: Boolean = true

View File

@@ -19,28 +19,39 @@ package org.jackhuang.hmcl.task
import org.jackhuang.hmcl.event.EventManager import org.jackhuang.hmcl.event.EventManager
import org.jackhuang.hmcl.event.FailedEvent import org.jackhuang.hmcl.event.FailedEvent
import org.jackhuang.hmcl.util.LOG import org.jackhuang.hmcl.util.*
import org.jackhuang.hmcl.util.closeQuietly
import org.jackhuang.hmcl.util.DigestUtils
import org.jackhuang.hmcl.util.makeDirectory
import org.jackhuang.hmcl.util.createConnection
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.RandomAccessFile import java.io.RandomAccessFile
import java.math.BigInteger
import java.net.Proxy import java.net.Proxy
import java.net.URL import java.net.URL
import java.math.BigInteger
import java.util.logging.Level import java.util.logging.Level
/**
* A task that can download a file online.
*
* @param url the URL of remote file.
* @param file the location that download to.
* @param hash the SHA-1 hash code of remote file, null if the hash is unknown or it is no need to check the hash code.
* @param retry the times for retrying if downloading fails.
* @param proxy the proxy.
*
* @author huangyuhui
*/
class FileDownloadTask @JvmOverloads constructor(val url: URL, val file: File, val hash: String? = null, val retry: Int = 5, val proxy: Proxy = Proxy.NO_PROXY): Task() { class FileDownloadTask @JvmOverloads constructor(val url: URL, val file: File, val hash: String? = null, val retry: Int = 5, val proxy: Proxy = Proxy.NO_PROXY): Task() {
override val scheduler: Scheduler = Scheduler.IO override val scheduler: Scheduler = Scheduler.IO
/**
* Once downloading fails, this event will be fired to gain the substitute URL.
*/
var onFailed = EventManager<FailedEvent<URL>>() var onFailed = EventManager<FailedEvent<URL>>()
private var rFile: RandomAccessFile? = null private var rFile: RandomAccessFile? = null
private var stream: InputStream? = null private var stream: InputStream? = null
fun closeFiles() { private fun closeFiles() {
rFile?.closeQuietly() rFile?.closeQuietly()
rFile = null rFile = null
stream?.closeQuietly() stream?.closeQuietly()

View File

@@ -25,6 +25,17 @@ import java.net.Proxy
import java.net.URL import java.net.URL
import java.nio.charset.Charset import java.nio.charset.Charset
/**
* A task that can read the content of a remote text file.
*
* @param url the URL of remote text file.
* @param encoding the encoding/charset of the remote text file.
* @param retry the times for retrying if downloading fails.
* @param proxy the proxy.
* @param id the result variable id, see [Task.variables]
*
* @author huangyuhui
*/
class GetTask @JvmOverloads constructor(val url: URL, val encoding: Charset = Charsets.UTF_8, private val retry: Int = 5, private val proxy: Proxy = Proxy.NO_PROXY, override val id: String = ID): TaskResult<String>() { class GetTask @JvmOverloads constructor(val url: URL, val encoding: Charset = Charsets.UTF_8, private val retry: Int = 5, private val proxy: Proxy = Proxy.NO_PROXY, override val id: String = ID): TaskResult<String>() {
override val scheduler: Scheduler = Scheduler.IO override val scheduler: Scheduler = Scheduler.IO
@@ -65,6 +76,9 @@ class GetTask @JvmOverloads constructor(val url: URL, val encoding: Charset = Ch
} }
companion object { companion object {
/**
* The default task result ID.
*/
const val ID = "http_get" const val ID = "http_get"
} }
} }

View File

@@ -17,6 +17,11 @@
*/ */
package org.jackhuang.hmcl.task package org.jackhuang.hmcl.task
/**
* The tasks that provides a way to execute tasks parallelly.
*
* @param tasks the tasks that can be executed parallelly.
*/
class ParallelTask(vararg tasks: Task): Task() { class ParallelTask(vararg tasks: Task): Task() {
override val hidden: Boolean = true override val hidden: Boolean = true
override val dependents: Collection<Task> = listOf(*tasks) override val dependents: Collection<Task> = listOf(*tasks)

View File

@@ -17,13 +17,25 @@
*/ */
package org.jackhuang.hmcl.task package org.jackhuang.hmcl.task
import javafx.application.Platform
import java.util.concurrent.* import java.util.concurrent.*
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
import javax.swing.SwingUtilities
/**
* Determines how a task is executed.
*
* @see [Task.scheduler]
*/
interface Scheduler { interface Scheduler {
/**
* Schedules the given task.
* @return the future, null if future is not supported.
*/
fun schedule(block: () -> Unit): Future<*>? = schedule(Callable { block() }) fun schedule(block: () -> Unit): Future<*>? = schedule(Callable { block() })
/**
* Schedules the given task.
* @return the future, null if future is not supported.
*/
fun schedule(block: Callable<Unit>): Future<*>? fun schedule(block: Callable<Unit>): Future<*>?
private class SchedulerImpl(val executor: (Runnable) -> Unit) : Scheduler { private class SchedulerImpl(val executor: (Runnable) -> Unit) : Scheduler {
@@ -58,7 +70,7 @@ interface Scheduler {
} }
private class SchedulerExecutorService(val executorService: ExecutorService) : Scheduler { private class SchedulerExecutorService(val executorService: ExecutorService) : Scheduler {
override fun schedule(block: Callable<Unit>) = executorService.submit(block) override fun schedule(block: Callable<Unit>): Future<*> = executorService.submit(block)
} }
companion object Schedulers { companion object Schedulers {
@@ -90,13 +102,44 @@ interface Scheduler {
return null return null
} }
} }
val JAVAFX: Scheduler = SchedulerImpl(Platform::runLater)
val SWING: Scheduler = SchedulerImpl(SwingUtilities::invokeLater) /**
* A scheduler for JavaFX UI operations.
*/
val JAVAFX: Scheduler = SchedulerImpl(javafx.application.Platform::runLater)
/**
* A scheduler for Swing UI operations.
*/
val SWING: Scheduler = SchedulerImpl(javax.swing.SwingUtilities::invokeLater)
/**
* A scheduler that always create new threads to execute tasks.
* For tasks that do not do heavy operations.
*/
val NEW_THREAD: Scheduler = SchedulerExecutorService(CACHED_EXECUTOR) val NEW_THREAD: Scheduler = SchedulerExecutorService(CACHED_EXECUTOR)
/**
* A scheduler that exclusively executes tasks that do I/O operations.
* The number tasks that do I/O operations in the meantime cannot be larger then 6.
*/
val IO: Scheduler = SchedulerExecutorService(IO_EXECUTOR) val IO: Scheduler = SchedulerExecutorService(IO_EXECUTOR)
/**
* A scheduler that exclusively executes tasks that do computations.
* The best way to do computations is an event queue.
*/
val COMPUTATION: Scheduler = SchedulerExecutorService(SINGLE_EXECUTOR) val COMPUTATION: Scheduler = SchedulerExecutorService(SINGLE_EXECUTOR)
/**
* The default scheduler for tasks to be executed.
* @see [Task.scheduler]
*/
val DEFAULT = NEW_THREAD val DEFAULT = NEW_THREAD
/**
* Shut down all executor services to guarantee that the application can stop implicitly.
*/
fun shutdown() { fun shutdown() {
CACHED_EXECUTOR.shutdown() CACHED_EXECUTOR.shutdown()
IO_EXECUTOR.shutdown() IO_EXECUTOR.shutdown()

View File

@@ -17,8 +17,7 @@
*/ */
package org.jackhuang.hmcl.task package org.jackhuang.hmcl.task
class SilentException : Exception { /**
constructor() : super() {} * If a task throws [SilentException], the task will be marked as failure but do not log the stacktrace.
constructor(message: String) : super(message) {} */
constructor(message: String, cause: Throwable) : super(message, cause) {} class SilentException : Exception()
}

View File

@@ -22,12 +22,15 @@ import javafx.beans.property.ReadOnlyDoubleWrapper
import javafx.beans.property.ReadOnlyStringProperty import javafx.beans.property.ReadOnlyStringProperty
import javafx.beans.property.ReadOnlyStringWrapper import javafx.beans.property.ReadOnlyStringWrapper
import org.jackhuang.hmcl.event.EventManager import org.jackhuang.hmcl.event.EventManager
import org.jackhuang.hmcl.util.* import org.jackhuang.hmcl.util.AutoTypingMap
import org.jackhuang.hmcl.util.updateAsync
import java.util.concurrent.Callable import java.util.concurrent.Callable
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
/** /**
* Disposable task. * Disposable task.
*
* @see [TaskExecutor]
*/ */
abstract class Task { abstract class Task {
/** /**

View File

@@ -19,11 +19,13 @@ package org.jackhuang.hmcl.task
import org.jackhuang.hmcl.util.AutoTypingMap import org.jackhuang.hmcl.util.AutoTypingMap
import org.jackhuang.hmcl.util.LOG import org.jackhuang.hmcl.util.LOG
import java.util.concurrent.* import java.util.concurrent.Callable
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Future
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import java.util.logging.Level import java.util.logging.Level
import kotlin.concurrent.thread
class TaskExecutor() { class TaskExecutor() {
var taskListener: TaskListener? = null var taskListener: TaskListener? = null

View File

@@ -17,6 +17,9 @@
*/ */
package org.jackhuang.hmcl.task package org.jackhuang.hmcl.task
/**
* A task that has a result.
*/
abstract class TaskResult<V> : Task() { abstract class TaskResult<V> : Task() {
open var result: V? = null open var result: V? = null

View File

@@ -17,6 +17,9 @@
*/ */
package org.jackhuang.hmcl.util package org.jackhuang.hmcl.util
/**
* Mark if the model is immutable.
*/
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE) @Retention(AnnotationRetention.SOURCE)
annotation class Immutable annotation class Immutable

View File

@@ -17,6 +17,9 @@
*/ */
package org.jackhuang.hmcl.util package org.jackhuang.hmcl.util
/**
* A map that support auto casting.
*/
class AutoTypingMap<K>(private val impl: MutableMap<K, Any>) { class AutoTypingMap<K>(private val impl: MutableMap<K, Any>) {
fun clear() = impl.clear() fun clear() = impl.clear()

View File

@@ -18,10 +18,6 @@
package org.jackhuang.hmcl.util package org.jackhuang.hmcl.util
import java.io.File import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.nio.file.Files
import java.nio.file.StandardCopyOption
fun File.makeDirectory(): Boolean = isDirectory || mkdirs() fun File.makeDirectory(): Boolean = isDirectory || mkdirs()

View File

@@ -20,15 +20,12 @@ package org.jackhuang.hmcl.util
import com.google.gson.* import com.google.gson.*
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import java.lang.reflect.Type
import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonToken
import java.io.IOException import com.google.gson.stream.JsonWriter
import com.google.gson.TypeAdapter
import com.google.gson.Gson
import com.google.gson.TypeAdapterFactory
import org.jackhuang.hmcl.game.Library import org.jackhuang.hmcl.game.Library
import java.io.File import java.io.File
import java.io.IOException
import java.lang.reflect.Type
import java.text.DateFormat import java.text.DateFormat
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat

View File

@@ -64,6 +64,10 @@ fun InputStream.copyToAndClose(dest: OutputStream) {
} }
} }
/**
* @param cmd the command line
* @return the final command line
*/
fun makeCommand(cmd: List<String>): String { fun makeCommand(cmd: List<String>): String {
val cmdbuf = StringBuilder(120) val cmdbuf = StringBuilder(120)
for (i in cmd.indices) { for (i in cmd.indices) {

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}. * along with this program. If not, see {http://www.gnu.org/licenses/}.
*/ */
package org.jackhuang.hmcl.util.property package org.jackhuang.hmcl.util
import javafx.beans.property.* import javafx.beans.property.*
import javafx.beans.value.ChangeListener import javafx.beans.value.ChangeListener

View File

@@ -24,12 +24,26 @@ import java.io.Serializable
import java.util.* import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
/**
* Represents a Java installation.
*/
data class JavaVersion internal constructor( data class JavaVersion internal constructor(
@SerializedName("location") @SerializedName("location")
val binary: File, val binary: File,
val longVersion: String, val longVersion: String,
val platform: Platform) : Serializable val platform: Platform) : Serializable
{ {
/**
* The major version of Java installation.
*
* @see JAVA_X
* @see JAVA_9
* @see JAVA_8
* @see JAVA_7
* @see JAVA_6
* @see JAVA_5
* @see UNKNOWN
*/
val version = parseVersion(longVersion) val version = parseVersion(longVersion)
companion object { companion object {
@@ -110,10 +124,12 @@ data class JavaVersion internal constructor(
return path.resolve("java") return path.resolve("java")
} }
private val currentJava: JavaVersion = JavaVersion( private val currentJava: JavaVersion by lazy {
binary = getJavaFile(File(System.getProperty("java.home"))), JavaVersion(
longVersion = System.getProperty("java.version"), binary = getJavaFile(File(System.getProperty("java.home"))),
platform = Platform.PLATFORM) longVersion = System.getProperty("java.version"),
platform = Platform.PLATFORM)
}
fun fromCurrentEnvironment() = currentJava fun fromCurrentEnvironment() = currentJava
init { init {

View File

@@ -18,12 +18,9 @@
package org.jackhuang.hmcl.util package org.jackhuang.hmcl.util
import javafx.beans.property.Property import javafx.beans.property.Property
import javafx.beans.value.ObservableValue
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
import kotlin.collections.HashMap import kotlin.collections.HashMap
import sun.text.normalizer.UTF16.append
import java.lang.reflect.Array.getLength
inline fun ignoreException(func: () -> Unit) { inline fun ignoreException(func: () -> Unit) {
try { try {

View File

@@ -18,13 +18,13 @@
@file:JvmName("HMCLog") @file:JvmName("HMCLog")
package org.jackhuang.hmcl.util package org.jackhuang.hmcl.util
import javafx.scene.paint.Color
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.PrintWriter import java.io.PrintWriter
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import java.util.logging.* import java.util.logging.*
import java.util.logging.Formatter import java.util.logging.Formatter
import javafx.scene.paint.Color
import java.util.regex.Pattern import java.util.regex.Pattern
val LOG = Logger.getLogger("HMCL").apply { val LOG = Logger.getLogger("HMCL").apply {

View File

@@ -20,25 +20,58 @@ package org.jackhuang.hmcl.util
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
class JavaProcess( /**
* The managed process.
*
* @param process the raw system process that this instance manages.
* @param commands the command line of [process].
* @see [org.jackhuang.hmcl.launch.ExitWaiter]
* @see [org.jackhuang.hmcl.launch.StreamPump]
*/
class ManagedProcess(
val process: Process, val process: Process,
val commands: List<String> val commands: List<String>
) { ) {
/**
* To save some information you need.
*/
val properties = mutableMapOf<String, Any>() val properties = mutableMapOf<String, Any>()
/**
* The standard output/error lines.
*/
val lines: Queue<String> = ConcurrentLinkedQueue<String>() val lines: Queue<String> = ConcurrentLinkedQueue<String>()
/**
* The related threads.
*
* If a thread is monitoring this raw process,
* you are required to add the instance to [relatedThreads].
*/
val relatedThreads = mutableListOf<Thread>() val relatedThreads = mutableListOf<Thread>()
/**
* True if the managed process is running.
*/
val isRunning: Boolean = try { val isRunning: Boolean = try {
process.exitValue() process.exitValue()
true true
} catch (ex: IllegalThreadStateException) { } catch (ex: IllegalThreadStateException) {
false false
} }
/**
* The exit code of raw process.
*/
val exitCode: Int get() = process.exitValue() val exitCode: Int get() = process.exitValue()
override fun toString() = "JavaProcess[commands=$commands, isRunning=$isRunning]" /**
* Destroys the raw process and other related threads that are monitoring this raw process.
*/
fun stop() { fun stop() {
process.destroy() process.destroy()
relatedThreads.forEach(Thread::interrupt) relatedThreads.forEach(Thread::interrupt)
} }
override fun toString() = "ManagedProcess[commands=$commands, isRunning=$isRunning]"
} }

View File

@@ -19,20 +19,19 @@ package org.jackhuang.hmcl.util
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.Proxy import java.net.Proxy
import java.net.URL import java.net.URL
import java.security.GeneralSecurityException import java.security.GeneralSecurityException
import java.security.SecureRandom import java.security.SecureRandom
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.util.*
import java.util.logging.Level
import javax.net.ssl.HostnameVerifier import javax.net.ssl.HostnameVerifier
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext import javax.net.ssl.SSLContext
import javax.net.ssl.X509TrustManager import javax.net.ssl.X509TrustManager
import java.io.OutputStream
import java.util.*
import java.util.logging.Level
import kotlin.text.Charsets
private val XTM = object : X509TrustManager { private val XTM = object : X509TrustManager {
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {} override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}

Some files were not shown because too many files have changed in this diff Show More