diff --git a/build.gradle.kts b/build.gradle.kts index 1f13fcb..4d05eb0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,6 +17,9 @@ dependencies { implementation("com.google.code.gson:gson:2.8.9") implementation("org.jetbrains:annotations:23.0.0") implementation(kotlin("stdlib-jdk8")) + + implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.3")) + implementation("com.squareup.okhttp3:okhttp") } java { diff --git a/src/main/java/io/github/openstarruler/launchpad/adapter/CopyFileVisitor.kt b/src/main/java/io/github/openstarruler/launchpad/adapter/CopyFileVisitor.kt index 6778649..85f8d62 100644 --- a/src/main/java/io/github/openstarruler/launchpad/adapter/CopyFileVisitor.kt +++ b/src/main/java/io/github/openstarruler/launchpad/adapter/CopyFileVisitor.kt @@ -7,7 +7,7 @@ import java.nio.file.Path import java.nio.file.SimpleFileVisitor import java.nio.file.attribute.BasicFileAttributes -class CopyFileVisitor(private val targetPath: Path) : SimpleFileVisitor() { +class CopyFileVisitor(private val targetPath: Path, private val fileHandler: FileProgressHandler? = null) : SimpleFileVisitor() { private var sourcePath: Path? = null @Throws(IOException::class) @@ -22,6 +22,7 @@ class CopyFileVisitor(private val targetPath: Path) : SimpleFileVisitor() @Throws(IOException::class) override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult { + fileHandler?.handle() Files.copy(file, targetPath.resolve(sourcePath!!.relativize(file))) return FileVisitResult.CONTINUE } diff --git a/src/main/java/io/github/openstarruler/launchpad/adapter/FileProgressHandler.kt b/src/main/java/io/github/openstarruler/launchpad/adapter/FileProgressHandler.kt new file mode 100644 index 0000000..9fc9315 --- /dev/null +++ b/src/main/java/io/github/openstarruler/launchpad/adapter/FileProgressHandler.kt @@ -0,0 +1,10 @@ +package io.github.openstarruler.launchpad.adapter + +class FileProgressHandler(private val count: Int, private val handler: TextHandler?) { + var current = 0 + + fun handle() { + current++ + handler?.handle("Copying file $current/$count") + } +} \ No newline at end of file diff --git a/src/main/java/io/github/openstarruler/launchpad/adapter/FormattingTextHandler.kt b/src/main/java/io/github/openstarruler/launchpad/adapter/FormattingTextHandler.kt new file mode 100644 index 0000000..6bee995 --- /dev/null +++ b/src/main/java/io/github/openstarruler/launchpad/adapter/FormattingTextHandler.kt @@ -0,0 +1,7 @@ +package io.github.openstarruler.launchpad.adapter + +class FormattingTextHandler(private val pattern: String, private val handler: TextHandler?): TextHandler { + override fun handle(text: String) { + handler?.handle(String.format(pattern, text)) + } +} diff --git a/src/main/java/io/github/openstarruler/launchpad/adapter/GitHub.kt b/src/main/java/io/github/openstarruler/launchpad/adapter/GitHub.kt new file mode 100644 index 0000000..a983e37 --- /dev/null +++ b/src/main/java/io/github/openstarruler/launchpad/adapter/GitHub.kt @@ -0,0 +1,26 @@ +package io.github.openstarruler.launchpad.adapter + +import com.google.gson.Gson +import io.github.openstarruler.launchpad.model.Release +import okhttp3.OkHttpClient +import okhttp3.Request + +object GitHub { + val GH_API = "https://api.github.com" + + fun getReleasesUrl(owner: String = "OpenSRProject", repo: String): String { + return "$GH_API/repos/$owner/$repo/releases" + } + + fun getReleases(owner: String = "OpenSRProject", repo: String): List { + val okHttpClient = OkHttpClient.Builder() + .build() + val request = Request.Builder() + .url(getReleasesUrl(owner, repo)) + .build() + okHttpClient.newCall(request).execute().body?.charStream().let { + return try { Gson().fromJson(it!!) } + catch(e: Exception) { listOf() } + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/openstarruler/launchpad/adapter/GitProgressHandler.kt b/src/main/java/io/github/openstarruler/launchpad/adapter/GitProgressHandler.kt new file mode 100644 index 0000000..b2bd933 --- /dev/null +++ b/src/main/java/io/github/openstarruler/launchpad/adapter/GitProgressHandler.kt @@ -0,0 +1,27 @@ +package io.github.openstarruler.launchpad.adapter + +import org.eclipse.jgit.lib.ProgressMonitor + +class GitProgressHandler(val task: String, val handler: TextHandler?): ProgressMonitor { + var taskTitle = "" + var tasksDone = 0 + var taskCount = 0 + + override fun start(totalTasks: Int) = Unit + + override fun beginTask(title: String?, totalWork: Int) { + taskTitle = title ?: "" + tasksDone = 0 + taskCount = totalWork + handler?.handle("$task $taskTitle $tasksDone/$taskCount") + } + + override fun update(completed: Int) { + tasksDone++ + handler?.handle("$task $taskTitle $tasksDone/$taskCount") + } + + override fun endTask() = Unit + + override fun isCancelled(): Boolean = false +} \ No newline at end of file diff --git a/src/main/java/io/github/openstarruler/launchpad/adapter/Gson.kt b/src/main/java/io/github/openstarruler/launchpad/adapter/Gson.kt new file mode 100644 index 0000000..ee709ea --- /dev/null +++ b/src/main/java/io/github/openstarruler/launchpad/adapter/Gson.kt @@ -0,0 +1,13 @@ +package io.github.openstarruler.launchpad.adapter + +import com.google.gson.Gson +import com.google.gson.JsonIOException +import com.google.gson.JsonSyntaxException +import com.google.gson.reflect.TypeToken +import java.io.Reader + +@Throws(JsonIOException::class, JsonSyntaxException::class) +inline fun Gson.fromJson(json: Reader): T = fromJson(json, object: TypeToken() {}.type) + +@Throws(JsonIOException::class, JsonSyntaxException::class) +inline fun Gson.fromJson(json: String): T = fromJson(json, object: TypeToken() {}.type) \ No newline at end of file diff --git a/src/main/java/io/github/openstarruler/launchpad/adapter/ModInstaller.kt b/src/main/java/io/github/openstarruler/launchpad/adapter/ModInstaller.kt index 9f32a99..bfceb67 100644 --- a/src/main/java/io/github/openstarruler/launchpad/adapter/ModInstaller.kt +++ b/src/main/java/io/github/openstarruler/launchpad/adapter/ModInstaller.kt @@ -3,7 +3,6 @@ package io.github.openstarruler.launchpad.adapter import com.google.gson.Gson import com.google.gson.JsonSyntaxException import com.google.gson.reflect.TypeToken -import io.github.openstarruler.launchpad.adapter.ModInstaller.TextHandler import io.github.openstarruler.launchpad.model.Modinfo import io.github.openstarruler.launchpad.model.RepoMetadata import org.eclipse.jgit.api.CreateBranchCommand @@ -90,8 +89,7 @@ object ModInstaller { } url = parseRepoURL(url) val localRoot = getLocalRoot(url) - progressHandler?.handle("Loading repository...") - val tmp = cloneRepository(localRoot, url) + val tmp = cloneRepository(localRoot, url, GitProgressHandler("Loading repository...", progressHandler)) if (repo != null) repo!!.close() currentBranch = null @@ -111,11 +109,13 @@ object ModInstaller { } @Throws(GitAPIException::class) - private fun cloneRepository(localRoot: Path, url: String): Git { + private fun cloneRepository(localRoot: Path, url: String, progressHandler: GitProgressHandler? = null): Git { return try { Git.open(localRoot.toFile()) } catch (e: IOException) { - val cmd = Git.cloneRepository().setDirectory(localRoot.toFile()) + val cmd = Git.cloneRepository() + .setDirectory(localRoot.toFile()) + .setProgressMonitor(progressHandler) try { cmd.setURI(url).call() } catch (e2: Exception) { @@ -248,7 +248,11 @@ object ModInstaller { progressHandler.handle("Copying mod files from repository...") destination.createDirectories() - Files.walkFileTree(source.absolute(), CopyFileVisitor(destination.toAbsolutePath())) + var count = Utils.countFiles(source) + Files.walkFileTree( + source.absolute(), + CopyFileVisitor(destination.toAbsolutePath(), FileProgressHandler(count, FormattingTextHandler("Copying mod files from repository... %s", progressHandler))) + ) infoHandler?.handle("Mod successfully installed!") } @@ -334,15 +338,16 @@ object ModInstaller { } val url = parseRepoURL(dependency.repository!!) val localRoot = getLocalRoot(url) - progressHandler.handle("Loading dependency \"" + dependency.name + "\"...") - val depRepo = cloneRepository(localRoot, url) + val loadingHandler = GitProgressHandler("Loading dependency \"${dependency.name}\"...", progressHandler) + val depRepo = cloneRepository(localRoot, url, loadingHandler) depRepo.checkout() .setName(dependency.branch) + .setProgressMonitor(loadingHandler) .setCreateBranch(depRepo.repository.resolve("refs/heads/" + dependency.branch) == null) .setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK) .setStartPoint("refs/remotes/origin/" + dependency.branch) .call() - depRepo.pull().call() + depRepo.pull().setProgressMonitor(loadingHandler).call() val internalWarningHandler = TextHandler { text -> warningHandler.handle( String.format( @@ -396,23 +401,23 @@ object ModInstaller { modName: String? ) { try { - progressHandler.handle("Checking out target branch or tag...") val isTag = currentBranch!!.name.startsWith("refs/tags/") val createBranch = !isTag && repo!!.repository.resolve( currentBranch!!.name.replaceFirst( - "refs/remotes/origin".toRegex(), + "refs/remotes/origin", "refs/heads" ) ) == null repo!!.checkout() - .setName(currentBranch!!.name.replaceFirst("refs/remotes/origin/".toRegex(), "")) + .setProgressMonitor(GitProgressHandler("Checking out target branch or tag...", progressHandler)) + .setName(currentBranch!!.name.replaceFirst("refs/remotes/origin/", "")) .setCreateBranch(createBranch) .setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK) .setStartPoint(currentBranch!!.name) .call() if (!isTag) { - progressHandler.handle("Pulling upstream changes...") - repo!!.pull().call() + repo!!.pull() + .setProgressMonitor(GitProgressHandler("Pulling upstream changes...", progressHandler)).call() } installModImpl(repo!!, warningHandler, progressHandler, infoHandler, errorHandler!!, modName) } catch (e: RefNotAdvertisedException) { @@ -470,7 +475,4 @@ object ModInstaller { return currentMetadata?.mods!! } - fun interface TextHandler { - fun handle(text: String) - } } diff --git a/src/main/java/io/github/openstarruler/launchpad/adapter/OpenSRManager.kt b/src/main/java/io/github/openstarruler/launchpad/adapter/OpenSRManager.kt new file mode 100644 index 0000000..6a6c12f --- /dev/null +++ b/src/main/java/io/github/openstarruler/launchpad/adapter/OpenSRManager.kt @@ -0,0 +1,124 @@ +package io.github.openstarruler.launchpad.adapter + +import io.github.openstarruler.launchpad.model.Release +import okhttp3.OkHttpClient +import okhttp3.Request +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.api.ResetCommand.ResetType +import org.eclipse.jgit.transport.URIish +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.util.concurrent.locks.ReentrantLock +import java.util.zip.ZipFile +import kotlin.concurrent.withLock + +object OpenSRManager { + val openSRVersions: List = GitHub.getReleases(repo = "OpenStarRuler") + + fun installOpenSR( + version: Release, + warningHandler: TextHandler?, + errorHandler: TextHandler?, + progressHandler: TextHandler? + ) { + progressHandler?.handle("Detecting OS version...") + val type = if (Utils.IS_WINDOWS) "windows" else "linux" + progressHandler?.handle("Searching for binaries...") + val zipAsset = version.assets.find { it.name.contains(type) } + if(zipAsset == null) { + errorHandler?.handle("Failed to find platform-appropriate binary package for selected version!\n\nPlease try installing a different version of OpenSR, and report this issue to the OpenSR team.") + return + } + + progressHandler?.handle("Downloading binary package...") + val okHttpClient = OkHttpClient.Builder() + .build() + val request = Request.Builder() + .url(zipAsset.downloadUrl) + .build() + okHttpClient.newCall(request).execute().body?.byteStream().use { zipBuffer -> + if(zipBuffer == null) { + errorHandler?.handle("Failed to download platform-appropriate binary package for selected version!\n\nPlease try installing a different version of OpenSR, and report this issue to the OpenSR team.") + return + } + progressHandler?.handle("Saving binary package...") + FileOutputStream("opensr-binaries.zip", false).use { zipBuffer.copyTo(it) } + } + + progressHandler?.handle("Connecting to OpenSR data repository...") + val dataRepo = try { + Git.open(File(Settings.instance.gamePath)) + } catch (e: IOException) { + Git.init().setDirectory(File(Settings.instance.gamePath)).call() + } + dataRepo.use { + val remotes = dataRepo.remoteList().call() + val origin = remotes.find { it.name == "origin" } + if (origin == null) + dataRepo.remoteAdd() + .setName("origin") + .setUri(URIish("https://github.com/OpenSRProject/OpenStarRuler-Data.git")) + .call() + else if (origin.urIs.find { it.toString() == "https://github.com/OpenSRProject/OpenStarRuler-Data.git" } == null) + dataRepo.remoteSetUrl() + .setRemoteName("origin") + .setRemoteUri(URIish("https://github.com/OpenSRProject/OpenStarRuler-Data.git")) + .call() + + if (version.tagName == "nightly") { + dataRepo.fetch() + .setProgressMonitor(GitProgressHandler("Downloading latest data...", progressHandler)) + .call() + + dataRepo.reset() + .setProgressMonitor(GitProgressHandler("Installing latest data...", progressHandler)) + .setMode(ResetType.HARD) + .setRef("refs/remotes/origin/master") + .call() + } else try { + dataRepo.fetch() + .setProgressMonitor(GitProgressHandler("Downloading appropriate data...", progressHandler)) + .setRefSpecs("refs/tags/${version.tagName}:refs/tags/${version.tagName}") + .call() + + dataRepo.reset() + .setProgressMonitor(GitProgressHandler("Installing appropriate data...", progressHandler)) + .setMode(ResetType.HARD) + .setRef("refs/tags/${version.tagName}") + .call() + } catch (e: Exception) { + warningHandler?.handle("Failed to get data tag corresponding to this release! Falling back to master branch.\n\nPlease report this issue to the OpenSR team. It should still be safe to play this version of OpenSR, but there is a slight possibility that something will go wrong.") + dataRepo.fetch() + .setProgressMonitor(GitProgressHandler("Downloading latest data...", progressHandler)) + .call() + dataRepo.reset() + .setProgressMonitor(GitProgressHandler("Installing latest data...", progressHandler)) + .setMode(ResetType.HARD) + .setRef("refs/remotes/origin/master") + .call() + } + } + + progressHandler?.handle("Extracting binaries...") + ZipFile("opensr-binaries.zip").use { zip -> + val total = zip.size() + var extracted = 0 + val lock = ReentrantLock() + zip.stream().parallel().forEach { entry -> + if(!entry.isDirectory) { + File(Settings.instance.gamePath, entry.name).also { file -> + file.parentFile.mkdirs() + file.createNewFile() + file.outputStream().use { + zip.getInputStream(entry).copyTo(it) + } + lock.withLock { + progressHandler?.handle("Extracting binaries... ${++extracted}/$total files extracted") + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/openstarruler/launchpad/adapter/TextHandler.kt b/src/main/java/io/github/openstarruler/launchpad/adapter/TextHandler.kt new file mode 100644 index 0000000..92826a1 --- /dev/null +++ b/src/main/java/io/github/openstarruler/launchpad/adapter/TextHandler.kt @@ -0,0 +1,5 @@ +package io.github.openstarruler.launchpad.adapter + +fun interface TextHandler { + fun handle(text: String) +} \ No newline at end of file diff --git a/src/main/java/io/github/openstarruler/launchpad/adapter/Utils.kt b/src/main/java/io/github/openstarruler/launchpad/adapter/Utils.kt index af83103..53b95a4 100644 --- a/src/main/java/io/github/openstarruler/launchpad/adapter/Utils.kt +++ b/src/main/java/io/github/openstarruler/launchpad/adapter/Utils.kt @@ -8,7 +8,13 @@ import org.eclipse.jgit.lib.Repository import org.eclipse.jgit.treewalk.TreeWalk import org.eclipse.jgit.treewalk.filter.PathSuffixFilter import java.io.* +import java.nio.file.FileVisitResult +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.SimpleFileVisitor +import java.nio.file.attribute.BasicFileAttributes import java.util.stream.Collectors +import kotlin.io.path.absolute /** Utility class containing a number of helper functions. */ object Utils { @@ -67,6 +73,18 @@ object Utils { return result } + fun countFiles(source: Path): Int { + var count = 0 + Files.walkFileTree(source.absolute(), object: SimpleFileVisitor() { + @Throws(IOException::class) + override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult { + count++ + return FileVisitResult.CONTINUE + } + }) + return count + } + @Throws(FileNotFoundException::class) fun generateModinfoWalker(repo: Repository?, tree: ObjectId?, root: String?): TreeWalk { var result: TreeWalk? = null diff --git a/src/main/java/io/github/openstarruler/launchpad/model/Asset.kt b/src/main/java/io/github/openstarruler/launchpad/model/Asset.kt new file mode 100644 index 0000000..8d8a7d7 --- /dev/null +++ b/src/main/java/io/github/openstarruler/launchpad/model/Asset.kt @@ -0,0 +1,8 @@ +package io.github.openstarruler.launchpad.model + +import com.google.gson.annotations.SerializedName + +data class Asset( + @SerializedName("browser_download_url") val downloadUrl: String, + val name: String +) \ No newline at end of file diff --git a/src/main/java/io/github/openstarruler/launchpad/model/Release.kt b/src/main/java/io/github/openstarruler/launchpad/model/Release.kt new file mode 100644 index 0000000..e37c780 --- /dev/null +++ b/src/main/java/io/github/openstarruler/launchpad/model/Release.kt @@ -0,0 +1,10 @@ +package io.github.openstarruler.launchpad.model + +import com.google.gson.annotations.SerializedName + +data class Release( + @SerializedName("tag_name") val tagName: String, + val name: String, + val assets: List, + val body: String +) \ No newline at end of file diff --git a/src/main/java/io/github/openstarruler/launchpad/view/Main.kt b/src/main/java/io/github/openstarruler/launchpad/view/Main.kt index 15651b5..cd660f2 100644 --- a/src/main/java/io/github/openstarruler/launchpad/view/Main.kt +++ b/src/main/java/io/github/openstarruler/launchpad/view/Main.kt @@ -1,17 +1,26 @@ @file:JvmName("Main") package io.github.openstarruler.launchpad.view +import io.github.openstarruler.launchpad.adapter.Settings import javafx.application.Application import javafx.fxml.FXMLLoader import javafx.scene.Parent import javafx.scene.Scene -import javafx.scene.control.Alert +import javafx.scene.control.Alert.AlertType import javafx.stage.Stage import java.io.IOException import kotlin.system.exitProcess class OSRLaunchpadApplication : Application() { override fun start(primaryStage: Stage) { + try { + Settings.load() + } catch (e: IOException) { + val msg = ResizableAlert(AlertType.ERROR, "Could not load config.json! Cause: $e") + e.printStackTrace() + msg.showAndWait() + } + try { val root = FXMLLoader.load(javaClass.getResource("MainFrame.fxml")) val scene = Scene(root) @@ -19,7 +28,7 @@ class OSRLaunchpadApplication : Application() { primaryStage.scene = scene primaryStage.show() } catch (e: IOException) { - val msg = ResizableAlert(Alert.AlertType.ERROR, "Cannot load GUI, application will now terminate.") + val msg = ResizableAlert(AlertType.ERROR, "Cannot load GUI, application will now terminate.") e.printStackTrace() msg.showAndWait().ifPresent { exitProcess(-1) } } diff --git a/src/main/java/io/github/openstarruler/launchpad/view/MainController.kt b/src/main/java/io/github/openstarruler/launchpad/view/MainController.kt index 8358249..ea37c95 100644 --- a/src/main/java/io/github/openstarruler/launchpad/view/MainController.kt +++ b/src/main/java/io/github/openstarruler/launchpad/view/MainController.kt @@ -1,8 +1,8 @@ package io.github.openstarruler.launchpad.view import io.github.openstarruler.launchpad.adapter.ModInstaller -import io.github.openstarruler.launchpad.adapter.ModInstaller.TextHandler import io.github.openstarruler.launchpad.adapter.Settings +import io.github.openstarruler.launchpad.adapter.TextHandler import io.github.openstarruler.launchpad.adapter.Utils import javafx.application.Platform import javafx.concurrent.Task @@ -17,7 +17,6 @@ import javafx.stage.Modality import javafx.stage.Stage import javafx.stage.StageStyle import java.io.File -import java.io.IOException import kotlin.system.exitProcess class MainController { @@ -44,23 +43,23 @@ class MainController { } osrPane.playButton = playButton - var needsConfig = true - try { - needsConfig = !Settings.load() - } catch (e: IOException) { - val msg: Alert = ResizableAlert(AlertType.ERROR, "Could not load config.json! Cause: $e") - e.printStackTrace() + if ( + Settings.instance.isFirstRun + ) { + val msg = ResizableAlert( + AlertType.INFORMATION, + "Welcome to the OpenSR Launchpad!\n\nAs this is your first time running the Launchpad, we probably don't know where you want to install Star Ruler 2 (or where it is already installed).\n\nIf you want to use an existing SR2 installation: Please navigate to the root folder of your SR2 installation, containing the 'data', 'locales', and 'scripts' folders.\n\nIf you want to install a fresh copy of OpenSR instead, please navigate to whatever folder you want to install the game into." + ) msg.showAndWait() + Platform.runLater { setSR2Path() } } - - if ( - needsConfig - || !File(Settings.instance.gamePath, "Star Ruler 2.exe").exists() + else if ( + !File(Settings.instance.gamePath, "Star Ruler 2.exe").exists() && !File(Settings.instance.gamePath, "StarRuler2.sh").exists() ) { val msg = ResizableAlert( AlertType.WARNING, - "Could not detect Star Ruler 2 launchers! Please navigate to the root folder of your SR2 installation, containing the files 'Star Ruler 2.exe' and/or 'StarRuler2.sh'!" + "Could not detect Star Ruler 2 launchers! Please navigate to the root folder of your SR2 installation, containing the 'data', 'locales', and 'scripts' folders!" ) msg.showAndWait() Platform.runLater { setSR2Path() } diff --git a/src/main/java/io/github/openstarruler/launchpad/view/ModInstallerPane.kt b/src/main/java/io/github/openstarruler/launchpad/view/ModInstallerPane.kt index 762b3f4..1810901 100644 --- a/src/main/java/io/github/openstarruler/launchpad/view/ModInstallerPane.kt +++ b/src/main/java/io/github/openstarruler/launchpad/view/ModInstallerPane.kt @@ -1,8 +1,8 @@ package io.github.openstarruler.launchpad.view import io.github.openstarruler.launchpad.adapter.ModInstaller -import io.github.openstarruler.launchpad.adapter.ModInstaller.TextHandler import io.github.openstarruler.launchpad.adapter.Recommendations +import io.github.openstarruler.launchpad.adapter.TextHandler import io.github.openstarruler.launchpad.model.RepoMetadata import javafx.application.Platform import javafx.concurrent.Task diff --git a/src/main/java/io/github/openstarruler/launchpad/view/OpenSRManagerPane.kt b/src/main/java/io/github/openstarruler/launchpad/view/OpenSRManagerPane.kt index d5ec152..7b1bac4 100644 --- a/src/main/java/io/github/openstarruler/launchpad/view/OpenSRManagerPane.kt +++ b/src/main/java/io/github/openstarruler/launchpad/view/OpenSRManagerPane.kt @@ -1,21 +1,27 @@ package io.github.openstarruler.launchpad.view +import io.github.openstarruler.launchpad.adapter.OpenSRManager import io.github.openstarruler.launchpad.adapter.Settings import io.github.openstarruler.launchpad.adapter.Utils +import io.github.openstarruler.launchpad.model.Release +import javafx.application.Platform +import javafx.concurrent.Task import javafx.fxml.FXML import javafx.fxml.FXMLLoader import javafx.scene.control.* import javafx.scene.control.Alert.AlertType import javafx.scene.layout.GridPane import javafx.stage.DirectoryChooser +import javafx.stage.Stage import javafx.stage.Window +import javafx.util.Callback import java.io.File import java.io.IOException class OpenSRManagerPane : GridPane() { lateinit var playButton: Button - @FXML lateinit var osrVersionList: ListView + @FXML lateinit var osrVersionList: ListView @FXML lateinit var gamePathCaption: Label @FXML lateinit var gamePathLabel: Label @FXML lateinit var findGameButton: Button @@ -35,6 +41,15 @@ class OpenSRManagerPane : GridPane() { @FXML fun initialize() { + gamePathLabel.text = Settings.instance.gamePath + osrVersionList.cellFactory = Callback { ReleaseCell() } + osrVersionList.items.setAll(OpenSRManager.openSRVersions) + osrVersionList.selectionModel.selectedItemProperty() + .addListener { _, _, newValue -> + osrVersionInfo.text = newValue?.body ?: "" + updateGameButton.isDisable = newValue == null + } + updateGameButton.isDisable = false } @FXML @@ -56,22 +71,62 @@ class OpenSRManagerPane : GridPane() { if (dir == null || !dir.exists()) return val launcher = File(dir, if (Utils.IS_WINDOWS) "Star Ruler 2.exe" else "StarRuler2.sh") if (!launcher.exists()) { - val msg: Alert = - ResizableAlert(AlertType.ERROR, "This is not the root directory of a Star Ruler 2 installation!") - msg.showAndWait() - setSR2Path(dir, window) - return + val msg = + ResizableAlert(AlertType.WARNING, "This is not the root directory of a Star Ruler 2 installation!") + msg.show() } Settings.instance.gamePath = dir.absolutePath - playButton.isDisable = false + gamePathLabel.text = Settings.instance.gamePath + playButton.isDisable = !launcher.exists() + if (Settings.instance.isFirstRun) { + val dlg = ResizableAlert( + AlertType.CONFIRMATION, + "Do you want to install the latest version of OpenSR now? If not, you can install OpenSR later via the 'Manage OpenSR' tab.\n\nWARNING: Steam users may wish to back up their SR2 binaries (the 'bin' folder) first, in order to upload mods to the Steam Workshop.") + dlg.dialogPane.buttonTypes.setAll(ButtonType.YES, ButtonType.NO) + dlg.headerText = "Install OpenSR now?" + dlg.showAndWait() + .filter { it == ButtonType.YES } + .ifPresent { installOpenSR() } + } + Settings.instance.isFirstRun = false try { Settings.instance.save() } catch (e: IOException) { - val msg: Alert = ResizableAlert(AlertType.WARNING, "Failed to save config file!") + val msg = ResizableAlert(AlertType.WARNING, "Failed to save config file!") msg.show() e.printStackTrace() } } - fun installOpenSR() {} + fun installOpenSR() { + MainController.executeTask("Preparing to install OpenSR...") { + taskInstallStage: Stage -> + object : Task() { + override fun call() { + updateMessage("Preparing to install OpenSR...") + OpenSRManager.installOpenSR( + osrVersionList.selectionModel.selectedItem ?: OpenSRManager.openSRVersions.first { it.tagName == "nightly" }, + { warning -> + Platform.runLater { + val msg = ResizableAlert(AlertType.WARNING, warning) + msg.show() + } + }, + { error -> + Platform.runLater { + val msg = ResizableAlert(AlertType.ERROR, error) + msg.show() + } + }, + { message -> updateMessage(message) } + ) + } + + override fun succeeded() { + taskInstallStage.close() + playButton.isDisable = false + } + } + } + } } diff --git a/src/main/java/io/github/openstarruler/launchpad/view/RefCell.kt b/src/main/java/io/github/openstarruler/launchpad/view/RefCell.kt new file mode 100644 index 0000000..b22334e --- /dev/null +++ b/src/main/java/io/github/openstarruler/launchpad/view/RefCell.kt @@ -0,0 +1,20 @@ +package io.github.openstarruler.launchpad.view + +import javafx.scene.control.ListCell +import org.eclipse.jgit.lib.Ref + +// This is something I'd like to use at some point, +// but I'd need to reconsider how some other stuff works, first. +// +// Not going to do that now, but I'm keeping the code here just in case. +class RefCell: ListCell() { + override fun updateItem(item: Ref?, empty: Boolean) { + super.updateItem(item, empty) + + text = if (item == null) "" else { + if (item.name.startsWith("refs/remotes/origin/")) "Branch: ${item.name.replaceFirst("refs/remotes/origin/", "")}" + else if (item.name.startsWith("refs/tags/")) "Tag: ${item.name.replaceFirst("refs/tags/", "")}" + else "Unexpected ref type: ${item.name}" + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/openstarruler/launchpad/view/ReleaseCell.kt b/src/main/java/io/github/openstarruler/launchpad/view/ReleaseCell.kt new file mode 100644 index 0000000..6219df9 --- /dev/null +++ b/src/main/java/io/github/openstarruler/launchpad/view/ReleaseCell.kt @@ -0,0 +1,12 @@ +package io.github.openstarruler.launchpad.view + +import io.github.openstarruler.launchpad.model.Release +import javafx.scene.control.ListCell + +class ReleaseCell: ListCell() { + override fun updateItem(item: Release?, empty: Boolean) { + super.updateItem(item, empty) + + text = if (item == null) "" else item.name + } +} \ No newline at end of file diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index ea0f1dc..6a604d8 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -10,6 +10,7 @@ requires java.desktop; requires static org.jetbrains.annotations; requires java.security.jgss; + requires okhttp3; exports io.github.openstarruler.launchpad.adapter; exports io.github.openstarruler.launchpad.model;