Skip to content

Commit ebf65a1

Browse files
add putFile method
1 parent 24f9305 commit ebf65a1

File tree

4 files changed

+188
-28
lines changed
  • firebase-storage/src
    • androidMain/kotlin/dev/gitlive/firebase/storage
    • commonMain/kotlin/dev/gitlive/firebase/storage
    • iosMain/kotlin/dev/gitlive/firebase/storage
    • jsMain/kotlin/dev/gitlive/firebase/storage

4 files changed

+188
-28
lines changed

firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
/*
2-
* Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright (c) 2023 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
@file:JvmName("android")
66
package dev.gitlive.firebase.storage
77

8+
import android.net.Uri
9+
import com.google.android.gms.tasks.OnCanceledListener
10+
import com.google.android.gms.tasks.OnCompleteListener
11+
import com.google.firebase.storage.OnPausedListener
12+
import com.google.firebase.storage.OnProgressListener
13+
import com.google.firebase.storage.UploadTask
814
import dev.gitlive.firebase.Firebase
915
import dev.gitlive.firebase.FirebaseApp
10-
import dev.gitlive.firebase.FirebaseException
16+
import kotlinx.coroutines.cancel
17+
import kotlinx.coroutines.channels.awaitClose
18+
import kotlinx.coroutines.channels.trySendBlocking
19+
import kotlinx.coroutines.flow.FlowCollector
20+
import kotlinx.coroutines.flow.callbackFlow
21+
import kotlinx.coroutines.flow.emitAll
1122
import kotlinx.coroutines.tasks.await
1223

1324
actual val Firebase.storage get() =
@@ -45,8 +56,40 @@ actual class StorageReference(val android: com.google.firebase.storage.StorageRe
4556
actual val storage: FirebaseStorage get() = FirebaseStorage(android.storage)
4657

4758
actual fun child(path: String): StorageReference = StorageReference(android.child(path))
59+
4860
actual suspend fun delete() = android.delete().await().run { Unit }
61+
4962
actual suspend fun getDownloadUrl(): String = android.downloadUrl.await().toString()
63+
64+
actual fun putFile(file: File): ProgressFlow {
65+
val android = android.putFile(file.uri)
66+
67+
val flow = callbackFlow {
68+
val onCanceledListener = OnCanceledListener { cancel() }
69+
val onCompleteListener = OnCompleteListener<UploadTask.TaskSnapshot> { close(it.exception) }
70+
val onPausedListener = OnPausedListener<UploadTask.TaskSnapshot> { trySendBlocking(Progress.Paused(it.bytesTransferred, it.totalByteCount)) }
71+
val onProgressListener = OnProgressListener<UploadTask.TaskSnapshot> { trySendBlocking(Progress.Running(it.bytesTransferred, it.totalByteCount)) }
72+
android.addOnCanceledListener(onCanceledListener)
73+
android.addOnCompleteListener(onCompleteListener)
74+
android.addOnPausedListener(onPausedListener)
75+
android.addOnProgressListener(onProgressListener)
76+
awaitClose {
77+
android.removeOnCanceledListener(onCanceledListener)
78+
android.removeOnCompleteListener(onCompleteListener)
79+
android.removeOnPausedListener(onPausedListener)
80+
android.removeOnProgressListener(onProgressListener)
81+
}
82+
}
83+
84+
return object : ProgressFlow {
85+
override suspend fun collect(collector: FlowCollector<Progress>) = collector.emitAll(flow)
86+
override fun pause() = android.pause().run {}
87+
override fun resume() = android.resume().run {}
88+
override fun cancel() = android.cancel().run {}
89+
}
90+
}
5091
}
5192

52-
actual open class StorageException(message: String) : FirebaseException(message)
93+
actual class File(val uri: Uri)
94+
95+
actual typealias FirebaseStorageException = com.google.firebase.storage.StorageException

firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,27 @@ expect class StorageReference {
2828
val storage: FirebaseStorage
2929

3030
fun child(path: String): StorageReference
31+
3132
suspend fun delete()
33+
3234
suspend fun getDownloadUrl(): String
35+
36+
fun putFile(file: File): ProgressFlow
37+
38+
}
39+
40+
expect class File
41+
42+
sealed class Progress(val bytesTransferred: Number, val totalByteCount: Number) {
43+
class Running internal constructor(bytesTransferred: Number, totalByteCount: Number): Progress(bytesTransferred, totalByteCount)
44+
class Paused internal constructor(bytesTransferred: Number, totalByteCount: Number): Progress(bytesTransferred, totalByteCount)
3345
}
3446

35-
expect open class StorageException : FirebaseException
47+
interface ProgressFlow : Flow<Progress> {
48+
fun pause()
49+
fun resume()
50+
fun cancel()
51+
}
52+
53+
54+
expect class FirebaseStorageException : FirebaseException

firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
11
/*
2-
* Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright (c) 2023 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package dev.gitlive.firebase.storage
66

77
import cocoapods.FirebaseStorage.FIRStorage
88
import cocoapods.FirebaseStorage.FIRStorageReference
9+
import cocoapods.FirebaseStorage.FIRStorageTaskStatusFailure
10+
import cocoapods.FirebaseStorage.FIRStorageTaskStatusPause
11+
import cocoapods.FirebaseStorage.FIRStorageTaskStatusProgress
12+
import cocoapods.FirebaseStorage.FIRStorageTaskStatusResume
13+
import cocoapods.FirebaseStorage.FIRStorageTaskStatusSuccess
914
import dev.gitlive.firebase.Firebase
1015
import dev.gitlive.firebase.FirebaseApp
1116
import dev.gitlive.firebase.FirebaseException
1217
import kotlinx.coroutines.CompletableDeferred
18+
import kotlinx.coroutines.cancel
19+
import kotlinx.coroutines.channels.awaitClose
20+
import kotlinx.coroutines.channels.trySendBlocking
21+
import kotlinx.coroutines.flow.FlowCollector
22+
import kotlinx.coroutines.flow.callbackFlow
23+
import kotlinx.coroutines.flow.emitAll
1324
import platform.Foundation.NSError
1425
import platform.Foundation.NSURL
1526

@@ -50,35 +61,73 @@ actual class StorageReference(val ios: FIRStorageReference) {
5061
actual val storage: FirebaseStorage get() = FirebaseStorage(ios.storage())
5162

5263
actual fun child(path: String): StorageReference = StorageReference(ios.child(path))
64+
5365
actual suspend fun delete() = await { ios.deleteWithCompletion(it) }
54-
actual suspend fun getDownloadUrl(): String = awaitResult<NSURL?> { ios.downloadURLWithCompletion(it) }?.absoluteString!!
55-
}
5666

57-
actual open class StorageException(message: String) : FirebaseException(message)
67+
actual suspend fun getDownloadUrl(): String = ios.awaitResult {
68+
downloadURLWithCompletion(completion = it)
69+
}.absoluteString()!!
70+
71+
actual fun putFile(file: File): ProgressFlow {
72+
val ios = ios.putFile(file.url)
73+
74+
val flow = callbackFlow {
75+
ios.observeStatus(FIRStorageTaskStatusProgress) {
76+
val progress = it!!.progress()!!
77+
trySendBlocking(Progress.Running(progress.completedUnitCount, progress.totalUnitCount))
78+
}
79+
ios.observeStatus(FIRStorageTaskStatusPause) {
80+
val progress = it!!.progress()!!
81+
trySendBlocking(Progress.Paused(progress.completedUnitCount, progress.totalUnitCount))
82+
}
83+
ios.observeStatus(FIRStorageTaskStatusResume) {
84+
val progress = it!!.progress()!!
85+
trySendBlocking(Progress.Running(progress.completedUnitCount, progress.totalUnitCount))
86+
}
87+
ios.observeStatus(FIRStorageTaskStatusSuccess) { close(FirebaseStorageException(it!!.error().toString())) }
88+
ios.observeStatus(FIRStorageTaskStatusFailure) {
89+
when(it!!.error()!!.code) {
90+
/*FIRStorageErrorCodeCancelled = */ -13040L -> cancel(it.error()!!.localizedDescription)
91+
else -> close(FirebaseStorageException(it.error().toString()))
92+
}
93+
}
94+
awaitClose { ios.removeAllObservers() }
95+
}
5896

