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