Skip to content

WorkspaceLastDocument.kt

src/jvmMain/kotlin/afield/document/workspace/WorkspaceLastDocument.kt · 83 lines

package afield.document.workspace

import afield.app.server.lastFn
import afield.data.table.div
import afield.data.table.get
import afield.data.table.minus
import afield.data.table.plus
import afield.data.table.rem
import afield.data.table.times
import afield.runtime.execution.plus
import afield.runtime.lifecycle.plus
import afield.runtime.lifecycle.rem

import afield.app.server.isFieldAppRouteDocumentName
import afield.audio.synth.*
import afield.document.persistence.SavingContext
import afield.graphics.vulkan.*
import afield.media.stream.*
import afield.media.video.*
import afield.tools.docs.format
import java.io.File
import java.nio.file.AtomicMoveNotSupportedException
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.security.MessageDigest

Where Field remembers the document you had open, keyed by workspace rather than by checkout.

This is the store behind lastFn, which is what a window opened without --document falls back to. The name is kept in a file named for the SHA-256 of the workspace's canonical path, under last-documents in the machine-wide user config directory, so two workspaces opened from one checkout remember different documents and one workspace opened from two checkouts remembers the same one.

Both directions are checked against the disk rather than trusted. A remembered name comes back only while a document by that name is still there, a name is written only if it resolves to something inside the workspace, and a name holding a path separator or naming one of the app's own routes is refused at both ends. The write goes to a temporary file beside the target and is moved into place, atomically where the filesystem supports it.

internal class WorkspaceLastDocument(workspace: File, stateDirectory: File)

Remembers a local document per canonical workspace, independently of the launching checkout.

internal class WorkspaceLastDocument(workspace: File, stateDirectory: File) {
    private val workspace = workspace.canonicalFile
    private val key = MessageDigest.getInstance("SHA-256")
        .digest(this.workspace.path.toByteArray(Charsets.UTF_8))
        .joinToString("") { "%02x".format(it) }
    private val stateFile = File(stateDirectory, "$key.txt")

fun read(): String?

The old checkout-wide lastFn has no workspace identity, so it cannot be migrated safely.

    fun read(): String? {
        val name = stateFile.takeIf { it.isFile }?.readText(Charsets.UTF_8)?.trim() ?: return null
        val document = localDocument(name) ?: return null
        return name.takeIf { SavingContext.documentExistsOnDisk(document.path) }
    }

fun remember(name: String?)

    fun remember(name: String?) {
        val clean = name?.trim() ?: return
        if (localDocument(clean) == null) return
        stateFile.parentFile.mkdirs()
        val temporary = Files.createTempFile(stateFile.parentFile.toPath(), key, ".tmp")
        try {
            Files.writeString(temporary, clean, Charsets.UTF_8)
            try {
                Files.move(temporary, stateFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING)
            } catch (_: AtomicMoveNotSupportedException) {
                Files.move(temporary, stateFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
            }
        } finally {
            Files.deleteIfExists(temporary)
        }
    }

    private fun localDocument(name: String): File? {
        if (name.isBlank() || name == "." || name == ".." ||
            name.any { it == '/' || it == '\\' || it == '\n' || it == '\r' } ||
            isFieldAppRouteDocumentName(name)
        ) return null
        return File(workspace, name)
    }
}