From 5d789b5553e6b024552589513c972f3be5aa4a10 Mon Sep 17 00:00:00 2001 From: Bas Buijsen Date: Thu, 23 May 2024 15:55:39 +0200 Subject: [PATCH 1/6] add metadata to storage --- .../dev/gitlive/firebase/storage/storage.kt | 32 +++++++++++++++++-- .../dev/gitlive/firebase/storage/storage.kt | 14 ++++++-- .../dev/gitlive/firebase/storage/storage.kt | 24 ++++++++++++-- .../firebase/storage/externals/storage.kt | 5 +-- .../dev/gitlive/firebase/storage/storage.kt | 6 ++-- .../gitlive/firebase/storage/storage.jvm.kt | 4 +-- 6 files changed, 70 insertions(+), 15 deletions(-) diff --git a/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt index 868d380a5..0b247ea69 100644 --- a/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -10,6 +10,7 @@ import com.google.android.gms.tasks.OnCanceledListener import com.google.android.gms.tasks.OnCompleteListener import com.google.firebase.storage.OnPausedListener import com.google.firebase.storage.OnProgressListener +import com.google.firebase.storage.StorageMetadata import com.google.firebase.storage.UploadTask import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp @@ -65,10 +66,20 @@ actual class StorageReference(val android: com.google.firebase.storage.StorageRe actual suspend fun listAll(): ListResult = ListResult(android.listAll().await()) - actual suspend fun putFile(file: File) = android.putFile(file.uri).await().run {} + actual suspend fun putFile(file: File, metadata: FirebaseStorageMetadata?) { + if (metadata != null) { + android.putFile(file.uri, metadata.toStorageMetadata()).await().run {} + } else { + android.putFile(file.uri).await().run {} + } + } - actual fun putFileResumable(file: File): ProgressFlow { - val android = android.putFile(file.uri) + actual fun putFileResumable(file: File, metadata: FirebaseStorageMetadata?): ProgressFlow { + val android = if (metadata != null) { + android.putFile(file.uri, metadata.toStorageMetadata()) + } else { + android.putFile(file.uri) + } val flow = callbackFlow { val onCanceledListener = OnCanceledListener { cancel() } @@ -105,3 +116,18 @@ actual class ListResult(android: com.google.firebase.storage.ListResult) { actual class File(val uri: Uri) actual typealias FirebaseStorageException = com.google.firebase.storage.StorageException + + +fun FirebaseStorageMetadata.toStorageMetadata(): StorageMetadata { + return StorageMetadata.Builder() + .setCacheControl(this.cacheControl) + .setContentDisposition(this.contentDisposition) + .setContentEncoding(this.contentEncoding) + .setContentLanguage(this.contentLanguage) + .setContentType(this.contentType) + .apply { + customMetadata?.entries?.forEach { + (key, value) -> setCustomMetadata(key, value) + } + }.build() +} \ No newline at end of file diff --git a/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt index ada31266b..dcb930382 100644 --- a/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -40,9 +40,9 @@ expect class StorageReference { suspend fun listAll(): ListResult - suspend fun putFile(file: File) + suspend fun putFile(file: File, metadata: FirebaseStorageMetadata? = null) - fun putFileResumable(file: File): ProgressFlow + fun putFileResumable(file: File, metadata: FirebaseStorageMetadata? = null): ProgressFlow } expect class ListResult { @@ -66,3 +66,13 @@ interface ProgressFlow : Flow { expect class FirebaseStorageException : FirebaseException + +data class FirebaseStorageMetadata( + val md5Hash: String? = null, + val cacheControl: String? = null, + val contentDisposition: String? = null, + val contentEncoding: String? = null, + val contentLanguage: String? = null, + val contentType: String? = null, + val customMetadata: Map? = null +) diff --git a/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt index 3a988926e..9c94091b8 100644 --- a/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -6,6 +6,7 @@ package dev.gitlive.firebase.storage import cocoapods.FirebaseStorage.FIRStorage import cocoapods.FirebaseStorage.FIRStorageListResult +import cocoapods.FirebaseStorage.FIRStorageMetadata import cocoapods.FirebaseStorage.FIRStorageReference import cocoapods.FirebaseStorage.FIRStorageTaskStatusFailure import cocoapods.FirebaseStorage.FIRStorageTaskStatusPause @@ -76,10 +77,10 @@ actual class StorageReference(val ios: FIRStorageReference) { } } - actual suspend fun putFile(file: File) = ios.awaitResult { putFile(file.url, null, completion = it) }.run {} + actual suspend fun putFile(file: File, metadata: FirebaseStorageMetadata?) = ios.awaitResult { putFile(file.url, metadata?.toFIRMetadata(), completion = it) }.run {} - actual fun putFileResumable(file: File): ProgressFlow { - val ios = ios.putFile(file.url) + actual fun putFileResumable(file: File, metadata: FirebaseStorageMetadata?): ProgressFlow { + val ios = ios.putFile(file.url, metadata?.toFIRMetadata()) val flow = callbackFlow { ios.observeStatus(FIRStorageTaskStatusProgress) { @@ -147,3 +148,20 @@ suspend inline fun T.awaitResult(function: T.(callback: (R?, NSEr } return job.await() as R } + +fun FirebaseStorageMetadata.toFIRMetadata(): FIRStorageMetadata { + val metadata = FIRStorageMetadata() + val customMetadata: Map? = this.customMetadata?.let { + it.mapKeys { entry -> + entry.key as Any to entry.value + } + } + + metadata.setCustomMetadata(customMetadata) + metadata.setCacheControl(this.cacheControl) + metadata.setContentDisposition(this.contentDisposition) + metadata.setContentEncoding(this.contentEncoding) + metadata.setContentLanguage(this.contentLanguage) + metadata.setContentType(this.contentType) + return metadata +} \ No newline at end of file diff --git a/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/externals/storage.kt b/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/externals/storage.kt index dbd7768d4..28297e3a3 100644 --- a/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/externals/storage.kt +++ b/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/externals/storage.kt @@ -4,6 +4,7 @@ package dev.gitlive.firebase.storage.externals import dev.gitlive.firebase.externals.FirebaseApp +import dev.gitlive.firebase.storage.FirebaseStorageMetadata import kotlin.js.Promise external fun getStorage(app: FirebaseApp? = definedExternally): FirebaseStorage @@ -13,9 +14,9 @@ external fun ref(ref: StorageReference, url: String? = definedExternally): Stora external fun getDownloadURL(ref: StorageReference): Promise -external fun uploadBytes(ref: StorageReference, file: dynamic): Promise +external fun uploadBytes(ref: StorageReference, file: dynamic, metadata: FirebaseStorageMetadata?): Promise -external fun uploadBytesResumable(ref: StorageReference, data: dynamic): UploadTask +external fun uploadBytesResumable(ref: StorageReference, data: dynamic, metadata: FirebaseStorageMetadata?): UploadTask external fun deleteObject(ref: StorageReference): Promise; diff --git a/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt index 9ba0380d5..43dc13362 100644 --- a/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -59,10 +59,10 @@ actual class StorageReference(val js: dev.gitlive.firebase.storage.externals.Sto actual suspend fun listAll(): ListResult = rethrow { ListResult(listAll(js).await()) } - actual suspend fun putFile(file: File): Unit = rethrow { uploadBytes(js, file).await() } + actual suspend fun putFile(file: File, metadata: FirebaseStorageMetadata?): Unit = rethrow { uploadBytes(js, file, metadata).await() } - actual fun putFileResumable(file: File): ProgressFlow = rethrow { - val uploadTask = uploadBytesResumable(js, file) + actual fun putFileResumable(file: File, metadata: FirebaseStorageMetadata?): ProgressFlow = rethrow { + val uploadTask = uploadBytesResumable(js, file, metadata) val flow = callbackFlow { val unsubscribe = uploadTask.on( diff --git a/firebase-storage/src/jvmMain/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt b/firebase-storage/src/jvmMain/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt index cc61e378b..d76e30e1b 100644 --- a/firebase-storage/src/jvmMain/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt +++ b/firebase-storage/src/jvmMain/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt @@ -66,11 +66,11 @@ actual class StorageReference { TODO("Not yet implemented") } - actual fun putFileResumable(file: File): ProgressFlow { + actual fun putFileResumable(file: File, metadata: FirebaseStorageMetadata?): ProgressFlow { TODO("Not yet implemented") } - actual suspend fun putFile(file: File) { + actual suspend fun putFile(file: File, metadata: FirebaseStorageMetadata?) { } } From 92277bf1c1fbc05c82682822ff5698255f961119 Mon Sep 17 00:00:00 2001 From: Bas Buijsen Date: Fri, 24 May 2024 00:27:57 +0200 Subject: [PATCH 2/6] fix for ios metadata crash --- .../kotlin/dev/gitlive/firebase/storage/storage.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt index 9c94091b8..d0ad460b8 100644 --- a/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -151,13 +151,10 @@ suspend inline fun T.awaitResult(function: T.(callback: (R?, NSEr fun FirebaseStorageMetadata.toFIRMetadata(): FIRStorageMetadata { val metadata = FIRStorageMetadata() - val customMetadata: Map? = this.customMetadata?.let { - it.mapKeys { entry -> - entry.key as Any to entry.value - } - } - - metadata.setCustomMetadata(customMetadata) + val mappedMetadata: Map? = this.customMetadata?.map { + it.key to it.value + }?.toMap() + metadata.setCustomMetadata(mappedMetadata) metadata.setCacheControl(this.cacheControl) metadata.setContentDisposition(this.contentDisposition) metadata.setContentEncoding(this.contentEncoding) From bc02afe7835a071a29d4eab0f9bfea602fe854da Mon Sep 17 00:00:00 2001 From: Bas Buijsen Date: Mon, 27 May 2024 16:46:53 +0200 Subject: [PATCH 3/6] add ktx syntax for storage metadata --- .../dev/gitlive/firebase/storage/storage.kt | 2 +- .../dev/gitlive/firebase/storage/storage.kt | 29 +++++++++++++------ .../dev/gitlive/firebase/storage/storage.kt | 4 +-- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt index 0b247ea69..95e5d8ae9 100644 --- a/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -126,7 +126,7 @@ fun FirebaseStorageMetadata.toStorageMetadata(): StorageMetadata { .setContentLanguage(this.contentLanguage) .setContentType(this.contentType) .apply { - customMetadata?.entries?.forEach { + customMetadata.entries.forEach { (key, value) -> setCustomMetadata(key, value) } }.build() diff --git a/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt index dcb930382..5c2753e6e 100644 --- a/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -64,15 +64,26 @@ interface ProgressFlow : Flow { fun cancel() } - expect class FirebaseStorageException : FirebaseException data class FirebaseStorageMetadata( - val md5Hash: String? = null, - val cacheControl: String? = null, - val contentDisposition: String? = null, - val contentEncoding: String? = null, - val contentLanguage: String? = null, - val contentType: String? = null, - val customMetadata: Map? = null -) + var md5Hash: String? = null, + var cacheControl: String? = null, + var contentDisposition: String? = null, + var contentEncoding: String? = null, + var contentLanguage: String? = null, + var contentType: String? = null, + var customMetadata: MutableMap = mutableMapOf() +) { + fun setCustomMetadata(key: String, value: String?) { + value?.let { + customMetadata[key] = it + } + } +} + +fun storageMetadata(init: FirebaseStorageMetadata.() -> Unit): FirebaseStorageMetadata { + val metadata = FirebaseStorageMetadata() + metadata.init() + return metadata +} \ No newline at end of file diff --git a/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt index d0ad460b8..eee0b23fa 100644 --- a/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -151,9 +151,9 @@ suspend inline fun T.awaitResult(function: T.(callback: (R?, NSEr fun FirebaseStorageMetadata.toFIRMetadata(): FIRStorageMetadata { val metadata = FIRStorageMetadata() - val mappedMetadata: Map? = this.customMetadata?.map { + val mappedMetadata: Map = this.customMetadata.map { it.key to it.value - }?.toMap() + }.toMap() metadata.setCustomMetadata(mappedMetadata) metadata.setCacheControl(this.cacheControl) metadata.setContentDisposition(this.contentDisposition) From 17f1ba730aa9f609f9413f3a5cb78b5697748aaf Mon Sep 17 00:00:00 2001 From: Bas Buijsen Date: Mon, 3 Jun 2024 20:16:24 +0200 Subject: [PATCH 4/6] add getter for metadata --- .../dev/gitlive/firebase/storage/storage.kt | 19 +++++++++- .../dev/gitlive/firebase/storage/storage.kt | 2 ++ .../dev/gitlive/firebase/storage/storage.kt | 25 +++++++++++++ .../firebase/storage/externals/storage.kt | 26 ++++++++++++-- .../dev/gitlive/firebase/storage/storage.kt | 35 +++++++++++++++++-- .../gitlive/firebase/storage/storage.jvm.kt | 4 +++ 6 files changed, 104 insertions(+), 7 deletions(-) diff --git a/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt index 95e5d8ae9..279c47837 100644 --- a/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -8,6 +8,7 @@ package dev.gitlive.firebase.storage import android.net.Uri import com.google.android.gms.tasks.OnCanceledListener import com.google.android.gms.tasks.OnCompleteListener +import com.google.android.gms.tasks.Task import com.google.firebase.storage.OnPausedListener import com.google.firebase.storage.OnProgressListener import com.google.firebase.storage.StorageMetadata @@ -58,6 +59,8 @@ actual class StorageReference(val android: com.google.firebase.storage.StorageRe actual val root: StorageReference get() = StorageReference(android.root) actual val storage: FirebaseStorage get() = FirebaseStorage(android.storage) + actual suspend fun getMetadata(): FirebaseStorageMetadata? = android.metadata.await().toFirebaseStorageMetadata() + actual fun child(path: String): StorageReference = StorageReference(android.child(path)) actual suspend fun delete() = android.delete().await().run { Unit } @@ -117,7 +120,6 @@ actual class File(val uri: Uri) actual typealias FirebaseStorageException = com.google.firebase.storage.StorageException - fun FirebaseStorageMetadata.toStorageMetadata(): StorageMetadata { return StorageMetadata.Builder() .setCacheControl(this.cacheControl) @@ -130,4 +132,19 @@ fun FirebaseStorageMetadata.toStorageMetadata(): StorageMetadata { (key, value) -> setCustomMetadata(key, value) } }.build() +} + +fun StorageMetadata.toFirebaseStorageMetadata(): FirebaseStorageMetadata { + val sdkMetadata = this + return storageMetadata { + md5Hash = sdkMetadata.md5Hash + cacheControl = sdkMetadata.cacheControl + contentDisposition = sdkMetadata.contentDisposition + contentEncoding = sdkMetadata.contentEncoding + contentLanguage = sdkMetadata.contentLanguage + contentType = sdkMetadata.contentType + sdkMetadata.customMetadataKeys.forEach { + setCustomMetadata(it, sdkMetadata.getCustomMetadata(it)) + } + } } \ No newline at end of file diff --git a/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt index 5c2753e6e..8414df377 100644 --- a/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -32,6 +32,8 @@ expect class StorageReference { val root: StorageReference val storage: FirebaseStorage + suspend fun getMetadata(): FirebaseStorageMetadata? + fun child(path: String): StorageReference suspend fun delete() diff --git a/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt index eee0b23fa..c8e5eed09 100644 --- a/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -65,6 +65,16 @@ actual class StorageReference(val ios: FIRStorageReference) { actual fun child(path: String): StorageReference = StorageReference(ios.child(path)) + actual suspend fun getMetadata(): FirebaseStorageMetadata? = ios.awaitResult { + metadataWithCompletion { metadata, error -> + if (error == null) { + it.invoke(metadata?.toFirebaseStorageMetadata(), null) + } else { + it.invoke(null, error) + } + } + } + actual suspend fun delete() = await { ios.deleteWithCompletion(it) } actual suspend fun getDownloadUrl(): String = ios.awaitResult { @@ -161,4 +171,19 @@ fun FirebaseStorageMetadata.toFIRMetadata(): FIRStorageMetadata { metadata.setContentLanguage(this.contentLanguage) metadata.setContentType(this.contentType) return metadata +} + +fun FIRStorageMetadata.toFirebaseStorageMetadata(): FirebaseStorageMetadata { + val sdkMetadata = this + return storageMetadata { + md5Hash = sdkMetadata.md5Hash() + cacheControl = sdkMetadata.cacheControl() + contentDisposition = sdkMetadata.contentDisposition() + contentEncoding = sdkMetadata.contentEncoding() + contentLanguage = sdkMetadata.contentLanguage() + contentType = sdkMetadata.contentType() + sdkMetadata.customMetadata()?.forEach { + setCustomMetadata(it.key.toString(), it.value.toString()) + } + } } \ No newline at end of file diff --git a/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/externals/storage.kt b/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/externals/storage.kt index 28297e3a3..b5266c13d 100644 --- a/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/externals/storage.kt +++ b/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/externals/storage.kt @@ -4,7 +4,6 @@ package dev.gitlive.firebase.storage.externals import dev.gitlive.firebase.externals.FirebaseApp -import dev.gitlive.firebase.storage.FirebaseStorageMetadata import kotlin.js.Promise external fun getStorage(app: FirebaseApp? = definedExternally): FirebaseStorage @@ -14,9 +13,11 @@ external fun ref(ref: StorageReference, url: String? = definedExternally): Stora external fun getDownloadURL(ref: StorageReference): Promise -external fun uploadBytes(ref: StorageReference, file: dynamic, metadata: FirebaseStorageMetadata?): Promise +external fun getMetadata(ref: StorageReference): Promise -external fun uploadBytesResumable(ref: StorageReference, data: dynamic, metadata: FirebaseStorageMetadata?): UploadTask +external fun uploadBytes(ref: StorageReference, file: dynamic, metadata: StorageMetadata?): Promise + +external fun uploadBytesResumable(ref: StorageReference, data: dynamic, metadata: StorageMetadata?): UploadTask external fun deleteObject(ref: StorageReference): Promise; @@ -59,6 +60,25 @@ external interface UploadTaskSnapshot { val totalBytes: Number } +external class StorageMetadata { + val bucket: String? + var cacheControl: String? + var contentDisposition: String? + var contentEncoding: String? + var contentLanguage: String? + var contentType: String? + var customMetadata: Map? + val fullPath: String? + val generation: String? + val md5Hash: String? + val metageneration: String? + val name: String? + val size: Number? + val timeCreated: String? + val updated: String? + +} + external class UploadTask : Promise { fun cancel(): Boolean; fun on(event: String, next: (snapshot: UploadTaskSnapshot) -> Unit, error: (a: StorageError) -> Unit, complete: () -> Unit): () -> Unit diff --git a/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt index 43dc13362..84ee139f9 100644 --- a/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -51,6 +51,8 @@ actual class StorageReference(val js: dev.gitlive.firebase.storage.externals.Sto actual val root: StorageReference get() = StorageReference(js.root) actual val storage: FirebaseStorage get() = FirebaseStorage(js.storage) + actual suspend fun getMetadata(): FirebaseStorageMetadata? = rethrow { getMetadata(js).await().toFirebaseStorageMetadata() } + actual fun child(path: String): StorageReference = StorageReference(ref(js, path)) actual suspend fun delete() = rethrow { deleteObject(js).await() } @@ -59,10 +61,10 @@ actual class StorageReference(val js: dev.gitlive.firebase.storage.externals.Sto actual suspend fun listAll(): ListResult = rethrow { ListResult(listAll(js).await()) } - actual suspend fun putFile(file: File, metadata: FirebaseStorageMetadata?): Unit = rethrow { uploadBytes(js, file, metadata).await() } + actual suspend fun putFile(file: File, metadata: FirebaseStorageMetadata?): Unit = rethrow { uploadBytes(js, file, metadata?.toStorageMetadata()).await() } actual fun putFileResumable(file: File, metadata: FirebaseStorageMetadata?): ProgressFlow = rethrow { - val uploadTask = uploadBytesResumable(js, file, metadata) + val uploadTask = uploadBytesResumable(js, file, metadata?.toStorageMetadata()) val flow = callbackFlow { val unsubscribe = uploadTask.on( @@ -123,4 +125,31 @@ internal fun errorToException(error: dynamic) = (error?.code ?: error?.message ? FirebaseStorageException(code, error) } } - } \ No newline at end of file + } + + +fun StorageMetadata.toFirebaseStorageMetadata(): FirebaseStorageMetadata { + val sdkMetadata = this + return storageMetadata { + md5Hash = sdkMetadata.md5Hash + cacheControl = sdkMetadata.cacheControl + contentDisposition = sdkMetadata.contentDisposition + contentEncoding = sdkMetadata.contentEncoding + contentLanguage = sdkMetadata.contentLanguage + contentType = sdkMetadata.contentType + sdkMetadata.customMetadata?.entries?.forEach { + setCustomMetadata(it.key, it.value) + } + } +} + +fun FirebaseStorageMetadata.toStorageMetadata(): StorageMetadata { + val metadata = StorageMetadata() + metadata.cacheControl = cacheControl + metadata.contentDisposition = contentDisposition + metadata.contentEncoding = contentEncoding + metadata.contentLanguage = contentLanguage + metadata.contentType = contentType + metadata.customMetadata = customMetadata + return metadata +} \ No newline at end of file diff --git a/firebase-storage/src/jvmMain/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt b/firebase-storage/src/jvmMain/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt index d76e30e1b..987ddd9b3 100644 --- a/firebase-storage/src/jvmMain/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt +++ b/firebase-storage/src/jvmMain/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt @@ -51,6 +51,10 @@ actual class StorageReference { actual val storage: FirebaseStorage get() = TODO("Not yet implemented") + actual suspend fun getMetadata(): FirebaseStorageMetadata? { + TODO("Not yet implemented") + } + actual fun child(path: String): StorageReference { TODO("Not yet implemented") } From d765d7492a2e2a7b7b09282451f356bf6950c23a Mon Sep 17 00:00:00 2001 From: Bas Buijsen Date: Thu, 6 Jun 2024 12:16:25 +0200 Subject: [PATCH 5/6] add tests --- .../firebase/storage/storage.android.kt | 7 ++ .../dev/gitlive/firebase/storage/storage.kt | 13 ++++ .../firebase/storage/storage.android.kt | 7 ++ .../dev/gitlive/firebase/storage/storage.kt | 7 ++ .../dev/gitlive/firebase/storage/storage.kt | 66 ++++++++++++++++++- .../dev/gitlive/firebase/storage/storage.kt | 11 +++- .../gitlive/firebase/storage/storage.ios.kt | 20 ++++++ .../dev/gitlive/firebase/storage/storage.kt | 3 + .../gitlive/firebase/storage/storage.js.kt | 5 ++ .../gitlive/firebase/storage/storage.jvm.kt | 5 +- .../gitlive/firebase/storage/storage.jvm.kt | 5 ++ 11 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 firebase-storage/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/storage/storage.android.kt create mode 100644 firebase-storage/src/androidUnitTest/kotlin/dev/gitlive/firebase/storage/storage.android.kt create mode 100644 firebase-storage/src/iosTest/kotlin/dev/gitlive/firebase/storage/storage.ios.kt create mode 100644 firebase-storage/src/jsTest/kotlin/dev/gitlive/firebase/storage/storage.js.kt create mode 100644 firebase-storage/src/jvmTest/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt diff --git a/firebase-storage/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/storage/storage.android.kt b/firebase-storage/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/storage/storage.android.kt new file mode 100644 index 000000000..22d885915 --- /dev/null +++ b/firebase-storage/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/storage/storage.android.kt @@ -0,0 +1,7 @@ +package dev.gitlive.firebase.storage + +import android.net.Uri + +actual fun createTestData(): Data { + return Data("test".toByteArray()) +} \ No newline at end of file diff --git a/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt index 279c47837..b1c678d60 100644 --- a/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -20,7 +20,10 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch import kotlinx.coroutines.tasks.await actual val Firebase.storage get() = @@ -77,6 +80,14 @@ actual class StorageReference(val android: com.google.firebase.storage.StorageRe } } + actual suspend fun putData(data: Data, metadata: FirebaseStorageMetadata?) { + if (metadata != null) { + android.putBytes(data.data, metadata.toStorageMetadata()).await().run {} + } else { + android.putBytes(data.data).await().run {} + } + } + actual fun putFileResumable(file: File, metadata: FirebaseStorageMetadata?): ProgressFlow { val android = if (metadata != null) { android.putFile(file.uri, metadata.toStorageMetadata()) @@ -118,6 +129,8 @@ actual class ListResult(android: com.google.firebase.storage.ListResult) { actual class File(val uri: Uri) +actual class Data(val data: ByteArray) + actual typealias FirebaseStorageException = com.google.firebase.storage.StorageException fun FirebaseStorageMetadata.toStorageMetadata(): StorageMetadata { diff --git a/firebase-storage/src/androidUnitTest/kotlin/dev/gitlive/firebase/storage/storage.android.kt b/firebase-storage/src/androidUnitTest/kotlin/dev/gitlive/firebase/storage/storage.android.kt new file mode 100644 index 000000000..22d885915 --- /dev/null +++ b/firebase-storage/src/androidUnitTest/kotlin/dev/gitlive/firebase/storage/storage.android.kt @@ -0,0 +1,7 @@ +package dev.gitlive.firebase.storage + +import android.net.Uri + +actual fun createTestData(): Data { + return Data("test".toByteArray()) +} \ No newline at end of file diff --git a/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt index 8414df377..3f0037ff3 100644 --- a/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -4,6 +4,9 @@ import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch /** Returns the [FirebaseStorage] instance of the default [FirebaseApp]. */ expect val Firebase.storage: FirebaseStorage @@ -44,6 +47,8 @@ expect class StorageReference { suspend fun putFile(file: File, metadata: FirebaseStorageMetadata? = null) + suspend fun putData(data: Data, metadata: FirebaseStorageMetadata? = null) + fun putFileResumable(file: File, metadata: FirebaseStorageMetadata? = null): ProgressFlow } @@ -55,6 +60,8 @@ expect class ListResult { expect class File +expect class Data + sealed class Progress(val bytesTransferred: Number, val totalByteCount: Number) { class Running internal constructor(bytesTransferred: Number, totalByteCount: Number): Progress(bytesTransferred, totalByteCount) class Paused internal constructor(bytesTransferred: Number, totalByteCount: Number): Progress(bytesTransferred, totalByteCount) diff --git a/firebase-storage/src/commonTest/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/commonTest/kotlin/dev/gitlive/firebase/storage/storage.kt index 0b96512af..cf5fbc127 100644 --- a/firebase-storage/src/commonTest/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/commonTest/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -8,7 +8,16 @@ import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseOptions import dev.gitlive.firebase.apps import dev.gitlive.firebase.initialize +import dev.gitlive.firebase.runBlockingTest +import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlin.test.AfterTest import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertNotNull expect val emulatorHost: String expect val context: Any @@ -35,6 +44,61 @@ class FirebaseStorageTest { storage = Firebase.storage(app).apply { useEmulator(emulatorHost, 9199) + setMaxOperationRetryTimeMillis(10000) + setMaxUploadRetryTimeMillis(10000) } } -} \ No newline at end of file + + @AfterTest + fun deinitializeFirebase() = runBlockingTest { + Firebase.apps(context).forEach { + it.delete() + } + } + + @Test + fun testStorageNotNull() { + assertNotNull(storage) + } + + @Test + fun testUploadShouldNotCrash() = runBlockingTest { + val data = createTestData() + val ref = storage.reference("test").child("testFile.txt") + ref.putData(data) + } + + @Test + fun testUploadMetadata() = runBlockingTest { + val data = createTestData() + val ref = storage.reference("test").child("testFile.txt") + val metadata = storageMetadata { + contentType = "text/plain" + } + ref.putData(data, metadata) + + val metadataResult = ref.getMetadata() + + assertNotNull(metadataResult) + assertNotNull(metadataResult.contentType) + assertEquals(metadataResult.contentType, metadata.contentType) + } + + @Test + fun testUploadCustomMetadata() = runBlockingTest { + val data = createTestData() + val ref = storage.reference("test").child("testFile.txt") + val metadata = storageMetadata { + contentType = "text/plain" + setCustomMetadata("key", "value") + } + ref.putData(data, metadata) + + val metadataResult = ref.getMetadata() + + assertNotNull(metadataResult) + assertEquals(metadataResult.customMetadata["key"], metadata.customMetadata["key"]) + } +} + +expect fun createTestData(): Data \ No newline at end of file diff --git a/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt index c8e5eed09..610f72e66 100644 --- a/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -23,6 +23,7 @@ import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.emitAll +import platform.Foundation.NSData import platform.Foundation.NSError import platform.Foundation.NSURL @@ -87,7 +88,13 @@ actual class StorageReference(val ios: FIRStorageReference) { } } - actual suspend fun putFile(file: File, metadata: FirebaseStorageMetadata?) = ios.awaitResult { putFile(file.url, metadata?.toFIRMetadata(), completion = it) }.run {} + actual suspend fun putFile(file: File, metadata: FirebaseStorageMetadata?) = ios.awaitResult { callback -> + putFile(file.url, metadata?.toFIRMetadata(), callback) + }.run {} + + actual suspend fun putData(data: Data, metadata: FirebaseStorageMetadata?) = ios.awaitResult { callback -> + putData(data.data, metadata?.toFIRMetadata(), callback) + }.run {} actual fun putFileResumable(file: File, metadata: FirebaseStorageMetadata?): ProgressFlow { val ios = ios.putFile(file.url, metadata?.toFIRMetadata()) @@ -133,6 +140,8 @@ actual class ListResult(ios: FIRStorageListResult) { actual class File(val url: NSURL) +actual class Data(val data: NSData) + actual class FirebaseStorageException(message: String): FirebaseException(message) suspend inline fun T.await(function: T.(callback: (NSError?) -> Unit) -> Unit) { diff --git a/firebase-storage/src/iosTest/kotlin/dev/gitlive/firebase/storage/storage.ios.kt b/firebase-storage/src/iosTest/kotlin/dev/gitlive/firebase/storage/storage.ios.kt new file mode 100644 index 000000000..b2da088d5 --- /dev/null +++ b/firebase-storage/src/iosTest/kotlin/dev/gitlive/firebase/storage/storage.ios.kt @@ -0,0 +1,20 @@ +package dev.gitlive.firebase.storage + +import kotlinx.cinterop.BetaInteropApi +import kotlinx.cinterop.utf8 +import platform.Foundation.NSCoder +import platform.Foundation.NSData +import platform.Foundation.NSSearchPathDirectory +import platform.Foundation.NSSearchPathDomainMask +import platform.Foundation.NSSearchPathForDirectoriesInDomains +import platform.Foundation.NSString +import platform.Foundation.NSURL +import platform.Foundation.NSUTF8StringEncoding +import platform.Foundation.create +import platform.Foundation.dataUsingEncoding + +@OptIn(BetaInteropApi::class) +actual fun createTestData(): Data { + val value = NSString.create(string = "test") + return Data(value.dataUsingEncoding(NSUTF8StringEncoding, false)!!) +} \ No newline at end of file diff --git a/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt index 84ee139f9..93b954aa6 100644 --- a/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -63,6 +63,8 @@ actual class StorageReference(val js: dev.gitlive.firebase.storage.externals.Sto actual suspend fun putFile(file: File, metadata: FirebaseStorageMetadata?): Unit = rethrow { uploadBytes(js, file, metadata?.toStorageMetadata()).await() } + actual suspend fun putData(data: Data, metadata: FirebaseStorageMetadata?): Unit = rethrow { uploadBytes(js, data.data, metadata?.toStorageMetadata()).await() } + actual fun putFileResumable(file: File, metadata: FirebaseStorageMetadata?): ProgressFlow = rethrow { val uploadTask = uploadBytesResumable(js, file, metadata?.toStorageMetadata()) @@ -101,6 +103,7 @@ actual class ListResult(js: dev.gitlive.firebase.storage.externals.ListResult) { } actual typealias File = org.w3c.files.File +actual class Data(val data: org.khronos.webgl.Uint8Array) actual open class FirebaseStorageException(code: String, cause: Throwable) : FirebaseException(code, cause) diff --git a/firebase-storage/src/jsTest/kotlin/dev/gitlive/firebase/storage/storage.js.kt b/firebase-storage/src/jsTest/kotlin/dev/gitlive/firebase/storage/storage.js.kt new file mode 100644 index 000000000..8a8c266b6 --- /dev/null +++ b/firebase-storage/src/jsTest/kotlin/dev/gitlive/firebase/storage/storage.js.kt @@ -0,0 +1,5 @@ +package dev.gitlive.firebase.storage + +actual fun createTestData(): Data { + TODO("Not yet implemented") +} \ No newline at end of file diff --git a/firebase-storage/src/jvmMain/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt b/firebase-storage/src/jvmMain/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt index 987ddd9b3..ef187c0d2 100644 --- a/firebase-storage/src/jvmMain/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt +++ b/firebase-storage/src/jvmMain/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt @@ -77,6 +77,8 @@ actual class StorageReference { actual suspend fun putFile(file: File, metadata: FirebaseStorageMetadata?) { } + actual suspend fun putData(data: Data, metadata: FirebaseStorageMetadata?) { + } } actual class ListResult { @@ -89,4 +91,5 @@ actual class ListResult { } actual class File -actual class FirebaseStorageException internal constructor(message: String) : FirebaseException(message) \ No newline at end of file +actual class FirebaseStorageException internal constructor(message: String) : FirebaseException(message) +actual class Data \ No newline at end of file diff --git a/firebase-storage/src/jvmTest/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt b/firebase-storage/src/jvmTest/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt new file mode 100644 index 000000000..8a8c266b6 --- /dev/null +++ b/firebase-storage/src/jvmTest/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt @@ -0,0 +1,5 @@ +package dev.gitlive.firebase.storage + +actual fun createTestData(): Data { + TODO("Not yet implemented") +} \ No newline at end of file From 0f557511defbf220ffc2f889abed3ed178424346 Mon Sep 17 00:00:00 2001 From: Bas Buijsen Date: Fri, 14 Jun 2024 14:19:18 +0200 Subject: [PATCH 6/6] fix js build --- .../kotlin/dev/gitlive/firebase/storage/externals/storage.kt | 2 +- .../src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/externals/storage.kt b/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/externals/storage.kt index b5266c13d..52fca0864 100644 --- a/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/externals/storage.kt +++ b/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/externals/storage.kt @@ -23,7 +23,7 @@ external fun deleteObject(ref: StorageReference): Promise; external fun listAll(ref: StorageReference): Promise; -external fun connectFirestoreEmulator( +external fun connectStorageEmulator( storage: FirebaseStorage, host: String, port: Double, diff --git a/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt index 93b954aa6..1ab30ebb7 100644 --- a/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -34,7 +34,7 @@ actual class FirebaseStorage(val js: dev.gitlive.firebase.storage.externals.Fire } actual fun useEmulator(host: String, port: Int) { - connectFirestoreEmulator(js, host, port.toDouble()) + connectStorageEmulator(js, host, port.toDouble()) } actual val reference: StorageReference get() = StorageReference(ref(js))