Port metadata code to Kotlin

This commit is contained in:
comp500
2019-12-19 21:11:47 +00:00
parent ecaab219c2
commit 0770029dc6
18 changed files with 454 additions and 521 deletions

View File

@@ -0,0 +1,27 @@
package link.infra.packwiz.installer.metadata
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import java.io.IOException
class EfficientBooleanAdapter : TypeAdapter<Boolean?>() {
@Throws(IOException::class)
override fun write(out: JsonWriter, value: Boolean?) {
if (value == null || !value) {
out.nullValue()
return
}
out.value(true)
}
@Throws(IOException::class)
override fun read(reader: JsonReader): Boolean? {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull()
return false
}
return reader.nextBoolean()
}
}

View File

@@ -0,0 +1,98 @@
package link.infra.packwiz.installer.metadata
import com.google.gson.annotations.SerializedName
import com.moandjiezana.toml.Toml
import link.infra.packwiz.installer.metadata.hash.Hash
import link.infra.packwiz.installer.metadata.hash.HashUtils.getHash
import link.infra.packwiz.installer.metadata.hash.HashUtils.getHasher
import link.infra.packwiz.installer.request.HandlerManager.getFileSource
import link.infra.packwiz.installer.request.HandlerManager.getNewLoc
import okio.Source
import okio.buffer
import java.nio.file.Paths
class IndexFile {
@SerializedName("hash-format")
var hashFormat: String = "sha-256"
var files: MutableList<File> = ArrayList()
class File {
var file: SpaceSafeURI? = null
@SerializedName("hash-format")
var hashFormat: String? = null
var hash: String? = null
var alias: SpaceSafeURI? = null
var metafile = false
var preserve = false
@Transient
var linkedFile: ModFile? = null
@Transient
var linkedFileURI: SpaceSafeURI? = null
@Throws(Exception::class)
fun downloadMeta(parentIndexFile: IndexFile, indexUri: SpaceSafeURI?) {
if (!metafile) {
return
}
if (hashFormat?.length ?: 0 == 0) {
hashFormat = parentIndexFile.hashFormat
}
// TODO: throw a proper exception instead of allowing NPE?
val fileHash = getHash(hashFormat!!, hash!!)
linkedFileURI = getNewLoc(indexUri, file)
val src = getFileSource(linkedFileURI!!)
val fileStream = getHasher(hashFormat!!).getHashingSource(src)
linkedFile = Toml().read(fileStream.buffer().inputStream()).to(ModFile::class.java)
if (!fileStream.hashIsEqual(fileHash)) {
throw Exception("Invalid mod file hash")
}
}
@Throws(Exception::class)
fun getSource(indexUri: SpaceSafeURI?): Source {
return if (metafile) {
if (linkedFile == null) {
throw Exception("Linked file doesn't exist!")
}
linkedFile!!.getSource(linkedFileURI)
} else {
val newLoc = getNewLoc(indexUri, file) ?: throw Exception("Index file URI is invalid")
getFileSource(newLoc)
}
}
@Throws(Exception::class)
fun getHashObj(): Hash {
if (hash == null) { // TODO: should these be more specific exceptions (e.g. IndexFileException?!)
throw Exception("Index file doesn't have a hash")
}
if (hashFormat == null) {
throw Exception("Index file doesn't have a hash format")
}
return getHash(hashFormat!!, hash!!)
}
// TODO: throw some kind of exception?
val name: String?
get() {
if (metafile) {
return linkedFile?.name ?: linkedFile?.filename
}
return file?.run { Paths.get(path).fileName.toString() } ?: "Invalid file"
}
// TODO: URIs are bad
val destURI: SpaceSafeURI?
get() {
if (alias != null) {
return alias
}
return if (metafile && linkedFile != null) {
linkedFile?.filename?.let { file?.resolve(it) }
} else {
file
}
}
}
}

View File

@@ -0,0 +1,44 @@
package link.infra.packwiz.installer.metadata
import com.google.gson.annotations.JsonAdapter
import link.infra.packwiz.installer.UpdateManager
import link.infra.packwiz.installer.metadata.hash.Hash
class ManifestFile {
var packFileHash: Hash? = null
var indexFileHash: Hash? = null
var cachedFiles: MutableMap<SpaceSafeURI, File> = HashMap()
// If the side changes, EVERYTHING invalidates. FUN!!!
var cachedSide = UpdateManager.Options.Side.CLIENT
// TODO: switch to Kotlin-friendly JSON/TOML libs?
class File {
@Transient
var revert: File? = null
private set
var hash: Hash? = null
var linkedFileHash: Hash? = null
var cachedLocation: String? = null
@JsonAdapter(EfficientBooleanAdapter::class)
var isOptional = false
var optionValue = true
@JsonAdapter(EfficientBooleanAdapter::class)
var onlyOtherSide = false
// When an error occurs, the state needs to be reverted. To do this, I have a crude revert system.
fun backup() {
revert = File().also {
it.hash = hash
it.linkedFileHash = linkedFileHash
it.cachedLocation = cachedLocation
it.isOptional = isOptional
it.optionValue = optionValue
it.onlyOtherSide = onlyOtherSide
}
}
}
}

View File

