diff --git a/korio/src/commonMain/kotlin/com/soywiz/korio/file/Vfs.kt b/korio/src/commonMain/kotlin/com/soywiz/korio/file/Vfs.kt index b70fed638..169fa5c3f 100644 --- a/korio/src/commonMain/kotlin/com/soywiz/korio/file/Vfs.kt +++ b/korio/src/commonMain/kotlin/com/soywiz/korio/file/Vfs.kt @@ -4,6 +4,7 @@ package com.soywiz.korio.file import com.soywiz.klock.* import com.soywiz.korio.async.* +import com.soywiz.korio.experimental.* import com.soywiz.korio.file.std.* import com.soywiz.korio.lang.* import com.soywiz.korio.stream.* @@ -31,14 +32,17 @@ abstract class Vfs : AsyncCloseable { 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 + lastAccessTime: DateTime = modifiedTime, extraInfo: Any? = null, id: String? = null, + cache: Boolean = false ) = VfsStat( file = file(path), exists = true, isDirectory = isDirectory, size = size, device = device, inode = inode, mode = mode, owner = owner, group = group, createTime = createTime, modifiedTime = modifiedTime, lastAccessTime = lastAccessTime, extraInfo = extraInfo, id = id - ) + ).also { + if (cache) it.file.cachedStat = it + } - fun createNonExistsStat(path: String, extraInfo: Any? = null) = VfsStat( + fun createNonExistsStat(path: String, extraInfo: Any? = null, cache: Boolean = false) = VfsStat( file = file(path), exists = false, isDirectory = false, size = 0L, device = -1L, inode = -1L, mode = 511, owner = "nobody", group = "nobody", createTime = DateTime.EPOCH, modifiedTime = DateTime.EPOCH, lastAccessTime = DateTime.EPOCH, extraInfo = extraInfo @@ -277,6 +281,7 @@ open class VfsProcessHandler { class VfsProcessException(message: String) : IOException(message) +@OptIn(KorioExperimentalApi::class) data class VfsStat( val file: VfsFile, val exists: Boolean, @@ -294,7 +299,11 @@ data class VfsStat( val kind: Vfs.FileKind? = null, val id: String? = null ) : Path by file { - val enrichedFile get() = file.copy().also { it.cachedStat = this } + val enrichedFile: VfsFile get() = file.copy().also { it.cachedStat = this } + + //@Deprecated("Use file instead") + //val enrichedFile: VfsFile get() = file + //init { file.cachedStat = this } fun toString(showFile: Boolean): String = "VfsStat(" + ArrayList(16).also { al -> if (showFile) al.add("file=$file") else al.add("file=${file.absolutePath}") diff --git a/korio/src/commonMain/kotlin/com/soywiz/korio/file/std/CatalogVfs.kt b/korio/src/commonMain/kotlin/com/soywiz/korio/file/std/CatalogVfs.kt new file mode 100644 index 000000000..b62b90c07 --- /dev/null +++ b/korio/src/commonMain/kotlin/com/soywiz/korio/file/std/CatalogVfs.kt @@ -0,0 +1,55 @@ +package com.soywiz.korio.file.std + +import com.soywiz.kds.* +import com.soywiz.klock.* +import com.soywiz.korio.dynamic.* +import com.soywiz.korio.file.* +import com.soywiz.korio.serialization.json.* +import kotlinx.coroutines.flow.* + +fun VfsFile.withCatalog(): VfsFile = CatalogVfs(this).root +fun VfsFile.withCatalogJail(): VfsFile = CatalogVfs(this.jail()).root + +open class CatalogVfs(val parent: VfsFile) : Vfs.Proxy() { + + override suspend fun access(path: String): VfsFile = parent[path] + + override suspend fun stat(path: String): VfsStat { + val normalizedPath = PathInfo(path).normalize() + if (normalizedPath == "/" || normalizedPath == "") { + return createExistsStat("/", isDirectory = true, size = 0L, cache = true) + } + val baseName = PathInfo(normalizedPath).baseName + val info = cachedListSimpleStats(PathInfo(normalizedPath).parent.fullPath) + return info[baseName] ?: createNonExistsStat(normalizedPath, cache = true) + } + + override suspend fun listFlow(path: String): Flow = listSimple(path).asFlow() + + override suspend fun listSimple(path: String): List = + cachedListSimpleStats(path).map { it.value.enrichedFile } + + private val catalogCache = FastStringMap>() + + suspend fun cachedListSimpleStats(path: String): Map { + val key = PathInfo(path).normalize() + return catalogCache.getOrPut(key) { listSimpleStats(key) } + } + + suspend fun listSimpleStats(path: String): Map { + val catalogJsonString = parent[path]["\$catalog.json"].readString() + val data = Json.parse(catalogJsonString).dyn + + return data.list.map { + val localName = PathInfo(it["name"].str).baseName + createExistsStat( + path = "$path/$localName", + isDirectory = it["isDirectory"].bool, + size = it["size"].long, + createTime = DateTime.fromUnix(it["createTime"].long), + modifiedTime = DateTime.fromUnix(it["modifiedTime"].long), + cache = true + ) + }.associateBy { it.baseName } + } +} diff --git a/korio/src/commonMain/kotlin/com/soywiz/korio/file/std/UrlVfs.kt b/korio/src/commonMain/kotlin/com/soywiz/korio/file/std/UrlVfs.kt index c6791ec98..6a5b97034 100644 --- a/korio/src/commonMain/kotlin/com/soywiz/korio/file/std/UrlVfs.kt +++ b/korio/src/commonMain/kotlin/com/soywiz/korio/file/std/UrlVfs.kt @@ -11,12 +11,14 @@ import com.soywiz.korio.serialization.json.* import com.soywiz.korio.stream.* import com.soywiz.korio.util.* -fun UrlVfs(url: String, client: HttpClient = createHttpClient(), failFromStatus: Boolean = true): VfsFile = UrlVfs(URL(url), client, failFromStatus) +fun UrlVfs(url: String, client: HttpClient = createHttpClient(), failFromStatus: Boolean = true): VfsFile = + UrlVfs(URL(url), client, failFromStatus) fun UrlVfs(url: URL, client: HttpClient = createHttpClient(), failFromStatus: Boolean = true): VfsFile = UrlVfs(url.copy(path = "", query = null).fullUrl, Unit, client, failFromStatus)[url.path] -fun UrlVfsJailed(url: String, client: HttpClient = createHttpClient(), failFromStatus: Boolean = true): VfsFile = UrlVfsJailed(URL(url), client, failFromStatus) +fun UrlVfsJailed(url: String, client: HttpClient = createHttpClient(), failFromStatus: Boolean = true): VfsFile = + UrlVfsJailed(URL(url), client, failFromStatus) fun UrlVfsJailed(url: URL, client: HttpClient = createHttpClient(), failFromStatus: Boolean = true): VfsFile = UrlVfs(url.fullUrl, Unit, client, failFromStatus)[url.path] @@ -161,23 +163,7 @@ class UrlVfs( } override suspend fun listSimple(path: String): List { - return listSimpleStats(path).map { it.file } - } - - suspend fun listSimpleStats(path: String): List { - val catalogJsonString = this[path]["\$catalog.json"].readString() - val data = Json.parse(catalogJsonString).dyn - - return data.list.map { - val localName = PathInfo(it["name"].str).baseName - createExistsStat( - path = "$path/$localName", - isDirectory = it["isDirectory"].bool, - size = it["size"].long, - createTime = DateTime.fromUnix(it["createTime"].long), - modifiedTime = DateTime.fromUnix(it["modifiedTime"].long), - ) - } + TODO() } override fun toString(): String = "UrlVfs" diff --git a/korio/src/commonTest/kotlin/com/soywiz/korio/vfs/CatalogVfsTest.kt b/korio/src/commonTest/kotlin/com/soywiz/korio/vfs/CatalogVfsTest.kt new file mode 100644 index 000000000..564e8d837 --- /dev/null +++ b/korio/src/commonTest/kotlin/com/soywiz/korio/vfs/CatalogVfsTest.kt @@ -0,0 +1,34 @@ +package com.soywiz.korio.vfs + +import com.soywiz.korio.async.* +import com.soywiz.korio.file.* +import com.soywiz.korio.file.std.* +import kotlinx.coroutines.flow.* +import kotlin.test.* + +class CatalogVfsTest { + @Test + fun test() = suspendTest { + val vfs = MemoryVfsMix( + "/\$catalog.json" to """[ + {"name": "demo", "size": 96, "modifiedTime": 0, "createTime": 0, "isDirectory": true}, + {"name": "korge.png", "size": 14015, "modifiedTime": 1, "createTime": 1, "isDirectory": false}, + {"name": "test.txt", "size": 11, "modifiedTime": 2, "createTime": 2, "isDirectory": false}, + ]""", + "/demo/\$catalog.json" to """[ + {"name": "test.txt", "size": 12, "modifiedTime": 2, "createTime": 2, "isDirectory": false}, + ]""", + ).withCatalog() + + assertEquals( + "/demo,/demo/test.txt,/korge.png,/test.txt", + vfs.listRecursive().toList().joinToString(",") { it.fullPathNormalized } + ) + + assertEquals(0L, vfs["/"].size()) + assertEquals(96L, vfs["/demo"].size()) + assertEquals(14015L, vfs["/korge.png"].size()) + assertEquals(11L, vfs["/test.txt"].size()) + assertEquals(12L, vfs["/demo/test.txt"].size()) + } +} diff --git a/korio/src/jsMain/kotlin/com/soywiz/korio/runtime/browser/JsRuntimeBrowser.kt b/korio/src/jsMain/kotlin/com/soywiz/korio/runtime/browser/JsRuntimeBrowser.kt index b1743d540..7114ddd4a 100644 --- a/korio/src/jsMain/kotlin/com/soywiz/korio/runtime/browser/JsRuntimeBrowser.kt +++ b/korio/src/jsMain/kotlin/com/soywiz/korio/runtime/browser/JsRuntimeBrowser.kt @@ -2,9 +2,7 @@ package com.soywiz.korio.runtime.browser import com.soywiz.korio.file.SimpleStorage import com.soywiz.korio.file.VfsFile -import com.soywiz.korio.file.std.MapLikeStorageVfs -import com.soywiz.korio.file.std.MemoryVfs -import com.soywiz.korio.file.std.UrlVfs +import com.soywiz.korio.file.std.* import com.soywiz.korio.net.QueryString import com.soywiz.korio.net.http.Http import com.soywiz.korio.net.http.HttpClient @@ -44,7 +42,7 @@ object JsRuntimeBrowser : JsRuntime() { override fun langs(): List = window.navigator.languages.asList() override fun openVfs(path: String): VfsFile { - return UrlVfs(currentDir())[path].also { + return UrlVfs(currentDir())[path].withCatalogJail().root.also { println("BROWSER openVfs: currentDir=${currentDir()}, path=$path, urlVfs=$it") } }