Skip to main content Skip to docs navigation

File System

PathInfo, VfsFile, Vfs, IsoVfs, JailVfs, LocalVfs, LogVfs, MemoryVfs, MergedVfs, MountableVfs, NodeVfs, UrlVfs, ZipVfs

Document not reviewed yet, might be outdated. Please, let us know if you find something invalid here.
On this page

KorIO has a Virtual File System functionality.

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
Was this article useful?