@@ -0,0 +1,57 @@
package link.infra.packwiz.installer.metadata
import com.google.gson.annotations.SerializedName
import link.infra.packwiz.installer.UpdateManager
import link.infra.packwiz.installer.metadata.hash.Hash
import link.infra.packwiz.installer.metadata.hash.HashUtils.getHash
import link.infra.packwiz.installer.request.HandlerManager.getFileSource
import link.infra.packwiz.installer.request.HandlerManager.getNewLoc
import okio.Source
class ModFile {
var name: String? = null
var filename: String? = null
var side: UpdateManager.Options.Side? = null
var download: Download? = null
class Download {
var url: SpaceSafeURI? = null
@SerializedName("hash-format")
var hashFormat: String? = null
var hash: String? = null
}
var update: Map<String, Any>? = null
var option: Option? = null
class Option {
var optional = false
var description: String? = null
@SerializedName("default")
var defaultValue = false
}
@Throws(Exception::class)
fun getSource(baseLoc: SpaceSafeURI?): Source {
download?.let {
if (it.url == null) {
throw Exception("Metadata file doesn't have a download URI")
}
val newLoc = getNewLoc(baseLoc, it.url) ?: throw Exception("Metadata file URI is invalid")
return getFileSource(newLoc)
} ?: throw Exception("Metadata file doesn't have download")
}
@get:Throws(Exception::class)
val hash: Hash
get() {
download?.let {
return getHash(
it.hashFormat ?: throw Exception("Metadata file doesn't have a hash format"),
it.hash ?: throw Exception("Metadata file doesn't have a hash")
)
} ?: throw Exception("Metadata file doesn't have download")
}
val isOptional: Boolean get() = option?.optional ?: false
}

View File

@@ -0,0 +1,19 @@
package link.infra.packwiz.installer.metadata
import com.google.gson.annotations.SerializedName
class PackFile {
var name: String? = null
var index: IndexFileLoc? = null
class IndexFileLoc {
var file: SpaceSafeURI? = null
@SerializedName("hash-format")
var hashFormat: String? = null
var hash: String? = null
}
var versions: Map<String, String>? = null
var client: Map<String, Any>? = null
var server: Map<String, Any>? = null
}

View File

@@ -0,0 +1,61 @@
package link.infra.packwiz.installer.metadata
import com.google.gson.annotations.JsonAdapter
import java.io.Serializable
import java.net.MalformedURLException
import java.net.URI
import java.net.URISyntaxException
import java.net.URL
// The world's worst URI wrapper
@JsonAdapter(SpaceSafeURIParser::class)
class SpaceSafeURI : Comparable<SpaceSafeURI>, Serializable {
private val u: URI
@Throws(URISyntaxException::class)
constructor(str: String) {
u = URI(str.replace(" ", "%20"))
}
constructor(uri: URI) {
u = uri
}
@Throws(URISyntaxException::class)
constructor(scheme: String?, authority: String?, path: String?, query: String?, fragment: String?) { // TODO: do all components need to be replaced?
u = URI(
scheme?.replace(" ", "%20"),
authority?.replace(" ", "%20"),
path?.replace(" ", "%20"),
query?.replace(" ", "%20"),
fragment?.replace(" ", "%20")
)
}
val path: String get() = u.path.replace("%20", " ")
override fun toString(): String = u.toString().replace("%20", " ")
fun resolve(path: String): SpaceSafeURI = SpaceSafeURI(u.resolve(path.replace(" ", "%20")))
fun resolve(loc: SpaceSafeURI): SpaceSafeURI = SpaceSafeURI(u.resolve(loc.u))
fun relativize(loc: SpaceSafeURI): SpaceSafeURI = SpaceSafeURI(u.relativize(loc.u))
override fun equals(other: Any?): Boolean {
return if (other is SpaceSafeURI) {
u == other.u
} else false
}
override fun hashCode() = u.hashCode()
override fun compareTo(other: SpaceSafeURI): Int = u.compareTo(other.u)
val scheme: String get() = u.scheme
val authority: String get() = u.authority
val host: String get() = u.host
@Throws(MalformedURLException::class)
fun toURL(): URL = u.toURL()
}

View File

@@ -0,0 +1,25 @@
package link.infra.packwiz.installer.metadata
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonParseException
import java.lang.reflect.Type
import java.net.URISyntaxException
/**
* This class encodes spaces before parsing the URI, so the URI can actually be
* parsed.
*/
internal class SpaceSafeURIParser : JsonDeserializer<SpaceSafeURI> {
@Throws(JsonParseException::class)
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): SpaceSafeURI {
return try {
SpaceSafeURI(json.asString)
} catch (e: URISyntaxException) {
throw JsonParseException("Failed to parse URI", e)
}
}
// TODO: replace this with a better solution?
}

View File

@@ -0,0 +1,47 @@
package link.infra.packwiz.installer.request
import link.infra.packwiz.installer.metadata.SpaceSafeURI
import link.infra.packwiz.installer.request.handlers.RequestHandlerGithub
import link.infra.packwiz.installer.request.handlers.RequestHandlerHTTP
import okio.Source
object HandlerManager {
private val handlers: List<IRequestHandler> = listOf(
RequestHandlerGithub(),
RequestHandlerHTTP()
)
@JvmStatic
fun getNewLoc(base: SpaceSafeURI?, loc: SpaceSafeURI?): SpaceSafeURI? {
if (loc == null) {
return null
}
val dest = base?.run { resolve(loc) } ?: loc
for (handler in handlers) with (handler) {
if (matchesHandler(dest)) {
return getNewLoc(dest)
}
}
return dest
}
// TODO: What if files are read multiple times??
// Zip handler discards once read, requesting multiple times on other handlers would cause multiple downloads
// Caching system? Copy from already downloaded files?
@JvmStatic
@Throws(Exception::class)
fun getFileSource(loc: SpaceSafeURI): Source {
for (handler in handlers) {
if (handler.matchesHandler(loc)) {
return handler.getFileSource(loc) ?: throw Exception("Couldn't find URI: $loc")
}
}
throw Exception("No handler available for URI: $loc")
}
// TODO: github toml resolution?
// e.g. https://github.com/comp500/Demagnetize -> demagnetize.toml
// https://github.com/comp500/Demagnetize/blob/master/demagnetize.toml
}