1import { EventEmitter, Subscription, UnavailabilityError } from '@unimodules/core'; 2import { Platform } from 'react-native'; 3import uuidv4 from 'uuid/v4'; 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} 382