All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.enchant.flare.LocalStorage.kt Maven / Gradle / Ivy

package enchant.flare

import enchant.flare.StorageException.Code.*
import kotlinx.datetime.Clock

class LocalStorage : FirebaseStorage {

    private sealed class Node {
        class FileNode(val name: String, val data: ByteArray, var metadata: StorageMetadata) :
            Node()

        class FolderNode(
            val name: String,
            val nodes: MutableMap = mutableMapOf()
        ) : Node()
    }

    private val db: Node.FolderNode = Node.FolderNode("")

    override suspend fun deleteFile(path: String) {
        var folder: Node.FolderNode = db
        val paths = path.split("/")
        paths.forEachIndexed { index, s ->
            if (index == paths.lastIndex) {
                if (folder.nodes[s] is Node.FolderNode) throw StorageException(
                    ObjectNotFound,
                    "The provided path points to a folder: $path"
                )
                folder.nodes[s] as? Node.FileNode ?: throw
                StorageException(ObjectNotFound, "Could not find file at path: $path")

                folder.nodes -= s
                return@deleteFile
            } else {
                folder = db.nodes[s] as? Node.FolderNode ?: throw
                StorageException(ObjectNotFound, "Could not find file at path: $path")
            }
        }
        throw StorageException(ObjectNotFound, "File path is invalid: $path")
    }

    private fun getFileNode(path: String): Node.FileNode {
        var folder: Node.FolderNode = db
        val paths = path.split("/")
        paths.forEachIndexed { index, s ->
            if (index == paths.lastIndex) {
                if (folder.nodes[s] is Node.FolderNode) throw StorageException(
                    ObjectNotFound,
                    "The provided path points to a folder: $path"
                )
                return@getFileNode folder.nodes[s] as? Node.FileNode ?: throw
                StorageException(ObjectNotFound, "Could not find file at path: $path")
            } else {
                folder = db.nodes[s] as? Node.FolderNode ?: throw
                StorageException(ObjectNotFound, "Could not find file at path: $path")
            }
        }
        throw StorageException(ObjectNotFound, "File path is invalid: $path")
    }

    private fun getOrCreateFolderNode(path: String): Node.FolderNode {
        if (path.isEmpty()) return db
        var folder: Node.FolderNode = db
        val paths = path.split("/")
        paths.dropWhile {
            if (folder.nodes[it] is Node.FolderNode) {
                folder = folder.nodes[it] as Node.FolderNode
                true
            } else false
        }.forEach {
            val newFolder = Node.FolderNode(it)
            folder.nodes[it] = newFolder
            folder = newFolder
        }
        return folder
    }

    override suspend fun getBytes(path: String, maxDownloadSize: Long): ByteArray {
        val file = getFileNode(path)
        if (file.data.size > maxDownloadSize) throw StorageException(
            Unknown, "File bytes exceeded maxDownloadSize: $maxDownloadSize"
        )
        return file.data
    }

    override suspend fun getDownloadUrl(path: String): String =
        "https://firebasestorage.googleapis.com/v0/b/${config.bucket}/o/${
            path.replace("/", "%2F")
        }"

    override suspend fun getFile(
        path: String,
        filePath: String,
        onProgress: ((bytesDownloaded: Long, totalBytes: Long) -> Unit)?
    ) = TODO("Not yet implemented")

    override suspend fun getMetadata(path: String): StorageMetadata = getFileNode(path).metadata

    override suspend fun list(path: String, maxResults: Int?, pageToken: String?): ListResult {
        TODO("Not yet implemented")
    }

    override suspend fun putBytes(
        path: String,
        bytes: ByteArray,
        metadata: FileMetadata?,
        onProgress: ((bytesUploaded: Long, totalBytes: Long) -> Unit)?
    ): StorageMetadata {
        val fileName = path.takeLastWhile { it != '/' }
        val folder = getOrCreateFolderNode(path.dropLast(fileName.length + 1))
        if (folder.nodes.contains(fileName)) {
            val type = if (folder.nodes[fileName] is Node.FileNode) "File" else "Folder"
            throw StorageException(
                Unknown,
                "$type at path $path already exists"
            )
        }
        val time = Clock.System.now().toEpochMilliseconds()
        val newMetadata = StorageMetadata(
            bucket = config.bucket,
            cacheControl = metadata?.cacheControl,
            contentDisposition = metadata?.contentDisposition,
            contentEncoding = metadata?.contentEncoding,
            contentLanguage = metadata?.contentLanguage,
            contentType = metadata?.contentType,
            creationTime = time,
            generation = 0,
            md5Hash = null,
            metadataGeneration = 0,
            name = fileName,
            path = path,
            size = bytes.size.toLong(),
            updatedTime = time,
            customMetadata = metadata?.customMetadata ?: mapOf()
        )
        if (onProgress != null) {
            bytes.forEachIndexed { i, _ ->
                onProgress(i.toLong(), bytes.size.toLong())
            }
        }
        folder.nodes[fileName] = Node.FileNode(fileName, bytes, newMetadata)
        onProgress?.invoke(bytes.size.toLong(), bytes.size.toLong())
        return newMetadata
    }

    override suspend fun putFile(
        path: String,
        filePath: String,
        metadata: FileMetadata?,
        onProgress: ((bytesUploaded: Long, totalBytes: Long) -> Unit)?
    ): StorageMetadata = TODO("Not yet implemented")

    override suspend fun updateMetadata(path: String, metadata: FileMetadata): StorageMetadata {
        val file = getFileNode(path)
        file.metadata = file.metadata.copy(
            cacheControl = metadata.cacheControl,
            contentDisposition = metadata.contentDisposition,
            contentEncoding = metadata.contentEncoding,
            contentLanguage = metadata.contentLanguage,
            contentType = metadata.contentType,
            customMetadata = metadata.customMetadata
        )
        return file.metadata
    }

    override val config: FirebaseStorage.Config
        get() = object : FirebaseStorage.Config {
            override var maxDownloadRetryTime: Long
                get() = TODO("Not yet implemented")
                set(value) {
                    TODO("Not yet implemented")
                }
            override var maxOperationRetryTime: Long
                get() = TODO("Not yet implemented")
                set(value) {
                    TODO("Not yet implemented")
                }
            override var maxUploadRetryTime: Long
                get() = TODO("Not yet implemented")
                set(value) {
                    TODO("Not yet implemented")
                }
            override val bucket: String
                get() = "my-firebase-project.appspot.com"

            override fun useEmulator(host: String, port: Int) {
            }

        }
    companion object {
        val instance: LocalStorage by lazy { LocalStorage() }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy