1
1
package dev.teamhub.firebase.database
2
2
3
+ import cocoapods.FirebaseDatabase.*
4
+ import cocoapods.FirebaseDatabase.FIRDataEventType.*
3
5
import dev.teamhub.firebase.Firebase
4
6
import dev.teamhub.firebase.FirebaseApp
7
+ import dev.teamhub.firebase.database.ChildEvent.Type
8
+ import dev.teamhub.firebase.database.ChildEvent.Type.*
5
9
import kotlinx.serialization.DeserializationStrategy
6
10
import kotlinx.serialization.SerializationStrategy
7
11
import kotlinx.serialization.ImplicitReflectionSerializer
12
+ import kotlinx.cinterop.*
13
+ import kotlinx.coroutines.channels.awaitClose
14
+ import kotlinx.coroutines.flow.*
15
+ import platform.Foundation.*
16
+ import kotlinx.coroutines.CompletableDeferred
17
+ import kotlinx.coroutines.coroutineScope
18
+ import dev.teamhub.firebase.decode
19
+ import kotlinx.coroutines.selects.select
8
20
9
- /* * Returns the [FirebaseDatabase] instance of the default [FirebaseApp]. */
10
- actual val Firebase .database: FirebaseDatabase
11
- get() = TODO (" not implemented" ) // To change initializer of created properties use File | Settings | File Templates.
21
+ fun encode (value : Any? ) =
22
+ dev.teamhub.firebase.encode(value, FIRServerValue .timestamp())
23
+ fun <T > encode (strategy : SerializationStrategy <T > , value : T ): Any? =
24
+ dev.teamhub.firebase.encode(strategy, value, FIRServerValue .timestamp())
12
25
13
- /* * Returns the [FirebaseDatabase] instance for the specified [url]. */
14
- actual fun Firebase.database (url : String ): FirebaseDatabase {
15
- TODO (" not implemented" ) // To change body of created functions use File | Settings | File Templates.
16
- }
26
+ actual val Firebase .database
27
+ by lazy { FirebaseDatabase (FIRDatabase .database()) }
17
28
18
- /* * Returns the [FirebaseDatabase] instance of the given [FirebaseApp]. */
19
- actual fun Firebase.database (app : FirebaseApp ): FirebaseDatabase {
20
- TODO (" not implemented" ) // To change body of created functions use File | Settings | File Templates.
21
- }
29
+ actual fun Firebase.database (url : String ) =
30
+ FirebaseDatabase (FIRDatabase .databaseWithURL(url))
22
31
23
- /* * Returns the [FirebaseDatabase] instance of the given [FirebaseApp] and [url]. */
24
- actual fun Firebase.database (
25
- app : FirebaseApp ,
26
- url : String
27
- ): FirebaseDatabase {
28
- TODO (" not implemented" ) // To change body of created functions use File | Settings | File Templates.
29
- }
32
+ actual fun Firebase.database (app : FirebaseApp ) =
33
+ FirebaseDatabase (FIRDatabase .databaseForApp(app.ios))
30
34
31
- actual class FirebaseDatabase {
32
- actual fun reference (path : String ): DatabaseReference {
33
- TODO (" not implemented" ) // To change body of created functions use File | Settings | File Templates.
34
- }
35
+ actual fun Firebase.database (app : FirebaseApp , url : String ) =
36
+ FirebaseDatabase (FIRDatabase .databaseForApp(app.ios, url))
37
+
38
+ actual class FirebaseDatabase internal constructor(val ios : FIRDatabase ) {
39
+
40
+ actual fun reference (path : String ) =
41
+ DatabaseReference (ios.referenceWithPath(path), ios.persistenceEnabled)
35
42
36
43
actual fun setPersistenceEnabled (enabled : Boolean ) {
44
+ ios.persistenceEnabled = enabled
37
45
}
38
46
39
- actual fun setLoggingEnabled (enabled : Boolean ) {
40
- }
47
+ actual fun setLoggingEnabled (enabled : Boolean ) =
48
+ FIRDatabase .setLoggingEnabled(enabled)
41
49
}
42
50
43
- actual open class Query {
44
- actual val valueEvents: kotlinx.coroutines.flow.Flow <DataSnapshot >
45
- get() = TODO (" not implemented" ) // To change initializer of created properties use File | Settings | File Templates.
51
+ fun Type.toEventType () = when (this ) {
52
+ ADDED -> FIRDataEventTypeChildAdded
53
+ CHANGED -> FIRDataEventTypeChildChanged
54
+ MOVED -> FIRDataEventTypeChildMoved
55
+ REMOVED -> FIRDataEventTypeChildRemoved
56
+ }
46
57
47
- actual fun childEvents (vararg types : ChildEvent .Type ): kotlinx.coroutines.flow.Flow <ChildEvent > {
48
- TODO (" not implemented" ) // To change body of created functions use File | Settings | File Templates.
49
- }
58
+ actual open class Query internal constructor(
59
+ open val ios : FIRDatabaseQuery ,
60
+ val persistenceEnabled : Boolean
61
+ ) {
62
+ actual fun orderByKey () = Query (ios.queryOrderedByKey(), persistenceEnabled)
50
63
51
- actual fun orderByKey (): Query {
52
- TODO (" not implemented" ) // To change body of created functions use File | Settings | File Templates.
53
- }
64
+ actual fun orderByChild (path : String ) = Query (ios.queryOrderedByChild(path), persistenceEnabled)
54
65
55
- actual fun orderByChild (path : String ): Query {
56
- TODO (" not implemented" ) // To change body of created functions use File | Settings | File Templates.
57
- }
66
+ actual fun startAt (value : String , key : String? ) = Query (ios.queryStartingAtValue(value, key), persistenceEnabled)
58
67
59
- actual fun startAt (value : String , key : String? ): Query {
60
- TODO ( " not implemented " ) // To change body of created functions use File | Settings | File Templates.
61
- }
68
+ actual fun startAt (value : Double , key : String? ) = Query (ios.queryStartingAtValue(value, key), persistenceEnabled)
69
+
70
+ actual fun startAt ( value : Boolean , key : String? ) = Query (ios.queryStartingAtValue(value, key), persistenceEnabled)
62
71
63
- actual fun startAt (value : Double , key : String? ): Query {
64
- TODO (" not implemented" ) // To change body of created functions use File | Settings | File Templates.
72
+ actual val valueEvents get() = callbackFlow {
73
+ val handle = ios.observeEventType(
74
+ FIRDataEventTypeValue ,
75
+ withBlock = { offer(DataSnapshot (it!! )) }
76
+ ) { close(DatabaseException (it.toString())) }
77
+ awaitClose { ios.removeObserverWithHandle(handle) }
65
78
}
66
79
67
- actual fun startAt (value : Boolean , key : String? ): Query {
68
- TODO (" not implemented" ) // To change body of created functions use File | Settings | File Templates.
80
+ actual fun childEvents (vararg types : Type ) = callbackFlow {
81
+ val handles = types.map { type ->
82
+ ios.observeEventType(
83
+ type.toEventType(),
84
+ andPreviousSiblingKeyWithBlock = { it, key -> offer(ChildEvent (DataSnapshot (it!! ), type, key)) }
85
+ ) { close(DatabaseException (it.toString())) }
86
+ }
87
+ awaitClose {
88
+ handles.forEach { ios.removeObserverWithHandle(it) }
89
+ }
69
90
}
91
+
92
+ override fun toString () = ios.toString()
70
93
}
71
94
72
- actual class DatabaseReference : Query () {
73
- actual val key: String?
74
- get() = TODO (" not implemented" ) // To change initializer of created properties use File | Settings | File Templates.
95
+ actual class DatabaseReference internal constructor(
96
+ override val ios : FIRDatabaseReference ,
97
+ persistenceEnabled : Boolean
98
+ ): Query(ios, persistenceEnabled) {
75
99
76
- actual fun push (): DatabaseReference {
77
- TODO (" not implemented" ) // To change body of created functions use File | Settings | File Templates.
78
- }
100
+ actual val key get() = ios.key
79
101
80
- actual fun child (path : String ): DatabaseReference {
81
- TODO (" not implemented" ) // To change body of created functions use File | Settings | File Templates.
82
- }
102
+ actual fun child (path : String ) = DatabaseReference (ios.child(path), persistenceEnabled)
83
103
84
- actual fun onDisconnect (): OnDisconnect {
85
- TODO (" not implemented" ) // To change body of created functions use File | Settings | File Templates.
86
- }
104
+ actual fun push () = DatabaseReference (ios.childByAutoId(), persistenceEnabled)
105
+ actual fun onDisconnect () = OnDisconnect (ios, persistenceEnabled)
87
106
88
- @ImplicitReflectionSerializer
89
107
actual suspend fun setValue (value : Any? ) {
108
+ ios.await(persistenceEnabled) { setValue(encode(value), it) }
90
109
}
91
110
92
111
actual suspend inline fun <reified T > setValue (strategy : SerializationStrategy <T >, value : T ) {
112
+ ios.await(persistenceEnabled) { setValue(encode(strategy, value), it) }
93
113
}
94
114
95
- @ImplicitReflectionSerializer
115
+ @Suppress( " UNCHECKED_CAST " )
96
116
actual suspend fun updateChildren (update : Map <String , Any ?>) {
117
+ ios.await(persistenceEnabled) { updateChildValues(encode(update) as Map <Any ?, * >, it) }
97
118
}
98
119
99
120
actual suspend fun removeValue () {
121
+ ios.await(persistenceEnabled) { removeValueWithCompletionBlock(it) }
100
122
}
101
123
}
102
124
103
- actual class DataSnapshot {
104
- actual val exists: Boolean
105
- get() = TODO (" not implemented" ) // To change initializer of created properties use File | Settings | File Templates.
106
- actual val key: String?
107
- get() = TODO (" not implemented" ) // To change initializer of created properties use File | Settings | File Templates.
125
+ @Suppress(" UNCHECKED_CAST" )
126
+ actual class DataSnapshot internal constructor(val ios : FIRDataSnapshot ) {
108
127
109
- @ImplicitReflectionSerializer
110
- actual inline fun <reified T > value (): T {
111
- TODO (" not implemented" ) // To change body of created functions use File | Settings | File Templates.
112
- }
128
+ actual val exists get() = ios.exists()
113
129
114
- actual inline fun <reified T > value (strategy : DeserializationStrategy <T >): T {
115
- TODO (" not implemented" ) // To change body of created functions use File | Settings | File Templates.
116
- }
130
+ actual val key: String? get() = ios.key
117
131
118
- actual fun child (path : String ): DataSnapshot {
119
- TODO (" not implemented" ) // To change body of created functions use File | Settings | File Templates.
120
- }
132
+ actual inline fun <reified T > value () =
133
+ decode<T >(value = ios.value)
134
+
135
+ actual inline fun <reified T > value (strategy : DeserializationStrategy <T >) =
136
+ decode(strategy, ios.value)
121
137
122
- actual val children : Iterable < DataSnapshot >
123
- get() = TODO ( " not implemented " ) // To change initializer of created properties use File | Settings | File Templates.
138
+ actual fun child ( path : String ) = DataSnapshot (ios.childSnapshotForPath(path))
139
+ actual val children : Iterable < DataSnapshot > get() = ios.children.allObjects.map { DataSnapshot (it as FIRDataSnapshot ) }
124
140
}
125
141
126
- actual class DatabaseException : RuntimeException ()
127
- actual class OnDisconnect {
142
+ actual class OnDisconnect internal constructor(
143
+ val ios : FIRDatabaseReference ,
144
+ val persistenceEnabled : Boolean
145
+ ) {
128
146
actual suspend fun removeValue () {
147
+ ios.await(persistenceEnabled) { onDisconnectRemoveValueWithCompletionBlock(it) }
129
148
}
130
149
131
150
actual suspend fun cancel () {
151
+ ios.await(persistenceEnabled) { cancelDisconnectOperationsWithCompletionBlock(it) }
132
152
}
133
153
134
- @ImplicitReflectionSerializer
135
154
actual suspend inline fun <reified T : Any > setValue (value : T ) {
155
+ ios.await(persistenceEnabled) { onDisconnectSetValue(encode(value), it) }
136
156
}
137
157
138
158
actual suspend inline fun <reified T > setValue (strategy : SerializationStrategy <T >, value : T ) {
159
+ ios.await(persistenceEnabled) { onDisconnectSetValue(encode(strategy, value), it) }
139
160
}
140
161
141
- @ImplicitReflectionSerializer
142
162
actual suspend fun updateChildren (update : Map <String , Any ?>) {
163
+ ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(update.mapValues { (_, it) -> encode(it) } as Map <Any ?, * >, it) }
164
+ }
165
+ }
166
+
167
+ actual class DatabaseException (message : String ) : RuntimeException(message)
168
+
169
+ private suspend fun <T , R > T.awaitResult (whileOnline : Boolean , function : T .(callback: (NSError ? , R ? ) -> Unit ) -> Unit ): R {
170
+ val job = CompletableDeferred <R >()
171
+ function { error, result ->
172
+ if (result != null ) {
173
+ job.complete(result)
174
+ } else if (error != null ) {
175
+ job.completeExceptionally(DatabaseException (error.toString()))
176
+ }
177
+ }
178
+ return job.run { if (whileOnline) awaitWhileOnline() else await() }
179
+ }
180
+
181
+ suspend fun <T > T.await (whileOnline : Boolean , function : T .(callback: (NSError ? , FIRDatabaseReference ? ) -> Unit ) -> Unit ) {
182
+ val job = CompletableDeferred <Unit >()
183
+ function { error, _ ->
184
+ if (error == null ) {
185
+ job.complete(Unit )
186
+ } else {
187
+ job.completeExceptionally(DatabaseException (error.toString()))
188
+ }
189
+ }
190
+ job.run { if (whileOnline) awaitWhileOnline() else await() }
191
+ }
192
+
193
+ private suspend fun <T > CompletableDeferred<T>.awaitWhileOnline (): T = coroutineScope {
194
+
195
+ val notConnected = Firebase .database
196
+ .reference(" .info/connected" )
197
+ .valueEvents
198
+ .filter { ! it.value<Boolean >() }
199
+ .produceIn(this )
200
+
201
+ select<T > {
202
+ onAwait { it.also { notConnected.cancel() } }
203
+ notConnected.onReceive { throw DatabaseException (" Database not connected" ) }
143
204
}
144
205
}
0 commit comments