1 | import { EventEmitter, Subscription, UnavailabilityError } from '@unimodules/core';
|
2 | import { Platform } from 'react-native';
|
3 | import { v4 as uuidv4 } from 'uuid';
|
4 |
|
5 | import ExponentFileSystem from './ExponentFileSystem';
|
6 | import {
|
7 | DownloadOptions,
|
8 | DownloadPauseState,
|
9 | DownloadProgressCallback,
|
10 | DownloadProgressData,
|
11 | DownloadResult,
|
12 | EncodingType,
|
13 | FileInfo,
|
14 | FileSystemAcceptedUploadHttpMethod,
|
15 | FileSystemDownloadResult,
|
16 | FileSystemRequestDirectoryPermissionsResult,
|
17 | FileSystemSessionType,
|
18 | FileSystemUploadOptions,
|
19 | FileSystemUploadResult,
|
20 | FileSystemUploadType,
|
21 | ProgressEvent,
|
22 | ReadingOptions,
|
23 | WritingOptions,
|
24 | } from './FileSystem.types';
|
25 |
|
26 | if (!ExponentFileSystem) {
|
27 | console.warn(
|
28 | "No native ExponentFileSystem module found, are you sure the expo-file-system's module is linked properly?"
|
29 | );
|
30 | }
|
31 |
|
32 | const _unused = new EventEmitter(ExponentFileSystem);
|
33 |
|
34 | export {
|
35 | DownloadOptions,
|
36 | DownloadPauseState,
|
37 | DownloadProgressCallback,
|
38 | DownloadProgressData,
|
39 | DownloadResult,
|
40 | EncodingType,
|
41 | FileInfo,
|
42 | FileSystemDownloadResult,
|
43 | FileSystemRequestDirectoryPermissionsResult,
|
44 | FileSystemAcceptedUploadHttpMethod,
|
45 | FileSystemSessionType,
|
46 | FileSystemUploadOptions,
|
47 | FileSystemUploadResult,
|
48 | FileSystemUploadType,
|
49 | ProgressEvent,
|
50 | ReadingOptions,
|
51 | WritingOptions,
|
52 | };
|
53 |
|
54 | function normalizeEndingSlash(p: string | null): string | null {
|
55 | if (p != null) {
|
56 | return p.replace(/\/*$/, '') + '/';
|
57 | }
|
58 | return null;
|
59 | }
|
60 |
|
61 | export const documentDirectory = normalizeEndingSlash(ExponentFileSystem.documentDirectory);
|
62 | export const cacheDirectory = normalizeEndingSlash(ExponentFileSystem.cacheDirectory);
|
63 |
|
64 | export const { bundledAssets, bundleDirectory } = ExponentFileSystem;
|
65 |
|
66 | export async function getInfoAsync(
|
67 | fileUri: string,
|
68 | options: { md5?: boolean; size?: boolean } = {}
|
69 | ): Promise<FileInfo> {
|
70 | if (!ExponentFileSystem.getInfoAsync) {
|
71 | throw new UnavailabilityError('expo-file-system', 'getInfoAsync');
|
72 | }
|
73 | return await ExponentFileSystem.getInfoAsync(fileUri, options);
|
74 | }
|
75 |
|
76 | export async function readAsStringAsync(
|
77 | fileUri: string,
|
78 | options?: ReadingOptions
|
79 | ): Promise<string> {
|
80 | if (!ExponentFileSystem.readAsStringAsync) {
|
81 | throw new UnavailabilityError('expo-file-system', 'readAsStringAsync');
|
82 | }
|
83 | return await ExponentFileSystem.readAsStringAsync(fileUri, options || {});
|
84 | }
|
85 |
|
86 | export async function getContentUriAsync(fileUri: string): Promise<string> {
|
87 | if (Platform.OS === 'android') {
|
88 | if (!ExponentFileSystem.getContentUriAsync) {
|
89 | throw new UnavailabilityError('expo-file-system', 'getContentUriAsync');
|
90 | }
|
91 | return await ExponentFileSystem.getContentUriAsync(fileUri);
|
92 | } else {
|
93 | return new Promise(function(resolve, reject) {
|
94 | resolve(fileUri);
|
95 | });
|
96 | }
|
97 | }
|
98 |
|
99 | export async function writeAsStringAsync(
|
100 | fileUri: string,
|
101 | contents: string,
|
102 | options: WritingOptions = {}
|
103 | ): Promise<void> {
|
104 | if (!ExponentFileSystem.writeAsStringAsync) {
|
105 | throw new UnavailabilityError('expo-file-system', 'writeAsStringAsync');
|
106 | }
|
107 | return await ExponentFileSystem.writeAsStringAsync(fileUri, contents, options);
|
108 | }
|
109 |
|
110 | export async function deleteAsync(
|
111 | fileUri: string,
|
112 | options: { idempotent?: boolean } = {}
|
113 | ): Promise<void> {
|
114 | if (!ExponentFileSystem.deleteAsync) {
|
115 | throw new UnavailabilityError('expo-file-system', 'deleteAsync');
|
116 | }
|
117 | return await ExponentFileSystem.deleteAsync(fileUri, options);
|
118 | }
|
119 |
|
120 | export async function deleteLegacyDocumentDirectoryAndroid(): Promise<void> {
|
121 | if (Platform.OS !== 'android' || documentDirectory == null) {
|
122 | return;
|
123 | }
|
124 | const legacyDocumentDirectory = `${documentDirectory}ExperienceData/`;
|
125 | return await deleteAsync(legacyDocumentDirectory, { idempotent: true });
|
126 | }
|
127 |
|
128 | export async function moveAsync(options: { from: string; to: string }): Promise<void> {
|
129 | if (!ExponentFileSystem.moveAsync) {
|
130 | throw new UnavailabilityError('expo-file-system', 'moveAsync');
|
131 | }
|
132 | return await ExponentFileSystem.moveAsync(options);
|
133 | }
|
134 |
|
135 | export async function copyAsync(options: { from: string; to: string }): Promise<void> {
|
136 | if (!ExponentFileSystem.copyAsync) {
|
137 | throw new UnavailabilityError('expo-file-system', 'copyAsync');
|
138 | }
|
139 | return await ExponentFileSystem.copyAsync(options);
|
140 | }
|
141 |
|
142 | export async function makeDirectoryAsync(
|
143 | fileUri: string,
|
144 | options: { intermediates?: boolean } = {}
|
145 | ): Promise<void> {
|
146 | if (!ExponentFileSystem.makeDirectoryAsync) {
|
147 | throw new UnavailabilityError('expo-file-system', 'makeDirectoryAsync');
|
148 | }
|
149 | return await ExponentFileSystem.makeDirectoryAsync(fileUri, options);
|
150 | }
|
151 |
|
152 | export async function readDirectoryAsync(fileUri: string): Promise<string[]> {
|
153 | if (!ExponentFileSystem.readDirectoryAsync) {
|
154 | throw new UnavailabilityError('expo-file-system', 'readDirectoryAsync');
|
155 | }
|
156 | return await ExponentFileSystem.readDirectoryAsync(fileUri, {});
|
157 | }
|
158 |
|
159 | export async function getFreeDiskStorageAsync(): Promise<number> {
|
160 | if (!ExponentFileSystem.getFreeDiskStorageAsync) {
|
161 | throw new UnavailabilityError('expo-file-system', 'getFreeDiskStorageAsync');
|
162 | }
|
163 | return await ExponentFileSystem.getFreeDiskStorageAsync();
|
164 | }
|
165 |
|
166 | export async function getTotalDiskCapacityAsync(): Promise<number> {
|
167 | if (!ExponentFileSystem.getTotalDiskCapacityAsync) {
|
168 | throw new UnavailabilityError('expo-file-system', 'getTotalDiskCapacityAsync');
|
169 | }
|
170 | return await ExponentFileSystem.getTotalDiskCapacityAsync();
|
171 | }
|
172 |
|
173 | export async function downloadAsync(
|
174 | uri: string,
|
175 | fileUri: string,
|
176 | options: DownloadOptions = {}
|
177 | ): Promise<FileSystemDownloadResult> {
|
178 | if (!ExponentFileSystem.downloadAsync) {
|
179 | throw new UnavailabilityError('expo-file-system', 'downloadAsync');
|
180 | }
|
181 |
|
182 | return await ExponentFileSystem.downloadAsync(uri, fileUri, {
|
183 | sessionType: FileSystemSessionType.BACKGROUND,
|
184 | ...options,
|
185 | });
|
186 | }
|
187 |
|
188 | export async function uploadAsync(
|
189 | url: string,
|
190 | fileUri: string,
|
191 | options: FileSystemUploadOptions = {}
|
192 | ): Promise<FileSystemUploadResult> {
|
193 | if (!ExponentFileSystem.uploadAsync) {
|
194 | throw new UnavailabilityError('expo-file-system', 'uploadAsync');
|
195 | }
|
196 |
|
197 | return await ExponentFileSystem.uploadAsync(url, fileUri, {
|
198 | sessionType: FileSystemSessionType.BACKGROUND,
|
199 | uploadType: FileSystemUploadType.BINARY_CONTENT,
|
200 | ...options,
|
201 | httpMethod: (options.httpMethod || 'POST').toUpperCase(),
|
202 | });
|
203 | }
|
204 |
|
205 | export function createDownloadResumable(
|
206 | uri: string,
|
207 | fileUri: string,
|
208 | options?: DownloadOptions,
|
209 | callback?: DownloadProgressCallback,
|
210 | resumeData?: string
|
211 | ): DownloadResumable {
|
212 | return new DownloadResumable(uri, fileUri, options, callback, resumeData);
|
213 | }
|
214 |
|
215 | export class DownloadResumable {
|
216 | _uuid: string;
|
217 | _url: string;
|
218 | _fileUri: string;
|
219 | _options: DownloadOptions;
|
220 | _resumeData?: string;
|
221 | _callback?: DownloadProgressCallback;
|
222 | _subscription?: Subscription | null;
|
223 | _emitter: EventEmitter;
|
224 |
|
225 | constructor(
|
226 | url: string,
|
227 | fileUri: string,
|
228 | options: DownloadOptions = {},
|
229 | callback?: DownloadProgressCallback,
|
230 | resumeData?: string
|
231 | ) {
|
232 | this._uuid = uuidv4();
|
233 | this._url = url;
|
234 | this._fileUri = fileUri;
|
235 | this._options = options;
|
236 | this._resumeData = resumeData;
|
237 | this._callback = callback;
|
238 | this._subscription = null;
|
239 | this._emitter = new EventEmitter(ExponentFileSystem);
|
240 | }
|
241 |
|
242 | async downloadAsync(): Promise<FileSystemDownloadResult | undefined> {
|
243 | if (!ExponentFileSystem.downloadResumableStartAsync) {
|
244 | throw new UnavailabilityError('expo-file-system', 'downloadResumableStartAsync');
|
245 | }
|
246 | this._addSubscription();
|
247 | return await ExponentFileSystem.downloadResumableStartAsync(
|
248 | this._url,
|
249 | this._fileUri,
|
250 | this._uuid,
|
251 | this._options,
|
252 | this._resumeData
|
253 | );
|
254 | }
|
255 |
|
256 | async pauseAsync(): Promise<DownloadPauseState> {
|
257 | if (!ExponentFileSystem.downloadResumablePauseAsync) {
|
258 | throw new UnavailabilityError('expo-file-system', 'downloadResumablePauseAsync');
|
259 | }
|
260 | const pauseResult = await ExponentFileSystem.downloadResumablePauseAsync(this._uuid);
|
261 | this._removeSubscription();
|
262 | if (pauseResult) {
|
263 | this._resumeData = pauseResult.resumeData;
|
264 | return this.savable();
|
265 | } else {
|
266 | throw new Error('Unable to generate a savable pause state');
|
267 | }
|
268 | }
|
269 |
|
270 | async resumeAsync(): Promise<FileSystemDownloadResult | undefined> {
|
271 | if (!ExponentFileSystem.downloadResumableStartAsync) {
|
272 | throw new UnavailabilityError('expo-file-system', 'downloadResumableStartAsync');
|
273 | }
|
274 | this._addSubscription();
|
275 | return await ExponentFileSystem.downloadResumableStartAsync(
|
276 | this._url,
|
277 | this._fileUri,
|
278 | this._uuid,
|
279 | this._options,
|
280 | this._resumeData
|
281 | );
|
282 | }
|
283 |
|
284 | savable(): DownloadPauseState {
|
285 | return {
|
286 | url: this._url,
|
287 | fileUri: this._fileUri,
|
288 | options: this._options,
|
289 | resumeData: this._resumeData,
|
290 | };
|
291 | }
|
292 |
|
293 | _addSubscription(): void {
|
294 | if (this._subscription) {
|
295 | return;
|
296 | }
|
297 | this._subscription = this._emitter.addListener(
|
298 | 'expo-file-system.downloadProgress',
|
299 | (event: ProgressEvent) => {
|
300 | if (event.uuid === this._uuid) {
|
301 | const callback = this._callback;
|
302 | if (callback) {
|
303 | callback(event.data);
|
304 | }
|
305 | }
|
306 | }
|
307 | );
|
308 | }
|
309 |
|
310 | _removeSubscription(): void {
|
311 | if (!this._subscription) {
|
312 | return;
|
313 | }
|
314 | this._emitter.removeSubscription(this._subscription);
|
315 | this._subscription = null;
|
316 | }
|
317 | }
|
318 |
|
319 | const baseReadAsStringAsync = readAsStringAsync;
|
320 | const baseWriteAsStringAsync = writeAsStringAsync;
|
321 | const baseDeleteAsync = deleteAsync;
|
322 | const baseMoveAsync = moveAsync;
|
323 | const baseCopyAsync = copyAsync;
|
324 |
|
325 |
|
326 |
|
327 | export namespace StorageAccessFramework {
|
328 | export function getUriForDirectoryInRoot(folderName: string) {
|
329 | return `content://com.android.externalstorage.documents/tree/primary:${folderName}/document/primary:${folderName}`;
|
330 | }
|
331 |
|
332 | export async function requestDirectoryPermissionsAsync(
|
333 | initialFileUrl: string | null = null
|
334 | ): Promise<FileSystemRequestDirectoryPermissionsResult> {
|
335 | if (!ExponentFileSystem.requestDirectoryPermissionsAsync) {
|
336 | throw new UnavailabilityError(
|
337 | 'expo-file-system',
|
338 | 'StorageAccessFramework.requestDirectoryPermissionsAsync'
|
339 | );
|
340 | }
|
341 |
|
342 | return await ExponentFileSystem.requestDirectoryPermissionsAsync(initialFileUrl);
|
343 | }
|
344 |
|
345 | export async function readDirectoryAsync(dirUri: string): Promise<string[]> {
|
346 | if (!ExponentFileSystem.readSAFDirectoryAsync) {
|
347 | throw new UnavailabilityError(
|
348 | 'expo-file-system',
|
349 | 'StorageAccessFramework.readDirectoryAsync'
|
350 | );
|
351 | }
|
352 | return await ExponentFileSystem.readSAFDirectoryAsync(dirUri, {});
|
353 | }
|
354 |
|
355 | export async function makeDirectoryAsync(parentUri: string, dirName: string): Promise<string> {
|
356 | if (!ExponentFileSystem.makeSAFDirectoryAsync) {
|
357 | throw new UnavailabilityError(
|
358 | 'expo-file-system',
|
359 | 'StorageAccessFramework.makeDirectoryAsync'
|
360 | );
|
361 | }
|
362 | return await ExponentFileSystem.makeSAFDirectoryAsync(parentUri, dirName);
|
363 | }
|
364 |
|
365 | export async function createFileAsync(
|
366 | parentUri: string,
|
367 | fileName: string,
|
368 | mimeType: string
|
369 | ): Promise<string> {
|
370 | if (!ExponentFileSystem.createSAFFileAsync) {
|
371 | throw new UnavailabilityError('expo-file-system', 'StorageAccessFramework.createFileAsync');
|
372 | }
|
373 | return await ExponentFileSystem.createSAFFileAsync(parentUri, fileName, mimeType);
|
374 | }
|
375 |
|
376 | export const writeAsStringAsync = baseWriteAsStringAsync;
|
377 | export const readAsStringAsync = baseReadAsStringAsync;
|
378 | export const deleteAsync = baseDeleteAsync;
|
379 | export const moveAsync = baseMoveAsync;
|
380 | export const copyAsync = baseCopyAsync;
|
381 | }
|