@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/android/src/test/java/io/invertase/firebase/storage/ReactNativeFirebaseStorageTaskTest.java
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
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.assertNotNull;
|
|
23
|
+
import static org.junit.Assert.assertNull;
|
|
24
|
+
import static org.junit.Assert.assertSame;
|
|
25
|
+
import static org.junit.Assert.assertTrue;
|
|
26
|
+
import static org.mockito.ArgumentMatchers.anyString;
|
|
27
|
+
import static org.mockito.Mockito.mock;
|
|
28
|
+
import static org.mockito.Mockito.mockStatic;
|
|
29
|
+
import static org.mockito.Mockito.when;
|
|
30
|
+
|
|
31
|
+
import android.util.Log;
|
|
32
|
+
import com.google.firebase.storage.StorageReference;
|
|
33
|
+
import com.google.firebase.storage.StorageTask;
|
|
34
|
+
import org.junit.After;
|
|
35
|
+
import org.junit.Test;
|
|
36
|
+
import org.mockito.MockedStatic;
|
|
37
|
+
|
|
38
|
+
public class ReactNativeFirebaseStorageTaskTest {
|
|
39
|
+
|
|
40
|
+
private static final class FakeHandle implements StoragePendingHandle {
|
|
41
|
+
int cancelCount;
|
|
42
|
+
|
|
43
|
+
@Override
|
|
44
|
+
public boolean pause() {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Override
|
|
49
|
+
public boolean resume() {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Override
|
|
54
|
+
public boolean cancel() {
|
|
55
|
+
cancelCount++;
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private static final class RecordingTask extends ReactNativeFirebaseStorageTask {
|
|
61
|
+
int beginCount;
|
|
62
|
+
|
|
63
|
+
RecordingTask(int taskId) {
|
|
64
|
+
super(taskId, null, "app");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
void begin() {
|
|
68
|
+
beginCount++;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@After
|
|
73
|
+
public void tearDown() {
|
|
74
|
+
ReactNativeFirebaseStorageTask.PENDING_TASKS.take(11);
|
|
75
|
+
ReactNativeFirebaseStorageTask.PENDING_TASKS.take(12);
|
|
76
|
+
ReactNativeFirebaseStorageTask.PENDING_TASKS.take(13);
|
|
77
|
+
ReactNativeFirebaseStorageTask.PENDING_TASKS.take(14);
|
|
78
|
+
ReactNativeFirebaseStorageTask.PENDING_TASKS.take(15);
|
|
79
|
+
ReactNativeFirebaseStorageTask.PENDING_TASKS.take(16);
|
|
80
|
+
ReactNativeFirebaseStorageTask.PENDING_TASKS.take(17);
|
|
81
|
+
ReactNativeFirebaseStorageTask.PENDING_TASKS.take(18);
|
|
82
|
+
ReactNativeFirebaseStorageTask.PENDING_TASKS.take(19);
|
|
83
|
+
ReactNativeFirebaseStorageTask.PENDING_TASKS.take(20);
|
|
84
|
+
ReactNativeFirebaseStorageTask.PENDING_TASKS.take(21);
|
|
85
|
+
ReactNativeFirebaseStorageTask.PENDING_TASKS.take(22);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private static StorageReference stubReference() {
|
|
89
|
+
StorageReference ref = mock(StorageReference.class);
|
|
90
|
+
when(ref.toString()).thenReturn("gs://bucket/path");
|
|
91
|
+
return ref;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private static ReactNativeFirebaseStorageTask registeredTask(int taskId) {
|
|
95
|
+
ReactNativeFirebaseStorageTask task =
|
|
96
|
+
new ReactNativeFirebaseStorageTask(taskId, stubReference(), "app");
|
|
97
|
+
assertTrue(task.registerPending());
|
|
98
|
+
return task;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@Test
|
|
102
|
+
public void constructor_uniqueId_registersPendingTask() {
|
|
103
|
+
RecordingTask task = new RecordingTask(11);
|
|
104
|
+
assertTrue(task.registerPending());
|
|
105
|
+
assertSame(task, ReactNativeFirebaseStorageTask.PENDING_TASKS.get(11));
|
|
106
|
+
task.begin();
|
|
107
|
+
assertEquals(1, task.beginCount);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@Test
|
|
111
|
+
public void registerPending_occupiedId_cancelsIncomingAndKeepsExisting() throws Exception {
|
|
112
|
+
try (MockedStatic<Log> ignored = mockStatic(Log.class)) {
|
|
113
|
+
when(Log.d(anyString(), anyString())).thenReturn(0);
|
|
114
|
+
FakeHandle first = new FakeHandle();
|
|
115
|
+
ReactNativeFirebaseStorageTask.PENDING_TASKS.put(12, first);
|
|
116
|
+
ReactNativeFirebaseStorageTask second =
|
|
117
|
+
new ReactNativeFirebaseStorageTask(12, stubReference(), "app");
|
|
118
|
+
assertFalse(second.registerPending());
|
|
119
|
+
assertSame(first, ReactNativeFirebaseStorageTask.PENDING_TASKS.get(12));
|
|
120
|
+
assertEquals(0, first.cancelCount);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Regression: when the incoming handle has a live StorageTask whose cancel() succeeds,
|
|
126
|
+
* destroyTask must not evict the existing mapping at the same taskId (identity-gated take).
|
|
127
|
+
*/
|
|
128
|
+
@Test
|
|
129
|
+
public void registerPending_occupiedId_successfulCancelDoesNotEvictExisting() {
|
|
130
|
+
try (MockedStatic<Log> ignored = mockStatic(Log.class)) {
|
|
131
|
+
when(Log.d(anyString(), anyString())).thenReturn(0);
|
|
132
|
+
ReactNativeFirebaseStorageTask existing = registeredTask(22);
|
|
133
|
+
|
|
134
|
+
ReactNativeFirebaseStorageTask incoming =
|
|
135
|
+
new ReactNativeFirebaseStorageTask(22, stubReference(), "app");
|
|
136
|
+
StorageTask<?> storageTask = mock(StorageTask.class);
|
|
137
|
+
incoming.setStorageTask(storageTask);
|
|
138
|
+
when(storageTask.isCanceled()).thenReturn(false);
|
|
139
|
+
when(storageTask.isComplete()).thenReturn(false);
|
|
140
|
+
when(storageTask.isPaused()).thenReturn(false);
|
|
141
|
+
when(storageTask.isInProgress()).thenReturn(true);
|
|
142
|
+
when(storageTask.cancel()).thenReturn(true);
|
|
143
|
+
|
|
144
|
+
assertFalse(incoming.registerPending());
|
|
145
|
+
assertSame(existing, ReactNativeFirebaseStorageTask.PENDING_TASKS.get(22));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@Test
|
|
150
|
+
public void pauseResumeCancel_whenStorageTaskNull_returnFalseWithoutNpe() {
|
|
151
|
+
try (MockedStatic<Log> ignored = mockStatic(Log.class)) {
|
|
152
|
+
when(Log.d(anyString(), anyString())).thenReturn(0);
|
|
153
|
+
ReactNativeFirebaseStorageTask task = registeredTask(13);
|
|
154
|
+
assertSame(task, ReactNativeFirebaseStorageTask.PENDING_TASKS.get(13));
|
|
155
|
+
assertFalse(task.pause());
|
|
156
|
+
assertFalse(task.resume());
|
|
157
|
+
assertFalse(task.cancel());
|
|
158
|
+
assertSame(task, ReactNativeFirebaseStorageTask.PENDING_TASKS.get(13));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@Test
|
|
163
|
+
public void cancelIfPresent_whenStorageTaskNull_doesNotTakeMapping() {
|
|
164
|
+
try (MockedStatic<Log> ignored = mockStatic(Log.class)) {
|
|
165
|
+
when(Log.d(anyString(), anyString())).thenReturn(0);
|
|
166
|
+
ReactNativeFirebaseStorageTask task = registeredTask(14);
|
|
167
|
+
assertFalse(ReactNativeFirebaseStorageTask.PENDING_TASKS.takeAndCancel(14));
|
|
168
|
+
assertSame(task, ReactNativeFirebaseStorageTask.PENDING_TASKS.get(14));
|
|
169
|
+
assertFalse(task.pause());
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
@Test
|
|
174
|
+
public void setTaskStatusCancel_whenStorageTaskNull_doesNotTakeMapping() {
|
|
175
|
+
try (MockedStatic<Log> ignored = mockStatic(Log.class)) {
|
|
176
|
+
when(Log.d(anyString(), anyString())).thenReturn(0);
|
|
177
|
+
registeredTask(15);
|
|
178
|
+
assertFalse(ReactNativeFirebaseStorageTask.PENDING_TASKS.takeAndCancel(15));
|
|
179
|
+
assertNotNull(ReactNativeFirebaseStorageTask.PENDING_TASKS.get(15));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@Test
|
|
184
|
+
public void destroyTask_takesPendingMapping() {
|
|
185
|
+
try (MockedStatic<Log> ignored = mockStatic(Log.class)) {
|
|
186
|
+
when(Log.d(anyString(), anyString())).thenReturn(0);
|
|
187
|
+
ReactNativeFirebaseStorageTask task = registeredTask(16);
|
|
188
|
+
task.destroyTask();
|
|
189
|
+
assertNull(ReactNativeFirebaseStorageTask.PENDING_TASKS.get(16));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@Test
|
|
194
|
+
public void invalidate_whenStorageTaskNull_takeAllThenCancelWithoutNpe() {
|
|
195
|
+
try (MockedStatic<Log> ignored = mockStatic(Log.class)) {
|
|
196
|
+
when(Log.d(anyString(), anyString())).thenReturn(0);
|
|
197
|
+
registeredTask(17);
|
|
198
|
+
ReactNativeFirebaseStorageTask.PENDING_TASKS.takeAllAndCancel();
|
|
199
|
+
assertNull(ReactNativeFirebaseStorageTask.PENDING_TASKS.get(17));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@Test
|
|
204
|
+
public void pauseResumeCancel_whenStorageTaskPresent_delegateToSdkTask() {
|
|
205
|
+
try (MockedStatic<Log> ignored = mockStatic(Log.class)) {
|
|
206
|
+
when(Log.d(anyString(), anyString())).thenReturn(0);
|
|
207
|
+
ReactNativeFirebaseStorageTask task = registeredTask(18);
|
|
208
|
+
StorageTask<?> storageTask = mock(StorageTask.class);
|
|
209
|
+
task.setStorageTask(storageTask);
|
|
210
|
+
|
|
211
|
+
when(storageTask.isPaused()).thenReturn(false);
|
|
212
|
+
when(storageTask.isInProgress()).thenReturn(true);
|
|
213
|
+
when(storageTask.pause()).thenReturn(true);
|
|
214
|
+
assertTrue(task.pause());
|
|
215
|
+
|
|
216
|
+
when(storageTask.isPaused()).thenReturn(true);
|
|
217
|
+
when(storageTask.resume()).thenReturn(true);
|
|
218
|
+
assertTrue(task.resume());
|
|
219
|
+
|
|
220
|
+
when(storageTask.isPaused()).thenReturn(false);
|
|
221
|
+
when(storageTask.isCanceled()).thenReturn(false);
|
|
222
|
+
when(storageTask.isInProgress()).thenReturn(true);
|
|
223
|
+
when(storageTask.cancel()).thenReturn(true);
|
|
224
|
+
assertTrue(task.cancel());
|
|
225
|
+
assertNull(ReactNativeFirebaseStorageTask.PENDING_TASKS.get(18));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
@Test
|
|
230
|
+
public void cancel_whenPaused_delegatesToSdkTask() {
|
|
231
|
+
try (MockedStatic<Log> ignored = mockStatic(Log.class)) {
|
|
232
|
+
when(Log.d(anyString(), anyString())).thenReturn(0);
|
|
233
|
+
ReactNativeFirebaseStorageTask task = registeredTask(19);
|
|
234
|
+
StorageTask<?> storageTask = mock(StorageTask.class);
|
|
235
|
+
task.setStorageTask(storageTask);
|
|
236
|
+
|
|
237
|
+
when(storageTask.isCanceled()).thenReturn(false);
|
|
238
|
+
when(storageTask.isComplete()).thenReturn(false);
|
|
239
|
+
when(storageTask.isPaused()).thenReturn(true);
|
|
240
|
+
when(storageTask.isInProgress()).thenReturn(false);
|
|
241
|
+
when(storageTask.cancel()).thenReturn(true);
|
|
242
|
+
|
|
243
|
+
assertTrue(task.cancel());
|
|
244
|
+
assertNull(ReactNativeFirebaseStorageTask.PENDING_TASKS.get(19));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
@Test
|
|
249
|
+
public void takeAndCancel_whenPausedCancelSucceeds_takesMapping() {
|
|
250
|
+
try (MockedStatic<Log> ignored = mockStatic(Log.class)) {
|
|
251
|
+
when(Log.d(anyString(), anyString())).thenReturn(0);
|
|
252
|
+
ReactNativeFirebaseStorageTask task = registeredTask(20);
|
|
253
|
+
StorageTask<?> storageTask = mock(StorageTask.class);
|
|
254
|
+
task.setStorageTask(storageTask);
|
|
255
|
+
|
|
256
|
+
when(storageTask.isCanceled()).thenReturn(false);
|
|
257
|
+
when(storageTask.isComplete()).thenReturn(false);
|
|
258
|
+
when(storageTask.isPaused()).thenReturn(true);
|
|
259
|
+
when(storageTask.isInProgress()).thenReturn(false);
|
|
260
|
+
when(storageTask.cancel()).thenReturn(true);
|
|
261
|
+
|
|
262
|
+
assertTrue(ReactNativeFirebaseStorageTask.PENDING_TASKS.takeAndCancel(20));
|
|
263
|
+
assertNull(ReactNativeFirebaseStorageTask.PENDING_TASKS.get(20));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@Test
|
|
268
|
+
public void cancel_whenSdkCancelReturnsFalse_keepsMapping() {
|
|
269
|
+
try (MockedStatic<Log> ignored = mockStatic(Log.class)) {
|
|
270
|
+
when(Log.d(anyString(), anyString())).thenReturn(0);
|
|
271
|
+
ReactNativeFirebaseStorageTask task = registeredTask(21);
|
|
272
|
+
StorageTask<?> storageTask = mock(StorageTask.class);
|
|
273
|
+
task.setStorageTask(storageTask);
|
|
274
|
+
|
|
275
|
+
when(storageTask.isCanceled()).thenReturn(false);
|
|
276
|
+
when(storageTask.isComplete()).thenReturn(false);
|
|
277
|
+
when(storageTask.isPaused()).thenReturn(true);
|
|
278
|
+
when(storageTask.isInProgress()).thenReturn(false);
|
|
279
|
+
when(storageTask.cancel()).thenReturn(false);
|
|
280
|
+
|
|
281
|
+
assertFalse(task.cancel());
|
|
282
|
+
assertSame(task, ReactNativeFirebaseStorageTask.PENDING_TASKS.get(21));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
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 { EmulatorMockTokenOptions, FirebaseStorage, FullMetadata, ListOptions, ListResult, SettableMetadata, StorageReference, Task, UploadMetadata, UploadResult, UploadTask } from './types/storage';
|
|
4
|
-
export declare const SDK_VERSION = "26.
|
|
4
|
+
export declare const SDK_VERSION = "26.4.0";
|
|
5
5
|
export declare function getStorage(app?: FirebaseApp, bucketUrl?: string): FirebaseStorage;
|
|
6
6
|
export type * from './types/storage';
|
|
7
7
|
export { StringFormat, TaskEvent, TaskState } from './StorageStatics';
|
|
@@ -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
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
#import "RNFBRCTEventEmitter.h"
|
|
29
29
|
#import "RNFBStorageCommon.h"
|
|
30
30
|
#import "RNFBStorageHelper.h"
|
|
31
|
+
#import "RNFBStorageTaskRegistry.h"
|
|
31
32
|
|
|
32
33
|
static NSString *const RNFB_STORAGE_EVENT = @"storage_event";
|
|
33
34
|
static NSString *const RNFB_STORAGE_STATE_CHANGED = @"state_changed";
|
|
@@ -36,7 +37,7 @@ static NSString *const RNFB_STORAGE_UPLOAD_FAILURE = @"upload_failure";
|
|
|
36
37
|
static NSString *const RNFB_STORAGE_DOWNLOAD_SUCCESS = @"download_success";
|
|
37
38
|
static NSString *const RNFB_STORAGE_DOWNLOAD_FAILURE = @"download_failure";
|
|
38
39
|
|
|
39
|
-
static
|
|
40
|
+
static RNFBStorageTaskRegistry *pendingTasks;
|
|
40
41
|
|
|
41
42
|
// The iOS SDK has a short memory on settings, store these globally and set them in each time
|
|
42
43
|
static NSString *emulatorHost = nil;
|
|
@@ -54,16 +55,14 @@ static NSTimeInterval maxOperationRetryTime = 120;
|
|
|
54
55
|
+ (void)initializeSharedStateOnce {
|
|
55
56
|
static dispatch_once_t onceToken;
|
|
56
57
|
dispatch_once(&onceToken, ^{
|
|
57
|
-
|
|
58
|
+
pendingTasks = [[RNFBStorageTaskRegistry alloc] init];
|
|
58
59
|
emulatorConfigs = [[NSMutableDictionary alloc] init];
|
|
59
60
|
});
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
+ (void)invalidate {
|
|
63
64
|
[self initializeSharedStateOnce];
|
|
64
|
-
|
|
65
|
-
[PENDING_TASKS removeObjectForKey:key];
|
|
66
|
-
}
|
|
65
|
+
[pendingTasks cancelAll];
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
#pragma mark -
|
|
@@ -283,7 +282,10 @@ static NSTimeInterval maxOperationRetryTime = 120;
|
|
|
283
282
|
downloadTask = [storageReference writeToFile:localFile];
|
|
284
283
|
});
|
|
285
284
|
|
|
286
|
-
|
|
285
|
+
if (![pendingTasks putOrDiscard:taskIdNumber value:downloadTask]) {
|
|
286
|
+
reject(@"internal", @"Handle id already registered", nil);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
287
289
|
|
|
288
290
|
[downloadTask
|
|
289
291
|
observeStatus:FIRStorageTaskStatusResume
|
|
@@ -324,7 +326,10 @@ static NSTimeInterval maxOperationRetryTime = 120;
|
|
|
324
326
|
[downloadTask
|
|
325
327
|
observeStatus:FIRStorageTaskStatusSuccess
|
|
326
328
|
handler:^(FIRStorageTaskSnapshot *snapshot) {
|
|
327
|
-
[
|
|
329
|
+
[pendingTasks takeIf:taskIdNumber
|
|
330
|
+
when:^BOOL(id value) {
|
|
331
|
+
return value == downloadTask;
|
|
332
|
+
}];
|
|
328
333
|
|
|
329
334
|
NSDictionary *stateChangedEventBody =
|
|
330
335
|
[RNFBStorageCommon getDownloadTaskAsDictionary:snapshot];
|
|
@@ -349,7 +354,10 @@ static NSTimeInterval maxOperationRetryTime = 120;
|
|
|
349
354
|
[downloadTask
|
|
350
355
|
observeStatus:FIRStorageTaskStatusFailure
|
|
351
356
|
handler:^(FIRStorageTaskSnapshot *snapshot) {
|
|
352
|
-
[
|
|
357
|
+
[pendingTasks takeIf:taskIdNumber
|
|
358
|
+
when:^BOOL(id value) {
|
|
359
|
+
return value == downloadTask;
|
|
360
|
+
}];
|
|
353
361
|
|
|
354
362
|
NSDictionary *stateChangedEventBody =
|
|
355
363
|
[RNFBStorageCommon getDownloadTaskAsDictionary:snapshot];
|
|
@@ -501,7 +509,7 @@ static NSTimeInterval maxOperationRetryTime = 120;
|
|
|
501
509
|
+ (NSNumber *)setTaskStatus:(NSString *)appName taskId:(double)taskId status:(double)status {
|
|
502
510
|
[self initializeSharedStateOnce];
|
|
503
511
|
NSNumber *taskIdNumber = @(taskId);
|
|
504
|
-
id task =
|
|
512
|
+
id task = [pendingTasks get:taskIdNumber];
|
|
505
513
|
if (task == nil) {
|
|
506
514
|
return @NO;
|
|
507
515
|
}
|
|
@@ -546,7 +554,15 @@ static NSTimeInterval maxOperationRetryTime = 120;
|
|
|
546
554
|
(currentStatus == FIRStorageTaskStatusResume ||
|
|
547
555
|
currentStatus == FIRStorageTaskStatusProgress ||
|
|
548
556
|
currentStatus == FIRStorageTaskStatusPause)) {
|
|
549
|
-
|
|
557
|
+
// Identity take then cancel so a replacement at taskId is not stolen.
|
|
558
|
+
// Ordering differs from registry takeAndCancel: (takeIf → cancel) vs Android
|
|
559
|
+
// (get → cancel → takeIf). Both are identity-safe; FIRStorage cancel is void.
|
|
560
|
+
if ([pendingTasks takeIf:taskIdNumber
|
|
561
|
+
when:^BOOL(id value) {
|
|
562
|
+
return value == task;
|
|
563
|
+
}] == nil) {
|
|
564
|
+
return @NO;
|
|
565
|
+
}
|
|
550
566
|
if ([task isKindOfClass:[FIRStorageDownloadTask class]]) {
|
|
551
567
|
[(FIRStorageDownloadTask *)task cancel];
|
|
552
568
|
} else {
|
|
@@ -595,7 +611,10 @@ static NSTimeInterval maxOperationRetryTime = 120;
|
|
|
595
611
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
596
612
|
rejecter:(RCTPromiseRejectBlock)reject {
|
|
597
613
|
[self initializeSharedStateOnce];
|
|
598
|
-
|
|
614
|
+
if (![pendingTasks putOrDiscard:taskId value:uploadTask]) {
|
|
615
|
+
reject(@"internal", @"Handle id already registered", nil);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
599
618
|
|
|
600
619
|
[uploadTask
|
|
601
620
|
observeStatus:FIRStorageTaskStatusResume
|
|
@@ -635,7 +654,10 @@ static NSTimeInterval maxOperationRetryTime = 120;
|
|
|
635
654
|
|
|
636
655
|
[uploadTask observeStatus:FIRStorageTaskStatusSuccess
|
|
637
656
|
handler:^(FIRStorageTaskSnapshot *snapshot) {
|
|
638
|
-
[
|
|
657
|
+
[pendingTasks takeIf:taskId
|
|
658
|
+
when:^BOOL(id value) {
|
|
659
|
+
return value == uploadTask;
|
|
660
|
+
}];
|
|
639
661
|
|
|
640
662
|
NSDictionary *eventBody =
|
|
641
663
|
[RNFBStorageCommon getUploadTaskAsDictionary:snapshot];
|
|
@@ -661,7 +683,10 @@ static NSTimeInterval maxOperationRetryTime = 120;
|
|
|
661
683
|
[uploadTask
|
|
662
684
|
observeStatus:FIRStorageTaskStatusFailure
|
|
663
685
|
handler:^(FIRStorageTaskSnapshot *snapshot) {
|
|
664
|
-
[
|
|
686
|
+
[pendingTasks takeIf:taskId
|
|
687
|
+
when:^BOOL(id value) {
|
|
688
|
+
return value == uploadTask;
|
|
689
|
+
}];
|
|
665
690
|
|
|
666
691
|
NSMutableDictionary *taskSnapshotDict =
|
|
667
692
|
[RNFBStorageCommon getUploadTaskAsDictionary:snapshot];
|
|
@@ -239,13 +239,12 @@ RCT_EXPORT_MODULE(NativeRNFBTurboStorage);
|
|
|
239
239
|
#pragma mark -
|
|
240
240
|
#pragma mark Constants
|
|
241
241
|
|
|
242
|
-
- (facebook::react::ModuleConstants<JS::NativeRNFBTurboStorage::Constants
|
|
243
|
-
constantsToExport {
|
|
242
|
+
- (facebook::react::ModuleConstants<JS::NativeRNFBTurboStorage::Constants>)constantsToExport {
|
|
244
243
|
return [_RCTTypedModuleConstants
|
|
245
244
|
newWithUnsafeDictionary:[RNFBStorageHelper storageConstantsDictionary]];
|
|
246
245
|
}
|
|
247
246
|
|
|
248
|
-
- (facebook::react::ModuleConstants<JS::NativeRNFBTurboStorage::Constants
|
|
247
|
+
- (facebook::react::ModuleConstants<JS::NativeRNFBTurboStorage::Constants>)getConstants {
|
|
249
248
|
return [self constantsToExport];
|
|
250
249
|
}
|
|
251
250
|
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
* Pending upload/download task map. Unique `put`; callers `take` or `cancelAll` then cancel
|
|
24
|
+
* outside the HandleMap lock. Stored values are expected to respond to `cancel`.
|
|
25
|
+
*/
|
|
26
|
+
@interface RNFBStorageTaskRegistry : NSObject
|
|
27
|
+
|
|
28
|
+
- (BOOL)put:(id)key value:(id)value error:(NSError *_Nullable *_Nullable)error;
|
|
29
|
+
/// Unique put. On collision, `cancel`s the incoming value and leaves the existing mapping.
|
|
30
|
+
- (BOOL)putOrDiscard:(id)key value:(id)value;
|
|
31
|
+
- (nullable id)get:(id)key;
|
|
32
|
+
- (nullable id)take:(id)key;
|
|
33
|
+
/// Identity-gated take; see RNFBHandleMap `takeIf:when:`.
|
|
34
|
+
- (nullable id)takeIf:(id)key when:(BOOL (^)(id value))condition;
|
|
35
|
+
/// get → cancel → identity take (Android shape). FIRStorage cancel is void (no keep-on-false).
|
|
36
|
+
/// Production setTaskStatus cancel uses takeIf then cancel; see RNFBStorageHelper.
|
|
37
|
+
- (BOOL)takeAndCancel:(id)key;
|
|
38
|
+
- (void)cancelAll;
|
|
39
|
+
|
|
40
|
+
@end
|
|
41
|
+
|
|
42
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,94 @@
|
|
|
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 "RNFBStorageTaskRegistry.h"
|
|
19
|
+
|
|
20
|
+
#if __has_include("RNFBHandleMap.h")
|
|
21
|
+
#import "RNFBHandleMap.h"
|
|
22
|
+
#else
|
|
23
|
+
#import "RNFBApp/RNFBHandleMap.h"
|
|
24
|
+
#endif
|
|
25
|
+
|
|
26
|
+
@interface RNFBStorageTaskRegistry ()
|
|
27
|
+
@property(nonatomic, strong) RNFBHandleMap *map;
|
|
28
|
+
@end
|
|
29
|
+
|
|
30
|
+
@implementation RNFBStorageTaskRegistry
|
|
31
|
+
|
|
32
|
+
- (instancetype)init {
|
|
33
|
+
self = [super init];
|
|
34
|
+
if (self) {
|
|
35
|
+
_map = [[RNFBHandleMap alloc] init];
|
|
36
|
+
}
|
|
37
|
+
return self;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
- (void)rnfb_cancelHandle:(id)handle {
|
|
41
|
+
if (handle && [handle respondsToSelector:@selector(cancel)]) {
|
|
42
|
+
[handle cancel];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
- (BOOL)put:(id)key value:(id)value error:(NSError **)error {
|
|
47
|
+
return [self.map put:key value:value error:error];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
- (BOOL)putOrDiscard:(id)key value:(id)value {
|
|
51
|
+
if ([self.map putIfAbsent:key value:value]) {
|
|
52
|
+
return YES;
|
|
53
|
+
}
|
|
54
|
+
[self rnfb_cancelHandle:value];
|
|
55
|
+
return NO;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
- (id)get:(id)key {
|
|
59
|
+
return [self.map get:key];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
- (id)take:(id)key {
|
|
63
|
+
return [self.map take:key];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
- (id)takeIf:(id)key when:(BOOL (^)(id))condition {
|
|
67
|
+
return [self.map takeIf:key when:condition];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
- (BOOL)takeAndCancel:(id)key {
|
|
71
|
+
// Align with Android RNFBStorageTaskRegistry.takeAndCancel: get → cancel → identity take.
|
|
72
|
+
// FIRStorage*Task cancel is void (no BOOL), so there is no keep-on-false path; always
|
|
73
|
+
// identity-take after cancel. Production setTaskStatus cancel uses takeIf then cancel
|
|
74
|
+
// (RNFBStorageHelper) — identity-safe; this helper matches Android ordering for registry/tests.
|
|
75
|
+
id handle = [self.map get:key];
|
|
76
|
+
if (handle == nil) {
|
|
77
|
+
return NO;
|
|
78
|
+
}
|
|
79
|
+
[self rnfb_cancelHandle:handle];
|
|
80
|
+
[self.map takeIf:key
|
|
81
|
+
when:^BOOL(id value) {
|
|
82
|
+
return value == handle;
|
|
83
|
+
}];
|
|
84
|
+
return YES;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
- (void)cancelAll {
|
|
88
|
+
NSArray *handlers = [self.map takeAll];
|
|
89
|
+
for (id handler in handlers) {
|
|
90
|
+
[self rnfb_cancelHandle:handler];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@end
|