KorIO File System

KorIO has a Virtual File System functionality.

Table of contents:

File System

PathInfo

expect val File_separatorChar: Char

inline class PathInfo(val fullPath: String)
val String.pathInfo: PathInfo
interface Path { val pathInfo: PathInfo }

fun PathInfo.parts(): List<String>
fun PathInfo.normalize(): String 
fun PathInfo.combine(access: PathInfo): PathInfo
fun PathInfo.lightCombine(access: PathInfo): PathInfo
fun PathInfo.isAbsolute(): Boolean
fun PathInfo.normalizeAbsolute(): PathInfo

// Direct PathInfo
val PathInfo.fullPathNormalized: String
val PathInfo.folder: String
val PathInfo.folderWithSlash: String
val PathInfo.baseName: String
val PathInfo.fullPathWithoutExtension: String
fun PathInfo.fullPathWithExtension(ext: String): String
val PathInfo.baseNameWithoutExtension: String
val PathInfo.baseNameWithoutCompoundExtension: String
val PathInfo.fullNameWithoutExtension: String
val PathInfo.fullNameWithoutCompoundExtension: String
fun PathInfo.baseNameWithExtension(ext: String): String
fun PathInfo.baseNameWithCompoundExtension(ext: String): String
val PathInfo.extension: String
val PathInfo.extensionLC: String
val PathInfo.compoundExtension: String
val PathInfo.compoundExtensionLC: String
val PathInfo.mimeTypeByExtension: MimeType
fun PathInfo.getPathComponents(): List<String>
fun PathInfo.getPathFullComponents(): List<String>
val PathInfo.fullName: String

// For instances including a pathInfo
val Path.fullPathNormalized: String
val Path.folder: String
val Path.folderWithSlash: String
val Path.baseName: String
val Path.fullPathWithoutExtension: String
fun Path.fullPathWithExtension(ext: String): String
val Path.fullNameWithoutExtension: String
val Path.baseNameWithoutExtension: String
val Path.fullNameWithoutCompoundExtension: String
val Path.baseNameWithoutCompoundExtension: String
fun Path.baseNameWithExtension(ext: String): String
fun Path.baseNameWithCompoundExtension(ext: String): String
val Path.extension: String
val Path.extensionLC: String
val Path.compoundExtension: String
val Path.compoundExtensionLC: String
val Path.mimeTypeByExtension: MimeType
fun Path.getPathComponents(): List<String>
fun Path.getPathFullComponents(): List<String>
val Path.fullName: String

VfsFile

