diff options
author | nea <romangraef@gmail.com> | 2021-08-12 23:37:47 +0200 |
---|---|---|
committer | nea <romangraef@gmail.com> | 2021-08-12 23:37:47 +0200 |
commit | 2ee4b7c2bef9f80ed12e58bc3cdbb965299920e1 (patch) | |
tree | d4ac288bb7c9e15acfd9cfa1868a69b0b15fc30e | |
parent | 14f72a3d6005dec3f9fc1e9832161b5de0d1b354 (diff) | |
download | webos-2ee4b7c2bef9f80ed12e58bc3cdbb965299920e1.tar.gz webos-2ee4b7c2bef9f80ed12e58bc3cdbb965299920e1.tar.bz2 webos-2ee4b7c2bef9f80ed12e58bc3cdbb965299920e1.zip |
file system rework
-rw-r--r-- | docs/DESIGN.md | 27 | ||||
-rw-r--r-- | src/main/kotlin/Path.kt | 75 | ||||
-rw-r--r-- | src/main/kotlin/WebOS.kt | 102 | ||||
-rw-r--r-- | src/main/kotlin/files.kt | 86 | ||||
-rw-r--r-- | src/main/kotlin/util/sequence.kt | 5 |
5 files changed, 174 insertions, 121 deletions
diff --git a/docs/DESIGN.md b/docs/DESIGN.md deleted file mode 100644 index f3dd5aa..0000000 --- a/docs/DESIGN.md +++ /dev/null @@ -1,27 +0,0 @@ -# Components of WebOs - -## WebOS - -A WebOS is the uppermost parent object of everything. It holds multiple console windows, one or more file systems, and other capabilities of the OS, such as whether network access, user control and system configurations. - -## Console - -A Console represents one access port to a [WebOS]. It can be a virtual console, which has no visual output port (potentially in the future used as a daemon), or one bound to a HTML Element, to which its output will be rendered. - -## Activity - -An Activity is a program which has direct access to a [Console]s rendering output, such as a shell which runs programs with only a STDOUT available. - -## FileSystem - -A FileSystem is composed of multiple [Mount]s to which it delegates file accesses to. - -## Mount - -A Mount processes individual file accesses and actions in its domain. It controls most files within its prefix, however it is not aware of it's prefix. A mount may be mounted at multiple paths. - - - -[Console]: #console -[WebOS]: #webos -[Activity]: #activity diff --git a/src/main/kotlin/Path.kt b/src/main/kotlin/Path.kt new file mode 100644 index 0000000..0e209cf --- /dev/null +++ b/src/main/kotlin/Path.kt @@ -0,0 +1,75 @@ +import util.expandWith + +sealed interface Path { + val parts: List<String> + fun toAbsolutePath(relativeTo: Absolute): Absolute { + return relativeTo.resolve(this) + } + + fun resolve(path: Path): Path + + companion object { + val root = Absolute(listOf()) + + fun ofShell(string: String, userHome: Absolute): Path = + ofShell(string.split("/"), userHome) + + fun ofShell(vararg parts: String, userHome: Absolute): Path = + ofShell(parts.toList(), userHome) + + fun of(vararg parts: String): Path = + of(parts.toList()) + + fun of(string: String): Path = + of(string.split("/")) + + fun ofShell(parts: List<String>, userHome: Absolute): Path { + if (parts.firstOrNull() == "~") + return userHome.resolve(Relative(parts.subList(1, parts.size).filter { it.isNotEmpty() })) + return of(parts) + } + + fun of(parts: List<String>): Path { + if (parts.isEmpty()) + return root + if (parts[0] == "") // Starts with a / + return Absolute(parts.subList(1, parts.size).filter { it.isNotEmpty() }) + return Relative(parts.filter { it.isNotEmpty() }) + } + } + + data class Relative internal constructor(override val parts: List<String>) : Path { + override fun resolve(path: Path): Path { + if (path is Absolute) return path + return Relative(this.parts + path.parts) + } + } + + data class Absolute internal constructor(override val parts: List<String>) : Path { + override fun resolve(path: Path): Absolute { + if (path is Absolute) return path + return Absolute(this.parts + path.parts) + } + + fun relativize(path: Path): Relative = when (path) { + is Relative -> path + is Absolute -> { + var commonPrefix = true + val partList = mutableListOf<String>() + var returns = 0 + for ((idx, part) in path.parts.withIndex()) { + if (idx < this.parts.size) { + if (this.parts[idx] == part && commonPrefix) { + continue + } else { + commonPrefix = false + returns++ + } + } + partList.add(part) + } + Relative(List(returns) { ".." } + partList) + } + } + } +} diff --git a/src/main/kotlin/WebOS.kt b/src/main/kotlin/WebOS.kt index 6fe4ceb..9160a75 100644 --- a/src/main/kotlin/WebOS.kt +++ b/src/main/kotlin/WebOS.kt @@ -29,15 +29,15 @@ class Console(val os: WebOS, val renderElement: Element?) { var shouldRerender = true - lateinit var currentUser: User + var currentUser: User? = null - private var _workingDirectory: AbsolutPath? = null + private var _workingDirectory: Path.Absolute? = null - var workingDirectory: AbsolutPath - get() { - if (_workingDirectory == null) _workingDirectory = currentUser.homeDirectory; return _workingDirectory!! + var workingDirectory: Path.Absolute + get() = _workingDirectory ?: currentUser?.homeDirectory ?: Path.root + set(value) { + _workingDirectory = value } - set(value) { _workingDirectory = value } fun openActivity(activity: Activity) { activityStack.addLast(activity) @@ -67,7 +67,7 @@ class Console(val os: WebOS, val renderElement: Element?) { class WebOS { private val _consoles = mutableListOf<Console>() val consoles get() = _consoles.toList() - val IOHandler = IOHandler() + val files = IOHandler() fun registerConsole(element: Element) { _consoles.add(Console(this, element)) } @@ -75,92 +75,6 @@ class WebOS { data class User( val name: String, - val homeDirectory: AbsolutPath + val homeDirectory: Path.Absolute ) -data class RelativePath internal constructor(override val parts: List<String>) : Path { - override fun toAbsolutPath(context: Console): AbsolutPath { - val result = mutableListOf<String>() - for (part in parts) { - when (part) { - "." -> result.addAll(context.workingDirectory.parts) - "~" -> result.addAll(context.currentUser.homeDirectory.parts) - ".." -> { result.removeLast() } - else -> result.add(part) - } - } - return AbsolutPath(result) - } -} - -data class AbsolutPath internal constructor(override val parts: List<String>) : Path { - override fun toAbsolutPath(context: Console): AbsolutPath = this -} - -sealed interface Path { - val parts: List<String> - fun toAbsolutPath(context: Console): AbsolutPath - companion object { - fun of(string: String): Path { - val isAbsolut = string.first() == '/' - val parts = string.split("/") - val aparts = parts.subList(1, parts.size) - if (aparts.contains(".") || aparts.contains("~")) - throw IllegalArgumentException("'.' and '~' are only allowed at the beginning of a relative path") - if (!isAbsolut) return RelativePath(parts) - if (aparts.contains("..")) throw IllegalArgumentException("'..' is only allowed in a relative path") - return AbsolutPath(aparts) - } - } -} - -class FileSystem { - fun read(relativePath: Path): ReadResult = TODO() - fun write(path: Path, data: ByteArray): Unit = TODO() - fun stat(path: Path): Unit = TODO() -} - -class IOHandler { - val mounts = mutableListOf<Mount>() - fun mount(absolutPath: AbsolutPath, fileSystem: FileSystem) { - mounts += Mount(absolutPath, fileSystem) - } - fun unmount(mountPoint: AbsolutPath) { - mounts.removeAll { it.mountPoint == mountPoint } - } - fun <T> findMountFor(context: Console, path: Path, operation: FileSystem.(relativePath: Path) -> T): T { - val absolutPath = path.toAbsolutPath(context) - val mount = mounts.filter { - var result = true - it.mountPoint.parts.forEachIndexed { index, part -> if (absolutPath.parts[index] != part) { - result = false - } } - result - }.maxByOrNull { it.mountPoint.parts.size} ?: throw IllegalStateException("No mount present") - return mount.fileSystem.operation( - AbsolutPath(absolutPath.parts.subList(mount.mountPoint.parts.size, absolutPath.parts.size)) - ) - } - - fun read(context: Console, path: Path): ReadResult = findMountFor(context, path) { read(it) } - fun write(context: Console, path: Path, data: ByteArray): Unit = findMountFor(context, path) { write(it, data) } - fun stat(context: Console, path: Path): Unit = findMountFor(context, path) { stat(it) } -} - -sealed class ReadResult { - class Success(val text: String) : ReadResult() - object NotFound - object NoAccess -} -data class Mount ( - val mountPoint: AbsolutPath, - val fileSystem: FileSystem -) - -data class Stat( - val exists: Boolean, - var owner: User, - val created: Long, - val edited: Long, - val size: Long -) diff --git a/src/main/kotlin/files.kt b/src/main/kotlin/files.kt new file mode 100644 index 0000000..990ae7e --- /dev/null +++ b/src/main/kotlin/files.kt @@ -0,0 +1,86 @@ +class IOHandler { + val mounts = mutableListOf<Mount>() + fun mount(absolutePath: Path.Absolute, fileSystem: FileSystem) { + if (mounts.any { it.mountPoint == absolutePath }) + return // TODO sensible error message handling + mounts += Mount(absolutePath, fileSystem) + } + + fun unmount(mountPoint: Path.Absolute) { + mounts.removeAll { it.mountPoint == mountPoint } + } + + fun <T> findMountFor( + workingDirectory: Path.Absolute, + path: Path, + operation: FileSystem.(relativePath: Path) -> T + ): T { + val absolutPath = path.toAbsolutePath(workingDirectory) + val mount = mounts.filter { + it.mountPoint.parts.zip(absolutPath.parts).all { (a, b) -> a == b } + }.maxByOrNull { it.mountPoint.parts.size } ?: throw IllegalStateException("No mount present") + return mount.fileSystem.operation( + Path.Absolute( + absolutPath.parts.subList( + mount.mountPoint.parts.size, + absolutPath.parts.size + ) + ) // TODO: unangenehm + ) + } + + fun findINode(absolutePath: Path.Absolute): INode { + val mount = mounts.filter { + it.mountPoint.parts.zip(absolutePath.parts).all { (a, b) -> a == b } + }.maxByOrNull { it.mountPoint.parts.size } ?: throw IllegalStateException("No mount present") + val iNode = mount.fileSystem.getINode(absolutePath.relativize(mount.mountPoint)) + return when (iNode) { + is INodeResult.File -> iNode.op + is INodeResult.ResolveAgain -> findINode(absolutePath.resolve(iNode.relativeToOriginal)) + } + } + + fun read(workingDirectory: Path.Absolute, path: Path): ReadResult = + findMountFor(workingDirectory, path) { read(it) } + + fun write(workingDirectory: Path.Absolute, path: Path, data: ByteArray): Unit = + findMountFor(workingDirectory, path) { write(it, data) } + + fun stat(workingDirectory: Path.Absolute, path: Path): Unit = + findMountFor(workingDirectory, path) { stat(it) } +} + +interface INode { + val fs: FileSystem +} + +sealed interface INodeResult { + class File(val op: INode) : INodeResult + class ResolveAgain(val relativeToOriginal: Path): INodeResult +} + +interface FileSystem { + fun getINode(relativePath: Path.Relative): INodeResult + fun read(relativePath: Path): ReadResult + fun write(path: Path, data: ByteArray): Unit // Write result + fun stat(path: Path): Unit // TODO Stat result +} + +sealed class ReadResult { + class Success(val text: String) : ReadResult() + object NotFound : ReadResult() + object NoAccess : ReadResult() +} + +data class Mount( + val mountPoint: Path.Absolute, + val fileSystem: FileSystem +) + +data class Stat( + val exists: Boolean, + var owner: User, + val created: Long, + val edited: Long, + val size: Long +) diff --git a/src/main/kotlin/util/sequence.kt b/src/main/kotlin/util/sequence.kt new file mode 100644 index 0000000..72b07dc --- /dev/null +++ b/src/main/kotlin/util/sequence.kt @@ -0,0 +1,5 @@ +package util + +fun <T> Iterable<T>.expandWith(t: T): Sequence<T> = + this.asSequence() + generateSequence { t }.asSequence() + |