@react-native-firebase/storage 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 +16 -0
- package/RNFBStorage.podspec +1 -1
- package/android/build.gradle +3 -0
- package/android/src/main/java/io/invertase/firebase/storage/NativeRNFBTurboStorage.java +24 -4
- package/android/src/main/java/io/invertase/firebase/storage/RNFBStorageTaskRegistry.java +93 -0
- package/android/src/main/java/io/invertase/firebase/storage/ReactNativeFirebaseStorageTask.java +25 -46
- package/android/src/main/java/io/invertase/firebase/storage/StoragePendingHandle.java +30 -0
- package/android/src/test/java/io/invertase/firebase/storage/RNFBStorageTaskRegistryTest.java +212 -0
- package/android/src/test/java/io/invertase/firebase/storage/ReactNativeFirebaseStorageTaskTest.java +285 -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/RNFBStorage/RNFBStorageHelper.m +38 -13
- package/ios/RNFBStorage/RNFBStorageModule.mm +2 -3
- package/ios/RNFBStorage/RNFBStorageTaskRegistry.h +42 -0
- package/ios/RNFBStorage/RNFBStorageTaskRegistry.m +94 -0
- package/ios/RNFBStorageUnitTests/RNFBStorageTaskRegistryTests.m +225 -0
- package/ios/RNFBStorageUnitTests/RNFBStorageUnitTests.xcodeproj/project.pbxproj +251 -0
- package/ios/RNFBStorageUnitTests/RNFBStorageUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBStorageUnitTests.xcscheme +69 -0
- package/lib/version.ts +1 -1
- package/package.json +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [26.4.0](https://github.com/invertase/react-native-firebase/compare/v26.3.3...v26.4.0) (2026-09-05)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **storage:** use RNFBHandleMap for pending tasks ([acf77f1](https://github.com/invertase/react-native-firebase/commit/acf77f1ff1680f1ce9c65f72cf0056df7b998aa5))
|
|
11
|
+
|
|
12
|
+
## [26.3.3](https://github.com/invertase/react-native-firebase/compare/v26.3.2...v26.3.3) (2026-09-01)
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
- **ios:** align TurboModule constants return type with generated spec ([c79e1d2](https://github.com/invertase/react-native-firebase/commit/c79e1d2bc43b00a6ef61d38a294feceaed447740)), closes [#9212](https://github.com/invertase/react-native-firebase/issues/9212)
|
|
17
|
+
|
|
18
|
+
### Reverts
|
|
19
|
+
|
|
20
|
+
- Revert "chore(release): release packages" ([0a85268](https://github.com/invertase/react-native-firebase/commit/0a85268ae21c706cf1392eaa5b088e85c3273cb7))
|
|
21
|
+
|
|
6
22
|
## [26.3.2](https://github.com/invertase/react-native-firebase/compare/v26.3.1...v26.3.2) (2026-08-21)
|
|
7
23
|
|
|
8
24
|
**Note:** Version bump only for package @react-native-firebase/storage
|
package/RNFBStorage.podspec
CHANGED
|
@@ -33,7 +33,7 @@ Pod::Spec.new do |s|
|
|
|
33
33
|
'ios/RNFBStorage/*.h',
|
|
34
34
|
'ios/generated/**/*.h',
|
|
35
35
|
]
|
|
36
|
-
s.exclude_files = 'ios/generated/RCTThirdPartyComponentsProvider.*', 'ios/generated/RCTAppDependencyProvider.*', 'ios/generated/RCTModuleProviders.*', 'ios/generated/RCTModulesConformingToProtocolsProvider.*', 'ios/generated/RCTUnstableModulesRequiringMainQueueSetupProvider.*'
|
|
36
|
+
s.exclude_files = 'ios/generated/RCTThirdPartyComponentsProvider.*', 'ios/generated/RCTAppDependencyProvider.*', 'ios/generated/RCTModuleProviders.*', 'ios/generated/RCTModulesConformingToProtocolsProvider.*', 'ios/generated/RCTUnstableModulesRequiringMainQueueSetupProvider.*', 'ios/*UnitTests/**'
|
|
37
37
|
|
|
38
38
|
# Must be set before install_modules_dependencies so RN can append use_frameworks
|
|
39
39
|
# HEADER_SEARCH_PATHS (React-debug etc.). Assigning after overwrites those paths
|
package/android/build.gradle
CHANGED
|
@@ -93,6 +93,9 @@ dependencies {
|
|
|
93
93
|
api appProject
|
|
94
94
|
implementation platform("com.google.firebase:firebase-bom:${ReactNative.ext.getVersion("firebase", "bom")}")
|
|
95
95
|
implementation "com.google.firebase:firebase-storage"
|
|
96
|
+
|
|
97
|
+
testImplementation "junit:junit:4.13.2"
|
|
98
|
+
testImplementation "org.mockito:mockito-core:5.14.2"
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
ReactNative.shared.applyPackageVersion()
|
|
@@ -56,7 +56,7 @@ public class NativeRNFBTurboStorage extends NativeRNFBTurboStorageSpec {
|
|
|
56
56
|
|
|
57
57
|
@Override
|
|
58
58
|
public void invalidate() {
|
|
59
|
-
ReactNativeFirebaseStorageTask.
|
|
59
|
+
ReactNativeFirebaseStorageTask.PENDING_TASKS.takeAllAndCancel();
|
|
60
60
|
super.invalidate();
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -274,6 +274,10 @@ public class NativeRNFBTurboStorage extends NativeRNFBTurboStorageSpec {
|
|
|
274
274
|
ReactNativeFirebaseStorageDownloadTask storageTask =
|
|
275
275
|
new ReactNativeFirebaseStorageDownloadTask((int) taskId, reference, appName);
|
|
276
276
|
storageTask.begin(getTransactionalExecutor(), localFilePath);
|
|
277
|
+
if (storageTask.hasStorageTask() && !storageTask.registerPending()) {
|
|
278
|
+
rejectPromiseWithCodeAndMessage(promise, "internal", "Handle id already registered");
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
277
281
|
storageTask.addOnCompleteListener(getTransactionalExecutor(), promise);
|
|
278
282
|
} catch (Exception e) {
|
|
279
283
|
promiseRejectStorageException(promise, e);
|
|
@@ -294,6 +298,10 @@ public class NativeRNFBTurboStorage extends NativeRNFBTurboStorageSpec {
|
|
|
294
298
|
ReactNativeFirebaseStorageUploadTask storageTask =
|
|
295
299
|
new ReactNativeFirebaseStorageUploadTask((int) taskId, reference, appName);
|
|
296
300
|
storageTask.begin(getTransactionalExecutor(), string, format, metadataMap);
|
|
301
|
+
if (!storageTask.registerPending()) {
|
|
302
|
+
rejectPromiseWithCodeAndMessage(promise, "internal", "Handle id already registered");
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
297
305
|
storageTask.addOnCompleteListener(getTransactionalExecutor(), promise);
|
|
298
306
|
} catch (Exception e) {
|
|
299
307
|
promiseRejectStorageException(promise, e);
|
|
@@ -313,6 +321,10 @@ public class NativeRNFBTurboStorage extends NativeRNFBTurboStorageSpec {
|
|
|
313
321
|
ReactNativeFirebaseStorageUploadTask storageTask =
|
|
314
322
|
new ReactNativeFirebaseStorageUploadTask((int) taskId, reference, appName);
|
|
315
323
|
storageTask.begin(getTransactionalExecutor(), localFilePath, metadata);
|
|
324
|
+
if (!storageTask.registerPending()) {
|
|
325
|
+
rejectPromiseWithCodeAndMessage(promise, "internal", "Handle id already registered");
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
316
328
|
storageTask.addOnCompleteListener(getTransactionalExecutor(), promise);
|
|
317
329
|
} catch (Exception e) {
|
|
318
330
|
promiseRejectStorageException(promise, e);
|
|
@@ -321,13 +333,21 @@ public class NativeRNFBTurboStorage extends NativeRNFBTurboStorageSpec {
|
|
|
321
333
|
|
|
322
334
|
@Override
|
|
323
335
|
public boolean setTaskStatus(String appName, double taskId, double status) {
|
|
336
|
+
RNFBStorageTaskRegistry tasks = ReactNativeFirebaseStorageTask.PENDING_TASKS;
|
|
337
|
+
int id = (int) taskId;
|
|
324
338
|
switch ((int) status) {
|
|
325
339
|
case 0:
|
|
326
|
-
|
|
340
|
+
{
|
|
341
|
+
StoragePendingHandle handle = tasks.get(id);
|
|
342
|
+
return handle != null && handle.pause();
|
|
343
|
+
}
|
|
327
344
|
case 1:
|
|
328
|
-
|
|
345
|
+
{
|
|
346
|
+
StoragePendingHandle handle = tasks.get(id);
|
|
347
|
+
return handle != null && handle.resume();
|
|
348
|
+
}
|
|
329
349
|
case 2:
|
|
330
|
-
return
|
|
350
|
+
return tasks.takeAndCancel(id);
|
|
331
351
|
default:
|
|
332
352
|
return false;
|
|
333
353
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
package io.invertase.firebase.storage;
|
|
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 io.invertase.firebase.common.RNFBHandleCollisionException;
|
|
21
|
+
import io.invertase.firebase.common.RNFBHandleMap;
|
|
22
|
+
import java.util.List;
|
|
23
|
+
import java.util.function.Predicate;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Pending upload/download task map. Unique {@link #put}; callers {@link #take} or {@link
|
|
27
|
+
* #takeAllAndCancel} then cancel outside the HandleMap lock.
|
|
28
|
+
*/
|
|
29
|
+
final class RNFBStorageTaskRegistry {
|
|
30
|
+
private final RNFBHandleMap<Integer, StoragePendingHandle> map = new RNFBHandleMap<>();
|
|
31
|
+
|
|
32
|
+
void put(int taskId, StoragePendingHandle handle) throws RNFBHandleCollisionException {
|
|
33
|
+
map.put(taskId, handle);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Unique put. On collision, {@code cancel()} the incoming handle and leave the existing mapping.
|
|
38
|
+
*
|
|
39
|
+
* @return true if stored
|
|
40
|
+
*/
|
|
41
|
+
boolean putOrDiscard(int taskId, StoragePendingHandle handle) {
|
|
42
|
+
if (map.putIfAbsent(taskId, handle)) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
if (handle != null) {
|
|
46
|
+
handle.cancel();
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
StoragePendingHandle get(int taskId) {
|
|
52
|
+
return map.get(taskId);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
StoragePendingHandle take(int taskId) {
|
|
56
|
+
return map.take(taskId);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
StoragePendingHandle takeIf(int taskId, Predicate<StoragePendingHandle> shouldTake) {
|
|
60
|
+
return map.takeIf(taskId, shouldTake);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Cancel outside the HandleMap lock; remove the mapping only when cancel succeeds. Returns {@code
|
|
65
|
+
* false} when no mapping existed or cancel failed. Trailing unregister is identity-gated so a
|
|
66
|
+
* replacement put after {@code cancel()} (e.g. production {@code destroyTask}) is not stolen.
|
|
67
|
+
*
|
|
68
|
+
* <p>Shape: get → cancel → identity takeIf on success (keep mapping when cancel returns false).
|
|
69
|
+
* iOS registry mirrors get → cancel → identity takeIf, but FIRStorage cancel is void so there is
|
|
70
|
+
* no keep-on-false path — see {@code RNFBStorageTaskRegistry.takeAndCancel:} / helper {@code
|
|
71
|
+
* setTaskStatus}.
|
|
72
|
+
*/
|
|
73
|
+
boolean takeAndCancel(int taskId) {
|
|
74
|
+
StoragePendingHandle handle = map.get(taskId);
|
|
75
|
+
if (handle == null) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
boolean cancelled = handle.cancel();
|
|
79
|
+
if (cancelled) {
|
|
80
|
+
map.takeIf(taskId, h -> h == handle);
|
|
81
|
+
}
|
|
82
|
+
return cancelled;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
void takeAllAndCancel() {
|
|
86
|
+
List<StoragePendingHandle> remaining = map.takeAll();
|
|
87
|
+
for (StoragePendingHandle handle : remaining) {
|
|
88
|
+
if (handle != null) {
|
|
89
|
+
handle.cancel();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
package/android/src/main/java/io/invertase/firebase/storage/ReactNativeFirebaseStorageTask.java
CHANGED
|
@@ -23,14 +23,13 @@ import static io.invertase.firebase.storage.ReactNativeFirebaseStorageCommon.STA
|
|
|
23
23
|
import static io.invertase.firebase.storage.ReactNativeFirebaseStorageCommon.getExceptionCodeAndMessage;
|
|
24
24
|
|
|
25
25
|
import android.util.Log;
|
|
26
|
-
import android.util.SparseArray;
|
|
27
26
|
import com.facebook.react.bridge.Arguments;
|
|
28
27
|
import com.facebook.react.bridge.WritableMap;
|
|
29
28
|
import com.google.firebase.storage.StorageReference;
|
|
30
29
|
import com.google.firebase.storage.StorageTask;
|
|
31
30
|
import javax.annotation.Nullable;
|
|
32
31
|
|
|
33
|
-
class ReactNativeFirebaseStorageTask {
|
|
32
|
+
class ReactNativeFirebaseStorageTask implements StoragePendingHandle {
|
|
34
33
|
static final String KEY_STATE = "state";
|
|
35
34
|
static final String KEY_META_DATA = "metadata";
|
|
36
35
|
static final String KEY_TOTAL_BYTES = "totalBytes";
|
|
@@ -39,8 +38,7 @@ class ReactNativeFirebaseStorageTask {
|
|
|
39
38
|
private static final String KEY_CODE = "code";
|
|
40
39
|
private static final String KEY_MESSAGE = "message";
|
|
41
40
|
private static final String KEY_NATIVE_ERROR_MESSAGE = "nativeErrorMessage";
|
|
42
|
-
|
|
43
|
-
new SparseArray<>();
|
|
41
|
+
static final RNFBStorageTaskRegistry PENDING_TASKS = new RNFBStorageTaskRegistry();
|
|
44
42
|
private static final String TAG = "RNFBStorageTask";
|
|
45
43
|
int taskId;
|
|
46
44
|
String appName;
|
|
@@ -51,43 +49,15 @@ class ReactNativeFirebaseStorageTask {
|
|
|
51
49
|
this.taskId = taskId;
|
|
52
50
|
this.storageReference = storageReference;
|
|
53
51
|
this.appName = appName;
|
|
54
|
-
PENDING_TASKS.put(taskId, this);
|
|
55
52
|
}
|
|
56
53
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return reactNativeFirebaseStorageTask.pause();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
static boolean resumeTaskById(int taskId) {
|
|
67
|
-
ReactNativeFirebaseStorageTask reactNativeFirebaseStorageTask = PENDING_TASKS.get(taskId);
|
|
68
|
-
if (reactNativeFirebaseStorageTask != null) {
|
|
69
|
-
return reactNativeFirebaseStorageTask.resume();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return false;
|
|
54
|
+
/** Register after the native {@link StorageTask} exists; collision cancels this handle. */
|
|
55
|
+
boolean registerPending() {
|
|
56
|
+
return PENDING_TASKS.putOrDiscard(taskId, this);
|
|
73
57
|
}
|
|
74
58
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (reactNativeFirebaseStorageTask != null) {
|
|
78
|
-
return reactNativeFirebaseStorageTask.cancel();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
static void destroyAllTasks() {
|
|
85
|
-
for (int i = 0, size = PENDING_TASKS.size(); i < size; i++) {
|
|
86
|
-
int key = PENDING_TASKS.keyAt(i);
|
|
87
|
-
ReactNativeFirebaseStorageTask reactNativeFirebaseStorageTask = PENDING_TASKS.get(key);
|
|
88
|
-
reactNativeFirebaseStorageTask.cancel();
|
|
89
|
-
}
|
|
90
|
-
PENDING_TASKS.clear();
|
|
59
|
+
boolean hasStorageTask() {
|
|
60
|
+
return storageTask != null;
|
|
91
61
|
}
|
|
92
62
|
|
|
93
63
|
static WritableMap buildCancelledSnapshotMap(WritableMap snapshot) {
|
|
@@ -108,39 +78,48 @@ class ReactNativeFirebaseStorageTask {
|
|
|
108
78
|
return taskMap;
|
|
109
79
|
}
|
|
110
80
|
|
|
111
|
-
|
|
81
|
+
@Override
|
|
82
|
+
public boolean pause() {
|
|
112
83
|
Log.d(TAG, "pausing task for " + storageReference.toString());
|
|
113
84
|
|
|
114
|
-
if (!storageTask.isPaused() && storageTask.isInProgress()) {
|
|
85
|
+
if (storageTask != null && !storageTask.isPaused() && storageTask.isInProgress()) {
|
|
115
86
|
return storageTask.pause();
|
|
116
87
|
}
|
|
117
88
|
|
|
118
89
|
return false;
|
|
119
90
|
}
|
|
120
91
|
|
|
121
|
-
|
|
92
|
+
@Override
|
|
93
|
+
public boolean resume() {
|
|
122
94
|
Log.d(TAG, "resuming task for " + storageReference.toString());
|
|
123
95
|
|
|
124
|
-
if (storageTask.isPaused()) {
|
|
96
|
+
if (storageTask != null && storageTask.isPaused()) {
|
|
125
97
|
return storageTask.resume();
|
|
126
98
|
}
|
|
127
99
|
|
|
128
100
|
return false;
|
|
129
101
|
}
|
|
130
102
|
|
|
131
|
-
|
|
103
|
+
@Override
|
|
104
|
+
public boolean cancel() {
|
|
132
105
|
Log.d(TAG, "cancelling task for " + storageReference.toString());
|
|
133
106
|
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
|
|
107
|
+
if (storageTask != null
|
|
108
|
+
&& !storageTask.isCanceled()
|
|
109
|
+
&& !storageTask.isComplete()
|
|
110
|
+
&& (storageTask.isInProgress() || storageTask.isPaused())) {
|
|
111
|
+
if (storageTask.cancel()) {
|
|
112
|
+
destroyTask();
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
137
115
|
}
|
|
138
116
|
|
|
139
117
|
return false;
|
|
140
118
|
}
|
|
141
119
|
|
|
142
120
|
void destroyTask() {
|
|
143
|
-
|
|
121
|
+
// Identity-gated: cancel of a discarded collision must not evict another handle at taskId.
|
|
122
|
+
PENDING_TASKS.takeIf(taskId, handle -> handle == this);
|
|
144
123
|
|
|
145
124
|
Log.d(TAG, "destroyed completed task for " + storageReference.toString());
|
|
146
125
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
package io.invertase.firebase.storage;
|
|
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
|
+
/**
|
|
21
|
+
* Pause / resume / cancel surface stored in {@link RNFBStorageTaskRegistry}. Invoked after {@code
|
|
22
|
+
* get}/{@code take}/{@code takeAll}, never under the HandleMap lock.
|
|
23
|
+
*/
|
|
24
|
+
interface StoragePendingHandle {
|
|
25
|
+
boolean pause();
|
|
26
|
+
|
|
27
|
+
boolean resume();
|
|
28
|
+
|
|
29
|
+
boolean cancel();
|
|
30
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
package io.invertase.firebase.storage;
|
|
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.assertEquals;
|
|
21
|
+
import static org.junit.Assert.assertFalse;
|
|
22
|
+
import static org.junit.Assert.assertNull;
|
|
23
|
+
import static org.junit.Assert.assertSame;
|
|
24
|
+
import static org.junit.Assert.assertTrue;
|
|
25
|
+
import static org.junit.Assert.fail;
|
|
26
|
+
|
|
27
|
+
import io.invertase.firebase.common.RNFBHandleCollisionException;
|
|
28
|
+
import org.junit.Test;
|
|
29
|
+
|
|
30
|
+
public class RNFBStorageTaskRegistryTest {
|
|
31
|
+
|
|
32
|
+
private static final class FakeHandle implements StoragePendingHandle {
|
|
33
|
+
int pauseCount;
|
|
34
|
+
int resumeCount;
|
|
35
|
+
int cancelCount;
|
|
36
|
+
boolean pauseResult = true;
|
|
37
|
+
boolean resumeResult = true;
|
|
38
|
+
boolean cancelResult = true;
|
|
39
|
+
Runnable onCancel;
|
|
40
|
+
|
|
41
|
+
@Override
|
|
42
|
+
public boolean pause() {
|
|
43
|
+
pauseCount++;
|
|
44
|
+
return pauseResult;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Override
|
|
48
|
+
public boolean resume() {
|
|
49
|
+
resumeCount++;
|
|
50
|
+
return resumeResult;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Override
|
|
54
|
+
public boolean cancel() {
|
|
55
|
+
cancelCount++;
|
|
56
|
+
if (onCancel != null) {
|
|
57
|
+
onCancel.run();
|
|
58
|
+
}
|
|
59
|
+
return cancelResult;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Test
|
|
64
|
+
public void putGetTake_happyPath() throws Exception {
|
|
65
|
+
RNFBStorageTaskRegistry registry = new RNFBStorageTaskRegistry();
|
|
66
|
+
FakeHandle handle = new FakeHandle();
|
|
67
|
+
registry.put(1, handle);
|
|
68
|
+
assertSame(handle, registry.get(1));
|
|
69
|
+
assertSame(handle, registry.take(1));
|
|
70
|
+
assertNull(registry.get(1));
|
|
71
|
+
assertEquals(0, handle.cancelCount);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@Test
|
|
75
|
+
public void put_occupiedId_throwsCollision() throws Exception {
|
|
76
|
+
RNFBStorageTaskRegistry registry = new RNFBStorageTaskRegistry();
|
|
77
|
+
FakeHandle first = new FakeHandle();
|
|
78
|
+
registry.put(1, first);
|
|
79
|
+
try {
|
|
80
|
+
registry.put(1, new FakeHandle());
|
|
81
|
+
fail("expected RNFBHandleCollisionException");
|
|
82
|
+
} catch (RNFBHandleCollisionException e) {
|
|
83
|
+
assertTrue(e.getMessage().contains("1"));
|
|
84
|
+
assertSame(first, registry.get(1));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@Test
|
|
89
|
+
public void putOrDiscard_collision_cancelsIncoming() throws Exception {
|
|
90
|
+
RNFBStorageTaskRegistry registry = new RNFBStorageTaskRegistry();
|
|
91
|
+
FakeHandle first = new FakeHandle();
|
|
92
|
+
FakeHandle duplicate = new FakeHandle();
|
|
93
|
+
registry.put(2, first);
|
|
94
|
+
assertFalse(registry.putOrDiscard(2, duplicate));
|
|
95
|
+
assertEquals(1, duplicate.cancelCount);
|
|
96
|
+
assertEquals(0, first.cancelCount);
|
|
97
|
+
assertSame(first, registry.get(2));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@Test
|
|
101
|
+
public void putOrDiscard_storesWhenFree() {
|
|
102
|
+
RNFBStorageTaskRegistry registry = new RNFBStorageTaskRegistry();
|
|
103
|
+
FakeHandle handle = new FakeHandle();
|
|
104
|
+
assertTrue(registry.putOrDiscard(3, handle));
|
|
105
|
+
assertSame(handle, registry.get(3));
|
|
106
|
+
assertEquals(0, handle.cancelCount);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@Test
|
|
110
|
+
public void putOrDiscard_collision_nullIncoming_isNoOp() throws Exception {
|
|
111
|
+
RNFBStorageTaskRegistry registry = new RNFBStorageTaskRegistry();
|
|
112
|
+
FakeHandle first = new FakeHandle();
|
|
113
|
+
registry.put(4, first);
|
|
114
|
+
assertFalse(registry.putOrDiscard(4, null));
|
|
115
|
+
assertSame(first, registry.get(4));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@Test
|
|
119
|
+
public void takeAndCancel_cancelsAfterTake() throws Exception {
|
|
120
|
+
RNFBStorageTaskRegistry registry = new RNFBStorageTaskRegistry();
|
|
121
|
+
FakeHandle handle = new FakeHandle();
|
|
122
|
+
registry.put(5, handle);
|
|
123
|
+
assertTrue(registry.takeAndCancel(5));
|
|
124
|
+
assertEquals(1, handle.cancelCount);
|
|
125
|
+
assertNull(registry.get(5));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@Test
|
|
129
|
+
public void takeAndCancel_missingKey_isNoOp() {
|
|
130
|
+
RNFBStorageTaskRegistry registry = new RNFBStorageTaskRegistry();
|
|
131
|
+
assertFalse(registry.takeAndCancel(99));
|
|
132
|
+
assertNull(registry.get(99));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@Test
|
|
136
|
+
public void takeAndCancel_whenCancelReturnsFalse_keepsMapping() throws Exception {
|
|
137
|
+
RNFBStorageTaskRegistry registry = new RNFBStorageTaskRegistry();
|
|
138
|
+
FakeHandle handle = new FakeHandle();
|
|
139
|
+
handle.cancelResult = false;
|
|
140
|
+
registry.put(6, handle);
|
|
141
|
+
assertFalse(registry.takeAndCancel(6));
|
|
142
|
+
assertEquals(1, handle.cancelCount);
|
|
143
|
+
assertSame(handle, registry.get(6));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Regression: production cancel may destroyTask (clear A) then a new put B can land at the same
|
|
148
|
+
* id before takeAndCancel's trailing unregister. Trailing take must be identity-gated so B stays.
|
|
149
|
+
*/
|
|
150
|
+
@Test
|
|
151
|
+
public void takeAndCancel_replacementDuringCancel_leavesReplacement() throws Exception {
|
|
152
|
+
RNFBStorageTaskRegistry registry = new RNFBStorageTaskRegistry();
|
|
153
|
+
FakeHandle replacement = new FakeHandle();
|
|
154
|
+
FakeHandle original = new FakeHandle();
|
|
155
|
+
original.onCancel =
|
|
156
|
+
() -> {
|
|
157
|
+
// Simulate destroyTask clearing this handle, then a new put at the same id.
|
|
158
|
+
assertSame(original, registry.take(9));
|
|
159
|
+
try {
|
|
160
|
+
registry.put(9, replacement);
|
|
161
|
+
} catch (RNFBHandleCollisionException e) {
|
|
162
|
+
fail(e.getMessage());
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
registry.put(9, original);
|
|
166
|
+
assertTrue(registry.takeAndCancel(9));
|
|
167
|
+
assertEquals(1, original.cancelCount);
|
|
168
|
+
assertSame(replacement, registry.get(9));
|
|
169
|
+
assertEquals(0, replacement.cancelCount);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@Test
|
|
173
|
+
public void takeAllAndCancel_cancelsSnapshotAndLeavesEmpty() throws Exception {
|
|
174
|
+
RNFBStorageTaskRegistry registry = new RNFBStorageTaskRegistry();
|
|
175
|
+
FakeHandle a = new FakeHandle();
|
|
176
|
+
FakeHandle b = new FakeHandle();
|
|
177
|
+
registry.put(1, a);
|
|
178
|
+
registry.put(2, b);
|
|
179
|
+
registry.takeAllAndCancel();
|
|
180
|
+
assertEquals(1, a.cancelCount);
|
|
181
|
+
assertEquals(1, b.cancelCount);
|
|
182
|
+
assertNull(registry.get(1));
|
|
183
|
+
assertNull(registry.get(2));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@Test
|
|
187
|
+
public void takeAllAndCancel_empty_isNoOp() {
|
|
188
|
+
RNFBStorageTaskRegistry registry = new RNFBStorageTaskRegistry();
|
|
189
|
+
registry.takeAllAndCancel();
|
|
190
|
+
assertNull(registry.get(1));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@Test
|
|
194
|
+
public void takeAllAndCancel_nullHandle_isNoOp() throws Exception {
|
|
195
|
+
RNFBStorageTaskRegistry registry = new RNFBStorageTaskRegistry();
|
|
196
|
+
registry.put(6, null);
|
|
197
|
+
registry.takeAllAndCancel();
|
|
198
|
+
assertNull(registry.get(6));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
@Test
|
|
202
|
+
public void put_afterTake_allowsReuse() throws Exception {
|
|
203
|
+
RNFBStorageTaskRegistry registry = new RNFBStorageTaskRegistry();
|
|
204
|
+
FakeHandle first = new FakeHandle();
|
|
205
|
+
FakeHandle second = new FakeHandle();
|
|
206
|
+
registry.put(1, first);
|
|
207
|
+
assertSame(first, registry.take(1));
|
|
208
|
+
registry.put(1, second);
|
|
209
|
+
assertSame(second, registry.get(1));
|
|
210
|
+
assertEquals(0, first.cancelCount);
|
|
211
|
+
}
|
|
212
|
+
}
|