data class VfsFile(
	val vfs: Vfs,
	val path: String
) : VfsNamed(path.pathInfo), AsyncInputOpenable, Extra by Extra.Mixin() {
	val parent: VfsFile
	val root: VfsFile
	val absolutePath: String

	operator fun get(path: String): VfsFile

	// @TODO: Kotlin suspend operator not supported yet!
	suspend fun set(path: String, content: String)
	suspend fun set(path: String, content: ByteArray)
	suspend fun set(path: String, content: AsyncStream)
	suspend fun set(path: String, content: VfsFile)

	suspend fun put(content: AsyncInputStream, attributes: List<Vfs.Attribute> = listOf()): Long
	suspend fun put(content: AsyncInputStream, vararg attributes: Vfs.Attribute): Long
	suspend fun write(data: ByteArray, vararg attributes: Vfs.Attribute): Long
	suspend fun writeBytes(data: ByteArray, vararg attributes: Vfs.Attribute): Long

	suspend fun writeStream(src: AsyncInputStream, vararg attributes: Vfs.Attribute, autoClose: Boolean = true): Long
	suspend fun writeFile(file: VfsFile, vararg attributes: Vfs.Attribute): Long
	suspend fun listNames(): List<String>
	suspend fun copyTo(target: AsyncOutputStream)
	suspend fun copyTo(target: VfsFile, vararg attributes: Vfs.Attribute): Long
	fun withExtension(ext: String): VfsFile
	fun withCompoundExtension(ext: String): VfsFile
	fun appendExtension(ext: String): VfsFile

	suspend fun open(mode: VfsOpenMode = VfsOpenMode.READ): AsyncStream
	suspend fun openInputStream(): AsyncInputStream

	override suspend fun openRead(): AsyncStream
	suspend inline fun <T> openUse(mode: VfsOpenMode
	suspend fun readRangeBytes(range: LongRange): ByteArray
	suspend fun readRangeBytes(range: IntRange): ByteArray

	suspend fun readAll(): ByteArray

	suspend fun read(): ByteArray
	suspend fun readBytes(): ByteArray

	suspend fun readLines(charset: Charset = UTF8): List<String>
	suspend fun writeLines(lines: List<String>, charset: Charset = UTF8)

	suspend fun readString(charset: Charset = UTF8): String
	suspend fun writeString(data: String, vararg attributes: Vfs.Attribute, charset: Charset = UTF8): Unit

	suspend fun readChunk(offset: Long, size: Int): ByteArray
	suspend fun writeChunk(data: ByteArray, offset: Long, resize: Boolean = false): Unit 

	suspend fun readAsSyncStream(): SyncStream

	suspend fun stat(): VfsStat
	suspend fun touch(time: DateTime, atime: DateTime = time): Unit
	suspend fun size(): Long
	suspend fun exists(): Boolean
	suspend fun isDirectory(): Boolean
	suspend fun setSize(size: Long): Unit
	suspend fun delete(): Unit
	suspend fun setAttributes(attributes: List<Vfs.Attribute>)
	suspend fun setAttributes(vararg attributes: Vfs.Attribute)

	suspend fun mkdir(attributes: List<Vfs.Attribute>)
	suspend fun mkdir(vararg attributes: Vfs.Attribute)

	suspend fun copyToTree(
		target: VfsFile,
		vararg attributes: Vfs.Attribute,
		notify: suspend (Pair<VfsFile, VfsFile>) -> Unit = {}
	): Unit

	suspend fun ensureParents(): VfsFile
	suspend fun renameTo(dstPath: String): Unit
	suspend fun list(): ReceiveChannel<VfsFile>
	suspend fun listRecursive(filter: (VfsFile) -> Boolean = { true }): ReceiveChannel<VfsFile>

	suspend fun exec(
		cmdAndArgs: List<String>,
		env: Map<String, String> = LinkedHashMap(),
		handler: VfsProcessHandler = VfsProcessHandler()
	): Int

	suspend fun execToString(
		cmdAndArgs: List<String>,
		env: Map<String, String> = LinkedHashMap(),
		charset: Charset = UTF8,
		captureError: Boolean = false,
		throwOnError: Boolean = true
	): String

	suspend fun execToString(vararg cmdAndArgs: String, charset: Charset = UTF8): String

	suspend fun passthru(
		cmdAndArgs: List<String>,
		env: Map<String, String> = LinkedHashMap(),
		charset: Charset = UTF8
	): Int

	suspend fun passthru(
		vararg cmdAndArgs: String,
		env: Map<String, String> = LinkedHashMap(),
		charset: Charset = UTF8
	): Int

	suspend fun watch(handler: suspend (Vfs.FileEvent) -> Unit): Closeable
	suspend fun redirected(pathRedirector: suspend VfsFile.(String) -> String): VfsFile
	fun jail(): VfsFile
	suspend fun getUnderlyingUnscapedFile(): FinalVfsFile
}

fun VfsFile.toUnscaped(): FinalVfsFile
fun FinalVfsFile.toFile(): VfsFile

data class FinalVfsFile(val file: VfsFile) {
    constructor(vfs: Vfs, path: String) : this(vfs[path])
    val vfs: Vfs
    val path: String
}

suspend inline fun <R> VfsFile.useVfs(callback: suspend (VfsFile) -> R): R

Vfs

open class VfsNamed(override val pathInfo: PathInfo) : Path
interface SimpleStorage {
	suspend fun get(key: String): String?
	suspend fun set(key: String, value: String)
	suspend fun remove(key: String)
}
abstract class Vfs : AsyncCloseable {
	open fun getAbsolutePath(path: String): String
	
    val root: VfsFile
	open val supportedAttributeTypes: List<KClass<out Attribute>>()
	
    operator fun get(path: String): VfsFile
	fun file(path: String) = root[path]

	override suspend fun close(): Unit = Unit

	fun createExistsStat(
		path: String, isDirectory: Boolean, size: Long, device: Long = -1, inode: Long = -1, mode: Int = 511,
		owner: String = "nobody", group: String = "nobody", createTime: DateTime = DateTime.EPOCH, modifiedTime: DateTime = DateTime.EPOCH,
		lastAccessTime: DateTime = modifiedTime, extraInfo: Any? = null, id: String? = null
	): VfsStat

	fun createNonExistsStat(path: String, extraInfo: Any? = null): VfsStat

	suspend fun exec(path: String, cmdAndArgs: List<String>, handler: VfsProcessHandler = VfsProcessHandler()): Int

	open suspend fun exec(path: String, cmdAndArgs: List<String>, env: Map<String, String>, handler: VfsProcessHandler = VfsProcessHandler()): Int

	open suspend fun open(path: String, mode: VfsOpenMode): AsyncStream
	open suspend fun openInputStream(path: String): AsyncInputStream
	open suspend fun readRange(path: String, range: LongRange): ByteArray

	open suspend fun put(path: String, content: AsyncInputStream, attributes: List<Attribute> = listOf()): Long
	suspend fun put(path: String, content: ByteArray, attributes: List<Attribute> = listOf()): Long
	suspend fun readChunk(path: String, offset: Long, size: Int): ByteArray
	suspend fun writeChunk(path: String, data: ByteArray, offset: Long, resize: Boolean)
	open suspend fun setSize(path: String, size: Long)
	open suspend fun setAttributes(path: String, attributes: List<Attribute>): Unit
	open suspend fun stat(path: String): VfsStat
	open suspend fun list(path: String): ReceiveChannel<VfsFile>
	open suspend fun mkdir(path: String, attributes: List<Attribute>): Boolean
	open suspend fun rmdir(path: String): Boolean
	open suspend fun delete(path: String): Boolean
	open suspend fun rename(src: String, dst: String): Boolean
	open suspend fun watch(path: String, handler: (FileEvent) -> Unit): Closeable
	open suspend fun touch(path: String, time: DateTime, atime: DateTime)
	open suspend fun getUnderlyingUnscapedFile(path: String): FinalVfsFile

	interface Attribute
	inline fun <reified T> Iterable<Attribute>.get(): T?

	abstract class Proxy : Vfs() {
		protected abstract suspend fun access(path: String): VfsFile
		protected open suspend fun VfsFile.transform(): VfsFile = file(this.path)
		final override suspend fun getUnderlyingUnscapedFile(path: String): FinalVfsFile = initOnce().access(path).getUnderlyingUnscapedFile()
		protected open suspend fun init() = Unit
		var initialized = false
	}

	open class Decorator(val parent: VfsFile) : Proxy() {
		val parentVfs = parent.vfs
		override suspend fun access(path: String): VfsFile = parentVfs[path]
	}

	data class FileEvent(val kind: Kind, val file: VfsFile, val other: VfsFile? = null) {
		enum class Kind { DELETED, MODIFIED, CREATED, RENAMED }
	}
}

enum class VfsOpenMode(
    val cmode: String,
    val write: Boolean,
    val createIfNotExists: Boolean = false,
    val truncate: Boolean = false
) {
    READ("rb", write = false),
    WRITE("r+b", write = true, createIfNotExists = true),
    APPEND("a+b", write = true, createIfNotExists = true),
    CREATE_OR_TRUNCATE("w+b", write = true, createIfNotExists = true, truncate = true),
    CREATE("w+b", write = true, createIfNotExists = true),
    CREATE_NEW("w+b", write = true);
}

open class VfsProcessHandler {
    open suspend fun onOut(data: ByteArray): Unit = Unit
    open suspend fun onErr(data: ByteArray): Unit = Unit
}

class VfsProcessException(message: String) : IOException(message)

data class VfsStat(
    val file: VfsFile,
    val exists: Boolean,
    val isDirectory: Boolean,
    val size: Long,
    val device: Long = -1L,
    val inode: Long = -1L,
    val mode: Int = 511,
    val owner: String = "nobody",
    val group: String = "nobody",
    val createTime: DateTime = DateTime.EPOCH,
    val modifiedTime: DateTime = createTime,
    val lastAccessTime: DateTime = modifiedTime,
    val extraInfo: Any? = null,
    val id: String? = null
) : Path by file

val VfsStat.createDate: DateTime get() = createTime
val VfsStat.modifiedDate: DateTime get() = modifiedTime
val VfsStat.lastAccessDate: DateTime get() = lastAccessTime

suspend fun ByteArray.writeToFile(path: String) = localVfs(path).write(this)
suspend fun ByteArray.writeToFile(file: VfsFile) = file.write(this)

Standard File Systems

var resourcesVfsDebug = false
val resourcesVfs: VfsFile
val rootLocalVfs: VfsFile
val applicationVfs: VfsFile
val applicationDataVfs: VfsFile
val cacheVfs: VfsFile
val externalStorageVfs: VfsFile
val userHomeVfs: VfsFile
val tempVfs: VfsFile
val localCurrentDirVfs: VfsFile
fun localVfs(path: String): VfsFile
fun jailedLocalVfs(base: String): VfsFile

IsoVfs

suspend fun IsoVfs(file: VfsFile): VfsFile
suspend fun IsoVfs(s: AsyncStream): VfsFile
suspend fun AsyncStream.openAsIso(): IsoVfs
suspend fun VfsFile.openAsIso(): IsoVfs
suspend fun <R> AsyncStream.openAsIso(callback: suspend (VfsFile) -> R): R
suspend fun <R> VfsFile.openAsIso(callback: suspend (VfsFile) -> R): R

JailVfs

fun VfsFile.jail(): VfsFile
fun JailVfs(jailRoot: VfsFile): VfsFile

LocalVfs

abstract class LocalVfs : Vfs() {
	companion object { operator fun get(base: String): VfsFile = localVfs(base) }
	override fun toString(): String = "LocalVfs"
}

LogVfs

fun VfsFile.log(): VfsFile = LogVfs(this).root

class LogVfs(val parent: VfsFile) : Vfs.Proxy() {
	val log: List<String>
	val logstr: Strin
	val modifiedFiles: Set<String>()
}

MapLikeStorageVfs

fun SimpleStorage.toVfs(): VfsFile = MapLikeStorageVfs(this).root
class MapLikeStorageVfs(val storage: SimpleStorage) : Vfs()

MemoryVfs

fun MemoryVfs(items: Map<String, AsyncStream> = LinkedHashMap(), caseSensitive: Boolean = true): VfsFile
fun MemoryVfsMix(
	items: Map<String, Any> = LinkedHashMap(),
	caseSensitive: Boolean = true,
	charset: Charset = UTF8
): VfsFile
fun MemoryVfsMix(vararg items: Pair<String, Any>, caseSensitive: Boolean = true, charset: Charset = UTF8): VfsFile

MergedVfs

open class MergedVfs(vfsList: List<VfsFile> = listOf()) : Vfs.Proxy() {
	operator fun plusAssign(other: VfsFile)
	operator fun minusAssign(other: VfsFile)
}

MountableVfs

interface Mountable {
	fun mount(folder: String, file: VfsFile): Mountable
	fun unmount(folder: String): Mountable
}

// VfsFile.vfs is Mountable
suspend fun MountableVfs(closeMounts: Boolean = false, callback: suspend Mountable.() -> Unit): VfsFile

NodeVfs

open class NodeVfs(val caseSensitive: Boolean = true) : Vfs() {
	val events = Signal<FileEvent>()
	val rootNode = Node("", isDirectory = true)

	open inner class Node() : Iterable<Node> {
		val name: String
		val isDirectory: Boolean
		parent: Node? = null
		val nameLC: String
		
		var parent: Node?
		var data: Any?
		val children: Map<String, Node>()
		val childrenLC: Map<String, Node>()
		val root: Node
		var stream: AsyncStream? = null

		fun child(name: String): Node?
		fun createChild(name: String, isDirectory: Boolean = false): Node

		operator fun get(path: String): Node

		fun getOrNull(path: String): Node?

		fun access(path: String, createFolders: Boolean = false): Node
		fun mkdir(name: String): Boolean
	}
}

UniversalVfs

val String.uniVfs: UniversalVfs
fun String.uniVfs(providers: UniSchemaProviders, base: VfsFile? = null): VfsFile 

object UniversalVfs {
	operator fun invoke(uri: String, providers: UniSchemaProviders, base: VfsFile? = null): VfsFile
}

class UniSchema(val name: String, val provider: (URL) -> VfsFile)

class UniSchemaProviders(val providers: Map<String, UniSchema>) {
	constructor(providers: Iterable<UniSchema>)
	constructor(vararg providers: UniSchema)
}

var defaultUniSchema = UniSchemaProviders(
	UniSchema("http") { UrlVfs(it) },
	UniSchema("https") { UrlVfs(it) },
	UniSchema("file") { rootLocalVfs[it.path] }
)

fun registerUniSchema(schema: UniSchema)

inline fun <T> registerUniSchemaTemporarily(schema: UniSchema, callback: () -> T): T

operator fun UniSchemaProviders.plus(other: UniSchemaProviders)
operator fun UniSchemaProviders.plus(other: UniSchema)

operator fun UniSchemaProviders.minus(other: UniSchemaProviders): UniSchemaProviders
operator fun UniSchemaProviders.minus(other: UniSchema)

UrlVfs

fun UrlVfs(url: String, client: HttpClient = createHttpClient()): VfsFile
fun UrlVfs(url: URL, client: HttpClient = createHttpClient()): VfsFile
fun UrlVfsJailed(url: String, client: HttpClient = createHttpClient()): VfsFile
fun UrlVfsJailed(url: URL, client: HttpClient = createHttpClient()): VfsFile

class UrlVfs(val url: String, val dummy: Unit, val client: HttpClient = createHttpClient()) : Vfs() {
	class HttpHeaders(val headers: Http.Headers) : Attribute

	fun getFullUrl(path: String): String {
		val result = url.trim('/') + '/' + path.trim('/')
		//println("UrlVfs.getFullUrl: url=$url, path=$path, result=$result")
		return result
	}
}

ZipVfs

// Create Zip File
suspend fun VfsFile.createZipFromTree(): ByteArray
suspend fun VfsFile.createZipFromTreeTo(s: AsyncStream)

// Opening Zip File
suspend fun VfsFile.openAsZip(caseSensitive: Boolean = true): VfsFile
suspend fun AsyncStream.openAsZip(caseSensitive: Boolean = true)suspend fun <R> VfsFile.openAsZip(caseSensitive: Boolean = true, callback: suspend (VfsFile) -> R): R
suspend fun <R> AsyncStream.openAsZip(caseSensitive: Boolean = true, callback: suspend (VfsFile) -> R): R
suspend fun ZipVfs(s: AsyncStream, zipFile: VfsFile? = null, caseSensitive: Boolean = true, closeStream: Boolean = false): VfsFile