59-
suspend inline fun <reified T> awaitResult(function: (callback: (T?, NSError?) -> Unit) -> Unit): T {
60-
val job = CompletableDeferred<T?>()
61-
function { result, error ->
62-
if(error == null) {
63-
job.complete(result)
64-
} else {
65-
job.completeExceptionally(error.toException())
97+
return object : ProgressFlow {
98+
override suspend fun collect(collector: FlowCollector<Progress>) = collector.emitAll(flow)
99+
override fun pause() = ios.pause()
100+
override fun resume() = ios.resume()
101+
override fun cancel() = ios.cancel()
66102
}
67103
}
68-
return job.await() as T
104+
69105
}
70106

71-
suspend inline fun <T> await(function: (callback: (NSError?) -> Unit) -> T): T {
107+
actual class File(val url: NSURL)
108+
109+
actual class FirebaseStorageException(message: String): FirebaseException(message)
110+
111+
suspend inline fun <T> T.await(function: T.(callback: (NSError?) -> Unit) -> Unit) {
72112
val job = CompletableDeferred<Unit>()
73-
val result = function { error ->
113+
function { error ->
74114
if(error == null) {
75115
job.complete(Unit)
76116
} else {
77-
job.completeExceptionally(error.toException())
117+
job.completeExceptionally(FirebaseStorageException(error.toString()))
78118
}
79119
}
80120
job.await()
81-
return result
82121
}
83122

84-
fun NSError.toException() = StorageException(description!!) // TODO: Improve error handling
123+
suspend inline fun <T, reified R> T.awaitResult(function: T.(callback: (R?, NSError?) -> Unit) -> Unit): R {
124+
val job = CompletableDeferred<R?>()
125+
function { result, error ->
126+
if(error == null) {
127+
job.complete(result)
128+
} else {
129+
job.completeExceptionally(FirebaseStorageException(error.toString()))
130+
}
131+
}
132+
return job.await() as R
133+
}

firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright (c) 2023 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package dev.gitlive.firebase.storage
@@ -9,6 +9,11 @@ import dev.gitlive.firebase.FirebaseApp
99
import dev.gitlive.firebase.FirebaseException
1010
import dev.gitlive.firebase.firebase
1111
import kotlinx.coroutines.await
12+
import kotlinx.coroutines.cancel
13+
import kotlinx.coroutines.channels.awaitClose
14+
import kotlinx.coroutines.flow.FlowCollector
15+
import kotlinx.coroutines.flow.callbackFlow
16+
import kotlinx.coroutines.flow.emitAll
1217

1318

1419
actual val Firebase.storage get() =
@@ -46,21 +51,65 @@ actual class StorageReference(val js: firebase.storage.Reference) {
4651
actual val storage: FirebaseStorage get() = FirebaseStorage(js.storage)
4752

4853
actual fun child(path: String): StorageReference = StorageReference(js.child(path))
54+
4955
actual suspend fun delete() = rethrow { js.delete().await() }
56+
5057
actual suspend fun getDownloadUrl(): String = rethrow { js.getDownloadURL().await().toString() }
58+
59+
actual fun putFile(file: File): ProgressFlow = rethrow {
60+
val uploadTask = js.put(file)
61+
62+
val flow = callbackFlow {
63+
val unsubscribe = uploadTask.on(
64+
"state_changed",
65+
{
66+
when(it.state) {
67+
"paused" -> trySend(Progress.Paused(it.bytesTransferred, it.totalBytes))
68+
"running" -> trySend(Progress.Running(it.bytesTransferred, it.totalBytes))
69+
"canceled" -> cancel()
70+
"success", "error" -> Unit
71+
else -> TODO("Unknown state ${it.state}")
72+
}
73+
},
74+
{ close(errorToException(it)) },
75+
{ close() }
76+
)
77+
awaitClose { unsubscribe() }
78+
}
79+
80+
return object : ProgressFlow {
81+
override suspend fun collect(collector: FlowCollector<Progress>) = collector.emitAll(flow)
82+
override fun pause() = uploadTask.pause().run {}
83+
override fun resume() = uploadTask.resume().run {}
84+
override fun cancel() = uploadTask.cancel().run {}
85+
}
86+
}
87+
5188
}
5289

53-
inline fun <T, R> T.rethrow(function: T.() -> R): R = dev.gitlive.firebase.storage.rethrow { function() }
90+
actual typealias File = org.w3c.files.File
91+
92+
actual open class FirebaseStorageException(code: String, cause: Throwable) :
93+
FirebaseException(code, cause)
5494

55-
inline fun <R> rethrow(function: () -> R): R {
95+
internal inline fun <R> rethrow(function: () -> R): R {
5696
try {
5797
return function()
5898
} catch (e: Exception) {
5999
throw e
60-
} catch(e: dynamic) {
61-
throw StorageException(e.code as String, e)
100+
} catch (e: dynamic) {
101+
throw errorToException(e)
62102
}
63103
}
64104

65-
actual open class StorageException(code: String, cause: Throwable) :
66-
FirebaseException(code, cause)
105+
internal fun errorToException(error: dynamic) = (error?.code ?: error?.message ?: "")
106+
.toString()
107+
.lowercase()
108+
.let { code ->
109+
when {
110+
else -> {
111+
println("Unknown error code in ${JSON.stringify(error)}")
112+
FirebaseStorageException(code, error)
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)