mirror of
				https://github.com/packwiz/packwiz-installer.git
				synced 2025-11-04 12:34:31 +01:00 
			
		
		
		
	Port metadata code to Kotlin
This commit is contained in:
		@@ -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()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
@@ -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()
 | 
			
		||||
}
 | 
			
		||||
@@ -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?
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user