Skip to content

Commit

Permalink
[#1] Implemented OpenSR installer. Also:
Browse files Browse the repository at this point in the history
- Settings are now correctly loaded before the UI starts loading.
- On first run, user will be asked if they want to install OpenSR.
- Added more detailed progress tracking to Git/FS operations during mod installation.
  • Loading branch information
DaloLorn committed Jan 21, 2022
1 parent 7e550c4 commit 8b34678
Show file tree
Hide file tree
Showing 20 changed files with 393 additions and 43 deletions.
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Path>() {
class CopyFileVisitor(private val targetPath: Path, private val fileHandler: FileProgressHandler? = null) : SimpleFileVisitor<Path>() {
private var sourcePath: Path? = null

@Throws(IOException::class)
Expand All @@ -22,6 +22,7 @@ class CopyFileVisitor(private val targetPath: Path) : SimpleFileVisitor<Path>()

@Throws(IOException::class)
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
fileHandler?.handle()
Files.copy(file, targetPath.resolve(sourcePath!!.relativize(file)))
return FileVisitResult.CONTINUE
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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")
}
}
Original file line number Diff line number Diff line change
@@ -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))
}
}
26 changes: 26 additions & 0 deletions src/main/java/io/github/openstarruler/launchpad/adapter/GitHub.kt
Original file line number Diff line number Diff line change
@@ -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<Release> {
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() }
}
}
}
Original file line number Diff line number Diff line change
@@ -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
}
13 changes: 13 additions & 0 deletions src/main/java/io/github/openstarruler/launchpad/adapter/Gson.kt
Original file line number Diff line number Diff line change
@@ -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 <reified T> Gson.fromJson(json: Reader): T = fromJson(json, object: TypeToken<T>() {}.type)

@Throws(JsonIOException::class, JsonSyntaxException::class)
inline fun <reified T> Gson.fromJson(json: String): T = fromJson(json, object: TypeToken<T>() {}.type)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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!")
}

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -470,7 +475,4 @@ object ModInstaller {
return currentMetadata?.mods!!
}

fun interface TextHandler {
fun handle(text: String)
}
}
Original file line number Diff line number Diff line change
@@ -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<Release> = 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")
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.github.openstarruler.launchpad.adapter

fun interface TextHandler {
fun handle(text: String)
}
18 changes: 18 additions & 0 deletions src/main/java/io/github/openstarruler/launchpad/adapter/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -67,6 +73,18 @@ object Utils {
return result
}

fun countFiles(source: Path): Int {
var count = 0
Files.walkFileTree(source.absolute(), object: SimpleFileVisitor<Path>() {
@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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
)
Loading

0 comments on commit 8b34678

Please sign in to comment.