Skip to content

Kotlin: Attach a file

fromFile copies a file the user picked into the client's attachment sandbox, queues its upload, and writes the resulting reference onto the row's attachment column as an ordinary local update. The bytes never cross the binding: the picker stays in the app and the engine works from the path.

Examples#

Basic#

// app/src/main/kotlin/com/example/todo/TodoScreen.kt (excerpt)
val result = kizunasync.fromFile(
    table = "todos",
    column = "image_path",
    pk = todoId,
    sourcePath = pickedFile.absolutePath,
    mediaType = "image/jpeg",
)

Insert the row first#

// app/src/main/kotlin/com/example/todo/TodoScreen.kt (excerpt)
import com.kizunasync.kizunasync.KizunaSyncOp
import java.util.UUID

val todoId = UUID.randomUUID().toString()
kizunasync.apply(
    table = "todos",
    pk = todoId,
    op = KizunaSyncOp.Insert,
    columns = mapOf("title" to "works on a plane", "done" to false),
)
val result = kizunasync.fromFile(
    table = "todos",
    column = "image_path",
    pk = todoId,
    sourcePath = pickedFile.absolutePath,
)

Both writes wake the Host scheduler, whose next run pushes the row and uploads the bytes.

Parameters#

NameTypeRequiredDescription
tableStringYesTable of the row that owns the file.
columnStringYesColumn declared as an attachment for that table in Initializing.
pkStringYesPrimary key of the row. The row has to exist already, because the reference is written onto it. A uuid names the row in any case, because the engine lowercases it as it does for a write.
sourcePathStringYesFilesystem path of the file to import. It is read once and copied into the sandbox.
mediaTypeString?NoContent type recorded for the object; it picks the reference's extension. Default: null, which records application/octet-stream.

Returns#

KizunaSyncFromFileResult, with these fields.

NameTypeRequiredDescription
referenceString—The value written onto the column, shaped <owner>/<pk>/<uploadId>.<extension>, where the owner comes from the row's configured owner column.
sha256String—Hex digest of the imported bytes. The sandbox is content addressed by this digest, so importing the same bytes twice stores one copy.
sizeLong—Size of the imported file in bytes.
mediaTypeString—The content type that was recorded.
localPathString—Path of the copy inside the sandbox, which is what a viewer reads until the upload finishes.

Errors#

CodeCondition
ATTACHMENT_PORTS_MISSINGThe client was created without attachmentRoot. See ATTACHMENT_PORTS_MISSING.
UNKNOWN_TABLEtable is not one of the tables the client was created with.
LOCAL_CONSTRAINTcolumn is not declared as an attachment on that table, or the row's owner or its pk is not a uuid.
ATTACHMENT_ROW_GONENo live row holds pk in that table. Insert the row before importing the file.
ATTACHMENT_OWNER_MISSINGThe row's owner column is missing or empty, so no object key can be derived.
TRANSFERThe source file could not be read.
STORE, JSONThe sandbox copy, the queue row, or the column update could not be written.
ENGINE_UNAVAILABLEThe client has no engine because Initializing has not run.

Notes#

The import writes the reference through the normal write path, so it is one more entry in the outbox and it is visible to Fetch data at once. The bytes move later: the next Sync pushes the column first and then drives the transfer, so the server never sees a reference to an object that is on its way. Media and attachments walks through the ordering.

Uploads go to the Storage bucket the column declares, using resumable uploads; Kizuna adds the durable queue around them, so an interrupted transfer resumes from its recorded offset instead of starting again. The object key is the reference, so a Storage policy scoped to the owner segment covers it, as access control describes.

If the column update fails after the file was queued, the queued object is evicted, since no server ever saw the reference: Vacuum attachments only deletes its sandbox bytes, and Storage is never asked. The original error from the write is raised.

Found a problem? Edit this page on GitHub