package ru.dbotthepony.kstarbound.api import com.google.common.collect.ImmutableMap import com.google.gson.JsonElement import com.google.gson.JsonParser import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.io.StarboundPak import ru.dbotthepony.kstarbound.stream import java.io.* import java.nio.ByteBuffer import java.util.stream.Stream fun interface ISBFileLocator { fun locate(path: String): IStarboundFile fun exists(path: String): Boolean = locate(path).exists /** * @throws IllegalStateException if file is a directory * @throws FileNotFoundException if file does not exist */ fun read(path: String) = locate(path).read() /** * @throws IllegalStateException if file is a directory * @throws FileNotFoundException if file does not exist */ fun readDirect(path: String) = locate(path).readDirect() /** * @throws IllegalStateException if file is a directory * @throws FileNotFoundException if file does not exist */ fun open(path: String) = locate(path).open() /** * @throws IllegalStateException if file is a directory * @throws FileNotFoundException if file does not exist */ fun reader(path: String) = locate(path).reader() } interface IStarboundFile : ISBFileLocator { val exists: Boolean val isDirectory: Boolean /** * null if root */ val parent: IStarboundFile? val isFile: Boolean /** * null if not a directory */ val children: Map? val name: String fun orNull(): IStarboundFile? = if (exists) this else null operator fun get(name: String): IStarboundFile? { return children?.get(name) } fun computeFullPath(): String { var path = name var parent = parent while (parent != null) { path = parent.name + "/" + path parent = parent.parent } return path } fun computeDirectory(): String { var path = "" var parent = parent while (parent != null) { if (path == "") path = parent.name else path = parent.name + "/" + path parent = parent.parent } return path } override fun locate(path: String): IStarboundFile { @Suppress("name_shadowing") val path = path.trim() if (path == "" || path == ".") { return this } if (path == "..") { return parent ?: NonExistingFile(computeFullPath() + "/..") } val split = path.lowercase().split("/") var file = this for (splitIndex in split.indices) { if (split[splitIndex].isEmpty()) { continue } val children = file.children ?: return NonExistingFile(name = split.last(), fullPath = computeFullPath() + "/" + path) val find = children[split[splitIndex]] if (find is StarboundPak.SBDirectory) { file = find } else if (find is StarboundPak.SBFile) { if (splitIndex + 1 != split.size) { return NonExistingFile(name = split.last(), fullPath = computeFullPath() + "/" + path) } return find } else { break } } return NonExistingFile(name = split.last(), fullPath = computeFullPath() + "/" + path) } /** * @throws IllegalStateException if file is a directory * @throws FileNotFoundException if file does not exist */ fun open(): InputStream /** * @throws IllegalStateException if file is a directory * @throws FileNotFoundException if file does not exist */ fun reader(): Reader = InputStreamReader(BufferedInputStream(open())) /** * @throws IllegalStateException if file is a directory * @throws FileNotFoundException if file does not exist */ fun read(): ByteBuffer { val stream = open() val read = stream.readAllBytes() stream.close() return ByteBuffer.wrap(read) } /** * @throws IllegalStateException if file is a directory * @throws FileNotFoundException if file does not exist */ fun readToString(): String { val stream = open() val read = stream.readAllBytes() stream.close() return String(read, charset = Charsets.UTF_8) } /** * @throws IllegalStateException if file is a directory * @throws FileNotFoundException if file does not exist */ fun readDirect(): ByteBuffer { val read = read() val buf = ByteBuffer.allocateDirect(read.capacity()) read.position(0) for (i in 0 until read.capacity()) { buf.put(read[i]) } buf.position(0) return buf } /** * non existent file, without any context */ companion object : IStarboundFile { override val exists: Boolean get() = false override val isDirectory: Boolean get() = false override val parent: IStarboundFile? get() = null override val isFile: Boolean get() = false override val children: Map? get() = null override val name: String get() = "" override fun open(): InputStream { throw FileNotFoundException() } } } class NonExistingFile( override val name: String, override val parent: IStarboundFile? = null, val fullPath: String? = null ) : IStarboundFile { override val isDirectory: Boolean get() = false override val isFile: Boolean get() = false override val children: Map? get() = null override val exists: Boolean get() = false override fun open(): InputStream { throw FileNotFoundException("File ${fullPath ?: computeFullPath()} does not exist") } } fun IStarboundFile.explore(): Stream { val children = children ?: return Stream.of(this) return Stream.concat(Stream.of(this), children.values.stream().flatMap { it.explore() }) } fun getPathFolder(path: String): String { return path.substringBeforeLast('/') } fun getPathFilename(path: String): String { return path.substringAfterLast('/') } class PhysicalFile(val real: File) : IStarboundFile { override val exists: Boolean get() = real.exists() override val isDirectory: Boolean get() = real.isDirectory override val parent: PhysicalFile? get() { return PhysicalFile(real.parentFile ?: return null) } override val isFile: Boolean get() = real.isFile override val children: Map? get() { return real.list()?.stream()?.map { it to PhysicalFile(File(it)) }?.collect(ImmutableMap.toImmutableMap({ it.first }, { it.second })) } override val name: String get() = real.name override fun open(): InputStream { return BufferedInputStream(real.inputStream()) } override fun toString(): String { return "PhysicalFile[$real]" } }