object oriented file system skeleton
so there at least some preparation for java nio filesystem
This commit is contained in:
parent
6cc7ae51a0
commit
e8eed40a73
@ -2,7 +2,11 @@ package ru.dbotthepony.kstarbound
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.TypeAdapter
|
||||
import java.util.Arrays
|
||||
import java.util.stream.Stream
|
||||
|
||||
inline fun <reified T> GsonBuilder.registerTypeAdapter(adapter: TypeAdapter<T>): GsonBuilder {
|
||||
return registerTypeAdapter(T::class.java, adapter)
|
||||
}
|
||||
|
||||
fun <T> Array<T>.stream(): Stream<T> = Arrays.stream(this)
|
||||
|
@ -3,10 +3,10 @@ package ru.dbotthepony.kstarbound
|
||||
import com.google.gson.*
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kstarbound.api.IVFS
|
||||
import ru.dbotthepony.kstarbound.api.PhysicalFS
|
||||
import ru.dbotthepony.kstarbound.api.getPathFilename
|
||||
import ru.dbotthepony.kstarbound.api.getPathFolder
|
||||
import ru.dbotthepony.kstarbound.api.IStarboundFile
|
||||
import ru.dbotthepony.kstarbound.api.NonExistingFile
|
||||
import ru.dbotthepony.kstarbound.api.PhysicalFile
|
||||
import ru.dbotthepony.kstarbound.api.explore
|
||||
import ru.dbotthepony.kstarbound.defs.*
|
||||
import ru.dbotthepony.kstarbound.defs.liquid.LiquidDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.projectile.*
|
||||
@ -25,7 +25,6 @@ import ru.dbotthepony.kvector.vector.ndouble.Vector2d
|
||||
import ru.dbotthepony.kvector.vector.nfloat.Vector2f
|
||||
import ru.dbotthepony.kvector.vector.nint.Vector2i
|
||||
import java.io.*
|
||||
import java.nio.ByteBuffer
|
||||
import java.text.DateFormat
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
@ -40,7 +39,7 @@ const val PIXELS_IN_STARBOUND_UNITf = 8.0f
|
||||
// class TileDefLoadingException(message: String, cause: Throwable? = null) : IllegalStateException(message, cause)
|
||||
// class ProjectileDefLoadingException(message: String, cause: Throwable? = null) : IllegalStateException(message, cause)
|
||||
|
||||
object Starbound : IVFS {
|
||||
object Starbound {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
|
||||
private val _readingFolder = ThreadLocal<String>()
|
||||
@ -110,20 +109,67 @@ object Starbound : IVFS {
|
||||
private set
|
||||
var initialized = false
|
||||
private set
|
||||
|
||||
@Volatile
|
||||
var terminateLoading = false
|
||||
|
||||
private val archivePaths = ArrayList<File>()
|
||||
private val fileSystems = ArrayList<IVFS>()
|
||||
private val fileSystems = ArrayList<IStarboundFile>()
|
||||
|
||||
fun addFilePath(path: File) {
|
||||
fileSystems.add(PhysicalFS(path))
|
||||
fileSystems.add(PhysicalFile(path))
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляет уже прочитанный pak
|
||||
*/
|
||||
fun addPak(pak: StarboundPak) {
|
||||
fileSystems.add(pak)
|
||||
fileSystems.add(pak.root)
|
||||
}
|
||||
|
||||
fun exists(path: String): Boolean {
|
||||
@Suppress("name_shadowing")
|
||||
var path = path
|
||||
|
||||
if (path[0] == '/') {
|
||||
path = path.substring(1)
|
||||
}
|
||||
|
||||
for (fs in fileSystems) {
|
||||
if (fs.locate(path).exists) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun locate(path: String): IStarboundFile {
|
||||
@Suppress("name_shadowing")
|
||||
var path = path
|
||||
|
||||
if (path[0] == '/') {
|
||||
path = path.substring(1)
|
||||
}
|
||||
|
||||
for (fs in fileSystems) {
|
||||
val file = fs.locate(path)
|
||||
|
||||
if (file.exists) {
|
||||
return file
|
||||
}
|
||||
}
|
||||
|
||||
return NonExistingFile(path.split("/").last(), fullPath = path)
|
||||
}
|
||||
|
||||
fun locate(vararg path: String): IStarboundFile {
|
||||
for (p in path) {
|
||||
val get = locate(p)
|
||||
|
||||
if (get.exists) {
|
||||
return get
|
||||
}
|
||||
}
|
||||
|
||||
return NonExistingFile(path[0].split("/").last(), fullPath = path[0])
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,9 +180,11 @@ object Starbound : IVFS {
|
||||
}
|
||||
|
||||
fun loadJson(path: String): JsonElement {
|
||||
return JsonParser.parseReader(getReader(path))
|
||||
return JsonParser.parseReader(locate(path).reader())
|
||||
}
|
||||
|
||||
fun readDirect(path: String) = locate(path).readDirect()
|
||||
|
||||
fun getTileDefinition(name: String) = tiles[name]
|
||||
private val initCallbacks = ArrayList<() -> Unit>()
|
||||
|
||||
@ -198,46 +246,6 @@ object Starbound : IVFS {
|
||||
}, "Asset Loader").start()
|
||||
}
|
||||
|
||||
override fun pathExists(path: String): Boolean {
|
||||
for (fs in fileSystems) {
|
||||
if (fs.pathExists(path)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun readOrNull(path: String): ByteBuffer? {
|
||||
for (fs in fileSystems) {
|
||||
if (fs.pathExists(path)) {
|
||||
return fs.read(path)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override fun listFiles(path: String): List<String> {
|
||||
val listing = mutableListOf<String>()
|
||||
|
||||
for (fs in fileSystems) {
|
||||
listing.addAll(fs.listFiles(path))
|
||||
}
|
||||
|
||||
return listing
|
||||
}
|
||||
|
||||
override fun listDirectories(path: String): Collection<String> {
|
||||
val listing = mutableListOf<String>()
|
||||
|
||||
for (fs in fileSystems) {
|
||||
listing.addAll(fs.listDirectories(path))
|
||||
}
|
||||
|
||||
return listing
|
||||
}
|
||||
|
||||
fun onInitialize(callback: () -> Unit) {
|
||||
if (initialized) {
|
||||
callback()
|
||||
@ -260,12 +268,12 @@ object Starbound : IVFS {
|
||||
readingFolder = "/tiles/materials"
|
||||
|
||||
for (fs in fileSystems) {
|
||||
for (listedFile in fs.listAllFilesWithExtension("material")) {
|
||||
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".material") }) {
|
||||
try {
|
||||
callback("Loading $listedFile")
|
||||
|
||||
readingFolder = getPathFolder(listedFile)
|
||||
val tileDef = gson.fromJson(getReader(listedFile), TileDefinition::class.java)
|
||||
readingFolder = listedFile.computeDirectory()
|
||||
val tileDef = gson.fromJson(listedFile.reader(), TileDefinition::class.java)
|
||||
|
||||
check(tiles[tileDef.materialName] == null) { "Already has material with name ${tileDef.materialName} loaded!" }
|
||||
check(tilesByMaterialID[tileDef.materialId] == null) { "Already has material with ID ${tileDef.materialId} loaded!" }
|
||||
@ -275,6 +283,10 @@ object Starbound : IVFS {
|
||||
//throw TileDefLoadingException("Loading tile file $listedFile", err)
|
||||
LOGGER.error("Loading tile file $listedFile", err)
|
||||
}
|
||||
|
||||
if (terminateLoading) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -283,28 +295,32 @@ object Starbound : IVFS {
|
||||
|
||||
private fun loadProjectiles(callback: (String) -> Unit) {
|
||||
for (fs in fileSystems) {
|
||||
for (listedFile in fs.listAllFilesWithExtension("projectile")) {
|
||||
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".projectile") }) {
|
||||
try {
|
||||
callback("Loading $listedFile")
|
||||
|
||||
val def = gson.fromJson(getReader(listedFile), ConfigurableProjectile::class.java).assemble(getPathFolder(listedFile))
|
||||
val def = gson.fromJson(listedFile.reader(), ConfigurableProjectile::class.java).assemble(listedFile.computeDirectory())
|
||||
check(projectiles[def.projectileName] == null) { "Already has projectile with ID ${def.projectileName} loaded!" }
|
||||
projectiles[def.projectileName] = def
|
||||
} catch(err: Throwable) {
|
||||
//throw ProjectileDefLoadingException("Loading projectile file $listedFile", err)
|
||||
LOGGER.error("Loading projectile file $listedFile", err)
|
||||
}
|
||||
|
||||
if (terminateLoading) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadFunctions(callback: (String) -> Unit) {
|
||||
for (fs in fileSystems) {
|
||||
for (listedFile in fs.listAllFilesWithExtension("functions")) {
|
||||
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".functions") }) {
|
||||
try {
|
||||
callback("Loading $listedFile")
|
||||
|
||||
val readObject = loadJson(listedFile) as JsonObject
|
||||
val readObject = JsonParser.parseReader(listedFile.reader()) as JsonObject
|
||||
|
||||
for (key in readObject.keySet()) {
|
||||
val def = gson.fromJson(readObject[key], JsonFunction::class.java)
|
||||
@ -313,22 +329,28 @@ object Starbound : IVFS {
|
||||
} catch(err: Throwable) {
|
||||
LOGGER.error("Loading function file $listedFile", err)
|
||||
}
|
||||
|
||||
if (terminateLoading) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadParallax(callback: (String) -> Unit) {
|
||||
for (fs in fileSystems) {
|
||||
for (listedFile in fs.listAllFiles("parallax")) {
|
||||
if (listedFile.endsWith(".parallax")) {
|
||||
try {
|
||||
callback("Loading $listedFile")
|
||||
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".parallax") }) {
|
||||
try {
|
||||
callback("Loading $listedFile")
|
||||
|
||||
val def = gson.fromJson(getReader(listedFile), ParallaxPrototype::class.java)
|
||||
parallax[getPathFilename(listedFile).substringBefore('.')] = def
|
||||
} catch(err: Throwable) {
|
||||
LOGGER.error("Loading parallax file $listedFile", err)
|
||||
}
|
||||
val def = gson.fromJson(listedFile.reader(), ParallaxPrototype::class.java)
|
||||
parallax[listedFile.name.substringBefore('.')] = def
|
||||
} catch(err: Throwable) {
|
||||
LOGGER.error("Loading parallax file $listedFile", err)
|
||||
}
|
||||
|
||||
if (terminateLoading) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -338,12 +360,12 @@ object Starbound : IVFS {
|
||||
readingFolder = "/tiles/materials"
|
||||
|
||||
for (fs in fileSystems) {
|
||||
for (listedFile in fs.listAllFilesWithExtension("matmod")) {
|
||||
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".matmod") }) {
|
||||
try {
|
||||
callback("Loading $listedFile")
|
||||
|
||||
readingFolder = getPathFolder(listedFile)
|
||||
val tileDef = gson.fromJson(getReader(listedFile), MaterialModifier::class.java)
|
||||
readingFolder = listedFile.computeDirectory()
|
||||
val tileDef = gson.fromJson(listedFile.reader(), MaterialModifier::class.java)
|
||||
|
||||
check(tileModifiers[tileDef.modName] == null) { "Already has material with name ${tileDef.modName} loaded!" }
|
||||
check(tileModifiersByID[tileDef.modId] == null) { "Already has material with ID ${tileDef.modId} loaded!" }
|
||||
@ -353,6 +375,10 @@ object Starbound : IVFS {
|
||||
//throw TileDefLoadingException("Loading tile file $listedFile", err)
|
||||
LOGGER.error("Loading tile modifier file $listedFile", err)
|
||||
}
|
||||
|
||||
if (terminateLoading) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -361,12 +387,12 @@ object Starbound : IVFS {
|
||||
|
||||
private fun loadLiquidDefinitions(callback: (String) -> Unit) {
|
||||
for (fs in fileSystems) {
|
||||
for (listedFile in fs.listAllFilesWithExtension("liquid")) {
|
||||
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".liquid") }) {
|
||||
try {
|
||||
callback("Loading $listedFile")
|
||||
|
||||
readingFolder = getPathFolder(listedFile)
|
||||
val liquidDef = gson.fromJson(getReader(listedFile), LiquidDefinition::class.java)
|
||||
readingFolder = listedFile.computeDirectory()
|
||||
val liquidDef = gson.fromJson(listedFile.reader(), LiquidDefinition::class.java)
|
||||
|
||||
check(liquid.put(liquidDef.name, liquidDef) == null) { "Already has liquid with name ${liquidDef.name} loaded!" }
|
||||
check(liquidByID.put(liquidDef.liquidId, liquidDef) == null) { "Already has liquid with ID ${liquidDef.liquidId} loaded!" }
|
||||
@ -374,6 +400,10 @@ object Starbound : IVFS {
|
||||
//throw TileDefLoadingException("Loading tile file $listedFile", err)
|
||||
LOGGER.error("Loading liquid definition file $listedFile", err)
|
||||
}
|
||||
|
||||
if (terminateLoading) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,228 @@
|
||||
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
|
||||
|
||||
interface IStarboundFile {
|
||||
val exists: Boolean
|
||||
val isDirectory: Boolean
|
||||
|
||||
/**
|
||||
* null if root
|
||||
*/
|
||||
val parent: IStarboundFile?
|
||||
val isFile: Boolean
|
||||
|
||||
/**
|
||||
* null if not a directory
|
||||
*/
|
||||
val children: Map<String, IStarboundFile>?
|
||||
val name: String
|
||||
|
||||
fun orNull(): IStarboundFile? = if (exists) this else null
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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(open())
|
||||
|
||||
/**
|
||||
* @throws IllegalStateException if file is a directory
|
||||
* @throws FileNotFoundException if file does not exist
|
||||
*/
|
||||
fun readJson(): JsonElement = JsonParser.parseReader(reader())
|
||||
|
||||
/**
|
||||
* @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 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<String, IStarboundFile>?
|
||||
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<String, IStarboundFile>?
|
||||
get() = null
|
||||
|
||||
override val exists: Boolean
|
||||
get() = false
|
||||
|
||||
override fun open(): InputStream {
|
||||
throw FileNotFoundException("File ${fullPath ?: computeFullPath()} does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
fun IStarboundFile.explore(): Stream<IStarboundFile> {
|
||||
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<String, PhysicalFile>?
|
||||
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]"
|
||||
}
|
||||
}
|
@ -1,164 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.api
|
||||
|
||||
import java.io.*
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
interface IVFS {
|
||||
fun pathExists(path: String): Boolean
|
||||
|
||||
fun pathExistsOrElse(path: String, orElse: String): String {
|
||||
if (pathExists(path))
|
||||
return path
|
||||
|
||||
return orElse
|
||||
}
|
||||
|
||||
fun firstExisting(vararg pathList: String): String {
|
||||
for (path in pathList)
|
||||
if (pathExists(path))
|
||||
return path
|
||||
|
||||
throw FileNotFoundException("Unable to find any of files specified")
|
||||
}
|
||||
|
||||
fun firstExistingOrNull(vararg pathList: String): String? {
|
||||
for (path in pathList)
|
||||
if (pathExists(path))
|
||||
return path
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun read(path: String): ByteBuffer {
|
||||
return readOrNull(path) ?: throw FileNotFoundException("No such file $path")
|
||||
}
|
||||
|
||||
fun readOrNull(path: String): ByteBuffer?
|
||||
|
||||
fun readString(path: String): String {
|
||||
return read(path).array().toString(Charsets.UTF_8)
|
||||
}
|
||||
|
||||
fun getReader(path: String): Reader {
|
||||
return InputStreamReader(ByteArrayInputStream(read(path).array()))
|
||||
}
|
||||
|
||||
fun listFiles(path: String): Collection<String>
|
||||
fun listDirectories(path: String): Collection<String>
|
||||
|
||||
fun listFilesAndDirectories(path: String): Collection<String> {
|
||||
val a = listFiles(path)
|
||||
val b = listDirectories(path)
|
||||
|
||||
return ArrayList<String>(a.size + b.size).also { it.addAll(a); it.addAll(b) }
|
||||
}
|
||||
|
||||
fun listAllFilesWithExtension(extension: String): Collection<String> {
|
||||
val listing = ArrayList<String>()
|
||||
val ext = ".$extension"
|
||||
|
||||
for (listedFile in listAllFiles("")) {
|
||||
if (listedFile.endsWith(ext)) {
|
||||
listing.add(listedFile)
|
||||
}
|
||||
}
|
||||
|
||||
return listing
|
||||
}
|
||||
|
||||
fun listAllFiles(path: String): Collection<String> {
|
||||
val lists = mutableListOf<Collection<String>>()
|
||||
|
||||
lists.add(listFiles(path))
|
||||
|
||||
for (dir in listDirectories(path)) {
|
||||
lists.add(listAllFiles(dir))
|
||||
}
|
||||
|
||||
// flatten медленный
|
||||
// return lists.flatten()
|
||||
|
||||
var size = 0
|
||||
|
||||
for (list in lists) {
|
||||
size += list.size
|
||||
}
|
||||
|
||||
return ArrayList<String>(size).also { lists.forEach(it::addAll) }
|
||||
}
|
||||
|
||||
fun readDirect(path: String): ByteBuffer {
|
||||
val read = read(path)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
fun getPathFolder(path: String): String {
|
||||
return path.substringBeforeLast('/')
|
||||
}
|
||||
|
||||
fun getPathFilename(path: String): String {
|
||||
return path.substringAfterLast('/')
|
||||
}
|
||||
|
||||
class PhysicalFS(root: File) : IVFS {
|
||||
val root: File = root.absoluteFile
|
||||
|
||||
override fun pathExists(path: String): Boolean {
|
||||
if (path.contains("..")) {
|
||||
return false
|
||||
}
|
||||
|
||||
return File(root.absolutePath, path).exists()
|
||||
}
|
||||
|
||||
override fun readOrNull(path: String): ByteBuffer? {
|
||||
if (path.contains("..")) {
|
||||
return null
|
||||
}
|
||||
|
||||
val fpath = File(root.path, path)
|
||||
|
||||
if (!fpath.exists()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val reader = fpath.inputStream()
|
||||
val buf = ByteBuffer.allocate(reader.channel.size().toInt())
|
||||
reader.read(buf.array())
|
||||
return buf
|
||||
}
|
||||
|
||||
override fun listFiles(path: String): List<String> {
|
||||
if (path.contains("..")) {
|
||||
return listOf()
|
||||
}
|
||||
|
||||
val fpath = File(root.absolutePath, path)
|
||||
return fpath.listFiles()?.filter { it.isFile }?.map {
|
||||
it.path.replace('\\', '/').substring(root.path.length)
|
||||
} ?: return listOf()
|
||||
}
|
||||
|
||||
override fun listDirectories(path: String): Collection<String> {
|
||||
if (path.contains("..")) {
|
||||
return listOf()
|
||||
}
|
||||
|
||||
val fpath = File(root.absolutePath, path)
|
||||
return fpath.listFiles()?.filter { it.isDirectory }?.map {
|
||||
it.path.replace('\\', '/').substring(root.path.length)
|
||||
} ?: return listOf()
|
||||
}
|
||||
}
|
@ -445,7 +445,7 @@ class GLStateTracker {
|
||||
|
||||
fun loadNamedTexture(path: String, memoryFormat: Int, fileFormat: Int): GLTexture2D {
|
||||
return named2DTextures.computeIfAbsent(path) {
|
||||
if (!Starbound.pathExists(path)) {
|
||||
if (!Starbound.exists(path)) {
|
||||
throw FileNotFoundException("Unable to locate $path")
|
||||
}
|
||||
|
||||
@ -455,7 +455,7 @@ class GLStateTracker {
|
||||
|
||||
fun loadNamedTexture(path: String): GLTexture2D {
|
||||
return named2DTextures.computeIfAbsent(path) {
|
||||
if (!Starbound.pathExists(path)) {
|
||||
if (!Starbound.exists(path)) {
|
||||
throw FileNotFoundException("Unable to locate $path")
|
||||
}
|
||||
|
||||
@ -473,7 +473,7 @@ class GLStateTracker {
|
||||
}
|
||||
|
||||
return named2DTextures.computeIfAbsent(path) {
|
||||
if (!Starbound.pathExists(path)) {
|
||||
if (!Starbound.exists(path)) {
|
||||
LOGGER.error("Texture {} is missing! Falling back to {}", path, missingTexturePath)
|
||||
return@computeIfAbsent named2DTextures[missingTexturePath]!!
|
||||
}
|
||||
@ -489,7 +489,7 @@ class GLStateTracker {
|
||||
}
|
||||
|
||||
return named2DTextures.computeIfAbsent(path) {
|
||||
if (!Starbound.pathExists(path)) {
|
||||
if (!Starbound.exists(path)) {
|
||||
LOGGER.error("Texture {} is missing! Falling back to {}", path, missingTexturePath)
|
||||
return@computeIfAbsent named2DTextures[missingTexturePath]!!
|
||||
}
|
||||
|
@ -266,7 +266,7 @@ interface IFrameGrid {
|
||||
if (splitLast.size == 1) {
|
||||
// имя уже абсолютное
|
||||
return cache.computeIfAbsent(path) {
|
||||
val frames = Starbound.firstExistingOrNull("$path.frames", "${splitPath.joinToString("/")}/default.frames")
|
||||
val frames = Starbound.locate("$path.frames", "${splitPath.joinToString("/")}/default.frames").orNull()
|
||||
|
||||
if (weak && frames == null) {
|
||||
LOGGER.warn("Expected animated texture at {}, but .frames metafile is missing.", path)
|
||||
@ -274,14 +274,14 @@ interface IFrameGrid {
|
||||
return@computeIfAbsent singleFrame("$path.png", weakSize)
|
||||
}
|
||||
|
||||
return@computeIfAbsent fromJson(Starbound.loadJson(frames ?: throw FileNotFoundException("Unable to find .frames meta for $path")) as JsonObject, path)
|
||||
return@computeIfAbsent fromJson((frames?.readJson() ?: throw FileNotFoundException("Unable to find .frames meta for $path")) as JsonObject, path)
|
||||
}
|
||||
}
|
||||
|
||||
val newPath = "${splitPath.joinToString("/")}/${splitLast[0]}"
|
||||
|
||||
return cache.computeIfAbsent(newPath) {
|
||||
val frames = Starbound.firstExistingOrNull("$newPath.frames", "${splitPath.joinToString("/")}/default.frames")
|
||||
val frames = Starbound.locate("$newPath.frames", "${splitPath.joinToString("/")}/default.frames").orNull()
|
||||
|
||||
if (weak && frames == null) {
|
||||
LOGGER.warn("Expected animated texture at {}, but .frames metafile is missing.", newPath)
|
||||
@ -289,7 +289,7 @@ interface IFrameGrid {
|
||||
return@computeIfAbsent singleFrame("$newPath.png", weakSize)
|
||||
}
|
||||
|
||||
return@computeIfAbsent fromJson(Starbound.loadJson(frames ?: throw FileNotFoundException("Unable to find .frames meta for $path")) as JsonObject, newPath)
|
||||
return@computeIfAbsent fromJson((frames?.readJson() ?: throw FileNotFoundException("Unable to find .frames meta for $path")) as JsonObject, newPath)
|
||||
}
|
||||
} catch (err: Throwable) {
|
||||
throw MalformedFrameGridException("Reading animated texture definition $path", err)
|
||||
|
@ -172,7 +172,7 @@ class ActionConfig : IConfigurableAction {
|
||||
|
||||
override fun configure(directory: String): IActionOnReap {
|
||||
return cache.computeIfAbsent(ensureAbsolutePath(file, directory)) {
|
||||
if (!Starbound.pathExists(it)) {
|
||||
if (!Starbound.exists(it)) {
|
||||
LOGGER.error("Config $it does not exist")
|
||||
return@computeIfAbsent CActionConfig(file, null)
|
||||
}
|
||||
|
@ -300,7 +300,7 @@ data class RenderTemplate(
|
||||
}
|
||||
|
||||
return cache.computeIfAbsent(path) {
|
||||
return@computeIfAbsent Starbound.gson.fromJson(Starbound.getReader(it), RenderTemplate::class.java)
|
||||
return@computeIfAbsent Starbound.gson.fromJson(Starbound.locate(it).reader(), RenderTemplate::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,134 +1,137 @@
|
||||
package ru.dbotthepony.kstarbound.io
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
|
||||
import ru.dbotthepony.kstarbound.api.IVFS
|
||||
import java.io.*
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||||
import ru.dbotthepony.kstarbound.api.IStarboundFile
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.Closeable
|
||||
import java.io.DataInputStream
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.RandomAccessFile
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.Channels
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
private fun readHeader(reader: RandomAccessFile, required: Int) {
|
||||
val read = reader.read()
|
||||
require(read == required) { "Bad Starbound Pak header, expected $required, got $read" }
|
||||
}
|
||||
|
||||
class StarboundPakFile(
|
||||
val storage: StarboundPak,
|
||||
val name: String,
|
||||
val offset: Long,
|
||||
val length: Long
|
||||
) {
|
||||
val directoryName: String
|
||||
val directoryHiearchy: Array<String>
|
||||
val fileName: String
|
||||
class StarboundPak(val path: File, callback: (finished: Boolean, status: String) -> Unit = { _, _ -> }) : Closeable {
|
||||
internal inner class SBDirectory(
|
||||
override val name: String,
|
||||
override val parent: IStarboundFile?,
|
||||
) : IStarboundFile {
|
||||
override val exists: Boolean
|
||||
get() = true
|
||||
|
||||
init {
|
||||
val split = name.substring(1).split('/').toMutableList()
|
||||
fileName = split.last()
|
||||
split.removeAt(split.size - 1)
|
||||
directoryHiearchy = split.toTypedArray()
|
||||
directoryName = split.joinToString("/")
|
||||
}
|
||||
override val isDirectory: Boolean
|
||||
get() = true
|
||||
|
||||
fun read(): ByteBuffer {
|
||||
val buf = ByteBuffer.allocate(length.toInt())
|
||||
storage.reader.seek(offset)
|
||||
storage.reader.readFully(buf.array())
|
||||
return buf
|
||||
}
|
||||
override val isFile: Boolean
|
||||
get() = false
|
||||
|
||||
override fun toString(): String {
|
||||
return name
|
||||
}
|
||||
private var frozen = false
|
||||
private val innerChildren = Object2ObjectOpenHashMap<String, IStarboundFile>()
|
||||
|
||||
fun readString(): String {
|
||||
return read().array().toString(Charsets.UTF_8)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun read(storage: StarboundPak, reader: RandomAccessFile): StarboundPakFile {
|
||||
val readLength = reader.read()
|
||||
//val name = reader.readASCIIString(readLength).lowercase()
|
||||
val name = reader.readASCIIString(readLength)
|
||||
require(name[0] == '/') { "$name appears to be not an absolute filename" }
|
||||
val offset = reader.readLong()
|
||||
val length = reader.readLong()
|
||||
|
||||
return StarboundPakFile(storage, name.intern(), offset, length)
|
||||
fun put(value: IStarboundFile) {
|
||||
check(!frozen) { "Can't put, already frozen!" }
|
||||
innerChildren[value.name] = value
|
||||
}
|
||||
|
||||
fun read(storage: StarboundPak, reader: DataInputStream): StarboundPakFile {
|
||||
val readLength = reader.readVarInt()
|
||||
val name = reader.readASCIIString(readLength)
|
||||
require(name[0] == '/') { "$name appears to be not an absolute filename" }
|
||||
val offset = reader.readLong()
|
||||
val length = reader.readLong()
|
||||
|
||||
return StarboundPakFile(storage, name.intern(), offset, length)
|
||||
fun subdir(name: String): SBDirectory {
|
||||
check(!frozen) { "Can't subdir, already frozen!" }
|
||||
require(name != "") { "Empty directory name provided" }
|
||||
return innerChildren.computeIfAbsent(name, Object2ObjectFunction { SBDirectory(it as String, this) }) as? SBDirectory ?: throw IllegalStateException("$name already exists (in ${computeFullPath()})")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StarboundPakDirectory(val name: String, val parent: StarboundPakDirectory? = null) {
|
||||
val files = HashMap<String, StarboundPakFile>()
|
||||
val directories = HashMap<String, StarboundPakDirectory>()
|
||||
override val children: Map<String, IStarboundFile> = Collections.unmodifiableMap(innerChildren)
|
||||
|
||||
fun resolve(path: Array<String>, level: Int = 0): StarboundPakDirectory {
|
||||
if (path.size == level)
|
||||
return this
|
||||
fun freeze() {
|
||||
check(!frozen) { "Already frozen" }
|
||||
frozen = true
|
||||
|
||||
if (level == 0 && path[0] == "" && name == "/")
|
||||
return resolve(path, 1)
|
||||
|
||||
if (directories.containsKey(path[level])) {
|
||||
return directories[path[level]]!!.resolve(path, level + 1)
|
||||
} else {
|
||||
val dir = StarboundPakDirectory(path[level], this)
|
||||
directories[path[level]] = dir
|
||||
return dir.resolve(path, level + 1)
|
||||
}
|
||||
}
|
||||
|
||||
fun getFile(name: String) = files[name]
|
||||
fun getDirectory(name: String) = directories[name]
|
||||
|
||||
fun listFiles(): Collection<StarboundPakFile> = Collections.unmodifiableCollection(files.values)
|
||||
fun listDirectories(): Collection<StarboundPakDirectory> = Collections.unmodifiableCollection(directories.values)
|
||||
|
||||
fun writeFile(file: StarboundPakFile) {
|
||||
files[file.name.split('/').last()] = file
|
||||
}
|
||||
|
||||
fun fullName(): String {
|
||||
var build = name
|
||||
var getParent = parent
|
||||
|
||||
while (getParent != null) {
|
||||
if (getParent.parent != null) {
|
||||
build = "${getParent.name}/$build"
|
||||
} else {
|
||||
build = "/$build"
|
||||
break
|
||||
for (children in innerChildren.values) {
|
||||
if (children is SBDirectory) {
|
||||
children.freeze()
|
||||
}
|
||||
}
|
||||
|
||||
getParent = getParent.parent
|
||||
}
|
||||
|
||||
return build
|
||||
override fun open(): InputStream {
|
||||
throw IllegalStateException("${computeFullPath()} is a directory")
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "SBDirectory[${computeFullPath()} @ $path]"
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return fullName()
|
||||
}
|
||||
}
|
||||
internal inner class SBFile(
|
||||
override val name: String,
|
||||
override val parent: IStarboundFile?,
|
||||
val offset: Long,
|
||||
val length: Long
|
||||
) : IStarboundFile {
|
||||
override val exists: Boolean
|
||||
get() = true
|
||||
|
||||
override val isDirectory: Boolean
|
||||
get() = false
|
||||
|
||||
override val isFile: Boolean
|
||||
get() = true
|
||||
|
||||
override val children: Map<String, IStarboundFile>?
|
||||
get() = null
|
||||
|
||||
override fun open(): InputStream {
|
||||
return object : InputStream() {
|
||||
private var innerOffset = 0L
|
||||
|
||||
override fun read(): Int {
|
||||
if (innerOffset >= length) {
|
||||
return -1
|
||||
}
|
||||
|
||||
reader.seek(innerOffset + offset)
|
||||
innerOffset++
|
||||
return reader.read()
|
||||
}
|
||||
|
||||
override fun read(b: ByteArray, off: Int, len: Int): Int {
|
||||
Objects.checkFromIndexSize(off, len, b.size)
|
||||
|
||||
// ok
|
||||
if (len == 0)
|
||||
return 0
|
||||
|
||||
val readMax = len.coerceAtMost((length - innerOffset).toInt())
|
||||
|
||||
if (readMax <= 0)
|
||||
return -1
|
||||
|
||||
reader.seek(innerOffset + offset)
|
||||
val readBytes = reader.read(b, off, readMax)
|
||||
|
||||
if (readBytes == -1) {
|
||||
throw RuntimeException("Unexpected EOF, want to read $readMax bytes from starting $offset in $path")
|
||||
}
|
||||
|
||||
innerOffset += readBytes
|
||||
return readBytes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "SBFile[${computeFullPath()} @ $path]"
|
||||
}
|
||||
}
|
||||
|
||||
class StarboundPak(val path: File, callback: (finished: Boolean, status: String) -> Unit = { _, _ -> }) : Closeable, IVFS {
|
||||
val reader = RandomAccessFile(path, "r")
|
||||
private val filesByExtension = Object2ObjectArrayMap<String, ArrayList<StarboundPakFile>>()
|
||||
private val filesByExtensionPath = Object2ObjectArrayMap<String, ArrayList<String>>()
|
||||
|
||||
init {
|
||||
readHeader(reader, 0x53) // S
|
||||
@ -163,75 +166,60 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String)
|
||||
// сразу за метаданными идёт количество файлов внутри данного pak в формате Big Endian variable int
|
||||
val indexNodeCount = reader.readVarLong()
|
||||
|
||||
private val _indexNodes = HashMap<String, StarboundPakFile>()
|
||||
val indexNodes: Map<String, StarboundPakFile> = Collections.unmodifiableMap(_indexNodes)
|
||||
private val _indexNodesLowercase = HashMap<String, StarboundPakFile>()
|
||||
val indexNodesLowercase: Map<String, StarboundPakFile> = Collections.unmodifiableMap(_indexNodesLowercase)
|
||||
val root = StarboundPakDirectory("/")
|
||||
val root: IStarboundFile = SBDirectory("", null)
|
||||
|
||||
init {
|
||||
// Сразу же за количеством файлов идут сами файлы в формате
|
||||
// byte (длинна имени файла)
|
||||
// VarInt (длинна имени файла)
|
||||
// byte[] (utf-8 имя файла)
|
||||
// long (offset от начала файла)
|
||||
// long (длинна файла)
|
||||
// long (длина файла)
|
||||
val stream = DataInputStream(BufferedInputStream(Channels.newInputStream(reader.channel)))
|
||||
|
||||
for (i in 0 until indexNodeCount) {
|
||||
var name: String? = null
|
||||
|
||||
try {
|
||||
callback(false, "Reading index node $i")
|
||||
val read = StarboundPakFile.read(this, stream)
|
||||
|
||||
if (read.offset > reader.length()) {
|
||||
throw IndexOutOfBoundsException("Garbage offset at index $i: ${read.offset}")
|
||||
val readLength = stream.readVarInt()
|
||||
name = stream.readASCIIString(readLength)
|
||||
require(name[0] == '/') { "index node at $i with '$name' appears to be not an absolute filename" }
|
||||
val offset = stream.readLong()
|
||||
val length = stream.readLong()
|
||||
|
||||
if (offset > reader.length()) {
|
||||
throw IndexOutOfBoundsException("Garbage offset at index $i: ${offset}")
|
||||
}
|
||||
|
||||
if (read.length > reader.length()) {
|
||||
throw IndexOutOfBoundsException("Garbage length at index $i: ${read.length}")
|
||||
if (length + offset > reader.length()) {
|
||||
throw IndexOutOfBoundsException("Garbage offset + length at index $i: ${length + offset}")
|
||||
}
|
||||
|
||||
_indexNodes[read.name] = read
|
||||
// Starbound игнорирует регистр букв когда ищет пути, даже внутри pak архивов
|
||||
_indexNodesLowercase[read.name.lowercase()] = read
|
||||
val split = name.lowercase().split("/")
|
||||
var parent = root as SBDirectory
|
||||
|
||||
root.resolve(read.directoryHiearchy).writeFile(read)
|
||||
|
||||
val last = read.name.substringAfterLast('/').substringAfterLast('.', "")
|
||||
|
||||
if (last != "") {
|
||||
filesByExtension.computeIfAbsent(last, Object2ObjectFunction { ArrayList() }).add(read)
|
||||
filesByExtensionPath.computeIfAbsent(last, Object2ObjectFunction { ArrayList() }).add(read.name)
|
||||
for (splitIndex in 1 until split.size - 1) {
|
||||
parent = parent.subdir(split[splitIndex])
|
||||
}
|
||||
|
||||
parent.put(SBFile(split.last(), parent, offset, length))
|
||||
} catch (err: Throwable) {
|
||||
throw IOException("Reading index node at $i", err)
|
||||
if (name == null) {
|
||||
throw IOException("Reading index node at $i", err)
|
||||
} else {
|
||||
throw IOException("Reading index node at $i ($name)", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callback(false, "Freezing virtual file system")
|
||||
(root as SBDirectory).freeze()
|
||||
callback(true, "Reading indexes finished")
|
||||
}
|
||||
|
||||
override fun listFiles(path: String): Collection<String> {
|
||||
return root.resolve(path.split("/").toTypedArray()).listFiles().map { it.name }
|
||||
}
|
||||
|
||||
override fun listDirectories(path: String): Collection<String> {
|
||||
return root.resolve(path.split("/").toTypedArray()).listDirectories().map { it.fullName() }
|
||||
}
|
||||
|
||||
override fun pathExists(path: String): Boolean {
|
||||
return _indexNodesLowercase.containsKey(path)
|
||||
}
|
||||
|
||||
override fun readOrNull(path: String): ByteBuffer? {
|
||||
val node = _indexNodesLowercase[path] ?: return null
|
||||
return node.read()
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
reader.close()
|
||||
}
|
||||
|
||||
override fun listAllFilesWithExtension(extension: String): Collection<String> {
|
||||
return filesByExtensionPath[extension]?.let(Collections::unmodifiableList) ?: listOf()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user