diff --git a/firebase-auth/src/jvmTest/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/jvmTest/kotlin/dev/gitlive/firebase/auth/auth.kt index 55c7b7cea..d83d421fb 100644 --- a/firebase-auth/src/jvmTest/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/jvmTest/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -9,3 +9,6 @@ package dev.gitlive.firebase.auth actual val emulatorHost: String = "10.0.2.2" actual val context: Any = Unit + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreForAndroidUnitTest diff --git a/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt index ffdcc32a9..3980e67f0 100644 --- a/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -2,6 +2,7 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ +@file:JvmName("databaseAndroid") package dev.gitlive.firebase.database import com.google.android.gms.tasks.Task @@ -16,7 +17,6 @@ import dev.gitlive.firebase.EncodeDecodeSettingsBuilder import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.database.ChildEvent.Type -import dev.gitlive.firebase.database.FirebaseDatabase.Companion.FirebaseDatabase import dev.gitlive.firebase.decode import dev.gitlive.firebase.reencodeTransformation import kotlinx.coroutines.CompletableDeferred @@ -50,23 +50,23 @@ suspend fun Task.awaitWhileOnline(database: FirebaseDatabase): T = .first() actual val Firebase.database - by lazy { FirebaseDatabase(com.google.firebase.database.FirebaseDatabase.getInstance()) } + by lazy { FirebaseDatabase.getInstance(com.google.firebase.database.FirebaseDatabase.getInstance()) } actual fun Firebase.database(url: String) = - FirebaseDatabase(com.google.firebase.database.FirebaseDatabase.getInstance(url)) + FirebaseDatabase.getInstance(com.google.firebase.database.FirebaseDatabase.getInstance(url)) actual fun Firebase.database(app: FirebaseApp) = - FirebaseDatabase(com.google.firebase.database.FirebaseDatabase.getInstance(app.android)) + FirebaseDatabase.getInstance(com.google.firebase.database.FirebaseDatabase.getInstance(app.android)) actual fun Firebase.database(app: FirebaseApp, url: String) = - FirebaseDatabase(com.google.firebase.database.FirebaseDatabase.getInstance(app.android, url)) + FirebaseDatabase.getInstance(com.google.firebase.database.FirebaseDatabase.getInstance(app.android, url)) -actual class FirebaseDatabase private constructor(val android: com.google.firebase.database.FirebaseDatabase) { +actual class FirebaseDatabase internal constructor(val android: com.google.firebase.database.FirebaseDatabase) { companion object { private val instances = WeakHashMap() - internal fun FirebaseDatabase( + internal fun getInstance( android: com.google.firebase.database.FirebaseDatabase ) = instances.getOrPut(android) { dev.gitlive.firebase.database.FirebaseDatabase(android) } } @@ -79,8 +79,14 @@ actual class FirebaseDatabase private constructor(val android: com.google.fireba actual fun reference() = DatabaseReference(NativeDatabaseReference(android.reference, persistenceEnabled)) - actual fun setPersistenceEnabled(enabled: Boolean) = - android.setPersistenceEnabled(enabled).also { persistenceEnabled = enabled } + actual fun setPersistenceEnabled(enabled: Boolean) { + android.setPersistenceEnabled(enabled) + persistenceEnabled = enabled + } + + actual fun setPersistenceCacheSizeBytes(cacheSizeInBytes: Long) { + android.setPersistenceCacheSizeBytes(cacheSizeInBytes) + } actual fun setLoggingEnabled(enabled: Boolean) = android.setLogLevel(Logger.Level.DEBUG.takeIf { enabled } ?: Logger.Level.NONE) diff --git a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt index d1e272a38..7bbd44935 100644 --- a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -32,10 +32,12 @@ expect fun Firebase.database(app: FirebaseApp): FirebaseDatabase expect fun Firebase.database(app: FirebaseApp, url: String): FirebaseDatabase expect class FirebaseDatabase { + fun reference(path: String): DatabaseReference fun reference(): DatabaseReference - fun setPersistenceEnabled(enabled: Boolean) fun setLoggingEnabled(enabled: Boolean) + fun setPersistenceEnabled(enabled: Boolean) + fun setPersistenceCacheSizeBytes(cacheSizeInBytes: Long) fun useEmulator(host: String, port: Int) } diff --git a/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt index a78a26fe1..92de88a7c 100644 --- a/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -37,6 +37,9 @@ import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer import platform.Foundation.NSError import platform.Foundation.allObjects +import platform.darwin.dispatch_queue_t +import kotlin.collections.component1 +import kotlin.collections.component2 actual val Firebase.database by lazy { FirebaseDatabase(FIRDatabase.database()) } @@ -64,6 +67,10 @@ actual class FirebaseDatabase internal constructor(val ios: FIRDatabase) { ios.persistenceEnabled = enabled } + actual fun setPersistenceCacheSizeBytes(cacheSizeInBytes: Long) { + ios.setPersistenceCacheSizeBytes(cacheSizeInBytes.toULong()) + } + actual fun setLoggingEnabled(enabled: Boolean) = FIRDatabase.setLoggingEnabled(enabled) diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt index cf0023d0e..dfb187d8e 100644 --- a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -67,9 +67,11 @@ actual fun Firebase.database(app: FirebaseApp, url: String) = rethrow { FirebaseDatabase(getDatabase(app = app.js, url = url)) } actual class FirebaseDatabase internal constructor(val js: Database) { + actual fun reference(path: String) = rethrow { DatabaseReference(NativeDatabaseReference(ref(js, path), js)) } actual fun reference() = rethrow { DatabaseReference(NativeDatabaseReference(ref(js), js)) } actual fun setPersistenceEnabled(enabled: Boolean) {} + actual fun setPersistenceCacheSizeBytes(cacheSizeInBytes: Long) {} actual fun setLoggingEnabled(enabled: Boolean) = rethrow { enableLogging(enabled) } actual fun useEmulator(host: String, port: Int) = rethrow { connectDatabaseEmulator(js, host, port) } } diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 38d676830..eb73173e5 100644 --- a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -5,19 +5,31 @@ @file:JvmName("android") package dev.gitlive.firebase.firestore +import com.google.android.gms.tasks.TaskExecutors +import com.google.firebase.firestore.MemoryCacheSettings +import com.google.firebase.firestore.MemoryEagerGcSettings +import com.google.firebase.firestore.MemoryLruGcSettings import com.google.firebase.firestore.MetadataChanges +import com.google.firebase.firestore.PersistentCacheSettings import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp +import kotlinx.coroutines.channels.ProducerScope import dev.gitlive.firebase.firestore.Source.* import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.runBlocking import kotlinx.coroutines.tasks.await -import kotlinx.serialization.Serializable +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.Executor import com.google.firebase.firestore.FieldPath as AndroidFieldPath import com.google.firebase.firestore.Filter as AndroidFilter import com.google.firebase.firestore.Query as AndroidQuery +import com.google.firebase.firestore.firestoreSettings as androidFirestoreSettings +import com.google.firebase.firestore.memoryCacheSettings as androidMemoryCacheSettings +import com.google.firebase.firestore.memoryEagerGcSettings as androidMemoryEagerGcSettings +import com.google.firebase.firestore.memoryLruGcSettings as androidMemoryLruGcSettings +import com.google.firebase.firestore.persistentCacheSettings as androidPersistentCacheSettings actual val Firebase.firestore get() = FirebaseFirestore(com.google.firebase.firestore.FirebaseFirestore.getInstance()) @@ -25,49 +37,143 @@ actual val Firebase.firestore get() = actual fun Firebase.firestore(app: FirebaseApp) = FirebaseFirestore(com.google.firebase.firestore.FirebaseFirestore.getInstance(app.android)) -actual class FirebaseFirestore(val android: com.google.firebase.firestore.FirebaseFirestore) { +val LocalCacheSettings.android: com.google.firebase.firestore.LocalCacheSettings get() = when (this) { + is LocalCacheSettings.Persistent -> androidPersistentCacheSettings { + setSizeBytes(sizeBytes) + } + is LocalCacheSettings.Memory -> androidMemoryCacheSettings { + setGcSettings( + when (garbaseCollectorSettings) { + is MemoryGarbageCollectorSettings.Eager -> androidMemoryEagerGcSettings { } + is MemoryGarbageCollectorSettings.LRUGC -> androidMemoryLruGcSettings { + setSizeBytes(garbaseCollectorSettings.sizeBytes) + } + } + ) + } +} + +// Since on iOS Callback threads are set as settings, we store the settings explicitly here as well +private val callbackExecutorMap = ConcurrentHashMap() + +actual typealias NativeFirebaseFirestore = com.google.firebase.firestore.FirebaseFirestore +internal actual class NativeFirebaseFirestoreWrapper actual constructor(actual val native: NativeFirebaseFirestore) { + + actual var settings: FirebaseFirestoreSettings + get() = with(native.firestoreSettings) { + FirebaseFirestoreSettings( + isSslEnabled, + host, + cacheSettings?.let { localCacheSettings -> + when (localCacheSettings) { + is MemoryCacheSettings -> { + val garbageCollectionSettings = when (val settings = localCacheSettings.garbageCollectorSettings) { + is MemoryEagerGcSettings -> MemoryGarbageCollectorSettings.Eager + is MemoryLruGcSettings -> MemoryGarbageCollectorSettings.LRUGC(settings.sizeBytes) + else -> throw IllegalArgumentException("Existing settings does not have valid GarbageCollectionSettings") + } + LocalCacheSettings.Memory(garbageCollectionSettings) + } + + is PersistentCacheSettings -> LocalCacheSettings.Persistent(localCacheSettings.sizeBytes) + else -> throw IllegalArgumentException("Existing settings is not of a valid type") + } + } ?: kotlin.run { + @Suppress("DEPRECATION") + when { + isPersistenceEnabled -> LocalCacheSettings.Persistent(cacheSizeBytes) + cacheSizeBytes == FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED -> LocalCacheSettings.Memory(MemoryGarbageCollectorSettings.Eager) + else -> LocalCacheSettings.Memory(MemoryGarbageCollectorSettings.LRUGC(cacheSizeBytes)) + } + }, + callbackExecutorMap[native] ?: TaskExecutors.MAIN_THREAD + ) + } + set(value) { + native.firestoreSettings = androidFirestoreSettings { + isSslEnabled = value.sslEnabled + host = value.host + setLocalCacheSettings(value.cacheSettings.android) + } + callbackExecutorMap[native] = value.callbackExecutor + } - actual fun collection(collectionPath: String) = CollectionReference(NativeCollectionReference(android.collection(collectionPath))) + actual fun collection(collectionPath: String) = NativeCollectionReference(native.collection(collectionPath)) - actual fun collectionGroup(collectionId: String) = Query(android.collectionGroup(collectionId).native) + actual fun collectionGroup(collectionId: String) = native.collectionGroup(collectionId).native - actual fun document(documentPath: String) = DocumentReference(NativeDocumentReference(android.document(documentPath))) + actual fun document(documentPath: String) = NativeDocumentReference(native.document(documentPath)) - actual fun batch() = WriteBatch(NativeWriteBatch(android.batch())) + actual fun batch() = NativeWriteBatch(native.batch()) actual fun setLoggingEnabled(loggingEnabled: Boolean) = com.google.firebase.firestore.FirebaseFirestore.setLoggingEnabled(loggingEnabled) - actual suspend fun runTransaction(func: suspend Transaction.() -> T): T = - android.runTransaction { runBlocking { Transaction(NativeTransaction(it)).func() } }.await() + actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T): T = + native.runTransaction { runBlocking { NativeTransaction(it).func() } }.await() actual suspend fun clearPersistence() = - android.clearPersistence().await().run { } + native.clearPersistence().await().run { } actual fun useEmulator(host: String, port: Int) { - android.useEmulator(host, port) - android.firestoreSettings = com.google.firebase.firestore.FirebaseFirestoreSettings.Builder() - .setPersistenceEnabled(false) - .build() - } - - actual fun setSettings(persistenceEnabled: Boolean?, sslEnabled: Boolean?, host: String?, cacheSizeBytes: Long?) { - android.firestoreSettings = com.google.firebase.firestore.FirebaseFirestoreSettings.Builder().also { builder -> - persistenceEnabled?.let { builder.setPersistenceEnabled(it) } - sslEnabled?.let { builder.isSslEnabled = it } - host?.let { builder.host = it } - cacheSizeBytes?.let { builder.cacheSizeBytes = it } - }.build() + native.useEmulator(host, port) } actual suspend fun disableNetwork() = - android.disableNetwork().await().run { } + native.disableNetwork().await().run { } actual suspend fun enableNetwork() = - android.enableNetwork().await().run { } + native.enableNetwork().await().run { } } +val FirebaseFirestore.android get() = native + +actual data class FirebaseFirestoreSettings( + actual val sslEnabled: Boolean, + actual val host: String, + actual val cacheSettings: LocalCacheSettings, + val callbackExecutor: Executor, +) { + + actual companion object { + actual val CACHE_SIZE_UNLIMITED: Long = -1L + internal actual val DEFAULT_HOST: String = "firestore.googleapis.com" + internal actual val MINIMUM_CACHE_BYTES: Long = 1 * 1024 * 1024 + internal actual val DEFAULT_CACHE_SIZE_BYTES: Long = 100 * 1024 * 1024 + } + + actual class Builder internal constructor( + actual var sslEnabled: Boolean, + actual var host: String, + actual var cacheSettings: LocalCacheSettings, + var callbackExecutor: Executor, + ) { + + actual constructor() : this( + true, + DEFAULT_HOST, + persistentCacheSettings { }, + TaskExecutors.MAIN_THREAD + ) + actual constructor(settings: FirebaseFirestoreSettings) : this(settings.sslEnabled, settings.host, settings.cacheSettings, settings.callbackExecutor) + + actual fun build(): FirebaseFirestoreSettings = FirebaseFirestoreSettings(sslEnabled, host, cacheSettings, callbackExecutor) + } +} + +actual fun firestoreSettings( + settings: FirebaseFirestoreSettings?, + builder: FirebaseFirestoreSettings.Builder.() -> Unit +): FirebaseFirestoreSettings = FirebaseFirestoreSettings.Builder().apply { + settings?.let { + sslEnabled = it.sslEnabled + host = it.host + cacheSettings = it.cacheSettings + callbackExecutor = it.callbackExecutor + } + }.apply(builder).build() + internal val SetOptions.android: com.google.firebase.firestore.SetOptions? get() = when (this) { is SetOptions.Merge -> com.google.firebase.firestore.SetOptions.merge() is SetOptions.Overwrite -> null @@ -206,19 +312,27 @@ internal actual class NativeDocumentReference actual constructor(actual val nati actual val snapshots: Flow get() = snapshots() - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE - val listener = android.addSnapshotListener(metadataChanges) { snapshot, exception -> - snapshot?.let { trySend(NativeDocumentSnapshot(snapshot)) } - exception?.let { close(exception) } - } - awaitClose { listener.remove() } + actual fun snapshots(includeMetadataChanges: Boolean) = addSnapshotListener(includeMetadataChanges) { snapshot, exception -> + snapshot?.let { trySend(NativeDocumentSnapshot(snapshot)) } + exception?.let { close(exception) } } override fun equals(other: Any?): Boolean = this === other || other is NativeDocumentReference && nativeValue == other.nativeValue override fun hashCode(): Int = nativeValue.hashCode() override fun toString(): String = nativeValue.toString() + + private fun addSnapshotListener( + includeMetadataChanges: Boolean = false, + listener: ProducerScope.(com.google.firebase.firestore.DocumentSnapshot?, com.google.firebase.firestore.FirebaseFirestoreException?) -> Unit + ) = callbackFlow { + val executor = callbackExecutorMap[android.firestore] ?: TaskExecutors.MAIN_THREAD + val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE + val registration = android.addSnapshotListener(executor, metadataChanges) { snapshots, exception -> + listener(snapshots, exception) + } + awaitClose { registration.remove() } + } } val DocumentReference.android get() = native.android @@ -235,21 +349,14 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) { actual fun limit(limit: Number) = Query(NativeQuery(android.limit(limit.toLong()))) - actual val snapshots get() = callbackFlow { - val listener = android.addSnapshotListener { snapshot, exception -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - exception?.let { close(exception) } - } - awaitClose { listener.remove() } + actual val snapshots get() = addSnapshotListener { snapshot, exception -> + snapshot?.let { trySend(QuerySnapshot(snapshot)) } + exception?.let { close(exception) } } - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE - val listener = android.addSnapshotListener(metadataChanges) { snapshot, exception -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - exception?.let { close(exception) } - } - awaitClose { listener.remove() } + actual fun snapshots(includeMetadataChanges: Boolean) = addSnapshotListener(includeMetadataChanges) { snapshot, exception -> + snapshot?.let { trySend(QuerySnapshot(snapshot)) } + exception?.let { close(exception) } } internal actual fun where(filter: Filter) = Query( @@ -331,6 +438,18 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) { internal actual fun _endBefore(vararg fieldValues: Any) = Query(android.endBefore(*fieldValues).native) internal actual fun _endAt(document: DocumentSnapshot) = Query(android.endAt(document.android).native) internal actual fun _endAt(vararg fieldValues: Any) = Query(android.endAt(*fieldValues).native) + + private fun addSnapshotListener( + includeMetadataChanges: Boolean = false, + listener: ProducerScope.(com.google.firebase.firestore.QuerySnapshot?, com.google.firebase.firestore.FirebaseFirestoreException?) -> Unit + ) = callbackFlow { + val executor = callbackExecutorMap[android.firestore] ?: TaskExecutors.MAIN_THREAD + val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE + val registration = android.addSnapshotListener(executor, metadataChanges) { snapshots, exception -> + listener(snapshots, exception) + } + awaitClose { registration.remove() } + } } actual typealias Direction = com.google.firebase.firestore.Query.Direction diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/LocalCacheSettings.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/LocalCacheSettings.kt new file mode 100644 index 000000000..a701b8a69 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/LocalCacheSettings.kt @@ -0,0 +1,69 @@ +package dev.gitlive.firebase.firestore + +sealed interface LocalCacheSettings { + + data class Persistent internal constructor(val sizeBytes: Long) : LocalCacheSettings { + + companion object { + fun newBuilder(): Builder = Builder() + } + + class Builder internal constructor() { + var sizeBytes: Long = FirebaseFirestoreSettings.DEFAULT_CACHE_SIZE_BYTES + fun build(): Persistent = Persistent(sizeBytes) + } + } + data class Memory internal constructor(val garbaseCollectorSettings: MemoryGarbageCollectorSettings) : LocalCacheSettings { + + companion object { + fun newBuilder(): Builder = Builder() + } + + class Builder internal constructor() { + + var gcSettings: MemoryGarbageCollectorSettings = MemoryGarbageCollectorSettings.Eager.newBuilder().build() + + fun build(): Memory = Memory(gcSettings) + } + } +} + +typealias PersistentCacheSettings = LocalCacheSettings.Persistent +typealias MemoryCacheSettings = LocalCacheSettings.Memory + +sealed interface MemoryGarbageCollectorSettings { + data object Eager : MemoryGarbageCollectorSettings { + + fun newBuilder(): Builder = Builder() + + class Builder internal constructor() { + fun build(): Eager = Eager + } + } + data class LRUGC internal constructor(val sizeBytes: Long) : MemoryGarbageCollectorSettings { + + companion object { + fun newBuilder(): Builder = Builder() + } + + class Builder internal constructor() { + var sizeBytes: Long = FirebaseFirestoreSettings.DEFAULT_CACHE_SIZE_BYTES + fun build(): LRUGC = LRUGC(sizeBytes) + } + } +} + +typealias MemoryEagerGcSettings = MemoryGarbageCollectorSettings.Eager +typealias MemoryLruGcSettings = MemoryGarbageCollectorSettings.LRUGC + +fun memoryCacheSettings(builder: LocalCacheSettings.Memory.Builder.() -> Unit): LocalCacheSettings.Memory = + LocalCacheSettings.Memory.newBuilder().apply(builder).build() + +fun memoryEagerGcSettings(builder: MemoryGarbageCollectorSettings.Eager.Builder.() -> Unit) = + MemoryGarbageCollectorSettings.Eager.newBuilder().apply(builder).build() + +fun memoryLruGcSettings(builder: MemoryGarbageCollectorSettings.LRUGC.Builder.() -> Unit) = + MemoryGarbageCollectorSettings.LRUGC.newBuilder().apply(builder).build() + +fun persistentCacheSettings(builder: LocalCacheSettings.Persistent.Builder.() -> Unit) = + LocalCacheSettings.Persistent.newBuilder().apply(builder).build() \ No newline at end of file diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 64f2a006e..6afe7dd7a 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -18,20 +18,98 @@ expect val Firebase.firestore: FirebaseFirestore /** Returns the [FirebaseFirestore] instance of a given [FirebaseApp]. */ expect fun Firebase.firestore(app: FirebaseApp): FirebaseFirestore -expect class FirebaseFirestore { - fun collection(collectionPath: String): CollectionReference - fun collectionGroup(collectionId: String): Query - fun document(documentPath: String): DocumentReference - fun batch(): WriteBatch +expect class NativeFirebaseFirestore + +internal expect class NativeFirebaseFirestoreWrapper internal constructor(native: NativeFirebaseFirestore) { + val native: NativeFirebaseFirestore + var settings: FirebaseFirestoreSettings + + fun collection(collectionPath: String): NativeCollectionReference + fun collectionGroup(collectionId: String): NativeQuery + fun document(documentPath: String): NativeDocumentReference + fun batch(): NativeWriteBatch fun setLoggingEnabled(loggingEnabled: Boolean) suspend fun clearPersistence() - suspend fun runTransaction(func: suspend Transaction.() -> T): T + suspend fun runTransaction(func: suspend NativeTransaction.() -> T): T fun useEmulator(host: String, port: Int) - fun setSettings(persistenceEnabled: Boolean? = null, sslEnabled: Boolean? = null, host: String? = null, cacheSizeBytes: Long? = null) suspend fun disableNetwork() suspend fun enableNetwork() } +class FirebaseFirestore internal constructor(private val wrapper: NativeFirebaseFirestoreWrapper) { + + constructor(native: NativeFirebaseFirestore) : this(NativeFirebaseFirestoreWrapper(native)) + + // Important to leave this as a get property since on JS it is initialized lazily + val native get() = wrapper.native + var settings: FirebaseFirestoreSettings + get() = wrapper.settings + set(value) { + wrapper.settings = value + } + + fun collection(collectionPath: String): CollectionReference = CollectionReference(wrapper.collection(collectionPath)) + fun collectionGroup(collectionId: String): Query = Query(wrapper.collectionGroup(collectionId)) + fun document(documentPath: String): DocumentReference = DocumentReference(wrapper.document(documentPath)) + fun batch(): WriteBatch = WriteBatch(wrapper.batch()) + fun setLoggingEnabled(loggingEnabled: Boolean) = wrapper.setLoggingEnabled(loggingEnabled) + suspend fun clearPersistence() = wrapper.clearPersistence() + suspend fun runTransaction(func: suspend Transaction.() -> T): T = wrapper.runTransaction { func(Transaction(this)) } + fun useEmulator(host: String, port: Int) = wrapper.useEmulator(host, port) + @Deprecated("Use settings instead", replaceWith = ReplaceWith("settings = firestoreSettings{}")) + fun setSettings( + persistenceEnabled: Boolean? = null, + sslEnabled: Boolean? = null, + host: String? = null, + cacheSizeBytes: Long? = null, + ) { + settings = firestoreSettings { + this.sslEnabled = sslEnabled ?: true + this.host = host ?: FirebaseFirestoreSettings.DEFAULT_HOST + this.cacheSettings = if (persistenceEnabled != false) { + LocalCacheSettings.Persistent(cacheSizeBytes ?: FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED) + } else { + val cacheSize = cacheSizeBytes ?: FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED + val garbageCollectionSettings = if (cacheSize == FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED) { + MemoryGarbageCollectorSettings.Eager + } else { + MemoryGarbageCollectorSettings.LRUGC(cacheSize) + } + LocalCacheSettings.Memory(garbageCollectionSettings) + } + } + } + suspend fun disableNetwork() = wrapper.disableNetwork() + suspend fun enableNetwork() = wrapper.enableNetwork() +} + +expect class FirebaseFirestoreSettings { + + companion object { + val CACHE_SIZE_UNLIMITED: Long + internal val DEFAULT_HOST: String + internal val MINIMUM_CACHE_BYTES: Long + internal val DEFAULT_CACHE_SIZE_BYTES: Long + } + + class Builder constructor() { + + constructor(settings: FirebaseFirestoreSettings) + + var sslEnabled: Boolean + var host: String + var cacheSettings: LocalCacheSettings + + fun build(): FirebaseFirestoreSettings + } + + val sslEnabled: Boolean + val host: String + val cacheSettings: LocalCacheSettings +} + +expect fun firestoreSettings(settings: FirebaseFirestoreSettings? = null, builder: FirebaseFirestoreSettings.Builder.() -> Unit): FirebaseFirestoreSettings + @PublishedApi internal sealed class SetOptions { data object Merge : SetOptions() diff --git a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreSourceTest.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreSourceTest.kt index 439d459e7..8d849900b 100644 --- a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreSourceTest.kt +++ b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreSourceTest.kt @@ -39,7 +39,13 @@ class FirestoreSourceTest { firestore = Firebase.firestore(app).apply { useEmulator(emulatorHost, 8080) - setSettings(persistenceEnabled = persistenceEnabled) + settings = firestoreSettings(settings) { + cacheSettings = if (persistenceEnabled) { + persistentCacheSettings { } + } else { + memoryCacheSettings { } + } + } } } diff --git a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt index f04c93853..4928a36cb 100644 --- a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -100,7 +100,11 @@ class FirebaseFirestoreTest { firestore = Firebase.firestore(app).apply { useEmulator(emulatorHost, 8080) - setSettings(persistenceEnabled = false) + settings = firestoreSettings(settings) { + cacheSettings = memoryCacheSettings { + gcSettings = memoryEagerGcSettings { } + } + } } } diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 124c68a28..266f77e35 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -9,15 +9,15 @@ import cocoapods.FirebaseFirestoreInternal.FIRDocumentChangeType.* import dev.gitlive.firebase.* import kotlinx.cinterop.* import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.Deferred import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.runBlocking -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationStrategy import platform.Foundation.NSError import platform.Foundation.NSNull +import platform.Foundation.NSNumber +import platform.Foundation.numberWithLong +import platform.darwin.dispatch_get_main_queue +import platform.darwin.dispatch_queue_t actual val Firebase.firestore get() = FirebaseFirestore(FIRFirestore.firestore()) @@ -26,52 +26,124 @@ actual fun Firebase.firestore(app: FirebaseApp): FirebaseFirestore = FirebaseFir FIRFirestore.firestoreForApp(app.ios as objcnames.classes.FIRApp) ) +val LocalCacheSettings.ios: FIRLocalCacheSettingsProtocol get() = when (this) { + is LocalCacheSettings.Persistent -> FIRPersistentCacheSettings(NSNumber.numberWithLong(sizeBytes)) + is LocalCacheSettings.Memory -> FIRMemoryCacheSettings( + when (garbaseCollectorSettings) { + is MemoryGarbageCollectorSettings.Eager -> FIRMemoryEagerGCSettings() + is MemoryGarbageCollectorSettings.LRUGC -> FIRMemoryLRUGCSettings(NSNumber.numberWithLong(garbaseCollectorSettings.sizeBytes)) + } + ) +} + +actual typealias NativeFirebaseFirestore = FIRFirestore + @Suppress("UNCHECKED_CAST") -actual class FirebaseFirestore(val ios: FIRFirestore) { +internal actual class NativeFirebaseFirestoreWrapper internal actual constructor(actual val native: NativeFirebaseFirestore) { + + actual var settings: FirebaseFirestoreSettings = firestoreSettings { }.also { + native.settings = it.ios + } + set(value) { + field = value + native.settings = value.ios + } - actual fun collection(collectionPath: String) = CollectionReference(NativeCollectionReference(ios.collectionWithPath(collectionPath))) + actual fun collection(collectionPath: String) = NativeCollectionReference(native.collectionWithPath(collectionPath)) - actual fun collectionGroup(collectionId: String) = Query(ios.collectionGroupWithID(collectionId).native) + actual fun collectionGroup(collectionId: String) = native.collectionGroupWithID(collectionId).native - actual fun document(documentPath: String) = DocumentReference(NativeDocumentReference(ios.documentWithPath(documentPath))) + actual fun document(documentPath: String) = NativeDocumentReference(native.documentWithPath(documentPath)) - actual fun batch() = WriteBatch(NativeWriteBatch(ios.batch())) + actual fun batch() = NativeWriteBatch(native.batch()) actual fun setLoggingEnabled(loggingEnabled: Boolean): Unit = FIRFirestore.enableLogging(loggingEnabled) - actual suspend fun runTransaction(func: suspend Transaction.() -> T) = - awaitResult { ios.runTransactionWithBlock({ transaction, _ -> runBlocking { Transaction(NativeTransaction(transaction!!)).func() } }, it) } as T + actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T) = + awaitResult { native.runTransactionWithBlock({ transaction, _ -> runBlocking { NativeTransaction(transaction!!).func() } }, it) } as T actual suspend fun clearPersistence() = - await { ios.clearPersistenceWithCompletion(it) } + await { native.clearPersistenceWithCompletion(it) } actual fun useEmulator(host: String, port: Int) { - ios.settings = ios.settings.apply { + native.useEmulatorWithHost(host, port.toLong()) + settings = firestoreSettings(settings) { this.host = "$host:$port" - persistenceEnabled = false + cacheSettings = memoryCacheSettings { } sslEnabled = false } } - actual fun setSettings(persistenceEnabled: Boolean?, sslEnabled: Boolean?, host: String?, cacheSizeBytes: Long?) { - ios.settings = FIRFirestoreSettings().also { settings -> - persistenceEnabled?.let { settings.persistenceEnabled = it } - sslEnabled?.let { settings.sslEnabled = it } - host?.let { settings.host = it } - cacheSizeBytes?.let { settings.cacheSizeBytes = it } - } - } - actual suspend fun disableNetwork() { - await { ios.disableNetworkWithCompletion(it) } + await { native.disableNetworkWithCompletion(it) } } actual suspend fun enableNetwork() { - await { ios.enableNetworkWithCompletion(it) } + await { native.enableNetworkWithCompletion(it) } } } +val FirebaseFirestore.ios get() = native + +actual data class FirebaseFirestoreSettings( + actual val sslEnabled: Boolean, + actual val host: String, + actual val cacheSettings: LocalCacheSettings, + val dispatchQueue: dispatch_queue_t, +) { + + actual companion object { + actual val CACHE_SIZE_UNLIMITED: Long = -1L + internal actual val DEFAULT_HOST: String = "firestore.googleapis.com" + internal actual val MINIMUM_CACHE_BYTES: Long = 1 * 1024 * 1024 + internal actual val DEFAULT_CACHE_SIZE_BYTES: Long = 100 * 1024 * 1024 + } + + actual class Builder( + actual var sslEnabled: Boolean, + actual var host: String, + actual var cacheSettings: LocalCacheSettings, + var dispatchQueue: dispatch_queue_t, + ) { + + actual constructor() : this( + true, + DEFAULT_HOST, + persistentCacheSettings { }, + dispatch_get_main_queue(), + ) + + actual constructor(settings: FirebaseFirestoreSettings) : this( + settings.sslEnabled, + settings.host, + settings.cacheSettings, + settings.dispatchQueue, + ) + + actual fun build(): FirebaseFirestoreSettings = FirebaseFirestoreSettings(sslEnabled, host, cacheSettings, dispatchQueue) + } + + val ios: FIRFirestoreSettings get() = FIRFirestoreSettings().apply { + cacheSettings = this@FirebaseFirestoreSettings.cacheSettings.ios + sslEnabled = this@FirebaseFirestoreSettings.sslEnabled + host = this@FirebaseFirestoreSettings.host + dispatchQueue = this@FirebaseFirestoreSettings.dispatchQueue + } +} + +actual fun firestoreSettings( + settings: FirebaseFirestoreSettings?, + builder: FirebaseFirestoreSettings.Builder.() -> Unit +): FirebaseFirestoreSettings = FirebaseFirestoreSettings.Builder().apply { + settings?.let { + sslEnabled = it.sslEnabled + host = it.host + cacheSettings = it.cacheSettings + dispatchQueue = it.dispatchQueue + } +}.apply(builder).build() + @Suppress("UNCHECKED_CAST") @PublishedApi internal actual class NativeWriteBatch(val ios: FIRWriteBatch) { diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt index 1206594ed..532118bfc 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt @@ -303,11 +303,32 @@ external interface FirestoreLocalCache { val kind: String } +external interface MemoryLocalCache : FirestoreLocalCache +external interface PersistentLocalCache : FirestoreLocalCache + +external interface MemoryCacheSettings { + val garbageCollector: MemoryGarbageCollector +} + +external interface MemoryGarbageCollector { + val kind: String +} + +external interface MemoryLruGarbageCollector : MemoryGarbageCollector +external interface MemoryEagerGarbageCollector : MemoryGarbageCollector + +external interface PersistentCacheSettings { + val cacheSizeBytes: Int + val tabManager: PersistentTabManager +} + external interface PersistentTabManager { val kind: String } -external fun memoryLocalCache(): FirestoreLocalCache -external fun persistentLocalCache(settings: dynamic = definedExternally): FirestoreLocalCache +external fun memoryLocalCache(settings: MemoryCacheSettings): MemoryLocalCache +external fun memoryEagerGarbageCollector(): MemoryEagerGarbageCollector +external fun memoryLruGarbageCollector(settings: dynamic = definedExternally): MemoryLruGarbageCollector +external fun persistentLocalCache(settings: PersistentCacheSettings): PersistentLocalCache external fun persistentSingleTabManager(settings: dynamic = definedExternally): PersistentTabManager external fun persistentMultipleTabManager(): PersistentTabManager diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 05c854444..4600481aa 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -7,17 +7,45 @@ package dev.gitlive.firebase.firestore import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException -import dev.gitlive.firebase.firestore.externals.* -import dev.gitlive.firebase.firestore.externals.documentId as jsDocumentId +import dev.gitlive.firebase.externals.getApp +import dev.gitlive.firebase.firestore.externals.MemoryCacheSettings +import dev.gitlive.firebase.firestore.externals.PersistentCacheSettings +import dev.gitlive.firebase.firestore.externals.QueryConstraint +import dev.gitlive.firebase.firestore.externals.addDoc +import dev.gitlive.firebase.firestore.externals.and +import dev.gitlive.firebase.firestore.externals.clearIndexedDbPersistence +import dev.gitlive.firebase.firestore.externals.connectFirestoreEmulator +import dev.gitlive.firebase.firestore.externals.deleteDoc +import dev.gitlive.firebase.firestore.externals.doc +import dev.gitlive.firebase.firestore.externals.getDoc +import dev.gitlive.firebase.firestore.externals.getDocFromCache +import dev.gitlive.firebase.firestore.externals.getDocFromServer +import dev.gitlive.firebase.firestore.externals.getDocs +import dev.gitlive.firebase.firestore.externals.getDocsFromCache +import dev.gitlive.firebase.firestore.externals.getDocsFromServer +import dev.gitlive.firebase.firestore.externals.initializeFirestore +import dev.gitlive.firebase.firestore.externals.memoryEagerGarbageCollector +import dev.gitlive.firebase.firestore.externals.memoryLocalCache +import dev.gitlive.firebase.firestore.externals.memoryLruGarbageCollector +import dev.gitlive.firebase.firestore.externals.onSnapshot +import dev.gitlive.firebase.firestore.externals.or +import dev.gitlive.firebase.firestore.externals.orderBy +import dev.gitlive.firebase.firestore.externals.persistentLocalCache +import dev.gitlive.firebase.firestore.externals.query +import dev.gitlive.firebase.firestore.externals.refEqual +import dev.gitlive.firebase.firestore.externals.setDoc +import dev.gitlive.firebase.firestore.externals.setLogLevel +import dev.gitlive.firebase.firestore.externals.writeBatch import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.await import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.promise -import kotlinx.serialization.Serializable import kotlin.js.Json import kotlin.js.json +import dev.gitlive.firebase.externals.FirebaseApp as JsFirebaseApp +import dev.gitlive.firebase.firestore.externals.Firestore as JsFirestore import dev.gitlive.firebase.firestore.externals.CollectionReference as JsCollectionReference import dev.gitlive.firebase.firestore.externals.DocumentChange as JsDocumentChange import dev.gitlive.firebase.firestore.externals.DocumentReference as JsDocumentReference @@ -31,6 +59,7 @@ import dev.gitlive.firebase.firestore.externals.WriteBatch as JsWriteBatch import dev.gitlive.firebase.firestore.externals.collection as jsCollection import dev.gitlive.firebase.firestore.externals.collectionGroup as jsCollectionGroup import dev.gitlive.firebase.firestore.externals.disableNetwork as jsDisableNetwork +import dev.gitlive.firebase.firestore.externals.documentId as jsDocumentId import dev.gitlive.firebase.firestore.externals.enableNetwork as jsEnableNetwork import dev.gitlive.firebase.firestore.externals.endAt as jsEndAt import dev.gitlive.firebase.firestore.externals.endBefore as jsEndBefore @@ -42,44 +71,72 @@ import dev.gitlive.firebase.firestore.externals.updateDoc as jsUpdate import dev.gitlive.firebase.firestore.externals.where as jsWhere actual val Firebase.firestore get() = - rethrow { FirebaseFirestore(getFirestore()) } + rethrow { FirebaseFirestore(NativeFirebaseFirestoreWrapper(getApp())) } actual fun Firebase.firestore(app: FirebaseApp) = - rethrow { FirebaseFirestore(getFirestore(app.js)) } + rethrow { FirebaseFirestore(NativeFirebaseFirestoreWrapper(app.js)) } + +actual data class NativeFirebaseFirestore(val js: JsFirestore) + +internal actual class NativeFirebaseFirestoreWrapper internal constructor( + private val createNative: NativeFirebaseFirestoreWrapper.() -> NativeFirebaseFirestore +){ + + internal actual constructor(native: NativeFirebaseFirestore) : this({ native }) + internal constructor(app: JsFirebaseApp) : this( + { + NativeFirebaseFirestore( + initializeFirestore(app, settings.js).also { + emulatorSettings?.run { + connectFirestoreEmulator(it, host, port) + } + } + ) + } + ) -actual class FirebaseFirestore(jsFirestore: Firestore) { + private data class EmulatorSettings(val host: String, val port: Int) + + actual var settings: FirebaseFirestoreSettings = FirebaseFirestoreSettings.Builder().build() + set(value) { + if (lazyNative.isInitialized()) { + throw IllegalStateException("FirebaseFirestore has already been started and its settings can no longer be changed. You can only call setFirestoreSettings() before calling any other methods on a FirebaseFirestore object.") + } else { + field = value + } + } + private var emulatorSettings: EmulatorSettings? = null - var js: Firestore = jsFirestore - private set + // initializeFirestore must be called before any call, including before `getFirestore()` + // To allow settings to be updated, we defer creating the wrapper until the first call to `native` + private val lazyNative = lazy { + createNative() + } + actual val native: NativeFirebaseFirestore by lazyNative + private val js get() = native.js - actual fun collection(collectionPath: String) = rethrow { CollectionReference(NativeCollectionReference(jsCollection(js, collectionPath))) } + actual fun collection(collectionPath: String) = rethrow { NativeCollectionReference(jsCollection(js, collectionPath)) } - actual fun collectionGroup(collectionId: String) = rethrow { Query(jsCollectionGroup(js, collectionId)) } + actual fun collectionGroup(collectionId: String) = rethrow { NativeQuery(jsCollectionGroup(js, collectionId)) } - actual fun document(documentPath: String) = rethrow { DocumentReference(NativeDocumentReference(doc(js, documentPath))) } + actual fun document(documentPath: String) = rethrow { NativeDocumentReference(doc(js, documentPath)) } - actual fun batch() = rethrow { WriteBatch(NativeWriteBatch(writeBatch(js))) } + actual fun batch() = rethrow { NativeWriteBatch(writeBatch(js)) } actual fun setLoggingEnabled(loggingEnabled: Boolean) = rethrow { setLogLevel( if(loggingEnabled) "error" else "silent") } - actual suspend fun runTransaction(func: suspend Transaction.() -> T) = - rethrow { jsRunTransaction(js, { GlobalScope.promise { Transaction(NativeTransaction(it)).func() } } ).await() } + actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T) = + rethrow { jsRunTransaction(js, { GlobalScope.promise { NativeTransaction(it).func() } } ).await() } actual suspend fun clearPersistence() = rethrow { clearIndexedDbPersistence(js).await() } - actual fun useEmulator(host: String, port: Int) = rethrow { connectFirestoreEmulator(js, host, port) } - - actual fun setSettings(persistenceEnabled: Boolean?, sslEnabled: Boolean?, host: String?, cacheSizeBytes: Long?) { - if(persistenceEnabled == true) enableIndexedDbPersistence(js) - - val settings = json().apply { - sslEnabled?.let { set("ssl", it) } - host?.let { set("host", it) } - cacheSizeBytes?.let { set("cacheSizeBytes", it) } + actual fun useEmulator(host: String, port: Int) = rethrow { + settings = firestoreSettings(settings) { + this.host = "$host:$port" } - js = initializeFirestore(js.app, settings) + emulatorSettings = EmulatorSettings(host, port) } actual suspend fun disableNetwork() { @@ -91,6 +148,71 @@ actual class FirebaseFirestore(jsFirestore: Firestore) { } } +val FirebaseFirestore.js: JsFirestore get() = native.js + +actual data class FirebaseFirestoreSettings( + actual val sslEnabled: Boolean, + actual val host: String, + actual val cacheSettings: LocalCacheSettings, +) { + + actual companion object { + actual val CACHE_SIZE_UNLIMITED: Long = -1L + internal actual val DEFAULT_HOST: String = "firestore.googleapis.com" + internal actual val MINIMUM_CACHE_BYTES: Long = 1 * 1024 * 1024 + // According to documentation, default JS Firestore cache size is 40MB, not 100MB + internal actual val DEFAULT_CACHE_SIZE_BYTES: Long = 40 * 1024 * 1024 + } + + actual class Builder internal constructor( + actual var sslEnabled: Boolean, + actual var host: String, + actual var cacheSettings: LocalCacheSettings, + ) { + + actual constructor() : this( + true, + DEFAULT_HOST, + persistentCacheSettings { }, + ) + actual constructor(settings: FirebaseFirestoreSettings) : this(settings.sslEnabled, settings.host, settings.cacheSettings) + + actual fun build(): FirebaseFirestoreSettings = FirebaseFirestoreSettings(sslEnabled, host, cacheSettings) + } + + @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") + val js: Json get() = json().apply { + set("ssl", sslEnabled) + set("host", host) + set("localCache", + when (cacheSettings) { + is LocalCacheSettings.Persistent -> persistentLocalCache( + json( + "cacheSizeBytes" to cacheSettings.sizeBytes + ).asDynamic() as PersistentCacheSettings + ) + is LocalCacheSettings.Memory -> { + val garbageCollecorSettings = when (val garbageCollectorSettings = cacheSettings.garbaseCollectorSettings) { + is MemoryGarbageCollectorSettings.Eager -> memoryEagerGarbageCollector() + is MemoryGarbageCollectorSettings.LRUGC -> memoryLruGarbageCollector(json("cacheSizeBytes" to garbageCollectorSettings.sizeBytes)) + } + memoryLocalCache(json("garbageCollector" to garbageCollecorSettings).asDynamic() as MemoryCacheSettings) + } + }) + } +} + +actual fun firestoreSettings( + settings: FirebaseFirestoreSettings?, + builder: FirebaseFirestoreSettings.Builder.() -> Unit +): FirebaseFirestoreSettings = FirebaseFirestoreSettings.Builder().apply { + settings?.let { + sslEnabled = it.sslEnabled + host = it.host + cacheSettings = it.cacheSettings + } +}.apply(builder).build() + internal val SetOptions.js: Json get() = when (this) { is SetOptions.Merge -> json("merge" to true) is SetOptions.Overwrite -> json("merge" to false) diff --git a/test-utils/src/commonMain/kotlin/dev/gitlive/firebase/TestUtils.kt b/test-utils/src/commonMain/kotlin/dev/gitlive/firebase/TestUtils.kt index bf9fcdc19..52ff28a51 100644 --- a/test-utils/src/commonMain/kotlin/dev/gitlive/firebase/TestUtils.kt +++ b/test-utils/src/commonMain/kotlin/dev/gitlive/firebase/TestUtils.kt @@ -1,4 +1,3 @@ -@file:JvmName("TestUtilsJVM") /* * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */