logic seems fine?
This commit is contained in:
parent
47de8220d5
commit
7db4d246d9
17 changed files with 254 additions and 511 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,6 +3,7 @@
|
|||
|
||||
# Ignore Gradle build output directory
|
||||
build
|
||||
bin
|
||||
|
||||
# Ignore VSCode settings
|
||||
.vscode
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* This Kotlin source file was generated by the Gradle 'init' task.
|
||||
*/
|
||||
package xyz.brysonsteck.serverfordummies
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
class App : Application() {
|
||||
|
||||
override fun start(stage: Stage) {
|
||||
var scene = Scene(loadFXML("primary"), 1500.0, 900.0)
|
||||
stage.title = "Server For Dummies"
|
||||
stage.scene = scene
|
||||
stage.show()
|
||||
}
|
||||
|
||||
private fun loadFXML(fxml: String) : Parent {
|
||||
val fxmlLoader = FXMLLoader(this.javaClass.getResource(fxml + ".fxml"))
|
||||
return fxmlLoader.load()
|
||||
}
|
||||
|
||||
public fun run() {
|
||||
launch()
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
package xyz.brysonsteck.serverfordummies
|
||||
|
||||
fun main() {
|
||||
App().run()
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
package xyz.brysonsteck.serverfordummies
|
||||
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.awt.Checkbox
|
||||
import java.util.Properties
|
||||
|
||||
import javafx.beans.value.ChangeListener
|
||||
import javafx.beans.value.ObservableValue
|
||||
import javafx.beans.property.BooleanProperty
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.fxml.FXML
|
||||
import javafx.geometry.Insets
|
||||
import javafx.scene.control.Button
|
||||
import javafx.scene.control.ChoiceBox
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.control.TextField
|
||||
import javafx.scene.control.Spinner
|
||||
import javafx.scene.control.TitledPane
|
||||
import javafx.scene.control.ButtonBar
|
||||
import javafx.scene.control.CheckBox
|
||||
import javafx.scene.layout.Border
|
||||
import javafx.scene.layout.BorderStroke
|
||||
import javafx.scene.layout.GridPane
|
||||
import javafx.scene.layout.Pane
|
||||
import javafx.scene.layout.HBox
|
||||
import javafx.scene.text.TextAlignment
|
||||
import javafx.scene.Scene
|
||||
import javafx.scene.input.MouseEvent
|
||||
import javafx.stage.FileChooser
|
||||
import javafx.stage.FileChooser.ExtensionFilter
|
||||
import javafx.stage.DirectoryChooser
|
||||
import javafx.event.EventHandler
|
||||
|
||||
class PrimaryController {
|
||||
@FXML
|
||||
lateinit private var currentDirectoryLabel: Label
|
||||
@FXML
|
||||
lateinit private var worldNameField: TextField
|
||||
@FXML
|
||||
lateinit private var seedField: TextField
|
||||
@FXML
|
||||
lateinit private var portSpinner: Spinner<kotlin.Int>
|
||||
@FXML
|
||||
lateinit private var difficultyBox: ChoiceBox<String>
|
||||
@FXML
|
||||
lateinit private var gamemodeBox: ChoiceBox<String>
|
||||
@FXML
|
||||
lateinit private var worldTypeBox: ChoiceBox<String>
|
||||
@FXML
|
||||
lateinit private var worldSettingsPane: HBox
|
||||
@FXML
|
||||
lateinit private var parentPane: Pane
|
||||
@FXML
|
||||
lateinit private var buttonBar: ButtonBar
|
||||
@FXML
|
||||
lateinit private var flightCheckbox: CheckBox
|
||||
@FXML
|
||||
lateinit private var netherCheckbox: CheckBox
|
||||
@FXML
|
||||
lateinit private var structuresCheckbox: CheckBox
|
||||
@FXML
|
||||
lateinit private var pvpCheckbox: CheckBox
|
||||
@FXML
|
||||
lateinit private var whitelistCheckbox: CheckBox
|
||||
@FXML
|
||||
lateinit private var cmdBlocksCheckbox: CheckBox
|
||||
@FXML
|
||||
lateinit private var playerCountCheckbox: CheckBox
|
||||
@FXML
|
||||
lateinit private var maxPlayersSpinner: Spinner<kotlin.Int>
|
||||
@FXML
|
||||
lateinit private var maxSizeSpinner: Spinner<kotlin.Int>
|
||||
@FXML
|
||||
lateinit private var memorySpinner: Spinner<kotlin.Int>
|
||||
@FXML
|
||||
lateinit private var spawnSpinner: Spinner<kotlin.Int>
|
||||
@FXML
|
||||
lateinit private var simulationSpinner: Spinner<kotlin.Int>
|
||||
@FXML
|
||||
lateinit private var renderSpinner: Spinner<kotlin.Int>
|
||||
@FXML
|
||||
lateinit private var maxTickSpinner: Spinner<kotlin.Int>
|
||||
@FXML
|
||||
lateinit private var progressBar: ProgressBar
|
||||
|
||||
@FXML
|
||||
private fun onDirectoryButtonClick() {
|
||||
val dirChooser = DirectoryChooser()
|
||||
dirChooser.title = "Open a server directory"
|
||||
dirChooser.initialDirectory = File(System.getProperty("user.home"))
|
||||
val result = dirChooser.showDialog(null)
|
||||
if (result != null) {
|
||||
currentDirectoryLabel.text = result.absolutePath
|
||||
parentPane.isDisable = false
|
||||
worldSettingsPane.isDisable = false
|
||||
buttonBar.isDisable = false
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private fun onWorldNameChange() {
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
private fun onSeedChange() {
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
private fun onPortChange() {
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
private fun onCheckboxClick() {
|
||||
}
|
||||
|
||||
@FXML
|
||||
private fun onSpinnerChange() {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
import java.net.http.HttpClient
|
||||
import kotlinx.coroutines.flow
|
||||
|
||||
sealed class DownloadStatus {
|
||||
|
||||
object Success : DownloadStatus()
|
||||
|
||||
data class Error(val message: String) : DownloadStatus()
|
||||
|
||||
data class Progress(val progress: Int): DownloadStatus()
|
||||
|
||||
}
|
||||
|
||||
// function from https://gist.githubusercontent.com/SG-K/63e379efcc3d1cd3ce4fb56ee0e29c42/raw/cd9a4a016401b7c54ec01303415b5871ffa26066/downloadFile.kt
|
||||
suspend fun HttpClient.downloadFile(file: File, url: String): Flow<DownloadStatus> {
|
||||
return flow {
|
||||
val response = call {
|
||||
url(url)
|
||||
method = HttpMethod.Get
|
||||
}.response
|
||||
val byteArray = ByteArray(response.contentLength()!!.toInt())
|
||||
var offset = 0
|
||||
do {
|
||||
val currentRead = response.content.readAvailable(byteArray, offset, byteArray.size)
|
||||
offset += currentRead
|
||||
val progress = (offset * 100f / byteArray.size).roundToInt()
|
||||
emit(DownloadStatus.Progress(progress))
|
||||
} while (currentRead > 0)
|
||||
response.close()
|
||||
if (response.status.isSuccess()) {
|
||||
file.writeBytes(byteArray)
|
||||
emit(DownloadStatus.Success)
|
||||
} else {
|
||||
emit(DownloadStatus.Error("File not downloaded"))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,253 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<?import javafx.scene.control.ChoiceBox?>
|
||||
<?import javafx.scene.control.ContextMenu?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.MenuItem?>
|
||||
<?import javafx.scene.control.ProgressBar?>
|
||||
<?import javafx.scene.control.Separator?>
|
||||
<?import javafx.scene.control.Spinner?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.control.TitledPane?>
|
||||
<?import javafx.scene.control.Tooltip?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
|
||||
<Pane fx:id="primary" maxHeight="900.0" maxWidth="1500.0" minHeight="620.0" minWidth="963.0" prefHeight="708.0" prefWidth="963.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="xyz.brysonsteck.serverfordummies.PrimaryController">
|
||||
<children>
|
||||
<HBox prefHeight="39.0" prefWidth="963.0">
|
||||
<children>
|
||||
<Button id="openFile" fx:id="chooseDirectoryButton" lineSpacing="10.0" mnemonicParsing="false" onMouseClicked="#onDirectoryButtonClick" text="Choose Directory...">
|
||||
<opaqueInsets>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</opaqueInsets>
|
||||
<HBox.margin>
|
||||
<Insets />
|
||||
</HBox.margin>
|
||||
</Button>
|
||||
<Separator orientation="VERTICAL" prefHeight="200.0">
|
||||
<HBox.margin>
|
||||
<Insets left="5.0" />
|
||||
</HBox.margin>
|
||||
</Separator>
|
||||
<Label text="Server Directory:">
|
||||
<HBox.margin>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</HBox.margin>
|
||||
<font>
|
||||
<Font name="System Bold" size="13.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Label id="currentFilename" fx:id="currentDirectoryLabel" text="<NONE>">
|
||||
<HBox.margin>
|
||||
<Insets bottom="5.0" right="5.0" top="5.0" />
|
||||
</HBox.margin>
|
||||
</Label>
|
||||
</children>
|
||||
<padding>
|
||||
<Insets bottom="7.0" left="7.0" right="7.0" top="7.0" />
|
||||
</padding>
|
||||
</HBox>
|
||||
<HBox fx:id="worldSettingsPane" disable="true" layoutY="39.0" prefHeight="41.0" prefWidth="963.0">
|
||||
<padding>
|
||||
<Insets bottom="7.0" left="7.0" right="7.0" top="7.0" />
|
||||
</padding>
|
||||
<children>
|
||||
<Label text="World Name:">
|
||||
<font>
|
||||
<Font name="System Bold" size="13.0" />
|
||||
</font>
|
||||
<HBox.margin>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="6.0" />
|
||||
</HBox.margin>
|
||||
</Label>
|
||||
<TextField fx:id="worldNameField" onInputMethodTextChanged="#onWorldNameChange">
|
||||
<HBox.margin>
|
||||
<Insets top="2.0" />
|
||||
</HBox.margin>
|
||||
</TextField>
|
||||
<Separator orientation="VERTICAL" prefHeight="200.0">
|
||||
<HBox.margin>
|
||||
<Insets left="5.0" />
|
||||
</HBox.margin>
|
||||
</Separator>
|
||||
<Label text="Seed:">
|
||||
<font>
|
||||
<Font name="System Bold" size="13.0" />
|
||||
</font>
|
||||
<HBox.margin>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="6.0" />
|
||||
</HBox.margin>
|
||||
</Label>
|
||||
<TextField fx:id="seedField" onInputMethodTextChanged="#onSeedChange" prefHeight="23.0" prefWidth="448.0">
|
||||
<HBox.margin>
|
||||
<Insets top="2.0" />
|
||||
</HBox.margin>
|
||||
</TextField>
|
||||
<Separator orientation="VERTICAL" prefHeight="200.0">
|
||||
<HBox.margin>
|
||||
<Insets left="5.0" />
|
||||
</HBox.margin>
|
||||
</Separator>
|
||||
<Label text="Server Port:">
|
||||
<font>
|
||||
<Font name="System Bold" size="13.0" />
|
||||
</font>
|
||||
<HBox.margin>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="6.0" />
|
||||
</HBox.margin>
|
||||
</Label>
|
||||
<Spinner fx:id="portSpinner" onInputMethodTextChanged="#onPortChange" prefHeight="23.0" prefWidth="112.0">
|
||||
<HBox.margin>
|
||||
<Insets top="2.0" />
|
||||
</HBox.margin>
|
||||
</Spinner>
|
||||
</children>
|
||||
<opaqueInsets>
|
||||
<Insets bottom="3.0" />
|
||||
</opaqueInsets>
|
||||
</HBox>
|
||||
<Pane fx:id="parentPane" disable="true" layoutY="78.0" prefHeight="555.0" prefWidth="970.0">
|
||||
<children>
|
||||
<TitledPane fx:id="settingsPane" animated="false" collapsible="false" layoutX="10.0" layoutY="7.0" prefHeight="273.0" prefWidth="627.0" text="Server Settings">
|
||||
<content>
|
||||
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="311.0" prefWidth="625.0">
|
||||
<children>
|
||||
<CheckBox fx:id="flightCheckbox" layoutX="14.0" layoutY="14.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" text="Allow Flight" />
|
||||
<CheckBox fx:id="netherCheckbox" layoutX="14.0" layoutY="42.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" text="Allow The Nether" />
|
||||
<CheckBox fx:id="structuresCheckbox" alignment="TOP_LEFT" layoutX="14.0" layoutY="70.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" prefHeight="45.0" prefWidth="231.0" text="Generate Structures (such as villages and strongholds)" />
|
||||
<CheckBox fx:id="pvpCheckbox" layoutX="14.0" layoutY="109.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" text="Allow PvP" />
|
||||
<CheckBox fx:id="whitelistCheckbox" alignment="TOP_LEFT" layoutX="14.0" layoutY="138.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" prefHeight="45.0" prefWidth="231.0" text="Enable Whitelist (Only users you specify can join)" />
|
||||
<Spinner fx:id="maxPlayerSpinner" layoutX="130.0" layoutY="179.0" prefHeight="23.0" prefWidth="99.0" />
|
||||
<Label layoutX="14.0" layoutY="183.0" text="Maximum Players:" />
|
||||
<Spinner fx:id="maxSizeSpinner" layoutX="214.0" layoutY="212.0" prefHeight="23.0" prefWidth="155.0" />
|
||||
<Label layoutX="14.0" layoutY="216.0" text="Maximum World Size (in blocks):" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</content>
|
||||
</TitledPane>
|
||||
<TitledPane fx:id="advancedPane" animated="false" layoutX="10.0" layoutY="289.0" prefHeight="259.0" prefWidth="627.0" text="Advanced Server Settings">
|
||||
<content>
|
||||
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="269.0" prefWidth="625.0">
|
||||
<children>
|
||||
<CheckBox fx:id="cmdBlocksCheckbox" layoutX="14.0" layoutY="14.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" text="Enable Command Blocks" />
|
||||
<CheckBox fx:id="playerCountCheckbox" layoutX="14.0" layoutY="41.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" text="Hide Online Player Count" />
|
||||
<Spinner fx:id="memorySpinner" layoutX="154.0" layoutY="69.0" prefHeight="23.0" prefWidth="99.0" />
|
||||
<Label ellipsisString="" layoutX="14.0" layoutY="73.0" text="Server Memory in MB:" textOverrun="CLIP">
|
||||
<tooltip>
|
||||
<Tooltip text="This is the amount of RAM that will get passed to Minecraft/the JVM. For simple servers, 1024 MB will be plenty. If you typically have more than 5 concurrent players, consider allocating more." />
|
||||
</tooltip>
|
||||
</Label>
|
||||
<Spinner fx:id="spawnSpinner" layoutX="172.0" layoutY="100.0" prefHeight="23.0" prefWidth="99.0" />
|
||||
<Label layoutX="14.0" layoutY="104.0" text="Spawn Protection Radius:">
|
||||
<tooltip>
|
||||
<Tooltip text="All blocks in a radius from 0,~,0 will be unbreakable. If you want to break blocks within spawn, change this value." />
|
||||
</tooltip>
|
||||
</Label>
|
||||
<Spinner fx:id="simulationSpinner" layoutX="147.0" layoutY="132.0" prefHeight="23.0" prefWidth="99.0" />
|
||||
<Label layoutX="14.0" layoutY="136.0" text="Simulation Distance:">
|
||||
<tooltip>
|
||||
<Tooltip text="The radius of chunks for each player where ticks will be updated. In other words, anything outside these circles, such as furnaces, mobs, etc, will not be updated or simulated." />
|
||||
</tooltip>
|
||||
</Label>
|
||||
<Spinner fx:id="renderSpinner" layoutX="124.0" layoutY="165.0" prefHeight="23.0" prefWidth="99.0" />
|
||||
<Label layoutX="14.0" layoutY="169.0" text="Render Distance:">
|
||||
<tooltip>
|
||||
<Tooltip text="The radius of chunks where the server will render the view distance. Any value higher on a client than what is set will be ignored. Higher values will be more demanding on the server." />
|
||||
</tooltip>
|
||||
</Label>
|
||||
<Label layoutX="14.0" layoutY="203.0" text="Maximum Tick Time (in milliseconds):">
|
||||
<tooltip>
|
||||
<Tooltip text="If the server cannot update ticks (i.e. "lags") for longer than this amount of time, the server will shutdown. 60000 ms (60 seconds) is the default." />
|
||||
</tooltip>
|
||||
</Label>
|
||||
<Spinner fx:id="maxTickSpinner" layoutX="246.0" layoutY="199.0" prefHeight="23.0" prefWidth="99.0" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</content>
|
||||
</TitledPane>
|
||||
<TitledPane fx:id="difficultyPane" animated="false" collapsible="false" layoutX="649.0" layoutY="7.0" prefHeight="77.0" prefWidth="305.0" text="Difficulty">
|
||||
<content>
|
||||
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="94.0" prefWidth="303.0">
|
||||
<children>
|
||||
<ChoiceBox fx:id="difficultyBox" layoutX="14.0" layoutY="14.0" prefHeight="23.0" prefWidth="276.0">
|
||||
<contextMenu>
|
||||
<ContextMenu>
|
||||
<items>
|
||||
<MenuItem mnemonicParsing="false" />
|
||||
</items>
|
||||
</ContextMenu>
|
||||
</contextMenu>
|
||||
</ChoiceBox>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</content>
|
||||
</TitledPane>
|
||||
<TitledPane fx:id="gamemodePane" animated="false" collapsible="false" layoutX="649.0" layoutY="92.0" prefHeight="77.0" prefWidth="305.0" text="Gamemode">
|
||||
<content>
|
||||
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="94.0" prefWidth="303.0">
|
||||
<children>
|
||||
<ChoiceBox fx:id="gamemodeBox" layoutX="14.0" layoutY="14.0" prefHeight="23.0" prefWidth="276.0" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</content>
|
||||
</TitledPane>
|
||||
<TitledPane fx:id="worldTypePane" animated="false" collapsible="false" layoutX="649.0" layoutY="178.0" prefHeight="77.0" prefWidth="305.0" text="World Type">
|
||||
<content>
|
||||
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="94.0" prefWidth="303.0">
|
||||
<children>
|
||||
<ChoiceBox fx:id="worldTypeBox" layoutX="14.0" layoutY="14.0" prefHeight="23.0" prefWidth="276.0" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</content>
|
||||
</TitledPane>
|
||||
<TitledPane fx:id="worldTypePane1" animated="false" collapsible="false" layoutX="649.0" layoutY="265.0" prefHeight="283.0" prefWidth="305.0" text="Whitelist">
|
||||
<content>
|
||||
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="94.0" prefWidth="303.0">
|
||||
<children>
|
||||
<ChoiceBox layoutX="14.0" layoutY="14.0" prefHeight="23.0" prefWidth="276.0" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</content>
|
||||
</TitledPane>
|
||||
</children>
|
||||
</Pane>
|
||||
<ButtonBar fx:id="buttonBar" disable="true" layoutY="635.0" prefHeight="40.0" prefWidth="963.0">
|
||||
<buttons>
|
||||
<Button mnemonicParsing="false" text="Build Server" />
|
||||
<Button defaultButton="true" mnemonicParsing="false" text="Start Server" />
|
||||
</buttons>
|
||||
<padding>
|
||||
<Insets bottom="8.0" left="8.0" right="8.0" top="8.0" />
|
||||
</padding>
|
||||
</ButtonBar>
|
||||
<HBox layoutY="680.0" prefHeight="33.0" prefWidth="963.0" style="-fx-background-color: ddd;">
|
||||
<children>
|
||||
<Label text="Status:">
|
||||
<font>
|
||||
<Font name="System Bold" size="13.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Label fx:id="statusBar" text="Ready.">
|
||||
<HBox.margin>
|
||||
<Insets left="5.0" />
|
||||
</HBox.margin>
|
||||
</Label>
|
||||
<ProgressBar fx:id="progressBar" prefWidth="400.0" progress="0.0" visible="false">
|
||||
<HBox.margin>
|
||||
<Insets left="460.0" />
|
||||
</HBox.margin>
|
||||
</ProgressBar>
|
||||
</children>
|
||||
<padding>
|
||||
<Insets bottom="9.0" left="9.0" right="9.0" top="9.0" />
|
||||
</padding>
|
||||
</HBox>
|
||||
</children>
|
||||
</Pane>
|
|
@ -1,14 +0,0 @@
|
|||
/*
|
||||
* This Kotlin source file was generated by the Gradle 'init' task.
|
||||
*/
|
||||
package xyz.brysonsteck.serverfordummies
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class AppTest {
|
||||
// @Test fun appHasAGreeting() {
|
||||
// val classUnderTest = App()
|
||||
// assertNotNull(classUnderTest.greeting, "app should have a greeting")
|
||||
// }
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
plugins {
|
||||
// Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.6.0'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.8.0'
|
||||
|
||||
// Apply the application plugin to add support for building a CLI application in Java.
|
||||
id 'application'
|
||||
|
@ -32,12 +32,9 @@ dependencies {
|
|||
// This dependency is used by the application.
|
||||
implementation 'com.google.guava:guava:31.1-jre'
|
||||
|
||||
// Ktor
|
||||
implementation "io.ktor:ktor-server-core:2.3.0"
|
||||
implementation "io.ktor:ktor-server-netty:2.3.0"
|
||||
|
||||
// Coroutines core
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-javafx:1.7.0"
|
||||
}
|
||||
|
||||
application {
|
||||
|
|
|
@ -18,7 +18,7 @@ class App : Application() {
|
|||
stage.show()
|
||||
}
|
||||
|
||||
private fun loadFXML(fxml: String) : Parent {
|
||||
public fun loadFXML(fxml: String) : Parent {
|
||||
val fxmlLoader = FXMLLoader(this.javaClass.getResource(fxml + ".fxml"))
|
||||
return fxmlLoader.load()
|
||||
}
|
||||
|
|
119
app/src/main/kotlin/xyz/brysonsteck/serverfordummies/Download.kt
Normal file
119
app/src/main/kotlin/xyz/brysonsteck/serverfordummies/Download.kt
Normal file
|
@ -0,0 +1,119 @@
|
|||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
class Download: Runnable {
|
||||
public enum class Status {
|
||||
DOWNLOADING, PAUSED, COMPLETE, CANCELLED, ERROR
|
||||
}
|
||||
public var size: Int
|
||||
public var downloaded: Int
|
||||
public var contentLength: Int
|
||||
|
||||
private final val MAX_BUFFER_SIZE: Number = 1024
|
||||
|
||||
private var url: URL
|
||||
private var status: Status
|
||||
|
||||
constructor (url: URL) {
|
||||
this.url = url
|
||||
size = -1
|
||||
downloaded = 0
|
||||
status = Status.DOWNLOADING
|
||||
contentLength = 1
|
||||
}
|
||||
|
||||
public fun status(): Status {
|
||||
return status
|
||||
}
|
||||
|
||||
public fun start() {
|
||||
val thread = Thread(this)
|
||||
thread.start()
|
||||
}
|
||||
|
||||
private fun getFilename(url: URL): String {
|
||||
val filename = url.getFile()
|
||||
return filename.substring(filename.lastIndexOf('/') + 1)
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
var stream: InputStream? = null
|
||||
var file: RandomAccessFile? = null
|
||||
|
||||
try {
|
||||
// Open connection to URL.
|
||||
var connection = url.openConnection() as HttpURLConnection;
|
||||
|
||||
// Specify what portion of file to download.
|
||||
connection.setRequestProperty("Range", "bytes=" + downloaded + "-");
|
||||
|
||||
// Connect to server.
|
||||
connection.connect();
|
||||
|
||||
// Make sure response code is in the 200 range.
|
||||
if (connection.responseCode / 100 != 2) {
|
||||
status = Status.ERROR
|
||||
}
|
||||
|
||||
// Check for valid content length.
|
||||
contentLength = connection.getContentLength();
|
||||
if (contentLength < 1) {
|
||||
status = Status.ERROR
|
||||
}
|
||||
|
||||
/* Set the size for this download if it
|
||||
hasn't been already set. */
|
||||
if (size == -1) {
|
||||
size = contentLength;
|
||||
}
|
||||
|
||||
// Open file and seek to the end of it.
|
||||
file = RandomAccessFile(getFilename(url), "rw");
|
||||
file.seek(downloaded.toLong());
|
||||
|
||||
stream = connection.getInputStream();
|
||||
while (status == Status.DOWNLOADING) {
|
||||
/* Size buffer according to how much of the
|
||||
file is left to download. */
|
||||
val buffer: ByteArray;
|
||||
if (size - downloaded > MAX_BUFFER_SIZE as Int) {
|
||||
buffer = ByteArray(MAX_BUFFER_SIZE)
|
||||
} else {
|
||||
buffer = ByteArray(size - downloaded);
|
||||
}
|
||||
|
||||
// Read from server into buffer.
|
||||
val read = stream.read(buffer);
|
||||
if (read == -1)
|
||||
break;
|
||||
|
||||
// Write buffer to file.
|
||||
file.write(buffer, 0, read);
|
||||
downloaded += read;
|
||||
}
|
||||
|
||||
/* Change status to complete if this point was
|
||||
reached because downloading has finished. */
|
||||
if (status == Status.DOWNLOADING) {
|
||||
status = Status.COMPLETE;
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
status = Status.ERROR
|
||||
} finally {
|
||||
// Close file.
|
||||
if (file != null) {
|
||||
try {
|
||||
file.close();
|
||||
} catch (e: Exception) {}
|
||||
}
|
||||
|
||||
// Close connection to server.
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (e: Exception) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,17 @@
|
|||
package xyz.brysonsteck.serverfordummies
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.javafx.JavaFx
|
||||
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.awt.Checkbox
|
||||
import java.util.Properties
|
||||
import java.net.URL
|
||||
|
||||
import javafx.beans.value.ChangeListener
|
||||
import javafx.beans.value.ObservableValue
|
||||
import javafx.concurrent.Task
|
||||
import javafx.beans.property.BooleanProperty
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.fxml.FXML
|
||||
|
@ -19,18 +24,24 @@ import javafx.scene.control.Spinner
|
|||
import javafx.scene.control.TitledPane
|
||||
import javafx.scene.control.ButtonBar
|
||||
import javafx.scene.control.CheckBox
|
||||
import javafx.scene.control.ProgressBar
|
||||
import javafx.scene.layout.Border
|
||||
import javafx.scene.layout.BorderStroke
|
||||
import javafx.scene.layout.GridPane
|
||||
import javafx.scene.layout.Pane
|
||||
import javafx.scene.layout.HBox
|
||||
import javafx.scene.layout.VBox
|
||||
import javafx.scene.text.TextAlignment
|
||||
import javafx.scene.text.Text
|
||||
import javafx.scene.Scene
|
||||
import javafx.scene.input.MouseEvent
|
||||
import javafx.stage.FileChooser
|
||||
import javafx.stage.FileChooser.ExtensionFilter
|
||||
import javafx.stage.DirectoryChooser
|
||||
import javafx.stage.Modality
|
||||
import javafx.stage.Stage
|
||||
import javafx.event.EventHandler
|
||||
import Download
|
||||
|
||||
class PrimaryController {
|
||||
@FXML
|
||||
|
@ -82,6 +93,8 @@ class PrimaryController {
|
|||
@FXML
|
||||
lateinit private var maxTickSpinner: Spinner<kotlin.Int>
|
||||
@FXML
|
||||
lateinit private var statusBar: Label
|
||||
@FXML
|
||||
lateinit private var progressBar: ProgressBar
|
||||
|
||||
@FXML
|
||||
|
@ -92,6 +105,7 @@ class PrimaryController {
|
|||
val result = dirChooser.showDialog(null)
|
||||
if (result != null) {
|
||||
currentDirectoryLabel.text = result.absolutePath
|
||||
val real = loadServerDir(result.absolutePath)
|
||||
parentPane.isDisable = false
|
||||
worldSettingsPane.isDisable = false
|
||||
buttonBar.isDisable = false
|
||||
|
@ -115,10 +129,91 @@ class PrimaryController {
|
|||
|
||||
@FXML
|
||||
private fun onCheckboxClick() {
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
private fun onSpinnerChange() {
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
private fun onBuild() {
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
withContext(Dispatchers.JavaFx){statusBar.text = "Downloading a file..."}
|
||||
progressBar.isVisible = true
|
||||
val download = Download(URL("https://brysonsteck.xyz/pub/a-really-big-file"))
|
||||
download.start()
|
||||
while (download.status() == Download.Status.DOWNLOADING) {
|
||||
var prog = (download.downloaded.toDouble() / download.contentLength.toDouble())
|
||||
// for whatever reason I need to print something to the screen in order for it to work
|
||||
print("")
|
||||
if (prog >= 0.01) {
|
||||
withContext(Dispatchers.JavaFx) {progressBar.progress = prog}
|
||||
}
|
||||
Thread.sleep(300)
|
||||
}
|
||||
progressBar.isVisible = false
|
||||
withContext(Dispatchers.JavaFx){statusBar.text = "Ready."}
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private fun onStart() {
|
||||
|
||||
}
|
||||
|
||||
private fun createDialog() {
|
||||
val dialog = Stage();
|
||||
dialog.initModality(Modality.APPLICATION_MODAL);
|
||||
val dialogScene = Scene(App().loadFXML("dialog"), 300.0, 200.0);
|
||||
dialog.setScene(dialogScene);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private fun loadServerDir(dir: String): Boolean {
|
||||
createDialog()
|
||||
var directory = dir
|
||||
var hasServer = false
|
||||
if (!File(directory).isDirectory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (directory[directory.length-1] != File.separatorChar)
|
||||
directory += File.separatorChar
|
||||
|
||||
val hasDummy = File(directory + "ServerForDummies").isDirectory
|
||||
|
||||
for (i in 20 downTo 8) {
|
||||
for (j in 15 downTo 0) {
|
||||
var spigotFile: String = ""
|
||||
if (j == 0)
|
||||
spigotFile += "spigot-1.$i.jar"
|
||||
else
|
||||
spigotFile += "spigot-1.$i.$j.jar";
|
||||
|
||||
hasServer = File(directory + spigotFile).isFile || File(directory + "server.jar").isFile
|
||||
if (hasServer)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
val hasProperties = File(directory + File.separator + "server.properties").isFile
|
||||
|
||||
if (hasDummy && hasServer) {
|
||||
// read jproperties
|
||||
println("read jproperties")
|
||||
} else if (hasDummy && !hasServer && hasProperties) {
|
||||
// just needs to be built
|
||||
println("build")
|
||||
} else if (!hasDummy && hasServer) {
|
||||
// server created externally
|
||||
println("server made externally")
|
||||
} else {
|
||||
// assume clean directory
|
||||
println("none")
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
import java.net.http.HttpClient
|
||||
import kotlinx.coroutines.flow
|
||||
|
||||
sealed class DownloadStatus {
|
||||
|
||||
object Success : DownloadStatus()
|
||||
|
||||
data class Error(val message: String) : DownloadStatus()
|
||||
|
||||
data class Progress(val progress: Int): DownloadStatus()
|
||||
|
||||
}
|
||||
|
||||
// function from https://gist.githubusercontent.com/SG-K/63e379efcc3d1cd3ce4fb56ee0e29c42/raw/cd9a4a016401b7c54ec01303415b5871ffa26066/downloadFile.kt
|
||||
suspend fun HttpClient.downloadFile(file: File, url: String): Flow<DownloadStatus> {
|
||||
return flow {
|
||||
val response = call {
|
||||
url(url)
|
||||
method = HttpMethod.Get
|
||||
}.response
|
||||
val byteArray = ByteArray(response.contentLength()!!.toInt())
|
||||
var offset = 0
|
||||
do {
|
||||
val currentRead = response.content.readAvailable(byteArray, offset, byteArray.size)
|
||||
offset += currentRead
|
||||
val progress = (offset * 100f / byteArray.size).roundToInt()
|
||||
emit(DownloadStatus.Progress(progress))
|
||||
} while (currentRead > 0)
|
||||
response.close()
|
||||
if (response.status.isSuccess()) {
|
||||
file.writeBytes(byteArray)
|
||||
emit(DownloadStatus.Success)
|
||||
} else {
|
||||
emit(DownloadStatus.Error("File not downloaded"))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package xyz.brysonsteck.serverfordummies.server
|
||||
|
||||
import java.io.File
|
||||
|
||||
public class Server {
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
|
||||
|
||||
<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="200.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<children>
|
||||
<Pane fx:id="iconPane" layoutX="14.0" layoutY="14.0" prefHeight="82.0" prefWidth="82.0" />
|
||||
<Label fx:id="dialogLabel" layoutX="115.0" layoutY="40.0" text="Dialog Box" />
|
||||
<ButtonBar layoutY="157.0" prefHeight="43.0" prefWidth="400.0">
|
||||
<buttons>
|
||||
<Button fx:id="otherButton" mnemonicParsing="false" text="Decline" />
|
||||
<Button fx:id="defaultButton" defaultButton="true" mnemonicParsing="false" text="Accept" />
|
||||
</buttons>
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</padding>
|
||||
</ButtonBar>
|
||||
</children>
|
||||
</Pane>
|
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 5 KiB |
|
@ -220,7 +220,7 @@
|
|||
</Pane>
|
||||
<ButtonBar fx:id="buttonBar" disable="true" layoutY="635.0" prefHeight="40.0" prefWidth="963.0">
|
||||
<buttons>
|
||||
<Button mnemonicParsing="false" text="Build Server" />
|
||||
<Button mnemonicParsing="false" onMouseClicked="#onBuild" text="Build Server" />
|
||||
<Button defaultButton="true" mnemonicParsing="false" text="Start Server" />
|
||||
</buttons>
|
||||
<padding>
|
||||
|
@ -239,9 +239,9 @@
|
|||
<Insets left="5.0" />
|
||||
</HBox.margin>
|
||||
</Label>
|
||||
<ProgressBar fx:id="progressBar" prefWidth="400.0" progress="0.0" visible="false">
|
||||
<ProgressBar fx:id="progressBar" prefWidth="400.0" visible="false">
|
||||
<HBox.margin>
|
||||
<Insets left="460.0" />
|
||||
<Insets left="10.0" />
|
||||
</HBox.margin>
|
||||
</ProgressBar>
|
||||
</children>
|
||||
|
|
Loading…
Add table
Reference in a new issue