summaryrefslogtreecommitdiff
path: root/src/jsMain/kotlin
diff options
context:
space:
mode:
authornea <romangraef@gmail.com>2021-08-20 15:33:32 +0200
committernea <romangraef@gmail.com>2021-08-20 15:33:58 +0200
commit5042498c8fe12751c1b6560ae1fde07e7cd78259 (patch)
tree688aa93978ffffd0b15c639768fdad6f17609bdd /src/jsMain/kotlin
parent65a6ffeb6761994d4d69011c87f23fe074f7a1f7 (diff)
downloadwebos-master.tar.gz
webos-master.tar.bz2
webos-master.zip
aaaaaaaaHEADmaster
Diffstat (limited to 'src/jsMain/kotlin')
-rw-r--r--src/jsMain/kotlin/WebOS.kt345
-rw-r--r--src/jsMain/kotlin/io/Path.kt78
-rw-r--r--src/jsMain/kotlin/io/files.kt203
-rw-r--r--src/jsMain/kotlin/util/sequence.kt5
4 files changed, 0 insertions, 631 deletions
diff --git a/src/jsMain/kotlin/WebOS.kt b/src/jsMain/kotlin/WebOS.kt
deleted file mode 100644
index 4ad2f86..0000000
--- a/src/jsMain/kotlin/WebOS.kt
+++ /dev/null
@@ -1,345 +0,0 @@
-import io.FileService
-import io.Path
-import io.PrimitiveFileService
-import io.PrimitiveINode
-import kotlinx.browser.document
-import kotlinx.browser.window
-import org.w3c.dom.Element
-import org.w3c.dom.Node
-import org.w3c.dom.asList
-import org.w3c.dom.events.Event
-import org.w3c.dom.events.KeyboardEvent
-import kotlin.properties.Delegates
-
-fun main() {
- console.log("Hello from Kotlin")
- val webos = WebOS()
- document.body?.addEventListener("load", {
- document.body?.querySelectorAll(".webosconsole")?.asList()?.forEach {
- if (it !is Element) return@forEach
- webos.registerConsole(it)
- }
- })
-}
-
-data class CharacterRun(val text: String, val color: String)
-
-abstract class Activity(val console: Console) {
-
- open fun handleKeyPress(key: Key) {}
- open fun update() {}
- abstract fun render(columns: Int, rows: Int): List<List<CharacterRun>>
-}
-
-class ShellRunner(console: Console) : Activity(console) {
-
- val history = mutableListOf<String>()
-
- val shellProgramStack = ArrayDeque<ShellProgram>()
-
- var lastLine = ""
-
- fun newLine() {
- history.add(lastLine)
- lastLine = ""
- console.invalidateRender()
- }
-
- fun println(it: String = "") {
- print(it)
- newLine()
- }
-
- fun print(it: String) {
- lastLine += it
- console.invalidateRender()
- }
-
- override fun update() {
- shellProgramStack.lastOrNull()?.update()
- }
-
- fun getInput(): String? {
- if (inputBuffer.contains("\n")) {
- val r = inputBuffer.substringBefore("\n")
- inputBuffer = inputBuffer.substringAfter("\n")
- return r
- }
- return null
- }
-
- override fun render(columns: Int, rows: Int): List<List<CharacterRun>> =
- history.map { listOf(CharacterRun(it.take(columns), "white")) }.takeLast(rows - 1) + listOf(
- listOf(
- CharacterRun(
- lastLine + inputBuffer,
- "white"
- )
- )
- )
-
- var inputBuffer = ""
-
- override fun handleKeyPress(key: Key) = when (key) {
- is Key.Printable -> {
- inputBuffer += key.char
- }
- is Key.Enter -> {
- inputBuffer += "\n"
- }
- else -> Unit
- }
-
- fun <T : ShellProgram> openShellProgram(program: (ShellRunner) -> T): ShellRunner = openShellProgram(program(this))
-
- fun <T : ShellProgram> openShellProgram(program: T): ShellRunner {
- shellProgramStack.addLast(program)
- return this
- }
-
-}
-
-abstract class ShellProgram(val shellRunner: ShellRunner) {
-
- abstract fun update(): Unit
-}
-
-sealed class Key {
- object Alt : Key()
- object AltGraph : Key()
- object CapsLock : Key()
- object Control : Key()
- object Function : Key()
- object FunctionLock : Key()
- object Hyper : Key()
- object Meta : Key()
- object NumLock : Key()
- object Shift : Key()
- object Super : Key()
- object Symbol : Key()
- object SymbolLock : Key()
- object Enter : Key()
- object Tab : Key()
- sealed class Arrow : Key() {
- object Down : Key()
- object Left : Key()
- object Right : Key()
- object Up : Key()
- }
-
- object End : Key()
- object Home : Key()
- object PageUp : Key()
- object PageDown : Key()
- object Backspace : Key()
- object Clear : Key()
- object Copy : Key()
- object CrSel : Key()
- object Cut : Key()
- object Delete : Key()
- object EraseEof : Key()
- object ExSel : Key()
- object Insert : Key()
- object Paste : Key()
- object Redo : Key()
- object Undo : Key()
- object Accept : Key()
- object Again : Key()
- object Attn : Key()
- object Cancel : Key()
- object ContextMenu : Key()
- object Escape : Key()
- object Execute : Key()
- object Find : Key()
- object Finish : Key()
- object Help : Key()
- class Printable(val char: Char) : Key()
- class FunctionN(val n: Int) : Key()
- companion object {
-
- fun from(string: String) = when (string) {
- "Alt" -> Alt
- "AltGraph" -> AltGraph
- "CapsLock" -> CapsLock
- "Control" -> Control
- "Fn" -> Function
- "FnLock" -> FunctionLock
- "Hyper" -> Hyper
- "Meta" -> Meta
- "NumLock" -> NumLock
- "Shift" -> Shift
- "Super" -> Super
- "Symbol" -> Symbol
- "SymbolLock" -> SymbolLock
- "Enter" -> Enter
- "Tab" -> Tab
- "Down" -> Arrow.Down
- "Left" -> Arrow.Left
- "Right" -> Arrow.Right
- "Up" -> Arrow.Up
- "End" -> End
- "Home" -> Home
- "PageUp" -> PageUp
- "PageDown" -> PageDown
- "Backspace" -> Backspace
- "Clear" -> Clear
- "Copy" -> Copy
- "CrSel" -> CrSel
- "Cut" -> Cut
- "Delete" -> Delete
- "EraseEof" -> EraseEof
- "ExSel" -> ExSel
- "Insert" -> Insert
- "Paste" -> Paste
- "Redo" -> Redo
- "Undo" -> Undo
- "Accept" -> Accept
- "Again" -> Again
- "Attn" -> Attn
- "Cancel" -> Cancel
- "ContextMenu" -> ContextMenu
- "Escape" -> Escape
- "Execute" -> Execute
- "Find" -> Find
- "Finish" -> Finish
- "Help" -> Help
- else -> if (string.length == 1)
- Printable(string.first())
- else if (string.first() == 'F')
- FunctionN(string.substring(1).toInt())
- else throw TODO()
- }
- }
-}
-
-class Login(shellRunner: ShellRunner) : ShellProgram(shellRunner) {
-
- override fun update() {
- while (true)
- when (state) {
- 0 -> {
- shellRunner.print("Username: ")
- state = 1
- }
- 1 -> {
- val inp = shellRunner.getInput() ?: return
- shellRunner.println(inp)
- shellRunner.print("Password: ")
- username = inp
- state = 2
- }
- 2 -> {
- val inp = shellRunner.getInput() ?: return
- shellRunner.println(inp)
- shellRunner.println("Login complete)")
- password = inp
- // TODO: check password and set user
- state = 3
- }
- 3 -> {
- TODO()
- }
- }
-
- }
-
- var state = 0
- var username = ""
- var password = ""
-
-}
-
-class Console(val os: WebOS, val renderElement: Element?) {
-
- val isVirtual get() = renderElement == null
- val activityStack = ArrayDeque<Activity>()
-
- var columns: Int = 80
- var rows: Int = 46
-
- var shouldRerender = true
-
- var currentUser: User? = null
-
- private var _workingDirectory: Path.Absolute? = null
-
- var workingDirectory: Path.Absolute
- get() = _workingDirectory ?: currentUser?.homeDirectory ?: Path.root
- set(value) {
- _workingDirectory = value
- }
-
- init {
- renderElement?.addEventListener("keydown", ::computeKeypressEvent)
- }
-
- fun computeKeypressEvent(event: Event) {
- if (!isInFocus()) return
- if (event !is KeyboardEvent) return
- activityStack.lastOrNull()?.handleKeyPress(Key.from(event.key))
- }
-
- private fun isInFocus(): Boolean = renderElement.containsOrIs(document.activeElement)
-
- private fun Node?.containsOrIs(node: Node?) = this == node || this?.contains(node) ?: false
-
- fun <T : Activity> openActivity(activity: (Console) -> T): Console = openActivity(activity(this))
-
- fun <T : Activity> openActivity(activity: T): Console {
- activityStack.addLast(activity)
- invalidateRender()
- return this
- }
-
- fun render() {
- if (renderElement == null) return
- if (!shouldRerender) return
- shouldRerender = false
- val x = activityStack.lastOrNull()?.render(columns, rows)
- console.log(x)
- }
-
- fun invalidateRender() {
- shouldRerender = true
- window.requestAnimationFrame { render() }
- }
-
- fun resize(newColumns: Int, newRows: Int) {
- invalidateRender()
- }
-
- // TODO: Handle resizes of the renderElement
-
- fun update(): Unit {
- activityStack.lastOrNull()?.update()
- }
-
- var updateInterval by Delegates.notNull<Int>()
-
- fun start(): Console {
- updateInterval = window.setInterval(::update, 10)
- return openActivity(ShellRunner(this).also { it.openShellProgram(Login(it)) })
- }
-
- fun destroy() {
- window.clearInterval(updateInterval)
- }
-
-}
-
-class WebOS {
-
- val hostname: String = "host"
- private val _consoles = mutableListOf<Console>()
- val consoles get() = _consoles.toList()
- val files: FileService<PrimitiveINode> = PrimitiveFileService()
- fun registerConsole(element: Element) {
- _consoles.add(Console(this, element))
- }
-}
-
-data class User(
- val name: String,
- val homeDirectory: Path.Absolute,
- val isRoot: Boolean = false,
-)
diff --git a/src/jsMain/kotlin/io/Path.kt b/src/jsMain/kotlin/io/Path.kt
deleted file mode 100644
index 241c559..0000000
--- a/src/jsMain/kotlin/io/Path.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-package io
-
-sealed class Path {
- abstract val parts: List<String>
- fun toAbsolutePath(relativeTo: Absolute): Absolute {
- return relativeTo.resolve(this)
- }
-
- abstract fun resolve(path: Path): Path
-
- abstract val stringPath: String
-
- 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)
- }
-
- override val stringPath: String get() = parts.joinToString("/")
- }
-
- 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)
- }
-
- override val stringPath: String get() = "/" + parts.joinToString("/")
-
- fun relativize(path: Path): Relative = when (path) {
- is Relative -> path
- is Absolute -> {
- var idx = 0
- while (idx < path.parts.size && idx < parts.size && path.parts[idx] == parts[idx]) {
- idx++
- }
- val returns = if (idx < parts.size) {
- parts.size - idx
- } else {
- 0
- }
- Relative(List(returns) { ".." } + path.parts.subList(idx, path.parts.size))
- }
- }
- }
-
- override fun toString(): String = "Path($stringPath)"
-}
diff --git a/src/jsMain/kotlin/io/files.kt b/src/jsMain/kotlin/io/files.kt
deleted file mode 100644
index 4c53c4e..0000000
--- a/src/jsMain/kotlin/io/files.kt
+++ /dev/null
@@ -1,203 +0,0 @@
-package io
-
-import User
-
-sealed interface CreateFileResult {
- object Created : CreateFileResult
- sealed interface Failure : CreateFileResult {
- object NoPermission : Failure
- object NoParent : Failure
- object AlreadyExists : Failure
- }
-}
-
-sealed interface WriteFileResult {
- object Written : WriteFileResult
- sealed interface Failure : WriteFileResult {
- object NoPermission : Failure
- object NotAFile : Failure
- data class NotEnoughSpace(
- val dataSize: Long,
- val spaceLeft: Long
- ) : Failure
- }
-}
-
-sealed interface ReadFileResult {
- data class Read(val data: ByteArray) : ReadFileResult {
- override fun equals(other: Any?): Boolean = (other as? Read)?.let { it.data.contentEquals(this.data) } ?: false
-
- override fun hashCode(): Int {
- return data.contentHashCode()
- }
- }
-
- sealed interface Failure : ReadFileResult {
- object NoPermission : Failure
- object NotAFile : Failure
- }
-}
-
-sealed interface DeleteFileResult {
- sealed interface Failure : DeleteFileResult {
- object NoPermission : Failure
- object NotAFile : Failure
- }
-
- object Deleted : DeleteFileResult
-}
-
-interface FileService<INode> {
- fun getPath(iNode: INode): Path.Absolute
- fun getINode(path: Path.Absolute): INode
- fun createFile(iNode: INode, user: User): CreateFileResult
- fun createSymlink(iNode: INode, user: User, path: Path): CreateFileResult
- fun createDirectory(iNode: INode, user: User): CreateFileResult
- fun writeToFile(iNode: INode, user: User, data: ByteArray): WriteFileResult
- fun readFromFile(iNode: INode, user: User): ReadFileResult
- fun deleteFile(iNode: INode, user: User): DeleteFileResult
- fun exists(iNode: INode): Boolean
- fun isFile(iNode: INode): Boolean
- fun isDirectory(iNode: INode): Boolean
- fun isSymlink(iNode: INode): Boolean
- fun resolve(iNode: INode, fragment: String): INode
- fun resolve(iNode: INode, path: Path.Relative): INode
- fun changePermissions(iNode: INode, user: User, permissionUpdate: Map<User, Permission>)
-}
-
-data class Permission(
- val read: Boolean,
- val write: Boolean,
- val execute: Boolean
-) {
- companion object {
- val default get() = Permission(read = true, write = true, execute = false)
- }
-}
-
-sealed interface PrimitiveStorageBlob {
- val permissions: MutableMap<User, Permission>
-
- class File(var data: ByteArray, override val permissions: MutableMap<User, Permission>) : PrimitiveStorageBlob
- class Symlink(val path: Path, override val permissions: MutableMap<User, Permission>) : PrimitiveStorageBlob
- class Directory(override val permissions: MutableMap<User, Permission>) : PrimitiveStorageBlob
-}
-
-data class PrimitiveINode internal constructor(internal val internalPath: String)
-class PrimitiveFileService : FileService<PrimitiveINode> {
- private val storageBlobs = mutableMapOf<String, PrimitiveStorageBlob>(
- "/" to PrimitiveStorageBlob.Directory(mutableMapOf())
- )
-
- override fun getPath(iNode: PrimitiveINode): Path.Absolute = Path.of(iNode.internalPath) as Path.Absolute
-
- override fun getINode(path: Path.Absolute): PrimitiveINode {
- return resolve(PrimitiveINode("/"), Path.Relative(path.parts))
- }
-
- override fun resolve(iNode: PrimitiveINode, fragment: String): PrimitiveINode {
- if (fragment == "..") {
- val up = iNode.internalPath.substringBeforeLast('/')
- if (up.isEmpty()) return PrimitiveINode("/")
- return PrimitiveINode(up)
- }
- if (fragment.isEmpty() || fragment == ".")
- return iNode
- val blob = storageBlobs[iNode.internalPath]
- return when (blob) {
- is PrimitiveStorageBlob.Symlink -> {
- when (blob.path) {
- is Path.Absolute -> getINode(blob.path)
- is Path.Relative -> resolve(resolve(iNode, ".."), blob.path)
- }
- }
- else -> {
- PrimitiveINode(iNode.internalPath + "/" + fragment)
- }
- }
- }
-
- override fun resolve(iNode: PrimitiveINode, path: Path.Relative): PrimitiveINode =
- path.parts.fold(iNode) { node, fragment -> resolve(node, fragment) }
-
- private fun getStorageBlob(iNode: PrimitiveINode): PrimitiveStorageBlob? = storageBlobs[iNode.internalPath]
-
- override fun writeToFile(iNode: PrimitiveINode, user: User, data: ByteArray): WriteFileResult {
- val file = getStorageBlob(iNode) as? PrimitiveStorageBlob.File ?: return WriteFileResult.Failure.NotAFile
- if (!hasPermission(user, file) { read })
- return WriteFileResult.Failure.NoPermission
- file.data = data
- return WriteFileResult.Written
- }
-
- override fun readFromFile(iNode: PrimitiveINode, user: User): ReadFileResult {
- val file = getStorageBlob(iNode) as? PrimitiveStorageBlob.File ?: return ReadFileResult.Failure.NotAFile
- if (!hasPermission(user, file) { read })
- return ReadFileResult.Failure.NoPermission
- return ReadFileResult.Read(file.data)
- }
-
- override fun exists(iNode: PrimitiveINode): Boolean =
- getStorageBlob(iNode) != null
-
- override fun isFile(iNode: PrimitiveINode): Boolean =
- getStorageBlob(iNode) is PrimitiveStorageBlob.File
-
- override fun isDirectory(iNode: PrimitiveINode): Boolean =
- getStorageBlob(iNode) is PrimitiveStorageBlob.Directory
-
- override fun isSymlink(iNode: PrimitiveINode): Boolean =
- getStorageBlob(iNode) is PrimitiveStorageBlob.Symlink
-
- override fun changePermissions(iNode: PrimitiveINode, user: User, permissionUpdate: Map<User, Permission>) {
- val file = getStorageBlob(iNode) ?: return // TODO Results
- if (!hasPermission(user, file) { write })
- return // TODO Results
- file.permissions.putAll(permissionUpdate)
- return // TODO Results
- }
-
- override fun deleteFile(iNode: PrimitiveINode, user: User): DeleteFileResult {
- val file = getStorageBlob(iNode) ?: return DeleteFileResult.Failure.NotAFile
- if (!hasPermission(user, file) { write })
- return DeleteFileResult.Failure.NoPermission
- (storageBlobs.keys.filter { it.startsWith(iNode.internalPath + "/") } + listOf(iNode.internalPath)).forEach {
- storageBlobs.remove(it)
- }
- return DeleteFileResult.Deleted
- }
-
- private fun hasPermission(user: User, blob: PrimitiveStorageBlob, check: Permission.() -> Boolean): Boolean {
- return user.isRoot || blob.permissions[user]?.let(check) ?: false
- }
-
- private fun checkCreationPreconditions(iNode: PrimitiveINode, user: User): CreateFileResult? {
- if (storageBlobs.containsKey(iNode.internalPath)) return CreateFileResult.Failure.AlreadyExists
- val parent = getStorageBlob(resolve(iNode, ".."))
- if (parent !is PrimitiveStorageBlob.Directory) return CreateFileResult.Failure.NoParent
- if (!hasPermission(user, parent) { write }) return CreateFileResult.Failure.NoPermission
- return null
- }
-
- override fun createFile(iNode: PrimitiveINode, user: User): CreateFileResult {
- val preconditions = checkCreationPreconditions(iNode, user)
- if (preconditions != null) return preconditions
- storageBlobs[iNode.internalPath] = PrimitiveStorageBlob.File(byteArrayOf(), mutableMapOf(user to Permission.default))
- return CreateFileResult.Created
- }
-
- override fun createSymlink(iNode: PrimitiveINode, user: User, path: Path): CreateFileResult {
- val preconditions = checkCreationPreconditions(iNode, user)
- if (preconditions != null) return preconditions
- storageBlobs[iNode.internalPath] = PrimitiveStorageBlob.Symlink(path, mutableMapOf(user to Permission.default))
- return CreateFileResult.Created
- }
-
- override fun createDirectory(iNode: PrimitiveINode, user: User): CreateFileResult {
- val preconditions = checkCreationPreconditions(iNode, user)
- if (preconditions != null) return preconditions
- storageBlobs[iNode.internalPath] = PrimitiveStorageBlob.Directory(mutableMapOf(user to Permission.default))
- return CreateFileResult.Created
- }
-
-}
diff --git a/src/jsMain/kotlin/util/sequence.kt b/src/jsMain/kotlin/util/sequence.kt
deleted file mode 100644
index 72b07dc..0000000
--- a/src/jsMain/kotlin/util/sequence.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package util
-
-fun <T> Iterable<T>.expandWith(t: T): Sequence<T> =
- this.asSequence() + generateSequence { t }.asSequence()
-