UNPKG

11.7 kBPlain TextView Raw
1import { EventEmitter, Subscription, UnavailabilityError } from '@unimodules/core';
2import { Platform } from 'react-native';
3import { v4 as uuidv4 } from 'uuid';
4
5import ExponentFileSystem from './ExponentFileSystem';
6import {
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
26if (!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// Prevent webpack from pruning this.
32const _unused = new EventEmitter(ExponentFileSystem); // eslint-disable-line
33
34export {
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
54function normalizeEndingSlash(p: string | null): string | null {
55 if (p != null) {
56 return p.replace(/\/*$/, '') + '/';
57 }
58 return null;
59}
60
61export const documentDirectory = normalizeEndingSlash(ExponentFileSystem.documentDirectory);
62export const cacheDirectory = normalizeEndingSlash(ExponentFileSystem.cacheDirectory);
63
64export const { bundledAssets, bundleDirectory } = ExponentFileSystem;
65
66export 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
76export 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
86export 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
99export 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
110export 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
120export 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
128export 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
135export 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
142export 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
152export 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
159export 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
166export 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
173export 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
188export 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
205export 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
215export 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
319const baseReadAsStringAsync = readAsStringAsync;
320const baseWriteAsStringAsync = writeAsStringAsync;
321const baseDeleteAsync = deleteAsync;
322const baseMoveAsync = moveAsync;
323const baseCopyAsync = copyAsync;
324/**
325 * Android only
326 */
327export 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}