Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,11 @@ val file = FileKit.openFileSaver(
)
file?.writeString(contentToSave)

// Work with files
val myFile = FileKit.filesDir / "document.pdf"
// Work with application storage on every platform, including the web.
val myFile = FileKit.filesDirectory().file(name = "document.pdf", create = true)
println(myFile.name)
println(myFile.size())
myFile.writeString("Hello, World!")
println(myFile.sizeAsync())
myFile.writeStringAsync("Hello, World!")

// Image operations
val compressedBytes = FileKit.compressImage(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
package io.github.vinceglb.filekit

public expect object FileKit

/**
* Returns the directory for persistent application files.
*
* On Android, this corresponds to `Context.filesDir`.
* On Apple, this corresponds to `NSApplicationSupportDirectory`.
* On JVM, this corresponds to a platform-specific application data directory.
* On web targets this is the root of the browser's origin private file system (OPFS).
*/
public expect suspend fun FileKit.filesDirectory(): PlatformFile
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,27 @@ public expect fun PlatformFile.lastModified(): Instant
*/
public expect suspend fun PlatformFile.readBytes(): ByteArray

/**
* Writes the given bytes to this file.
*
* @param bytes The bytes to write.
*/
public expect suspend infix fun PlatformFile.write(bytes: ByteArray): Unit

/**
* Reads the content of the file as a string.
*
* @return The content of the file as a [String].
*/
public expect suspend fun PlatformFile.readString(): String

/**
* Writes the given string to this file.
*
* @param string The string to write.
*/
public expect suspend fun PlatformFile.writeString(string: String): Unit

/**
* Returns the MIME type of the file.
*
Expand Down Expand Up @@ -110,6 +124,20 @@ public expect inline fun PlatformFile.list(block: (List<PlatformFile>) -> Unit)
*/
public expect fun PlatformFile.list(): List<PlatformFile>

/**
* Deletes this file.
*
* @param mustExist If `true`, fails if the file does not exist. Defaults to `true`.
* @param recursively If `true`, a directory is emptied before it is removed. Defaults to `false`,
* which fails on a filesystem directory that still has contents. Symbolic links (including dangling
* links) and Windows directory junctions are unlinked, never followed. Android document URI deletion
* is handled by the document provider regardless of this flag.
*/
public expect suspend fun PlatformFile.delete(
mustExist: Boolean = true,
recursively: Boolean = false,
)

/**
* Starts accessing a security-scoped resource.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.github.vinceglb.filekit

import io.github.vinceglb.filekit.mimeType.MimeType
import kotlin.time.ExperimentalTime
import kotlin.time.Instant

/** Lists this directory's current children. */
public expect suspend fun PlatformFile.listAsync(): List<PlatformFile>

/** Returns a child file, creating it when [create] is true. */
public expect suspend fun PlatformFile.file(
name: String,
create: Boolean = false,
): PlatformFile

/** Returns a child directory, creating it when [create] is true. */
public expect suspend fun PlatformFile.directory(
name: String,
create: Boolean = false,
): PlatformFile

/** Returns whether this file currently exists. */
public expect suspend fun PlatformFile.existsAsync(): Boolean

/** Returns this file's current size in bytes. */
public expect suspend fun PlatformFile.sizeAsync(): Long

/** Returns this file's current MIME type, if available. */
public expect suspend fun PlatformFile.mimeTypeAsync(): MimeType?

/** Returns this file's current modification time. */
@OptIn(ExperimentalTime::class)
public expect suspend fun PlatformFile.lastModifiedAsync(): Instant

/**
* Refreshes metadata cached by this platform file.
*
* Files whose metadata is read directly from their backing filesystem do not need an update.
*/
public suspend fun PlatformFile.update() {
updatePlatformData()
}

internal expect suspend fun PlatformFile.updatePlatformData()
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.github.vinceglb.filekit

import org.khronos.webgl.Uint8Array
import kotlin.js.ExperimentalWasmJsInterop
import kotlin.js.JsAny
import kotlin.js.unsafeCast

@OptIn(ExperimentalWasmJsInterop::class)
internal actual fun ByteArray.toWebBytes(): JsAny =
Uint8Array(toTypedArray()).unsafeCast<JsAny>()
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ class FileKitLinuxTest {
)
}

