@react-native-firebase/database 26.3.2 → 26.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/RNFBDatabase.podspec +3 -1
- package/android/build.gradle +2 -0
- package/android/src/main/java/io/invertase/firebase/database/DatabaseAbortable.java +22 -0
- package/android/src/main/java/io/invertase/firebase/database/DatabaseQueryHandle.java +24 -0
- package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseListenerRegistry.java +117 -0
- package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseQueryRegistry.java +78 -0
- package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseTransactionRegistry.java +71 -0
- package/android/src/reactnative/java/io/invertase/firebase/database/NativeRNFBTurboDatabaseQuery.java +18 -22
- package/android/src/reactnative/java/io/invertase/firebase/database/NativeRNFBTurboDatabaseTransaction.java +12 -25
- package/android/src/reactnative/java/io/invertase/firebase/database/ReactNativeFirebaseDatabaseQuery.java +30 -33
- package/android/src/reactnative/java/io/invertase/firebase/database/ReactNativeFirebaseDatabaseTransactionHandler.java +3 -3
- package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseListenerRegistryTest.java +160 -0
- package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseQueryRegistryTest.java +434 -0
- package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseTransactionRegistryTest.java +199 -0
- package/dist/module/version.js +1 -1
- package/dist/typescript/lib/index.d.ts +1 -1
- package/dist/typescript/lib/version.d.ts +1 -1
- package/ios/RNFBDatabase/RNFBDatabaseListenerRegistry.h +37 -0
- package/ios/RNFBDatabase/RNFBDatabaseListenerRegistry.m +84 -0
- package/ios/RNFBDatabase/RNFBDatabaseQuery.h +3 -2
- package/ios/RNFBDatabase/RNFBDatabaseQuery.m +14 -13
- package/ios/RNFBDatabase/RNFBDatabaseQueryHelper.m +24 -21
- package/ios/RNFBDatabase/RNFBDatabaseQueryRegistry.h +37 -0
- package/ios/RNFBDatabase/RNFBDatabaseQueryRegistry.m +105 -0
- package/ios/RNFBDatabaseUnitTests/RNFBDatabaseListenerRegistryTests.m +90 -0
- package/ios/RNFBDatabaseUnitTests/RNFBDatabaseQueryRegistryTests.m +390 -0
- package/ios/RNFBDatabaseUnitTests/RNFBDatabaseUnitTests.xcodeproj/project.pbxproj +261 -0
- package/ios/RNFBDatabaseUnitTests/RNFBDatabaseUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBDatabaseUnitTests.xcscheme +69 -0
- package/lib/version.ts +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
package io.invertase.firebase.database;
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Copyright (c) 2016-present Invertase Limited & Contributors
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this library except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import static org.junit.Assert.assertFalse;
|
|
21
|
+
import static org.junit.Assert.assertNull;
|
|
22
|
+
import static org.junit.Assert.assertSame;
|
|
23
|
+
import static org.junit.Assert.assertTrue;
|
|
24
|
+
import static org.junit.Assert.fail;
|
|
25
|
+
|
|
26
|
+
import io.invertase.firebase.common.RNFBHandleCollisionException;
|
|
27
|
+
import java.util.concurrent.CountDownLatch;
|
|
28
|
+
import java.util.concurrent.TimeUnit;
|
|
29
|
+
import org.junit.Test;
|
|
30
|
+
|
|
31
|
+
public class RNFBDatabaseTransactionRegistryTest {
|
|
32
|
+
|
|
33
|
+
private static final class FakeAbortable implements DatabaseAbortable {
|
|
34
|
+
int abortCount;
|
|
35
|
+
|
|
36
|
+
@Override
|
|
37
|
+
public void abort() {
|
|
38
|
+
abortCount++;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@Test
|
|
43
|
+
public void putGetTake_happyPath() throws Exception {
|
|
44
|
+
RNFBDatabaseTransactionRegistry registry = new RNFBDatabaseTransactionRegistry();
|
|
45
|
+
FakeAbortable handler = new FakeAbortable();
|
|
46
|
+
registry.put(1, handler);
|
|
47
|
+
assertSame(handler, registry.get(1));
|
|
48
|
+
assertSame(handler, registry.take(1));
|
|
49
|
+
assertNull(registry.get(1));
|
|
50
|
+
assertTrue(handler.abortCount == 0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Test
|
|
54
|
+
public void put_occupiedId_abortsIncomingAndLeavesExisting() throws Exception {
|
|
55
|
+
RNFBDatabaseTransactionRegistry registry = new RNFBDatabaseTransactionRegistry();
|
|
56
|
+
FakeAbortable first = new FakeAbortable();
|
|
57
|
+
FakeAbortable duplicate = new FakeAbortable();
|
|
58
|
+
registry.put(1, first);
|
|
59
|
+
try {
|
|
60
|
+
registry.put(1, duplicate);
|
|
61
|
+
fail("expected RNFBHandleCollisionException");
|
|
62
|
+
} catch (RNFBHandleCollisionException e) {
|
|
63
|
+
assertTrue(e.getMessage().contains("1"));
|
|
64
|
+
assertSame(first, registry.get(1));
|
|
65
|
+
assertTrue(first.abortCount == 0);
|
|
66
|
+
assertTrue(duplicate.abortCount == 1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@Test
|
|
71
|
+
public void put_occupiedId_nullIncoming_doesNotAbort() throws Exception {
|
|
72
|
+
RNFBDatabaseTransactionRegistry registry = new RNFBDatabaseTransactionRegistry();
|
|
73
|
+
FakeAbortable first = new FakeAbortable();
|
|
74
|
+
registry.put(2, first);
|
|
75
|
+
try {
|
|
76
|
+
registry.put(2, null);
|
|
77
|
+
fail("expected RNFBHandleCollisionException");
|
|
78
|
+
} catch (RNFBHandleCollisionException e) {
|
|
79
|
+
assertSame(first, registry.get(2));
|
|
80
|
+
assertTrue(first.abortCount == 0);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@Test
|
|
85
|
+
public void registerReplacing_replacesLastWins() throws Exception {
|
|
86
|
+
RNFBDatabaseTransactionRegistry registry = new RNFBDatabaseTransactionRegistry();
|
|
87
|
+
FakeAbortable first = new FakeAbortable();
|
|
88
|
+
FakeAbortable second = new FakeAbortable();
|
|
89
|
+
registry.put(3, first);
|
|
90
|
+
registry.registerReplacing(3, second);
|
|
91
|
+
assertSame(second, registry.get(3));
|
|
92
|
+
assertTrue(first.abortCount == 0);
|
|
93
|
+
assertTrue(second.abortCount == 0);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@Test
|
|
97
|
+
public void registerReplacing_sameIdFromTwoThreads_lastWins() throws Exception {
|
|
98
|
+
RNFBDatabaseTransactionRegistry registry = new RNFBDatabaseTransactionRegistry();
|
|
99
|
+
FakeAbortable first = new FakeAbortable();
|
|
100
|
+
registry.put(5, first);
|
|
101
|
+
|
|
102
|
+
CountDownLatch start = new CountDownLatch(1);
|
|
103
|
+
CountDownLatch done = new CountDownLatch(2);
|
|
104
|
+
FakeAbortable second = new FakeAbortable();
|
|
105
|
+
FakeAbortable third = new FakeAbortable();
|
|
106
|
+
|
|
107
|
+
Thread t1 =
|
|
108
|
+
new Thread(
|
|
109
|
+
() -> {
|
|
110
|
+
try {
|
|
111
|
+
start.await();
|
|
112
|
+
registry.registerReplacing(5, second);
|
|
113
|
+
} catch (InterruptedException e) {
|
|
114
|
+
Thread.currentThread().interrupt();
|
|
115
|
+
} finally {
|
|
116
|
+
done.countDown();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
Thread t2 =
|
|
120
|
+
new Thread(
|
|
121
|
+
() -> {
|
|
122
|
+
try {
|
|
123
|
+
start.await();
|
|
124
|
+
registry.registerReplacing(5, third);
|
|
125
|
+
} catch (InterruptedException e) {
|
|
126
|
+
Thread.currentThread().interrupt();
|
|
127
|
+
} finally {
|
|
128
|
+
done.countDown();
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
t1.start();
|
|
133
|
+
t2.start();
|
|
134
|
+
start.countDown();
|
|
135
|
+
assertTrue(done.await(5, TimeUnit.SECONDS));
|
|
136
|
+
|
|
137
|
+
DatabaseAbortable stored = registry.get(5);
|
|
138
|
+
assertTrue(stored == second || stored == third);
|
|
139
|
+
assertTrue(first.abortCount == 0);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@Test
|
|
143
|
+
public void takeAndAbort_abortsAfterTake() throws Exception {
|
|
144
|
+
RNFBDatabaseTransactionRegistry registry = new RNFBDatabaseTransactionRegistry();
|
|
145
|
+
FakeAbortable handler = new FakeAbortable();
|
|
146
|
+
registry.put(3, handler);
|
|
147
|
+
registry.takeAndAbort(3);
|
|
148
|
+
assertTrue(handler.abortCount == 1);
|
|
149
|
+
assertNull(registry.get(3));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@Test
|
|
153
|
+
public void takeAndAbort_missingKey_isNoOp() {
|
|
154
|
+
RNFBDatabaseTransactionRegistry registry = new RNFBDatabaseTransactionRegistry();
|
|
155
|
+
registry.takeAndAbort(99);
|
|
156
|
+
assertNull(registry.get(99));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@Test
|
|
160
|
+
public void takeAllAndAbort_abortsSnapshotAndLeavesEmpty() throws Exception {
|
|
161
|
+
RNFBDatabaseTransactionRegistry registry = new RNFBDatabaseTransactionRegistry();
|
|
162
|
+
FakeAbortable a = new FakeAbortable();
|
|
163
|
+
FakeAbortable b = new FakeAbortable();
|
|
164
|
+
registry.put(1, a);
|
|
165
|
+
registry.put(2, b);
|
|
166
|
+
registry.takeAllAndAbort();
|
|
167
|
+
assertTrue(a.abortCount == 1);
|
|
168
|
+
assertTrue(b.abortCount == 1);
|
|
169
|
+
assertNull(registry.get(1));
|
|
170
|
+
assertNull(registry.get(2));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
@Test
|
|
174
|
+
public void takeAllAndAbort_empty_isNoOp() {
|
|
175
|
+
RNFBDatabaseTransactionRegistry registry = new RNFBDatabaseTransactionRegistry();
|
|
176
|
+
registry.takeAllAndAbort();
|
|
177
|
+
assertNull(registry.get(1));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@Test
|
|
181
|
+
public void takeAllAndAbort_nullHandler_isNoOp() throws Exception {
|
|
182
|
+
RNFBDatabaseTransactionRegistry registry = new RNFBDatabaseTransactionRegistry();
|
|
183
|
+
registry.put(4, null);
|
|
184
|
+
registry.takeAllAndAbort();
|
|
185
|
+
assertNull(registry.get(4));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@Test
|
|
189
|
+
public void put_afterTake_allowsReuse() throws Exception {
|
|
190
|
+
RNFBDatabaseTransactionRegistry registry = new RNFBDatabaseTransactionRegistry();
|
|
191
|
+
FakeAbortable first = new FakeAbortable();
|
|
192
|
+
FakeAbortable second = new FakeAbortable();
|
|
193
|
+
registry.put(1, first);
|
|
194
|
+
assertSame(first, registry.take(1));
|
|
195
|
+
registry.put(1, second);
|
|
196
|
+
assertSame(second, registry.get(1));
|
|
197
|
+
assertFalse(first.abortCount != 0);
|
|
198
|
+
}
|
|
199
|
+
}
|
package/dist/module/version.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FirebaseApp } from '@react-native-firebase/app';
|
|
2
2
|
import './types/internal';
|
|
3
3
|
import type { Database, DatabaseReference, EmulatorMockTokenOptions } from './types/database';
|
|
4
|
-
export declare const SDK_VERSION = "26.
|
|
4
|
+
export declare const SDK_VERSION = "26.4.0";
|
|
5
5
|
export declare function getDatabase(app?: FirebaseApp, url?: string): Database;
|
|
6
6
|
export declare function connectDatabaseEmulator(db: Database, host: string, port: number, options?: {
|
|
7
7
|
mockUserToken?: EmulatorMockTokenOptions | string;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const version = "26.
|
|
1
|
+
export declare const version = "26.4.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2016-present Invertase Limited & Contributors
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this library except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
#import <Foundation/Foundation.h>
|
|
19
|
+
|
|
20
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Per-query listener handle map. Unique `put`; callers `take` or `takeAll` then remove the SDK
|
|
24
|
+
* observer outside the HandleMap lock. Values are typically NSNumber observer handles.
|
|
25
|
+
*/
|
|
26
|
+
@interface RNFBDatabaseListenerRegistry : NSObject
|
|
27
|
+
|
|
28
|
+
- (BOOL)put:(id)key value:(id)value error:(NSError *_Nullable *_Nullable)error;
|
|
29
|
+
- (nullable id)get:(id)key;
|
|
30
|
+
- (nullable id)take:(id)key;
|
|
31
|
+
- (NSArray *)takeAll;
|
|
32
|
+
- (BOOL)hasEventListener:(id)key;
|
|
33
|
+
- (BOOL)hasListeners;
|
|
34
|
+
|
|
35
|
+
@end
|
|
36
|
+
|
|
37
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2016-present Invertase Limited & Contributors
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this library except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
#import "RNFBDatabaseListenerRegistry.h"
|
|
19
|
+
|
|
20
|
+
#if __has_include("RNFBHandleMap.h")
|
|
21
|
+
#import "RNFBHandleMap.h"
|
|
22
|
+
#else
|
|
23
|
+
#import "RNFBApp/RNFBHandleMap.h"
|
|
24
|
+
#endif
|
|
25
|
+
|
|
26
|
+
@interface RNFBDatabaseListenerRegistry ()
|
|
27
|
+
@property(nonatomic, strong) RNFBHandleMap *map;
|
|
28
|
+
@property(nonatomic, assign) NSInteger occupancy;
|
|
29
|
+
@end
|
|
30
|
+
|
|
31
|
+
@implementation RNFBDatabaseListenerRegistry
|
|
32
|
+
|
|
33
|
+
- (instancetype)init {
|
|
34
|
+
self = [super init];
|
|
35
|
+
if (self) {
|
|
36
|
+
_map = [[RNFBHandleMap alloc] init];
|
|
37
|
+
_occupancy = 0;
|
|
38
|
+
}
|
|
39
|
+
return self;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
- (BOOL)put:(id)key value:(id)value error:(NSError **)error {
|
|
43
|
+
@synchronized(self) {
|
|
44
|
+
if ([self.map put:key value:value error:error]) {
|
|
45
|
+
_occupancy += 1;
|
|
46
|
+
return YES;
|
|
47
|
+
}
|
|
48
|
+
return NO;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
- (id)get:(id)key {
|
|
53
|
+
return [self.map get:key];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
- (id)take:(id)key {
|
|
57
|
+
@synchronized(self) {
|
|
58
|
+
id value = [self.map take:key];
|
|
59
|
+
if (value != nil) {
|
|
60
|
+
_occupancy -= 1;
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
- (NSArray *)takeAll {
|
|
67
|
+
@synchronized(self) {
|
|
68
|
+
NSArray *remaining = [self.map takeAll];
|
|
69
|
+
_occupancy -= (NSInteger)remaining.count;
|
|
70
|
+
return remaining;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
- (BOOL)hasEventListener:(id)key {
|
|
75
|
+
return [self.map get:key] != nil;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
- (BOOL)hasListeners {
|
|
79
|
+
@synchronized(self) {
|
|
80
|
+
return _occupancy > 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@end
|
|
@@ -28,10 +28,11 @@
|
|
|
28
28
|
@import FirebaseDatabaseInternal;
|
|
29
29
|
#endif
|
|
30
30
|
#import <React/RCTBridgeModule.h>
|
|
31
|
+
#import "RNFBDatabaseListenerRegistry.h"
|
|
31
32
|
|
|
32
33
|
@interface RNFBDatabaseQuery : NSObject
|
|
33
34
|
@property FIRDatabaseQuery *query;
|
|
34
|
-
@property
|
|
35
|
+
@property(nonatomic, strong, readonly) RNFBDatabaseListenerRegistry *listeners;
|
|
35
36
|
|
|
36
37
|
- (RNFBDatabaseQuery *)initWithReferenceAndModifiers:(FIRDatabaseReference *)reference
|
|
37
38
|
modifiers:(NSArray *)modifiers;
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
|
|
41
42
|
- (BOOL)hasListeners;
|
|
42
43
|
|
|
43
|
-
- (
|
|
44
|
+
- (BOOL)addEventListener:(NSString *)eventRegistrationKey handle:(FIRDatabaseHandle)handle;
|
|
44
45
|
|
|
45
46
|
- (void)removeEventListener:(NSString *)eventRegistrationKey;
|
|
46
47
|
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
#import "RNFBDatabaseQuery.h"
|
|
20
|
+
#import "RNFBDatabaseListenerRegistry.h"
|
|
20
21
|
|
|
21
22
|
@implementation RNFBDatabaseQuery
|
|
22
23
|
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
|
|
27
28
|
if (self) {
|
|
28
29
|
_query = [self buildQueryWithModifiers:reference modifiers:modifiers];
|
|
29
|
-
_listeners = [
|
|
30
|
+
_listeners = [[RNFBDatabaseListenerRegistry alloc] init];
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
return self;
|
|
@@ -112,32 +113,32 @@
|
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
- (
|
|
116
|
-
|
|
116
|
+
- (BOOL)addEventListener:(NSString *)eventRegistrationKey handle:(FIRDatabaseHandle)handle {
|
|
117
|
+
NSError *error = nil;
|
|
118
|
+
return [_listeners put:eventRegistrationKey value:@(handle) error:&error];
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
- (void)removeEventListener:(NSString *)eventRegistrationKey {
|
|
120
|
-
|
|
121
|
-
if (
|
|
122
|
-
[_query removeObserverWithHandle:
|
|
123
|
-
[_listeners removeObjectForKey:eventRegistrationKey];
|
|
122
|
+
NSNumber *handleNumber = [_listeners take:eventRegistrationKey];
|
|
123
|
+
if (handleNumber != nil) {
|
|
124
|
+
[_query removeObserverWithHandle:(FIRDatabaseHandle)[handleNumber integerValue]];
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
- (void)removeAllEventListeners {
|
|
128
|
-
NSArray *
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
[
|
|
129
|
+
NSArray *handles = [_listeners takeAll];
|
|
130
|
+
for (NSNumber *handleNumber in handles) {
|
|
131
|
+
FIRDatabaseHandle handle = (FIRDatabaseHandle)[handleNumber integerValue];
|
|
132
|
+
[_query removeObserverWithHandle:handle];
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
- (BOOL)hasEventListener:(NSString *)eventRegistrationKey {
|
|
136
|
-
return _listeners
|
|
137
|
+
return [_listeners hasEventListener:eventRegistrationKey];
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
- (BOOL)hasListeners {
|
|
140
|
-
return [
|
|
141
|
+
return [_listeners hasListeners];
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
@end
|
|
@@ -26,29 +26,24 @@
|
|
|
26
26
|
#import "RNFBDatabaseCommon.h"
|
|
27
27
|
#import "RNFBDatabaseQuery.h"
|
|
28
28
|
#import "RNFBDatabaseQueryHelper.h"
|
|
29
|
+
#import "RNFBDatabaseQueryRegistry.h"
|
|
29
30
|
#import "RNFBRCTEventEmitter.h"
|
|
30
31
|
|
|
31
|
-
static
|
|
32
|
+
static RNFBDatabaseQueryRegistry *queryRegistry;
|
|
32
33
|
static NSString *const RNFB_DATABASE_SYNC = @"database_sync_event";
|
|
33
34
|
|
|
34
35
|
@implementation RNFBDatabaseQueryHelper
|
|
35
36
|
|
|
36
|
-
+ (
|
|
37
|
+
+ (RNFBDatabaseQueryRegistry *)queryRegistry {
|
|
37
38
|
static dispatch_once_t onceToken;
|
|
38
39
|
dispatch_once(&onceToken, ^{
|
|
39
|
-
|
|
40
|
+
queryRegistry = [[RNFBDatabaseQueryRegistry alloc] init];
|
|
40
41
|
});
|
|
41
|
-
return
|
|
42
|
+
return queryRegistry;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
+ (void)invalidate {
|
|
45
|
-
|
|
46
|
-
NSArray *queryKeys = [queries allKeys];
|
|
47
|
-
for (NSString *key in queryKeys) {
|
|
48
|
-
RNFBDatabaseQuery *query = queries[key];
|
|
49
|
-
[query removeAllEventListeners];
|
|
50
|
-
[queries removeObjectForKey:key];
|
|
51
|
-
}
|
|
46
|
+
[[self queryRegistry] removeAll];
|
|
52
47
|
}
|
|
53
48
|
|
|
54
49
|
+ (RNFBDatabaseQuery *)getDatabaseQueryInstance:(FIRDatabaseReference *)reference
|
|
@@ -59,17 +54,23 @@ static NSString *const RNFB_DATABASE_SYNC = @"database_sync_event";
|
|
|
59
54
|
+ (RNFBDatabaseQuery *)getDatabaseQueryInstance:(NSString *)key
|
|
60
55
|
reference:(FIRDatabaseReference *)reference
|
|
61
56
|
modifiers:(NSArray *)modifiers {
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
RNFBDatabaseQueryRegistry *queries = [self queryRegistry];
|
|
58
|
+
id cachedQuery = [queries get:key];
|
|
64
59
|
|
|
65
|
-
if (cachedQuery
|
|
60
|
+
if ([cachedQuery isKindOfClass:[RNFBDatabaseQuery class]]) {
|
|
66
61
|
return cachedQuery;
|
|
67
62
|
}
|
|
68
63
|
|
|
69
64
|
RNFBDatabaseQuery *query = [[RNFBDatabaseQuery alloc] initWithReferenceAndModifiers:reference
|
|
70
65
|
modifiers:modifiers];
|
|
71
66
|
|
|
72
|
-
|
|
67
|
+
NSError *error = nil;
|
|
68
|
+
if (![queries put:key value:query error:&error]) {
|
|
69
|
+
id winner = [queries get:key];
|
|
70
|
+
if ([winner isKindOfClass:[RNFBDatabaseQuery class]]) {
|
|
71
|
+
return winner;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
73
74
|
return query;
|
|
74
75
|
}
|
|
75
76
|
|
|
@@ -167,7 +168,9 @@ static NSString *const RNFB_DATABASE_SYNC = @"database_sync_event";
|
|
|
167
168
|
FIRDatabaseHandle handle = [databaseQuery.query observeEventType:firDataEventType
|
|
168
169
|
andPreviousSiblingKeyWithBlock:andPreviousSiblingKeyWithBlock
|
|
169
170
|
withCancelBlock:errorBlock];
|
|
170
|
-
[databaseQuery addEventListener:eventRegistrationKey handle:handle]
|
|
171
|
+
if (![databaseQuery addEventListener:eventRegistrationKey handle:handle]) {
|
|
172
|
+
[databaseQuery.query removeObserverWithHandle:handle];
|
|
173
|
+
}
|
|
171
174
|
}
|
|
172
175
|
}
|
|
173
176
|
|
|
@@ -206,14 +209,14 @@ static NSString *const RNFB_DATABASE_SYNC = @"database_sync_event";
|
|
|
206
209
|
}
|
|
207
210
|
|
|
208
211
|
+ (void)off:(NSString *)queryKey eventRegistrationKey:(NSString *)eventRegistrationKey {
|
|
209
|
-
|
|
210
|
-
|
|
212
|
+
RNFBDatabaseQueryRegistry *queries = [self queryRegistry];
|
|
213
|
+
id cached = [queries get:queryKey];
|
|
211
214
|
|
|
212
|
-
if (
|
|
215
|
+
if ([cached isKindOfClass:[RNFBDatabaseQuery class]]) {
|
|
216
|
+
RNFBDatabaseQuery *databaseQuery = cached;
|
|
213
217
|
[databaseQuery removeEventListener:eventRegistrationKey];
|
|
214
218
|
|
|
215
|
-
if (
|
|
216
|
-
[queries removeObjectForKey:queryKey];
|
|
219
|
+
if ([queries takeIfIdle:queryKey] != nil) {
|
|
217
220
|
[RNFBDatabaseCommon removeReferenceByKey:queryKey];
|
|
218
221
|
}
|
|
219
222
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2016-present Invertase Limited & Contributors
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this library except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
#import <Foundation/Foundation.h>
|
|
19
|
+
|
|
20
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Cached query map. Unique `put`; callers `take`, `takeIfIdle`, or `removeAll` then
|
|
24
|
+
* `removeAllEventListeners` outside the HandleMap lock. Stored values are expected to respond to
|
|
25
|
+
* `removeAllEventListeners` and `hasListeners`.
|
|
26
|
+
*/
|
|
27
|
+
@interface RNFBDatabaseQueryRegistry : NSObject
|
|
28
|
+
|
|
29
|
+
- (BOOL)put:(id)key value:(id)value error:(NSError *_Nullable *_Nullable)error;
|
|
30
|
+
- (nullable id)get:(id)key;
|
|
31
|
+
- (nullable id)take:(id)key;
|
|
32
|
+
- (nullable id)takeIfIdle:(id)key;
|
|
33
|
+
- (void)removeAll;
|
|
34
|
+
|
|
35
|
+
@end
|
|
36
|
+
|
|
37
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2016-present Invertase Limited & Contributors
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this library except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
#import "RNFBDatabaseQueryRegistry.h"
|
|
19
|
+
|
|
20
|
+
#if __has_include("RNFBHandleMap.h")
|
|
21
|
+
#import "RNFBHandleMap.h"
|
|
22
|
+
#else
|
|
23
|
+
#import "RNFBApp/RNFBHandleMap.h"
|
|
24
|
+
#endif
|
|
25
|
+
|
|
26
|
+
@protocol RNFBDatabaseHasListeners
|
|
27
|
+
- (BOOL)hasListeners;
|
|
28
|
+
@end
|
|
29
|
+
|
|
30
|
+
@interface RNFBDatabaseQueryRegistry ()
|
|
31
|
+
@property(nonatomic, strong) RNFBHandleMap *map;
|
|
32
|
+
@end
|
|
33
|
+
|
|
34
|
+
@implementation RNFBDatabaseQueryRegistry
|
|
35
|
+
|
|
36
|
+
- (instancetype)init {
|
|
37
|
+
self = [super init];
|
|
38
|
+
if (self) {
|
|
39
|
+
_map = [[RNFBHandleMap alloc] init];
|
|
40
|
+
}
|
|
41
|
+
return self;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
- (void)rnfb_removeAllListeners:(id)query {
|
|
45
|
+
if (query && [query respondsToSelector:@selector(removeAllEventListeners)]) {
|
|
46
|
+
#pragma clang diagnostic push
|
|
47
|
+
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
|
48
|
+
[query performSelector:@selector(removeAllEventListeners)];
|
|
49
|
+
#pragma clang diagnostic pop
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
- (BOOL)put:(id)key value:(id)value error:(NSError **)error {
|
|
54
|
+
return [self.map put:key value:value error:error];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
- (id)get:(id)key {
|
|
58
|
+
return [self.map get:key];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
- (id)take:(id)key {
|
|
62
|
+
return [self.map take:key];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
- (id)takeIfIdle:(id)key {
|
|
66
|
+
// Read hasListeners outside the HandleMap lock (avoids nesting listener occupancy), then
|
|
67
|
+
// identity-take and put-back if listeners appeared.
|
|
68
|
+
id query = [self.map get:key];
|
|
69
|
+
if (query == nil) {
|
|
70
|
+
return nil;
|
|
71
|
+
}
|
|
72
|
+
if (![query respondsToSelector:@selector(hasListeners)]) {
|
|
73
|
+
return nil;
|
|
74
|
+
}
|
|
75
|
+
if ([(id<RNFBDatabaseHasListeners>)query hasListeners]) {
|
|
76
|
+
return nil;
|
|
77
|
+
}
|
|
78
|
+
id taken = [self.map takeIf:key
|
|
79
|
+
when:^BOOL(id value) {
|
|
80
|
+
return value == query;
|
|
81
|
+
}];
|
|
82
|
+
if (taken == nil) {
|
|
83
|
+
return nil;
|
|
84
|
+
}
|
|
85
|
+
if ([taken respondsToSelector:@selector(hasListeners)] &&
|
|
86
|
+
[(id<RNFBDatabaseHasListeners>)taken hasListeners]) {
|
|
87
|
+
// Prefer put-back so an active query stays registered. If a concurrent put claimed the
|
|
88
|
+
// slot, putIfAbsent fails and this taken query would be an orphan with listeners — clear
|
|
89
|
+
// them so SDK callbacks are not left attached outside the registry.
|
|
90
|
+
if (![self.map putIfAbsent:key value:taken]) {
|
|
91
|
+
[self rnfb_removeAllListeners:taken];
|
|
92
|
+
}
|
|
93
|
+
return nil;
|
|
94
|
+
}
|
|
95
|
+
return taken;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
- (void)removeAll {
|
|
99
|
+
NSArray *queries = [self.map takeAll];
|
|
100
|
+
for (id query in queries) {
|
|
101
|
+
[self rnfb_removeAllListeners:query];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@end
|