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