@Test
fun FileKit_asyncStorageDirectories_matchPlatformDirectories() = runTest {
FileKit.init(appId = APP_ID)

assertEquals(FileKit.filesDir.path, FileKit.filesDirectory().path)
}

@Test
fun FileKit_filesDir_withoutCustomDirectory_resolvesUnderXdgDataHome() {
FileKit.init(appId = APP_ID)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public expect val FileKit.databasesDir: PlatformFile
*/
public expect val FileKit.projectDir: PlatformFile

public actual suspend fun FileKit.filesDirectory(): PlatformFile = filesDir

/**
* Saves an image to the platform's gallery or photo album.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public actual suspend fun PlatformFile.readString(): String =
*
* @param bytes The bytes to write.
*/
public suspend infix fun PlatformFile.write(bytes: ByteArray): Unit =
public actual suspend infix fun PlatformFile.write(bytes: ByteArray): Unit =
withContext(Dispatchers.IO) {
this@write
.sink()
Expand Down Expand Up @@ -163,7 +163,7 @@ public suspend infix fun PlatformFile.write(platformFile: PlatformFile): Unit =
*
* @param string The string to write.
*/
public suspend fun PlatformFile.writeString(string: String): Unit =
public actual suspend fun PlatformFile.writeString(string: String): Unit =
withContext(Dispatchers.IO) {
this@writeString
.sink()
Expand Down Expand Up @@ -212,20 +212,6 @@ public expect fun PlatformFile.createDirectories(mustCreate: Boolean = false)
*/
public expect suspend fun PlatformFile.atomicMove(destination: PlatformFile)

/**
* Deletes this file.
*
* @param mustExist If `true`, fails if the file does not exist. Defaults to `true`.
* @param recursively If `true`, a directory is emptied before it is removed. Defaults to `false`,
* which fails on a filesystem directory that still has contents. Symbolic links (including dangling
* links) and Windows directory junctions are unlinked, never followed. Android document URI deletion
* is handled by the document provider regardless of this flag.
*/
public expect suspend fun PlatformFile.delete(
mustExist: Boolean = true,
recursively: Boolean = false,
)

/**
* Empties this directory, depth first, leaving the directory itself in place. Does nothing when
* this is not a directory.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.github.vinceglb.filekit

import io.github.vinceglb.filekit.exceptions.FileKitException
import io.github.vinceglb.filekit.mimeType.MimeType
import kotlin.time.ExperimentalTime
import kotlin.time.Instant

public actual suspend fun PlatformFile.listAsync(): List<PlatformFile> = list()

public actual suspend fun PlatformFile.file(
name: String,
create: Boolean,
): PlatformFile {
val file = this / name
if (create && !file.exists()) {
file write ByteArray(0)
}
if (!file.exists() || !file.isRegularFile()) {
throw FileKitException("File does not exist: $file")
}
return file
}

public actual suspend fun PlatformFile.directory(
name: String,
create: Boolean,
): PlatformFile {
val directory = this / name
if (create) {
directory.createDirectories()
} else if (!directory.exists() || !directory.isDirectory()) {
throw FileKitException("Directory does not exist: $directory")
}
return directory
}

public actual suspend fun PlatformFile.existsAsync(): Boolean = exists()

public actual suspend fun PlatformFile.sizeAsync(): Long = size()

public actual suspend fun PlatformFile.mimeTypeAsync(): MimeType? = mimeType()

@OptIn(ExperimentalTime::class)
public actual suspend fun PlatformFile.lastModifiedAsync(): Instant = lastModified()

internal actual suspend fun PlatformFile.updatePlatformData() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.github.vinceglb.filekit

import org.khronos.webgl.toInt8Array
import kotlin.js.ExperimentalWasmJsInterop
import kotlin.js.JsAny
import kotlin.js.unsafeCast

@OptIn(ExperimentalWasmJsInterop::class)
internal actual fun ByteArray.toWebBytes(): JsAny =
toInt8Array().unsafeCast<JsAny>()
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
package io.github.vinceglb.filekit

/**
* Reads the root directory of the browser's origin private file system (OPFS).
*
* The returned [PlatformFile] retains a live OPFS directory handle.
*
* OPFS is available only in secure contexts in browsers that implement the File
* System API.
*/
public actual suspend fun FileKit.filesDirectory(): PlatformFile =
PlatformFile.fromOriginPrivateFileSystem()

/**
* Downloads a file in the browser.
*
Expand Down
Loading
Loading