1import { EventEmitter, Subscription, UnavailabilityError, uuidv4 } from 'expo-modules-core'; 2import { Platform } from 'react-native'; 3 4import ExponentFileSystem from './ExponentFileSystem'; 5import { 6 DownloadOptions, 7 DownloadPauseState, 8 FileSystemNetworkTaskProgressCallback, 9 DownloadProgressData, 10 UploadProgressData, 11 FileInfo, 12 FileSystemAcceptedUploadHttpMethod, 13 FileSystemDownloadResult, 14 FileSystemRequestDirectoryPermissionsResult, 15 FileSystemSessionType, 16 FileSystemUploadOptions, 17 FileSystemUploadResult, 18 FileSystemUploadType, 19 ProgressEvent, 20 ReadingOptions, 21 WritingOptions, 22 DeletingOptions, 23 InfoOptions, 24 RelocatingOptions, 25 MakeDirectoryOptions, 26} from './FileSystem.types'; 27 28if (!ExponentFileSystem) { 29 console.warn( 30 "No native ExponentFileSystem module found, are you sure the expo-file-system's module is linked properly?" 31 ); 32} 33// Prevent webpack from pruning this. 34const _unused = new EventEmitter(ExponentFileSystem); // eslint-disable-line 35 36function normalizeEndingSlash(p: string | null): string | null { 37 if (p != null) { 38 return p.replace(/\/*$/, '') + '/'; 39 } 40 return null; 41} 42 43/** 44 * `file://` URI pointing to the directory where user documents for this app will be stored. 45 * Files stored here will remain until explicitly deleted by the app. Ends with a trailing `/`. 46 * Example uses are for files the user saves that they expect to see again. 47 */ 48export const documentDirectory = normalizeEndingSlash(ExponentFileSystem.documentDirectory); 49 50/** 51 * `file://` URI pointing to the directory where temporary files used by this app will be stored. 52 * Files stored here may be automatically deleted by the system when low on storage. 53 * Example uses are for downloaded or generated files that the app just needs for one-time usage. 54 */ 55export const cacheDirectory = normalizeEndingSlash(ExponentFileSystem.cacheDirectory); 56 57// @docsMissing 58export const { bundledAssets, bundleDirectory } = ExponentFileSystem; 59 60/** 61 * Get metadata information about a file, directory or external content/asset. 62 * @param fileUri URI to the file or directory. See [supported URI schemes](#supported-uri-schemes). 63 * @param options A map of options represented by [`InfoOptions`](#infooptions) type. 64 * @return A Promise that resolves to a `FileInfo` object. If no item exists at this URI, 65 * the returned Promise resolves to `FileInfo` object in form of `{ exists: false, isDirectory: false }`. 66 */ 67export async function getInfoAsync(fileUri: string, options: InfoOptions = {}): Promise<FileInfo> { 68 if (!ExponentFileSystem.getInfoAsync) { 69 throw new UnavailabilityError('expo-file-system', 'getInfoAsync'); 70 } 71 return await ExponentFileSystem.getInfoAsync(fileUri, options); 72} 73 74/** 75 * Read the entire contents of a file as a string. Binary will be returned in raw format, you will need to append `data:image/png;base64,` to use it as Base64. 76 * @param fileUri `file://` or [SAF](#saf-uri) URI to the file or directory. 77 * @param options A map of read options represented by [`ReadingOptions`](#readingoptions) type. 78 * @return A Promise that resolves to a string containing the entire contents of the file. 79 */ 80export async function readAsStringAsync( 81 fileUri: string, 82 options: ReadingOptions = {} 83): Promise<string> { 84 if (!ExponentFileSystem.readAsStringAsync) { 85 throw new UnavailabilityError('expo-file-system', 'readAsStringAsync'); 86 } 87 return await ExponentFileSystem.readAsStringAsync(fileUri, options); 88} 89 90/** 91 * Takes a `file://` URI and converts it into content URI (`content://`) so that it can be accessed by other applications outside of Expo. 92 * @param fileUri The local URI of the file. If there is no file at this URI, an exception will be thrown. 93 * @example 94 * ```js 95 * FileSystem.getContentUriAsync(uri).then(cUri => { 96 * console.log(cUri); 97 * IntentLauncher.startActivityAsync('android.intent.action.VIEW', { 98 * data: cUri, 99 * flags: 1, 100 * }); 101 * }); 102 * ``` 103 * @return Returns a Promise that resolves to a `string` containing a `content://` URI pointing to the file. 104 * The URI is the same as the `fileUri` input parameter but in a different format. 105 * @platform android 106 */ 107export async function getContentUriAsync(fileUri: string): Promise<string> { 108 if (Platform.OS === 'android') { 109 if (!ExponentFileSystem.getContentUriAsync) { 110 throw new UnavailabilityError('expo-file-system', 'getContentUriAsync'); 111 } 112 return await ExponentFileSystem.getContentUriAsync(fileUri); 113 } else { 114 return fileUri; 115 } 116} 117 118/** 119 * Write the entire contents of a file as a string. 120 * @param fileUri `file://` or [SAF](#saf-uri) URI to the file or directory. 121 * > Note: when you're using SAF URI the file needs to exist. You can't create a new file. 122 * @param contents The string to replace the contents of the file with. 123 * @param options A map of write options represented by [`WritingOptions`](#writingoptions) type. 124 */ 125export async function writeAsStringAsync( 126 fileUri: string, 127 contents: string, 128 options: WritingOptions = {} 129): Promise<void> { 130 if (!ExponentFileSystem.writeAsStringAsync) { 131 throw new UnavailabilityError('expo-file-system', 'writeAsStringAsync'); 132 } 133 return await ExponentFileSystem.writeAsStringAsync(fileUri, contents, options); 134} 135 136/** 137 * Delete a file or directory. If the URI points to a directory, the directory and all its contents are recursively deleted. 138 * @param fileUri `file://` or [SAF](#saf-uri) URI to the file or directory. 139 * @param options A map of write options represented by [`DeletingOptions`](#deletingoptions) type. 140 */ 141export async function deleteAsync(fileUri: string, options: DeletingOptions = {}): Promise<void> { 142 if (!ExponentFileSystem.deleteAsync) { 143 throw new UnavailabilityError('expo-file-system', 'deleteAsync'); 144 } 145 return await ExponentFileSystem.deleteAsync(fileUri, options); 146} 147 148export async function deleteLegacyDocumentDirectoryAndroid(): Promise<void> { 149 if (Platform.OS !== 'android' || documentDirectory == null) { 150 return; 151 } 152 const legacyDocumentDirectory = `${documentDirectory}ExperienceData/`; 153 return await deleteAsync(legacyDocumentDirectory, { idempotent: true }); 154} 155 156/** 157 * Move a file or directory to a new location. 158 * @param options A map of move options represented by [`RelocatingOptions`](#relocatingoptions) type. 159 */ 160export async function moveAsync(options: RelocatingOptions): Promise<void> { 161 if (!ExponentFileSystem.moveAsync) { 162 throw new UnavailabilityError('expo-file-system', 'moveAsync'); 163 } 164 return await ExponentFileSystem.moveAsync(options); 165} 166 167/** 168 * Create a copy of a file or directory. Directories are recursively copied with all of their contents. 169 * It can be also used to copy content shared by other apps to local filesystem. 170 * @param options A map of move options represented by [`RelocatingOptions`](#relocatingoptions) type. 171 */ 172export async function copyAsync(options: RelocatingOptions): Promise<void> { 173 if (!ExponentFileSystem.copyAsync) { 174 throw new UnavailabilityError('expo-file-system', 'copyAsync'); 175 } 176 return await ExponentFileSystem.copyAsync(options); 177} 178 179/** 180 * Create a new empty directory. 181 * @param fileUri `file://` URI to the new directory to create. 182 * @param options A map of create directory options represented by [`MakeDirectoryOptions`](#makedirectoryoptions) type. 183 */ 184export async function makeDirectoryAsync( 185 fileUri: string, 186 options: MakeDirectoryOptions = {} 187): Promise<void> { 188 if (!ExponentFileSystem.makeDirectoryAsync) { 189 throw new UnavailabilityError('expo-file-system', 'makeDirectoryAsync'); 190 } 191 return await ExponentFileSystem.makeDirectoryAsync(fileUri, options); 192} 193 194/** 195 * Enumerate the contents of a directory. 196 * @param fileUri `file://` URI to the directory. 197 * @return A Promise that resolves to an array of strings, each containing the name of a file or directory contained in the directory at `fileUri`. 198 */ 199export async function readDirectoryAsync(fileUri: string): Promise<string[]> { 200 if (!ExponentFileSystem.readDirectoryAsync) { 201 throw new UnavailabilityError('expo-file-system', 'readDirectoryAsync'); 202 } 203 return await ExponentFileSystem.readDirectoryAsync(fileUri); 204} 205 206/** 207 * Gets the available internal disk storage size, in bytes. This returns the free space on the data partition that hosts all of the internal storage for all apps on the device. 208 * @return Returns a Promise that resolves to the number of bytes available on the internal disk, or JavaScript's [`MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER) 209 * if the capacity is greater than 2<sup>53</sup> - 1 bytes. 210 */ 211export async function getFreeDiskStorageAsync(): Promise<number> { 212 if (!ExponentFileSystem.getFreeDiskStorageAsync) { 213 throw new UnavailabilityError('expo-file-system', 'getFreeDiskStorageAsync'); 214 } 215 return await ExponentFileSystem.getFreeDiskStorageAsync(); 216} 217 218/** 219 * Gets total internal disk storage size, in bytes. This is the total capacity of the data partition that hosts all the internal storage for all apps on the device. 220 * @return Returns a Promise that resolves to a number that specifies the total internal disk storage capacity in bytes, or JavaScript's [`MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER) 221 * if the capacity is greater than 2<sup>53</sup> - 1 bytes. 222 */ 223export async function getTotalDiskCapacityAsync(): Promise<number> { 224 if (!ExponentFileSystem.getTotalDiskCapacityAsync) { 225 throw new UnavailabilityError('expo-file-system', 'getTotalDiskCapacityAsync'); 226 } 227 return await ExponentFileSystem.getTotalDiskCapacityAsync(); 228} 229 230/** 231 * Download the contents at a remote URI to a file in the app's file system. The directory for a local file uri must exist prior to calling this function. 232 * @param uri The remote URI to download from. 233 * @param fileUri The local URI of the file to download to. If there is no file at this URI, a new one is created. 234 * If there is a file at this URI, its contents are replaced. The directory for the file must exist. 235 * @param options A map of download options represented by [`DownloadOptions`](#downloadoptions) type. 236 * @example 237 * ```js 238 * FileSystem.downloadAsync( 239 * 'http://techslides.com/demos/sample-videos/small.mp4', 240 * FileSystem.documentDirectory + 'small.mp4' 241 * ) 242 * .then(({ uri }) => { 243 * console.log('Finished downloading to ', uri); 244 * }) 245 * .catch(error => { 246 * console.error(error); 247 * }); 248 * ``` 249 * @return Returns a Promise that resolves to a `FileSystemDownloadResult` object. 250 */ 251export async function downloadAsync( 252 uri: string, 253 fileUri: string, 254 options: DownloadOptions = {} 255): Promise<FileSystemDownloadResult> { 256 if (!ExponentFileSystem.downloadAsync) { 257 throw new UnavailabilityError('expo-file-system', 'downloadAsync'); 258 } 259 260 return await ExponentFileSystem.downloadAsync(uri, fileUri, { 261 sessionType: FileSystemSessionType.BACKGROUND, 262 ...options, 263 }); 264} 265 266/** 267 * Upload the contents of the file pointed by `fileUri` to the remote url. 268 * @param url The remote URL, where the file will be sent. 269 * @param fileUri The local URI of the file to send. The file must exist. 270 * @param options A map of download options represented by [`FileSystemUploadOptions`](#filesystemuploadoptions) type. 271 * @example 272 * **Client** 273 * 274 * ```js 275 * import * as FileSystem from 'expo-file-system'; 276 * 277 * try { 278 * const response = await FileSystem.uploadAsync(`http://192.168.0.1:1234/binary-upload`, fileUri, { 279 * fieldName: 'file', 280 * httpMethod: 'PATCH', 281 * uploadType: FileSystem.FileSystemUploadType.BINARY_CONTENT, 282 * }); 283 * console.log(JSON.stringify(response, null, 4)); 284 * } catch (error) { 285 * console.log(error); 286 * } 287 * ``` 288 * 289 * **Server** 290 * 291 * Please refer to the "[Server: Handling multipart requests](#server-handling-multipart-requests)" example - there is code for a simple Node.js server. 292 * @return Returns a Promise that resolves to `FileSystemUploadResult` object. 293 */ 294export async function uploadAsync( 295 url: string, 296 fileUri: string, 297 options: FileSystemUploadOptions = {} 298): Promise<FileSystemUploadResult> { 299 if (!ExponentFileSystem.uploadAsync) { 300 throw new UnavailabilityError('expo-file-system', 'uploadAsync'); 301 } 302 303 return await ExponentFileSystem.uploadAsync(url, fileUri, { 304 sessionType: FileSystemSessionType.BACKGROUND, 305 uploadType: FileSystemUploadType.BINARY_CONTENT, 306 ...options, 307 httpMethod: (options.httpMethod || 'POST').toUpperCase(), 308 }); 309} 310 311/** 312 * Create a `DownloadResumable` object which can start, pause, and resume a download of contents at a remote URI to a file in the app's file system. 313 * > Note: You need to call `downloadAsync()`, on a `DownloadResumable` instance to initiate the download. 314 * The `DownloadResumable` object has a callback that provides download progress updates. 315 * Downloads can be resumed across app restarts by using `AsyncStorage` to store the `DownloadResumable.savable()` object for later retrieval. 316 * The `savable` object contains the arguments required to initialize a new `DownloadResumable` object to resume the download after an app restart. 317 * The directory for a local file uri must exist prior to calling this function. 318 * @param uri The remote URI to download from. 319 * @param fileUri The local URI of the file to download to. If there is no file at this URI, a new one is created. 320 * If there is a file at this URI, its contents are replaced. The directory for the file must exist. 321 * @param options A map of download options represented by [`DownloadOptions`](#downloadoptions) type. 322 * @param callback This function is called on each data write to update the download progress. 323 * > **Note**: When the app has been moved to the background, this callback won't be fired until it's moved to the foreground. 324 * @param resumeData The string which allows the api to resume a paused download. This is set on the `DownloadResumable` object automatically when a download is paused. 325 * When initializing a new `DownloadResumable` this should be `null`. 326 */ 327export function createDownloadResumable( 328 uri: string, 329 fileUri: string, 330 options?: DownloadOptions, 331 callback?: FileSystemNetworkTaskProgressCallback<DownloadProgressData>, 332 resumeData?: string 333): DownloadResumable { 334 return new DownloadResumable(uri, fileUri, options, callback, resumeData); 335} 336 337export function createUploadTask( 338 url: string, 339 fileUri: string, 340 options?: FileSystemUploadOptions, 341 callback?: FileSystemNetworkTaskProgressCallback<UploadProgressData> 342): UploadTask { 343 return new UploadTask(url, fileUri, options, callback); 344} 345 346export abstract class FileSystemCancellableNetworkTask< 347 T extends DownloadProgressData | UploadProgressData 348> { 349 private _uuid = uuidv4(); 350 protected taskWasCanceled = false; 351 private emitter = new EventEmitter(ExponentFileSystem); 352 private subscription?: Subscription | null; 353 354 // @docsMissing 355 public async cancelAsync(): Promise<void> { 356 if (!ExponentFileSystem.networkTaskCancelAsync) { 357 throw new UnavailabilityError('expo-file-system', 'networkTaskCancelAsync'); 358 } 359 360 this.removeSubscription(); 361 this.taskWasCanceled = true; 362 return await ExponentFileSystem.networkTaskCancelAsync(this.uuid); 363 } 364 365 protected isTaskCancelled(): boolean { 366 if (this.taskWasCanceled) { 367 console.warn('This task was already canceled.'); 368 return true; 369 } 370 371 return false; 372 } 373 374 protected get uuid(): string { 375 return this._uuid; 376 } 377 378 protected abstract getEventName(): string; 379 380 protected abstract getCallback(): FileSystemNetworkTaskProgressCallback<T> | undefined; 381 382 protected addSubscription() { 383 if (this.subscription) { 384 return; 385 } 386 387 this.subscription = this.emitter.addListener(this.getEventName(), (event: ProgressEvent<T>) => { 388 if (event.uuid === this.uuid) { 389 const callback = this.getCallback(); 390 if (callback) { 391 callback(event.data); 392 } 393 } 394 }); 395 } 396 397 protected removeSubscription() { 398 if (!this.subscription) { 399 return; 400 } 401 this.emitter.removeSubscription(this.subscription); 402 this.subscription = null; 403 } 404} 405 406export class UploadTask extends FileSystemCancellableNetworkTask<UploadProgressData> { 407 private options: FileSystemUploadOptions; 408 409 constructor( 410 private url: string, 411 private fileUri: string, 412 options?: FileSystemUploadOptions, 413 private callback?: FileSystemNetworkTaskProgressCallback<UploadProgressData> 414 ) { 415 super(); 416 417 const httpMethod = (options?.httpMethod?.toUpperCase() || 418 'POST') as FileSystemAcceptedUploadHttpMethod; 419 420 this.options = { 421 sessionType: FileSystemSessionType.BACKGROUND, 422 uploadType: FileSystemUploadType.BINARY_CONTENT, 423 ...options, 424 httpMethod, 425 }; 426 } 427 428 protected getEventName(): string { 429 return 'expo-file-system.uploadProgress'; 430 } 431 protected getCallback(): FileSystemNetworkTaskProgressCallback<UploadProgressData> | undefined { 432 return this.callback; 433 } 434 435 // @docsMissing 436 public async uploadAsync(): Promise<FileSystemUploadResult | undefined> { 437 if (!ExponentFileSystem.uploadTaskStartAsync) { 438 throw new UnavailabilityError('expo-file-system', 'uploadTaskStartAsync'); 439 } 440 441 if (this.isTaskCancelled()) { 442 return; 443 } 444 445 this.addSubscription(); 446 const result = await ExponentFileSystem.uploadTaskStartAsync( 447 this.url, 448 this.fileUri, 449 this.uuid, 450 this.options 451 ); 452 this.removeSubscription(); 453 454 return result; 455 } 456} 457 458export class DownloadResumable extends FileSystemCancellableNetworkTask<DownloadProgressData> { 459 constructor( 460 private url: string, 461 private _fileUri: string, 462 private options: DownloadOptions = {}, 463 private callback?: FileSystemNetworkTaskProgressCallback<DownloadProgressData>, 464 private resumeData?: string 465 ) { 466 super(); 467 } 468 469 public get fileUri(): string { 470 return this._fileUri; 471 } 472 473 protected getEventName(): string { 474 return 'expo-file-system.downloadProgress'; 475 } 476 477 protected getCallback(): FileSystemNetworkTaskProgressCallback<DownloadProgressData> | undefined { 478 return this.callback; 479 } 480 481 /** 482 * Download the contents at a remote URI to a file in the app's file system. 483 * @return Returns a Promise that resolves to `FileSystemDownloadResult` object, or to `undefined` when task was cancelled. 484 */ 485 async downloadAsync(): Promise<FileSystemDownloadResult | undefined> { 486 if (!ExponentFileSystem.downloadResumableStartAsync) { 487 throw new UnavailabilityError('expo-file-system', 'downloadResumableStartAsync'); 488 } 489 490 if (this.isTaskCancelled()) { 491 return; 492 } 493 494 this.addSubscription(); 495 return await ExponentFileSystem.downloadResumableStartAsync( 496 this.url, 497 this._fileUri, 498 this.uuid, 499 this.options, 500 this.resumeData 501 ); 502 } 503 504 /** 505 * Pause the current download operation. `resumeData` is added to the `DownloadResumable` object after a successful pause operation. 506 * Returns an object that can be saved with `AsyncStorage` for future retrieval (the same object that is returned from calling `FileSystem.DownloadResumable.savable()`). 507 * @return Returns a Promise that resolves to `DownloadPauseState` object. 508 */ 509 async pauseAsync(): Promise<DownloadPauseState> { 510 if (!ExponentFileSystem.downloadResumablePauseAsync) { 511 throw new UnavailabilityError('expo-file-system', 'downloadResumablePauseAsync'); 512 } 513 514 if (this.isTaskCancelled()) { 515 return { 516 fileUri: this._fileUri, 517 options: this.options, 518 url: this.url, 519 }; 520 } 521 522 const pauseResult = await ExponentFileSystem.downloadResumablePauseAsync(this.uuid); 523 this.removeSubscription(); 524 if (pauseResult) { 525 this.resumeData = pauseResult.resumeData; 526 return this.savable(); 527 } else { 528 throw new Error('Unable to generate a savable pause state'); 529 } 530 } 531 532 /** 533 * Resume a paused download operation. 534 * @return Returns a Promise that resolves to `FileSystemDownloadResult` object, or to `undefined` when task was cancelled. 535 */ 536 async resumeAsync(): Promise<FileSystemDownloadResult | undefined> { 537 if (!ExponentFileSystem.downloadResumableStartAsync) { 538 throw new UnavailabilityError('expo-file-system', 'downloadResumableStartAsync'); 539 } 540 541 if (this.isTaskCancelled()) { 542 return; 543 } 544 545 this.addSubscription(); 546 return await ExponentFileSystem.downloadResumableStartAsync( 547 this.url, 548 this.fileUri, 549 this.uuid, 550 this.options, 551 this.resumeData 552 ); 553 } 554 555 /** 556 * Method to get the object which can be saved with `AsyncStorage` for future retrieval. 557 * @returns Returns object in shape of `DownloadPauseState` type. 558 */ 559 savable(): DownloadPauseState { 560 return { 561 url: this.url, 562 fileUri: this.fileUri, 563 options: this.options, 564 resumeData: this.resumeData, 565 }; 566 } 567} 568 569const baseReadAsStringAsync = readAsStringAsync; 570const baseWriteAsStringAsync = writeAsStringAsync; 571const baseDeleteAsync = deleteAsync; 572const baseMoveAsync = moveAsync; 573const baseCopyAsync = copyAsync; 574 575/** 576 * The `StorageAccessFramework` is a namespace inside of the `expo-file-system` module, which encapsulates all functions which can be used with [SAF URIs](#saf-uri). 577 * You can read more about SAF in the [Android documentation](https://developer.android.com/guide/topics/providers/document-provider). 578 * 579 * @example 580 * # Basic Usage 581 * 582 * ```ts 583 * import { StorageAccessFramework } from 'expo-file-system'; 584 * 585 * // Requests permissions for external directory 586 * const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync(); 587 * 588 * if (permissions.granted) { 589 * // Gets SAF URI from response 590 * const uri = permissions.directoryUri; 591 * 592 * // Gets all files inside of selected directory 593 * const files = await StorageAccessFramework.readDirectoryAsync(uri); 594 * alert(`Files inside ${uri}:\n\n${JSON.stringify(files)}`); 595 * } 596 * ``` 597 * 598 * # Migrating an album 599 * 600 * ```ts 601 * import * as MediaLibrary from 'expo-media-library'; 602 * import * as FileSystem from 'expo-file-system'; 603 * const { StorageAccessFramework } = FileSystem; 604 * 605 * async function migrateAlbum(albumName: string) { 606 * // Gets SAF URI to the album 607 * const albumUri = StorageAccessFramework.getUriForDirectoryInRoot(albumName); 608 * 609 * // Requests permissions 610 * const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync(albumUri); 611 * if (!permissions.granted) { 612 * return; 613 * } 614 * 615 * const permittedUri = permissions.directoryUri; 616 * // Checks if users selected the correct folder 617 * if (!permittedUri.includes(albumName)) { 618 * return; 619 * } 620 * 621 * const mediaLibraryPermissions = await MediaLibrary.requestPermissionsAsync(); 622 * if (!mediaLibraryPermissions.granted) { 623 * return; 624 * } 625 * 626 * // Moves files from external storage to internal storage 627 * await StorageAccessFramework.moveAsync({ 628 * from: permittedUri, 629 * to: FileSystem.documentDirectory!, 630 * }); 631 * 632 * const outputDir = FileSystem.documentDirectory! + albumName; 633 * const migratedFiles = await FileSystem.readDirectoryAsync(outputDir); 634 * 635 * // Creates assets from local files 636 * const [newAlbumCreator, ...assets] = await Promise.all( 637 * migratedFiles.map<Promise<MediaLibrary.Asset>>( 638 * async fileName => await MediaLibrary.createAssetAsync(outputDir + '/' + fileName) 639 * ) 640 * ); 641 * 642 * // Album was empty 643 * if (!newAlbumCreator) { 644 * return; 645 * } 646 * 647 * // Creates a new album in the scoped directory 648 * const newAlbum = await MediaLibrary.createAlbumAsync(albumName, newAlbumCreator, false); 649 * if (assets.length) { 650 * await MediaLibrary.addAssetsToAlbumAsync(assets, newAlbum, false); 651 * } 652 * } 653 * ``` 654 * @platform Android 655 */ 656export namespace StorageAccessFramework { 657 /** 658 * Gets a [SAF URI](#saf-uri) pointing to a folder in the Android root directory. You can use this function to get URI for 659 * `StorageAccessFramework.requestDirectoryPermissionsAsync()` when you trying to migrate an album. In that case, the name of the album is the folder name. 660 * @param folderName The name of the folder which is located in the Android root directory. 661 * @return Returns a [SAF URI](#saf-uri) to a folder. 662 */ 663 export function getUriForDirectoryInRoot(folderName: string) { 664 return `content://com.android.externalstorage.documents/tree/primary:${folderName}/document/primary:${folderName}`; 665 } 666 667 /** 668 * Allows users to select a specific directory, granting your app access to all of the files and sub-directories within that directory. 669 * @param initialFileUrl The [SAF URI](#saf-uri) of the directory that the file picker should display when it first loads. 670 * If URI is incorrect or points to a non-existing folder, it's ignored. 671 * @platform android 11+ 672 * @return Returns a Promise that resolves to `FileSystemRequestDirectoryPermissionsResult` object. 673 */ 674 export async function requestDirectoryPermissionsAsync( 675 initialFileUrl: string | null = null 676 ): Promise<FileSystemRequestDirectoryPermissionsResult> { 677 if (!ExponentFileSystem.requestDirectoryPermissionsAsync) { 678 throw new UnavailabilityError( 679 'expo-file-system', 680 'StorageAccessFramework.requestDirectoryPermissionsAsync' 681 ); 682 } 683 684 return await ExponentFileSystem.requestDirectoryPermissionsAsync(initialFileUrl); 685 } 686 687 /** 688 * Enumerate the contents of a directory. 689 * @param dirUri [SAF](#saf-uri) URI to the directory. 690 * @return A Promise that resolves to an array of strings, each containing the full [SAF URI](#saf-uri) of a file or directory contained in the directory at `fileUri`. 691 */ 692 export async function readDirectoryAsync(dirUri: string): Promise<string[]> { 693 if (!ExponentFileSystem.readSAFDirectoryAsync) { 694 throw new UnavailabilityError( 695 'expo-file-system', 696 'StorageAccessFramework.readDirectoryAsync' 697 ); 698 } 699 return await ExponentFileSystem.readSAFDirectoryAsync(dirUri); 700 } 701 702 /** 703 * Creates a new empty directory. 704 * @param parentUri The [SAF](#saf-uri) URI to the parent directory. 705 * @param dirName The name of new directory. 706 * @return A Promise that resolves to a [SAF URI](#saf-uri) to the created directory. 707 */ 708 export async function makeDirectoryAsync(parentUri: string, dirName: string): Promise<string> { 709 if (!ExponentFileSystem.makeSAFDirectoryAsync) { 710 throw new UnavailabilityError( 711 'expo-file-system', 712 'StorageAccessFramework.makeDirectoryAsync' 713 ); 714 } 715 return await ExponentFileSystem.makeSAFDirectoryAsync(parentUri, dirName); 716 } 717 718 /** 719 * Creates a new empty file. 720 * @param parentUri The [SAF](#saf-uri) URI to the parent directory. 721 * @param fileName The name of new file **without the extension**. 722 * @param mimeType The MIME type of new file. 723 * @return A Promise that resolves to a [SAF URI](#saf-uri) to the created file. 724 */ 725 export async function createFileAsync( 726 parentUri: string, 727 fileName: string, 728 mimeType: string 729 ): Promise<string> { 730 if (!ExponentFileSystem.createSAFFileAsync) { 731 throw new UnavailabilityError('expo-file-system', 'StorageAccessFramework.createFileAsync'); 732 } 733 return await ExponentFileSystem.createSAFFileAsync(parentUri, fileName, mimeType); 734 } 735 736 /** 737 * Alias for [`writeAsStringAsync`](#filesystemwriteasstringasyncfileuri-contents-options) method. 738 */ 739 export const writeAsStringAsync = baseWriteAsStringAsync; 740 /** 741 * Alias for [`readAsStringAsync`](#filesystemreadasstringasyncfileuri-options) method. 742 */ 743 export const readAsStringAsync = baseReadAsStringAsync; 744 /** 745 * Alias for [`deleteAsync`](#filesystemdeleteasyncfileuri-options) method. 746 */ 747 export const deleteAsync = baseDeleteAsync; 748 /** 749 * Alias for [`moveAsync`](#filesystemmoveasyncoptions) method. 750 */ 751 export const moveAsync = baseMoveAsync; 752 /** 753 * Alias for [`copyAsync`](#filesystemcopyasyncoptions) method. 754 */ 755 export const copyAsync = baseCopyAsync; 756} 757