1*6e021b28SWojciech Dróżdżimport { EventEmitter, Subscription, UnavailabilityError, uuid } from 'expo-modules-core'; 2e4897761STongyu Zhuimport { Platform } from 'react-native'; 379a5fc63SJames Ide 479a5fc63SJames Ideimport ExponentFileSystem from './ExponentFileSystem'; 52307aaceSEvan Baconimport { 62307aaceSEvan Bacon DownloadOptions, 7da65a760SŁukasz Kosmaty DownloadPauseState, 852eeb172SŁukasz Kosmaty FileSystemNetworkTaskProgressCallback, 92307aaceSEvan Bacon DownloadProgressData, 1052eeb172SŁukasz Kosmaty UploadProgressData, 11da65a760SŁukasz Kosmaty FileInfo, 12da65a760SŁukasz Kosmaty FileSystemAcceptedUploadHttpMethod, 1337630a92SŁukasz Kosmaty FileSystemDownloadResult, 14400de723SŁukasz Kosmaty FileSystemRequestDirectoryPermissionsResult, 15da65a760SŁukasz Kosmaty FileSystemSessionType, 16da65a760SŁukasz Kosmaty FileSystemUploadOptions, 17da65a760SŁukasz Kosmaty FileSystemUploadResult, 1837630a92SŁukasz Kosmaty FileSystemUploadType, 19da65a760SŁukasz Kosmaty ProgressEvent, 202307aaceSEvan Bacon ReadingOptions, 212307aaceSEvan Bacon WritingOptions, 226b7802baSBartosz Kaszubowski DeletingOptions, 236b7802baSBartosz Kaszubowski InfoOptions, 246b7802baSBartosz Kaszubowski RelocatingOptions, 256b7802baSBartosz Kaszubowski MakeDirectoryOptions, 262307aaceSEvan Bacon} from './FileSystem.types'; 272307aaceSEvan Bacon 287e929553SEvan Baconif (!ExponentFileSystem) { 292307aaceSEvan Bacon console.warn( 302307aaceSEvan Bacon "No native ExponentFileSystem module found, are you sure the expo-file-system's module is linked properly?" 312307aaceSEvan Bacon ); 322307aaceSEvan Bacon} 338f6e1365SEvan Bacon// Prevent webpack from pruning this. 3479a5fc63SJames Ideconst _unused = new EventEmitter(ExponentFileSystem); // eslint-disable-line 352307aaceSEvan Bacon 362307aaceSEvan Baconfunction normalizeEndingSlash(p: string | null): string | null { 372307aaceSEvan Bacon if (p != null) { 382307aaceSEvan Bacon return p.replace(/\/*$/, '') + '/'; 392307aaceSEvan Bacon } 402307aaceSEvan Bacon return null; 412307aaceSEvan Bacon} 422307aaceSEvan Bacon 436b7802baSBartosz Kaszubowski/** 446b7802baSBartosz Kaszubowski * `file://` URI pointing to the directory where user documents for this app will be stored. 456b7802baSBartosz Kaszubowski * Files stored here will remain until explicitly deleted by the app. Ends with a trailing `/`. 466b7802baSBartosz Kaszubowski * Example uses are for files the user saves that they expect to see again. 476b7802baSBartosz Kaszubowski */ 487e929553SEvan Baconexport const documentDirectory = normalizeEndingSlash(ExponentFileSystem.documentDirectory); 496b7802baSBartosz Kaszubowski 506b7802baSBartosz Kaszubowski/** 516b7802baSBartosz Kaszubowski * `file://` URI pointing to the directory where temporary files used by this app will be stored. 526b7802baSBartosz Kaszubowski * Files stored here may be automatically deleted by the system when low on storage. 536b7802baSBartosz Kaszubowski * Example uses are for downloaded or generated files that the app just needs for one-time usage. 546b7802baSBartosz Kaszubowski */ 557e929553SEvan Baconexport const cacheDirectory = normalizeEndingSlash(ExponentFileSystem.cacheDirectory); 562307aaceSEvan Bacon 576b7802baSBartosz Kaszubowski// @docsMissing 587e929553SEvan Baconexport const { bundledAssets, bundleDirectory } = ExponentFileSystem; 592307aaceSEvan Bacon 606b7802baSBartosz Kaszubowski/** 616b7802baSBartosz Kaszubowski * Get metadata information about a file, directory or external content/asset. 626b7802baSBartosz Kaszubowski * @param fileUri URI to the file or directory. See [supported URI schemes](#supported-uri-schemes). 63de172362SValentin Vetter * @param options A map of options represented by [`InfoOptions`](#infooptions) type. 646b7802baSBartosz Kaszubowski * @return A Promise that resolves to a `FileInfo` object. If no item exists at this URI, 656b7802baSBartosz Kaszubowski * the returned Promise resolves to `FileInfo` object in form of `{ exists: false, isDirectory: false }`. 666b7802baSBartosz Kaszubowski */ 676b7802baSBartosz Kaszubowskiexport async function getInfoAsync(fileUri: string, options: InfoOptions = {}): Promise<FileInfo> { 687e929553SEvan Bacon if (!ExponentFileSystem.getInfoAsync) { 692307aaceSEvan Bacon throw new UnavailabilityError('expo-file-system', 'getInfoAsync'); 702307aaceSEvan Bacon } 717e929553SEvan Bacon return await ExponentFileSystem.getInfoAsync(fileUri, options); 722307aaceSEvan Bacon} 732307aaceSEvan Bacon 746b7802baSBartosz Kaszubowski/** 756b7802baSBartosz Kaszubowski * 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. 766b7802baSBartosz Kaszubowski * @param fileUri `file://` or [SAF](#saf-uri) URI to the file or directory. 776b7802baSBartosz Kaszubowski * @param options A map of read options represented by [`ReadingOptions`](#readingoptions) type. 786b7802baSBartosz Kaszubowski * @return A Promise that resolves to a string containing the entire contents of the file. 796b7802baSBartosz Kaszubowski */ 802307aaceSEvan Baconexport async function readAsStringAsync( 812307aaceSEvan Bacon fileUri: string, 826b7802baSBartosz Kaszubowski options: ReadingOptions = {} 832307aaceSEvan Bacon): Promise<string> { 847e929553SEvan Bacon if (!ExponentFileSystem.readAsStringAsync) { 852307aaceSEvan Bacon throw new UnavailabilityError('expo-file-system', 'readAsStringAsync'); 862307aaceSEvan Bacon } 876b7802baSBartosz Kaszubowski return await ExponentFileSystem.readAsStringAsync(fileUri, options); 882307aaceSEvan Bacon} 892307aaceSEvan Bacon 906b7802baSBartosz Kaszubowski/** 916b7802baSBartosz Kaszubowski * Takes a `file://` URI and converts it into content URI (`content://`) so that it can be accessed by other applications outside of Expo. 926b7802baSBartosz Kaszubowski * @param fileUri The local URI of the file. If there is no file at this URI, an exception will be thrown. 936b7802baSBartosz Kaszubowski * @example 946b7802baSBartosz Kaszubowski * ```js 956b7802baSBartosz Kaszubowski * FileSystem.getContentUriAsync(uri).then(cUri => { 966b7802baSBartosz Kaszubowski * console.log(cUri); 976b7802baSBartosz Kaszubowski * IntentLauncher.startActivityAsync('android.intent.action.VIEW', { 986b7802baSBartosz Kaszubowski * data: cUri, 996b7802baSBartosz Kaszubowski * flags: 1, 1006b7802baSBartosz Kaszubowski * }); 1016b7802baSBartosz Kaszubowski * }); 1026b7802baSBartosz Kaszubowski * ``` 1036b7802baSBartosz Kaszubowski * @return Returns a Promise that resolves to a `string` containing a `content://` URI pointing to the file. 1046b7802baSBartosz Kaszubowski * The URI is the same as the `fileUri` input parameter but in a different format. 105ca75dd58SBartosz Kaszubowski * @platform android 1066b7802baSBartosz Kaszubowski */ 107e4897761STongyu Zhuexport async function getContentUriAsync(fileUri: string): Promise<string> { 108e4897761STongyu Zhu if (Platform.OS === 'android') { 109e4897761STongyu Zhu if (!ExponentFileSystem.getContentUriAsync) { 110e4897761STongyu Zhu throw new UnavailabilityError('expo-file-system', 'getContentUriAsync'); 111e4897761STongyu Zhu } 112e4897761STongyu Zhu return await ExponentFileSystem.getContentUriAsync(fileUri); 113e4897761STongyu Zhu } else { 114ca75dd58SBartosz Kaszubowski return fileUri; 115e4897761STongyu Zhu } 116e4897761STongyu Zhu} 117e4897761STongyu Zhu 1186b7802baSBartosz Kaszubowski/** 1196b7802baSBartosz Kaszubowski * Write the entire contents of a file as a string. 1206b7802baSBartosz Kaszubowski * @param fileUri `file://` or [SAF](#saf-uri) URI to the file or directory. 1216b7802baSBartosz Kaszubowski * > Note: when you're using SAF URI the file needs to exist. You can't create a new file. 1226b7802baSBartosz Kaszubowski * @param contents The string to replace the contents of the file with. 1236b7802baSBartosz Kaszubowski * @param options A map of write options represented by [`WritingOptions`](#writingoptions) type. 1246b7802baSBartosz Kaszubowski */ 1252307aaceSEvan Baconexport async function writeAsStringAsync( 1262307aaceSEvan Bacon fileUri: string, 1272307aaceSEvan Bacon contents: string, 1282307aaceSEvan Bacon options: WritingOptions = {} 1292307aaceSEvan Bacon): Promise<void> { 1307e929553SEvan Bacon if (!ExponentFileSystem.writeAsStringAsync) { 1312307aaceSEvan Bacon throw new UnavailabilityError('expo-file-system', 'writeAsStringAsync'); 1322307aaceSEvan Bacon } 1337e929553SEvan Bacon return await ExponentFileSystem.writeAsStringAsync(fileUri, contents, options); 1342307aaceSEvan Bacon} 1352307aaceSEvan Bacon 1366b7802baSBartosz Kaszubowski/** 1376b7802baSBartosz Kaszubowski * Delete a file or directory. If the URI points to a directory, the directory and all its contents are recursively deleted. 1386b7802baSBartosz Kaszubowski * @param fileUri `file://` or [SAF](#saf-uri) URI to the file or directory. 1396b7802baSBartosz Kaszubowski * @param options A map of write options represented by [`DeletingOptions`](#deletingoptions) type. 1406b7802baSBartosz Kaszubowski */ 1416b7802baSBartosz Kaszubowskiexport async function deleteAsync(fileUri: string, options: DeletingOptions = {}): Promise<void> { 1427e929553SEvan Bacon if (!ExponentFileSystem.deleteAsync) { 1432307aaceSEvan Bacon throw new UnavailabilityError('expo-file-system', 'deleteAsync'); 1442307aaceSEvan Bacon } 1457e929553SEvan Bacon return await ExponentFileSystem.deleteAsync(fileUri, options); 1462307aaceSEvan Bacon} 1472307aaceSEvan Bacon 1480d6db115SEric Samelsonexport async function deleteLegacyDocumentDirectoryAndroid(): Promise<void> { 1490d6db115SEric Samelson if (Platform.OS !== 'android' || documentDirectory == null) { 1500d6db115SEric Samelson return; 1510d6db115SEric Samelson } 1520d6db115SEric Samelson const legacyDocumentDirectory = `${documentDirectory}ExperienceData/`; 1530d6db115SEric Samelson return await deleteAsync(legacyDocumentDirectory, { idempotent: true }); 1540d6db115SEric Samelson} 1550d6db115SEric Samelson 1566b7802baSBartosz Kaszubowski/** 1576b7802baSBartosz Kaszubowski * Move a file or directory to a new location. 1586b7802baSBartosz Kaszubowski * @param options A map of move options represented by [`RelocatingOptions`](#relocatingoptions) type. 1596b7802baSBartosz Kaszubowski */ 1606b7802baSBartosz Kaszubowskiexport async function moveAsync(options: RelocatingOptions): Promise<void> { 1617e929553SEvan Bacon if (!ExponentFileSystem.moveAsync) { 1622307aaceSEvan Bacon throw new UnavailabilityError('expo-file-system', 'moveAsync'); 1632307aaceSEvan Bacon } 1647e929553SEvan Bacon return await ExponentFileSystem.moveAsync(options); 1652307aaceSEvan Bacon} 1662307aaceSEvan Bacon 1676b7802baSBartosz Kaszubowski/** 1686b7802baSBartosz Kaszubowski * Create a copy of a file or directory. Directories are recursively copied with all of their contents. 1696b7802baSBartosz Kaszubowski * It can be also used to copy content shared by other apps to local filesystem. 1706b7802baSBartosz Kaszubowski * @param options A map of move options represented by [`RelocatingOptions`](#relocatingoptions) type. 1716b7802baSBartosz Kaszubowski */ 1726b7802baSBartosz Kaszubowskiexport async function copyAsync(options: RelocatingOptions): Promise<void> { 1737e929553SEvan Bacon if (!ExponentFileSystem.copyAsync) { 1742307aaceSEvan Bacon throw new UnavailabilityError('expo-file-system', 'copyAsync'); 1752307aaceSEvan Bacon } 1767e929553SEvan Bacon return await ExponentFileSystem.copyAsync(options); 1772307aaceSEvan Bacon} 1782307aaceSEvan Bacon 1796b7802baSBartosz Kaszubowski/** 1806b7802baSBartosz Kaszubowski * Create a new empty directory. 1816b7802baSBartosz Kaszubowski * @param fileUri `file://` URI to the new directory to create. 1826b7802baSBartosz Kaszubowski * @param options A map of create directory options represented by [`MakeDirectoryOptions`](#makedirectoryoptions) type. 1836b7802baSBartosz Kaszubowski */ 1842307aaceSEvan Baconexport async function makeDirectoryAsync( 1852307aaceSEvan Bacon fileUri: string, 1866b7802baSBartosz Kaszubowski options: MakeDirectoryOptions = {} 1872307aaceSEvan Bacon): Promise<void> { 1887e929553SEvan Bacon if (!ExponentFileSystem.makeDirectoryAsync) { 1892307aaceSEvan Bacon throw new UnavailabilityError('expo-file-system', 'makeDirectoryAsync'); 1902307aaceSEvan Bacon } 1917e929553SEvan Bacon return await ExponentFileSystem.makeDirectoryAsync(fileUri, options); 1922307aaceSEvan Bacon} 1932307aaceSEvan Bacon 1946b7802baSBartosz Kaszubowski/** 1956b7802baSBartosz Kaszubowski * Enumerate the contents of a directory. 1966b7802baSBartosz Kaszubowski * @param fileUri `file://` URI to the directory. 1976b7802baSBartosz Kaszubowski * @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`. 1986b7802baSBartosz Kaszubowski */ 1992307aaceSEvan Baconexport async function readDirectoryAsync(fileUri: string): Promise<string[]> { 2007e929553SEvan Bacon if (!ExponentFileSystem.readDirectoryAsync) { 2012307aaceSEvan Bacon throw new UnavailabilityError('expo-file-system', 'readDirectoryAsync'); 2022307aaceSEvan Bacon } 2034fde8ae7SAlan Hughes return await ExponentFileSystem.readDirectoryAsync(fileUri); 2042307aaceSEvan Bacon} 2052307aaceSEvan Bacon 2066b7802baSBartosz Kaszubowski/** 2076b7802baSBartosz Kaszubowski * 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. 2086b7802baSBartosz Kaszubowski * @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) 2096b7802baSBartosz Kaszubowski * if the capacity is greater than 2<sup>53</sup> - 1 bytes. 2106b7802baSBartosz Kaszubowski */ 211ae9a5f1aSTongyu Zhuexport async function getFreeDiskStorageAsync(): Promise<number> { 212ae9a5f1aSTongyu Zhu if (!ExponentFileSystem.getFreeDiskStorageAsync) { 213ae9a5f1aSTongyu Zhu throw new UnavailabilityError('expo-file-system', 'getFreeDiskStorageAsync'); 214ae9a5f1aSTongyu Zhu } 215ae9a5f1aSTongyu Zhu return await ExponentFileSystem.getFreeDiskStorageAsync(); 216ae9a5f1aSTongyu Zhu} 217ae9a5f1aSTongyu Zhu 2186b7802baSBartosz Kaszubowski/** 2196b7802baSBartosz Kaszubowski * 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. 2206b7802baSBartosz Kaszubowski * @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) 2216b7802baSBartosz Kaszubowski * if the capacity is greater than 2<sup>53</sup> - 1 bytes. 2226b7802baSBartosz Kaszubowski */ 223ae9a5f1aSTongyu Zhuexport async function getTotalDiskCapacityAsync(): Promise<number> { 224ae9a5f1aSTongyu Zhu if (!ExponentFileSystem.getTotalDiskCapacityAsync) { 225ae9a5f1aSTongyu Zhu throw new UnavailabilityError('expo-file-system', 'getTotalDiskCapacityAsync'); 226ae9a5f1aSTongyu Zhu } 227ae9a5f1aSTongyu Zhu return await ExponentFileSystem.getTotalDiskCapacityAsync(); 228ae9a5f1aSTongyu Zhu} 229ae9a5f1aSTongyu Zhu 2306b7802baSBartosz Kaszubowski/** 2316b7802baSBartosz Kaszubowski * 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. 2326b7802baSBartosz Kaszubowski * @param uri The remote URI to download from. 2336b7802baSBartosz Kaszubowski * @param fileUri The local URI of the file to download to. If there is no file at this URI, a new one is created. 2346b7802baSBartosz Kaszubowski * If there is a file at this URI, its contents are replaced. The directory for the file must exist. 2356b7802baSBartosz Kaszubowski * @param options A map of download options represented by [`DownloadOptions`](#downloadoptions) type. 2366b7802baSBartosz Kaszubowski * @example 2376b7802baSBartosz Kaszubowski * ```js 2386b7802baSBartosz Kaszubowski * FileSystem.downloadAsync( 2396b7802baSBartosz Kaszubowski * 'http://techslides.com/demos/sample-videos/small.mp4', 2406b7802baSBartosz Kaszubowski * FileSystem.documentDirectory + 'small.mp4' 2416b7802baSBartosz Kaszubowski * ) 2426b7802baSBartosz Kaszubowski * .then(({ uri }) => { 2436b7802baSBartosz Kaszubowski * console.log('Finished downloading to ', uri); 2446b7802baSBartosz Kaszubowski * }) 2456b7802baSBartosz Kaszubowski * .catch(error => { 2466b7802baSBartosz Kaszubowski * console.error(error); 2476b7802baSBartosz Kaszubowski * }); 2486b7802baSBartosz Kaszubowski * ``` 2496b7802baSBartosz Kaszubowski * @return Returns a Promise that resolves to a `FileSystemDownloadResult` object. 2506b7802baSBartosz Kaszubowski */ 2512307aaceSEvan Baconexport async function downloadAsync( 2522307aaceSEvan Bacon uri: string, 2532307aaceSEvan Bacon fileUri: string, 2542307aaceSEvan Bacon options: DownloadOptions = {} 255da65a760SŁukasz Kosmaty): Promise<FileSystemDownloadResult> { 2567e929553SEvan Bacon if (!ExponentFileSystem.downloadAsync) { 2572307aaceSEvan Bacon throw new UnavailabilityError('expo-file-system', 'downloadAsync'); 2582307aaceSEvan Bacon } 259da65a760SŁukasz Kosmaty 260da65a760SŁukasz Kosmaty return await ExponentFileSystem.downloadAsync(uri, fileUri, { 261da65a760SŁukasz Kosmaty sessionType: FileSystemSessionType.BACKGROUND, 262da65a760SŁukasz Kosmaty ...options, 263da65a760SŁukasz Kosmaty }); 264da65a760SŁukasz Kosmaty} 265da65a760SŁukasz Kosmaty 2666b7802baSBartosz Kaszubowski/** 2676b7802baSBartosz Kaszubowski * Upload the contents of the file pointed by `fileUri` to the remote url. 2686b7802baSBartosz Kaszubowski * @param url The remote URL, where the file will be sent. 2696b7802baSBartosz Kaszubowski * @param fileUri The local URI of the file to send. The file must exist. 2706b7802baSBartosz Kaszubowski * @param options A map of download options represented by [`FileSystemUploadOptions`](#filesystemuploadoptions) type. 2716b7802baSBartosz Kaszubowski * @example 2726b7802baSBartosz Kaszubowski * **Client** 2736b7802baSBartosz Kaszubowski * 2746b7802baSBartosz Kaszubowski * ```js 2756b7802baSBartosz Kaszubowski * import * as FileSystem from 'expo-file-system'; 2766b7802baSBartosz Kaszubowski * 2776b7802baSBartosz Kaszubowski * try { 2786b7802baSBartosz Kaszubowski * const response = await FileSystem.uploadAsync(`http://192.168.0.1:1234/binary-upload`, fileUri, { 2796b7802baSBartosz Kaszubowski * fieldName: 'file', 2806b7802baSBartosz Kaszubowski * httpMethod: 'PATCH', 2816b7802baSBartosz Kaszubowski * uploadType: FileSystem.FileSystemUploadType.BINARY_CONTENT, 2826b7802baSBartosz Kaszubowski * }); 2836b7802baSBartosz Kaszubowski * console.log(JSON.stringify(response, null, 4)); 2846b7802baSBartosz Kaszubowski * } catch (error) { 2856b7802baSBartosz Kaszubowski * console.log(error); 2866b7802baSBartosz Kaszubowski * } 2876b7802baSBartosz Kaszubowski * ``` 2886b7802baSBartosz Kaszubowski * 2896b7802baSBartosz Kaszubowski * **Server** 2906b7802baSBartosz Kaszubowski * 2916b7802baSBartosz Kaszubowski * Please refer to the "[Server: Handling multipart requests](#server-handling-multipart-requests)" example - there is code for a simple Node.js server. 2926b7802baSBartosz Kaszubowski * @return Returns a Promise that resolves to `FileSystemUploadResult` object. 2936b7802baSBartosz Kaszubowski */ 294da65a760SŁukasz Kosmatyexport async function uploadAsync( 295da65a760SŁukasz Kosmaty url: string, 296da65a760SŁukasz Kosmaty fileUri: string, 297da65a760SŁukasz Kosmaty options: FileSystemUploadOptions = {} 298da65a760SŁukasz Kosmaty): Promise<FileSystemUploadResult> { 299da65a760SŁukasz Kosmaty if (!ExponentFileSystem.uploadAsync) { 300da65a760SŁukasz Kosmaty throw new UnavailabilityError('expo-file-system', 'uploadAsync'); 301da65a760SŁukasz Kosmaty } 302da65a760SŁukasz Kosmaty 303da65a760SŁukasz Kosmaty return await ExponentFileSystem.uploadAsync(url, fileUri, { 304da65a760SŁukasz Kosmaty sessionType: FileSystemSessionType.BACKGROUND, 30537630a92SŁukasz Kosmaty uploadType: FileSystemUploadType.BINARY_CONTENT, 306da65a760SŁukasz Kosmaty ...options, 307da65a760SŁukasz Kosmaty httpMethod: (options.httpMethod || 'POST').toUpperCase(), 308da65a760SŁukasz Kosmaty }); 3092307aaceSEvan Bacon} 3102307aaceSEvan Bacon 3116b7802baSBartosz Kaszubowski/** 3126b7802baSBartosz Kaszubowski * 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. 3136b7802baSBartosz Kaszubowski * > Note: You need to call `downloadAsync()`, on a `DownloadResumable` instance to initiate the download. 3146b7802baSBartosz Kaszubowski * The `DownloadResumable` object has a callback that provides download progress updates. 3156b7802baSBartosz Kaszubowski * Downloads can be resumed across app restarts by using `AsyncStorage` to store the `DownloadResumable.savable()` object for later retrieval. 3166b7802baSBartosz Kaszubowski * The `savable` object contains the arguments required to initialize a new `DownloadResumable` object to resume the download after an app restart. 3176b7802baSBartosz Kaszubowski * The directory for a local file uri must exist prior to calling this function. 3186b7802baSBartosz Kaszubowski * @param uri The remote URI to download from. 3196b7802baSBartosz Kaszubowski * @param fileUri The local URI of the file to download to. If there is no file at this URI, a new one is created. 3206b7802baSBartosz Kaszubowski * If there is a file at this URI, its contents are replaced. The directory for the file must exist. 3216b7802baSBartosz Kaszubowski * @param options A map of download options represented by [`DownloadOptions`](#downloadoptions) type. 3226b7802baSBartosz Kaszubowski * @param callback This function is called on each data write to update the download progress. 3236b7802baSBartosz Kaszubowski * > **Note**: When the app has been moved to the background, this callback won't be fired until it's moved to the foreground. 3246b7802baSBartosz Kaszubowski * @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. 3256b7802baSBartosz Kaszubowski * When initializing a new `DownloadResumable` this should be `null`. 3266b7802baSBartosz Kaszubowski */ 3272307aaceSEvan Baconexport function createDownloadResumable( 3282307aaceSEvan Bacon uri: string, 3292307aaceSEvan Bacon fileUri: string, 3302307aaceSEvan Bacon options?: DownloadOptions, 33152eeb172SŁukasz Kosmaty callback?: FileSystemNetworkTaskProgressCallback<DownloadProgressData>, 3322307aaceSEvan Bacon resumeData?: string 3332307aaceSEvan Bacon): DownloadResumable { 3342307aaceSEvan Bacon return new DownloadResumable(uri, fileUri, options, callback, resumeData); 3352307aaceSEvan Bacon} 3362307aaceSEvan Bacon 33752eeb172SŁukasz Kosmatyexport function createUploadTask( 3382307aaceSEvan Bacon url: string, 3392307aaceSEvan Bacon fileUri: string, 34052eeb172SŁukasz Kosmaty options?: FileSystemUploadOptions, 34152eeb172SŁukasz Kosmaty callback?: FileSystemNetworkTaskProgressCallback<UploadProgressData> 34252eeb172SŁukasz Kosmaty): UploadTask { 34352eeb172SŁukasz Kosmaty return new UploadTask(url, fileUri, options, callback); 34452eeb172SŁukasz Kosmaty} 34552eeb172SŁukasz Kosmaty 34652eeb172SŁukasz Kosmatyexport abstract class FileSystemCancellableNetworkTask< 3478a424bebSJames Ide T extends DownloadProgressData | UploadProgressData, 34852eeb172SŁukasz Kosmaty> { 349*6e021b28SWojciech Dróżdż private _uuid = uuid.v4(); 35052eeb172SŁukasz Kosmaty protected taskWasCanceled = false; 35152eeb172SŁukasz Kosmaty private emitter = new EventEmitter(ExponentFileSystem); 35252eeb172SŁukasz Kosmaty private subscription?: Subscription | null; 35352eeb172SŁukasz Kosmaty 3546b7802baSBartosz Kaszubowski // @docsMissing 35552eeb172SŁukasz Kosmaty public async cancelAsync(): Promise<void> { 35652eeb172SŁukasz Kosmaty if (!ExponentFileSystem.networkTaskCancelAsync) { 35752eeb172SŁukasz Kosmaty throw new UnavailabilityError('expo-file-system', 'networkTaskCancelAsync'); 35852eeb172SŁukasz Kosmaty } 35952eeb172SŁukasz Kosmaty 36052eeb172SŁukasz Kosmaty this.removeSubscription(); 36152eeb172SŁukasz Kosmaty this.taskWasCanceled = true; 36252eeb172SŁukasz Kosmaty return await ExponentFileSystem.networkTaskCancelAsync(this.uuid); 36352eeb172SŁukasz Kosmaty } 36452eeb172SŁukasz Kosmaty 36552eeb172SŁukasz Kosmaty protected isTaskCancelled(): boolean { 36652eeb172SŁukasz Kosmaty if (this.taskWasCanceled) { 36752eeb172SŁukasz Kosmaty console.warn('This task was already canceled.'); 36852eeb172SŁukasz Kosmaty return true; 36952eeb172SŁukasz Kosmaty } 37052eeb172SŁukasz Kosmaty 37152eeb172SŁukasz Kosmaty return false; 37252eeb172SŁukasz Kosmaty } 37352eeb172SŁukasz Kosmaty 37452eeb172SŁukasz Kosmaty protected get uuid(): string { 37552eeb172SŁukasz Kosmaty return this._uuid; 37652eeb172SŁukasz Kosmaty } 37752eeb172SŁukasz Kosmaty 37852eeb172SŁukasz Kosmaty protected abstract getEventName(): string; 37952eeb172SŁukasz Kosmaty 38052eeb172SŁukasz Kosmaty protected abstract getCallback(): FileSystemNetworkTaskProgressCallback<T> | undefined; 38152eeb172SŁukasz Kosmaty 38252eeb172SŁukasz Kosmaty protected addSubscription() { 38352eeb172SŁukasz Kosmaty if (this.subscription) { 38452eeb172SŁukasz Kosmaty return; 38552eeb172SŁukasz Kosmaty } 38652eeb172SŁukasz Kosmaty 38752eeb172SŁukasz Kosmaty this.subscription = this.emitter.addListener(this.getEventName(), (event: ProgressEvent<T>) => { 38852eeb172SŁukasz Kosmaty if (event.uuid === this.uuid) { 38952eeb172SŁukasz Kosmaty const callback = this.getCallback(); 39052eeb172SŁukasz Kosmaty if (callback) { 39152eeb172SŁukasz Kosmaty callback(event.data); 39252eeb172SŁukasz Kosmaty } 39352eeb172SŁukasz Kosmaty } 39452eeb172SŁukasz Kosmaty }); 39552eeb172SŁukasz Kosmaty } 39652eeb172SŁukasz Kosmaty 39752eeb172SŁukasz Kosmaty protected removeSubscription() { 39852eeb172SŁukasz Kosmaty if (!this.subscription) { 39952eeb172SŁukasz Kosmaty return; 40052eeb172SŁukasz Kosmaty } 40152eeb172SŁukasz Kosmaty this.emitter.removeSubscription(this.subscription); 40252eeb172SŁukasz Kosmaty this.subscription = null; 40352eeb172SŁukasz Kosmaty } 40452eeb172SŁukasz Kosmaty} 40552eeb172SŁukasz Kosmaty 40652eeb172SŁukasz Kosmatyexport class UploadTask extends FileSystemCancellableNetworkTask<UploadProgressData> { 40752eeb172SŁukasz Kosmaty private options: FileSystemUploadOptions; 40852eeb172SŁukasz Kosmaty 40952eeb172SŁukasz Kosmaty constructor( 41052eeb172SŁukasz Kosmaty private url: string, 41152eeb172SŁukasz Kosmaty private fileUri: string, 41252eeb172SŁukasz Kosmaty options?: FileSystemUploadOptions, 41352eeb172SŁukasz Kosmaty private callback?: FileSystemNetworkTaskProgressCallback<UploadProgressData> 4142307aaceSEvan Bacon ) { 41552eeb172SŁukasz Kosmaty super(); 41652eeb172SŁukasz Kosmaty 41700d910e2SLuke Brandon Farrell const httpMethod = (options?.httpMethod?.toUpperCase() || 41852eeb172SŁukasz Kosmaty 'POST') as FileSystemAcceptedUploadHttpMethod; 41952eeb172SŁukasz Kosmaty 42052eeb172SŁukasz Kosmaty this.options = { 42152eeb172SŁukasz Kosmaty sessionType: FileSystemSessionType.BACKGROUND, 42252eeb172SŁukasz Kosmaty uploadType: FileSystemUploadType.BINARY_CONTENT, 42352eeb172SŁukasz Kosmaty ...options, 42452eeb172SŁukasz Kosmaty httpMethod, 42552eeb172SŁukasz Kosmaty }; 42652eeb172SŁukasz Kosmaty } 42752eeb172SŁukasz Kosmaty 42852eeb172SŁukasz Kosmaty protected getEventName(): string { 42952eeb172SŁukasz Kosmaty return 'expo-file-system.uploadProgress'; 43052eeb172SŁukasz Kosmaty } 43152eeb172SŁukasz Kosmaty protected getCallback(): FileSystemNetworkTaskProgressCallback<UploadProgressData> | undefined { 43252eeb172SŁukasz Kosmaty return this.callback; 43352eeb172SŁukasz Kosmaty } 43452eeb172SŁukasz Kosmaty 4356b7802baSBartosz Kaszubowski // @docsMissing 43652eeb172SŁukasz Kosmaty public async uploadAsync(): Promise<FileSystemUploadResult | undefined> { 43752eeb172SŁukasz Kosmaty if (!ExponentFileSystem.uploadTaskStartAsync) { 43852eeb172SŁukasz Kosmaty throw new UnavailabilityError('expo-file-system', 'uploadTaskStartAsync'); 43952eeb172SŁukasz Kosmaty } 44052eeb172SŁukasz Kosmaty 44152eeb172SŁukasz Kosmaty if (this.isTaskCancelled()) { 44252eeb172SŁukasz Kosmaty return; 44352eeb172SŁukasz Kosmaty } 44452eeb172SŁukasz Kosmaty 44552eeb172SŁukasz Kosmaty this.addSubscription(); 44652eeb172SŁukasz Kosmaty const result = await ExponentFileSystem.uploadTaskStartAsync( 44752eeb172SŁukasz Kosmaty this.url, 44852eeb172SŁukasz Kosmaty this.fileUri, 44952eeb172SŁukasz Kosmaty this.uuid, 45052eeb172SŁukasz Kosmaty this.options 45152eeb172SŁukasz Kosmaty ); 45252eeb172SŁukasz Kosmaty this.removeSubscription(); 45352eeb172SŁukasz Kosmaty 45452eeb172SŁukasz Kosmaty return result; 45552eeb172SŁukasz Kosmaty } 45652eeb172SŁukasz Kosmaty} 45752eeb172SŁukasz Kosmaty 45852eeb172SŁukasz Kosmatyexport class DownloadResumable extends FileSystemCancellableNetworkTask<DownloadProgressData> { 45952eeb172SŁukasz Kosmaty constructor( 46052eeb172SŁukasz Kosmaty private url: string, 46152eeb172SŁukasz Kosmaty private _fileUri: string, 46252eeb172SŁukasz Kosmaty private options: DownloadOptions = {}, 46352eeb172SŁukasz Kosmaty private callback?: FileSystemNetworkTaskProgressCallback<DownloadProgressData>, 46452eeb172SŁukasz Kosmaty private resumeData?: string 46552eeb172SŁukasz Kosmaty ) { 46652eeb172SŁukasz Kosmaty super(); 46752eeb172SŁukasz Kosmaty } 46852eeb172SŁukasz Kosmaty 46952eeb172SŁukasz Kosmaty public get fileUri(): string { 47052eeb172SŁukasz Kosmaty return this._fileUri; 47152eeb172SŁukasz Kosmaty } 47252eeb172SŁukasz Kosmaty 47352eeb172SŁukasz Kosmaty protected getEventName(): string { 47452eeb172SŁukasz Kosmaty return 'expo-file-system.downloadProgress'; 47552eeb172SŁukasz Kosmaty } 47652eeb172SŁukasz Kosmaty 47752eeb172SŁukasz Kosmaty protected getCallback(): FileSystemNetworkTaskProgressCallback<DownloadProgressData> | undefined { 47852eeb172SŁukasz Kosmaty return this.callback; 4792307aaceSEvan Bacon } 4802307aaceSEvan Bacon 4816b7802baSBartosz Kaszubowski /** 4826b7802baSBartosz Kaszubowski * Download the contents at a remote URI to a file in the app's file system. 4836b7802baSBartosz Kaszubowski * @return Returns a Promise that resolves to `FileSystemDownloadResult` object, or to `undefined` when task was cancelled. 4846b7802baSBartosz Kaszubowski */ 485da65a760SŁukasz Kosmaty async downloadAsync(): Promise<FileSystemDownloadResult | undefined> { 4867e929553SEvan Bacon if (!ExponentFileSystem.downloadResumableStartAsync) { 4872307aaceSEvan Bacon throw new UnavailabilityError('expo-file-system', 'downloadResumableStartAsync'); 4882307aaceSEvan Bacon } 48952eeb172SŁukasz Kosmaty 49052eeb172SŁukasz Kosmaty if (this.isTaskCancelled()) { 49152eeb172SŁukasz Kosmaty return; 49252eeb172SŁukasz Kosmaty } 49352eeb172SŁukasz Kosmaty 49452eeb172SŁukasz Kosmaty this.addSubscription(); 4957e929553SEvan Bacon return await ExponentFileSystem.downloadResumableStartAsync( 49652eeb172SŁukasz Kosmaty this.url, 4972307aaceSEvan Bacon this._fileUri, 49852eeb172SŁukasz Kosmaty this.uuid, 49952eeb172SŁukasz Kosmaty this.options, 50052eeb172SŁukasz Kosmaty this.resumeData 5012307aaceSEvan Bacon ); 5022307aaceSEvan Bacon } 5032307aaceSEvan Bacon 5046b7802baSBartosz Kaszubowski /** 5056b7802baSBartosz Kaszubowski * Pause the current download operation. `resumeData` is added to the `DownloadResumable` object after a successful pause operation. 5066b7802baSBartosz Kaszubowski * Returns an object that can be saved with `AsyncStorage` for future retrieval (the same object that is returned from calling `FileSystem.DownloadResumable.savable()`). 5076b7802baSBartosz Kaszubowski * @return Returns a Promise that resolves to `DownloadPauseState` object. 5086b7802baSBartosz Kaszubowski */ 5092307aaceSEvan Bacon async pauseAsync(): Promise<DownloadPauseState> { 5107e929553SEvan Bacon if (!ExponentFileSystem.downloadResumablePauseAsync) { 5112307aaceSEvan Bacon throw new UnavailabilityError('expo-file-system', 'downloadResumablePauseAsync'); 5122307aaceSEvan Bacon } 51352eeb172SŁukasz Kosmaty 51452eeb172SŁukasz Kosmaty if (this.isTaskCancelled()) { 51552eeb172SŁukasz Kosmaty return { 51652eeb172SŁukasz Kosmaty fileUri: this._fileUri, 51752eeb172SŁukasz Kosmaty options: this.options, 51852eeb172SŁukasz Kosmaty url: this.url, 51952eeb172SŁukasz Kosmaty }; 52052eeb172SŁukasz Kosmaty } 52152eeb172SŁukasz Kosmaty 52252eeb172SŁukasz Kosmaty const pauseResult = await ExponentFileSystem.downloadResumablePauseAsync(this.uuid); 52352eeb172SŁukasz Kosmaty this.removeSubscription(); 5242307aaceSEvan Bacon if (pauseResult) { 52552eeb172SŁukasz Kosmaty this.resumeData = pauseResult.resumeData; 5262307aaceSEvan Bacon return this.savable(); 5272307aaceSEvan Bacon } else { 5282307aaceSEvan Bacon throw new Error('Unable to generate a savable pause state'); 5292307aaceSEvan Bacon } 5302307aaceSEvan Bacon } 5312307aaceSEvan Bacon 5326b7802baSBartosz Kaszubowski /** 5336b7802baSBartosz Kaszubowski * Resume a paused download operation. 5346b7802baSBartosz Kaszubowski * @return Returns a Promise that resolves to `FileSystemDownloadResult` object, or to `undefined` when task was cancelled. 5356b7802baSBartosz Kaszubowski */ 536da65a760SŁukasz Kosmaty async resumeAsync(): Promise<FileSystemDownloadResult | undefined> { 5377e929553SEvan Bacon if (!ExponentFileSystem.downloadResumableStartAsync) { 5382307aaceSEvan Bacon throw new UnavailabilityError('expo-file-system', 'downloadResumableStartAsync'); 5392307aaceSEvan Bacon } 54052eeb172SŁukasz Kosmaty 54152eeb172SŁukasz Kosmaty if (this.isTaskCancelled()) { 54252eeb172SŁukasz Kosmaty return; 54352eeb172SŁukasz Kosmaty } 54452eeb172SŁukasz Kosmaty 54552eeb172SŁukasz Kosmaty this.addSubscription(); 5467e929553SEvan Bacon return await ExponentFileSystem.downloadResumableStartAsync( 54752eeb172SŁukasz Kosmaty this.url, 54852eeb172SŁukasz Kosmaty this.fileUri, 54952eeb172SŁukasz Kosmaty this.uuid, 55052eeb172SŁukasz Kosmaty this.options, 55152eeb172SŁukasz Kosmaty this.resumeData 5522307aaceSEvan Bacon ); 5532307aaceSEvan Bacon } 5542307aaceSEvan Bacon 5556b7802baSBartosz Kaszubowski /** 5566b7802baSBartosz Kaszubowski * Method to get the object which can be saved with `AsyncStorage` for future retrieval. 5576b7802baSBartosz Kaszubowski * @returns Returns object in shape of `DownloadPauseState` type. 5586b7802baSBartosz Kaszubowski */ 5592307aaceSEvan Bacon savable(): DownloadPauseState { 5602307aaceSEvan Bacon return { 56152eeb172SŁukasz Kosmaty url: this.url, 56252eeb172SŁukasz Kosmaty fileUri: this.fileUri, 56352eeb172SŁukasz Kosmaty options: this.options, 56452eeb172SŁukasz Kosmaty resumeData: this.resumeData, 5652307aaceSEvan Bacon }; 5662307aaceSEvan Bacon } 5672307aaceSEvan Bacon} 568400de723SŁukasz Kosmaty 569400de723SŁukasz Kosmatyconst baseReadAsStringAsync = readAsStringAsync; 570400de723SŁukasz Kosmatyconst baseWriteAsStringAsync = writeAsStringAsync; 571400de723SŁukasz Kosmatyconst baseDeleteAsync = deleteAsync; 572400de723SŁukasz Kosmatyconst baseMoveAsync = moveAsync; 573400de723SŁukasz Kosmatyconst baseCopyAsync = copyAsync; 5746b7802baSBartosz Kaszubowski 575400de723SŁukasz Kosmaty/** 5766b7802baSBartosz Kaszubowski * 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). 5776b7802baSBartosz Kaszubowski * You can read more about SAF in the [Android documentation](https://developer.android.com/guide/topics/providers/document-provider). 5786b7802baSBartosz Kaszubowski * 5796b7802baSBartosz Kaszubowski * @example 5806b7802baSBartosz Kaszubowski * # Basic Usage 5816b7802baSBartosz Kaszubowski * 5826b7802baSBartosz Kaszubowski * ```ts 5836b7802baSBartosz Kaszubowski * import { StorageAccessFramework } from 'expo-file-system'; 5846b7802baSBartosz Kaszubowski * 5856b7802baSBartosz Kaszubowski * // Requests permissions for external directory 5866b7802baSBartosz Kaszubowski * const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync(); 5876b7802baSBartosz Kaszubowski * 5886b7802baSBartosz Kaszubowski * if (permissions.granted) { 5896b7802baSBartosz Kaszubowski * // Gets SAF URI from response 5906b7802baSBartosz Kaszubowski * const uri = permissions.directoryUri; 5916b7802baSBartosz Kaszubowski * 5926b7802baSBartosz Kaszubowski * // Gets all files inside of selected directory 5936b7802baSBartosz Kaszubowski * const files = await StorageAccessFramework.readDirectoryAsync(uri); 5946b7802baSBartosz Kaszubowski * alert(`Files inside ${uri}:\n\n${JSON.stringify(files)}`); 5956b7802baSBartosz Kaszubowski * } 5966b7802baSBartosz Kaszubowski * ``` 5976b7802baSBartosz Kaszubowski * 5986b7802baSBartosz Kaszubowski * # Migrating an album 5996b7802baSBartosz Kaszubowski * 6006b7802baSBartosz Kaszubowski * ```ts 6016b7802baSBartosz Kaszubowski * import * as MediaLibrary from 'expo-media-library'; 6026b7802baSBartosz Kaszubowski * import * as FileSystem from 'expo-file-system'; 6036b7802baSBartosz Kaszubowski * const { StorageAccessFramework } = FileSystem; 6046b7802baSBartosz Kaszubowski * 6056b7802baSBartosz Kaszubowski * async function migrateAlbum(albumName: string) { 6066b7802baSBartosz Kaszubowski * // Gets SAF URI to the album 6076b7802baSBartosz Kaszubowski * const albumUri = StorageAccessFramework.getUriForDirectoryInRoot(albumName); 6086b7802baSBartosz Kaszubowski * 6096b7802baSBartosz Kaszubowski * // Requests permissions 6106b7802baSBartosz Kaszubowski * const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync(albumUri); 6116b7802baSBartosz Kaszubowski * if (!permissions.granted) { 6126b7802baSBartosz Kaszubowski * return; 6136b7802baSBartosz Kaszubowski * } 6146b7802baSBartosz Kaszubowski * 6156b7802baSBartosz Kaszubowski * const permittedUri = permissions.directoryUri; 6166b7802baSBartosz Kaszubowski * // Checks if users selected the correct folder 6176b7802baSBartosz Kaszubowski * if (!permittedUri.includes(albumName)) { 6186b7802baSBartosz Kaszubowski * return; 6196b7802baSBartosz Kaszubowski * } 6206b7802baSBartosz Kaszubowski * 6216b7802baSBartosz Kaszubowski * const mediaLibraryPermissions = await MediaLibrary.requestPermissionsAsync(); 6226b7802baSBartosz Kaszubowski * if (!mediaLibraryPermissions.granted) { 6236b7802baSBartosz Kaszubowski * return; 6246b7802baSBartosz Kaszubowski * } 6256b7802baSBartosz Kaszubowski * 6266b7802baSBartosz Kaszubowski * // Moves files from external storage to internal storage 6276b7802baSBartosz Kaszubowski * await StorageAccessFramework.moveAsync({ 6286b7802baSBartosz Kaszubowski * from: permittedUri, 6296b7802baSBartosz Kaszubowski * to: FileSystem.documentDirectory!, 6306b7802baSBartosz Kaszubowski * }); 6316b7802baSBartosz Kaszubowski * 6326b7802baSBartosz Kaszubowski * const outputDir = FileSystem.documentDirectory! + albumName; 6336b7802baSBartosz Kaszubowski * const migratedFiles = await FileSystem.readDirectoryAsync(outputDir); 6346b7802baSBartosz Kaszubowski * 6356b7802baSBartosz Kaszubowski * // Creates assets from local files 6366b7802baSBartosz Kaszubowski * const [newAlbumCreator, ...assets] = await Promise.all( 6376b7802baSBartosz Kaszubowski * migratedFiles.map<Promise<MediaLibrary.Asset>>( 6386b7802baSBartosz Kaszubowski * async fileName => await MediaLibrary.createAssetAsync(outputDir + '/' + fileName) 6396b7802baSBartosz Kaszubowski * ) 6406b7802baSBartosz Kaszubowski * ); 6416b7802baSBartosz Kaszubowski * 6426b7802baSBartosz Kaszubowski * // Album was empty 6436b7802baSBartosz Kaszubowski * if (!newAlbumCreator) { 6446b7802baSBartosz Kaszubowski * return; 6456b7802baSBartosz Kaszubowski * } 6466b7802baSBartosz Kaszubowski * 6476b7802baSBartosz Kaszubowski * // Creates a new album in the scoped directory 6486b7802baSBartosz Kaszubowski * const newAlbum = await MediaLibrary.createAlbumAsync(albumName, newAlbumCreator, false); 6496b7802baSBartosz Kaszubowski * if (assets.length) { 6506b7802baSBartosz Kaszubowski * await MediaLibrary.addAssetsToAlbumAsync(assets, newAlbum, false); 6516b7802baSBartosz Kaszubowski * } 6526b7802baSBartosz Kaszubowski * } 6536b7802baSBartosz Kaszubowski * ``` 6546b7802baSBartosz Kaszubowski * @platform Android 655400de723SŁukasz Kosmaty */ 656400de723SŁukasz Kosmatyexport namespace StorageAccessFramework { 6576b7802baSBartosz Kaszubowski /** 6586b7802baSBartosz Kaszubowski * Gets a [SAF URI](#saf-uri) pointing to a folder in the Android root directory. You can use this function to get URI for 6596b7802baSBartosz Kaszubowski * `StorageAccessFramework.requestDirectoryPermissionsAsync()` when you trying to migrate an album. In that case, the name of the album is the folder name. 6606b7802baSBartosz Kaszubowski * @param folderName The name of the folder which is located in the Android root directory. 6616b7802baSBartosz Kaszubowski * @return Returns a [SAF URI](#saf-uri) to a folder. 6626b7802baSBartosz Kaszubowski */ 663400de723SŁukasz Kosmaty export function getUriForDirectoryInRoot(folderName: string) { 664400de723SŁukasz Kosmaty return `content://com.android.externalstorage.documents/tree/primary:${folderName}/document/primary:${folderName}`; 665400de723SŁukasz Kosmaty } 666400de723SŁukasz Kosmaty 6676b7802baSBartosz Kaszubowski /** 6686b7802baSBartosz Kaszubowski * Allows users to select a specific directory, granting your app access to all of the files and sub-directories within that directory. 6696b7802baSBartosz Kaszubowski * @param initialFileUrl The [SAF URI](#saf-uri) of the directory that the file picker should display when it first loads. 6706b7802baSBartosz Kaszubowski * If URI is incorrect or points to a non-existing folder, it's ignored. 6716b7802baSBartosz Kaszubowski * @platform android 11+ 6726b7802baSBartosz Kaszubowski * @return Returns a Promise that resolves to `FileSystemRequestDirectoryPermissionsResult` object. 6736b7802baSBartosz Kaszubowski */ 674400de723SŁukasz Kosmaty export async function requestDirectoryPermissionsAsync( 675400de723SŁukasz Kosmaty initialFileUrl: string | null = null 676400de723SŁukasz Kosmaty ): Promise<FileSystemRequestDirectoryPermissionsResult> { 677400de723SŁukasz Kosmaty if (!ExponentFileSystem.requestDirectoryPermissionsAsync) { 678400de723SŁukasz Kosmaty throw new UnavailabilityError( 679400de723SŁukasz Kosmaty 'expo-file-system', 680400de723SŁukasz Kosmaty 'StorageAccessFramework.requestDirectoryPermissionsAsync' 681400de723SŁukasz Kosmaty ); 682400de723SŁukasz Kosmaty } 683400de723SŁukasz Kosmaty 684400de723SŁukasz Kosmaty return await ExponentFileSystem.requestDirectoryPermissionsAsync(initialFileUrl); 685400de723SŁukasz Kosmaty } 686400de723SŁukasz Kosmaty 6876b7802baSBartosz Kaszubowski /** 6886b7802baSBartosz Kaszubowski * Enumerate the contents of a directory. 6896b7802baSBartosz Kaszubowski * @param dirUri [SAF](#saf-uri) URI to the directory. 6906b7802baSBartosz Kaszubowski * @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`. 6916b7802baSBartosz Kaszubowski */ 692400de723SŁukasz Kosmaty export async function readDirectoryAsync(dirUri: string): Promise<string[]> { 693400de723SŁukasz Kosmaty if (!ExponentFileSystem.readSAFDirectoryAsync) { 694400de723SŁukasz Kosmaty throw new UnavailabilityError( 695400de723SŁukasz Kosmaty 'expo-file-system', 696400de723SŁukasz Kosmaty 'StorageAccessFramework.readDirectoryAsync' 697400de723SŁukasz Kosmaty ); 698400de723SŁukasz Kosmaty } 6994fde8ae7SAlan Hughes return await ExponentFileSystem.readSAFDirectoryAsync(dirUri); 700400de723SŁukasz Kosmaty } 701400de723SŁukasz Kosmaty 7026b7802baSBartosz Kaszubowski /** 7036b7802baSBartosz Kaszubowski * Creates a new empty directory. 7046b7802baSBartosz Kaszubowski * @param parentUri The [SAF](#saf-uri) URI to the parent directory. 7056b7802baSBartosz Kaszubowski * @param dirName The name of new directory. 7066b7802baSBartosz Kaszubowski * @return A Promise that resolves to a [SAF URI](#saf-uri) to the created directory. 7076b7802baSBartosz Kaszubowski */ 708400de723SŁukasz Kosmaty export async function makeDirectoryAsync(parentUri: string, dirName: string): Promise<string> { 709400de723SŁukasz Kosmaty if (!ExponentFileSystem.makeSAFDirectoryAsync) { 710400de723SŁukasz Kosmaty throw new UnavailabilityError( 711400de723SŁukasz Kosmaty 'expo-file-system', 712400de723SŁukasz Kosmaty 'StorageAccessFramework.makeDirectoryAsync' 713400de723SŁukasz Kosmaty ); 714400de723SŁukasz Kosmaty } 715400de723SŁukasz Kosmaty return await ExponentFileSystem.makeSAFDirectoryAsync(parentUri, dirName); 716400de723SŁukasz Kosmaty } 717400de723SŁukasz Kosmaty 7186b7802baSBartosz Kaszubowski /** 7196b7802baSBartosz Kaszubowski * Creates a new empty file. 7206b7802baSBartosz Kaszubowski * @param parentUri The [SAF](#saf-uri) URI to the parent directory. 7216b7802baSBartosz Kaszubowski * @param fileName The name of new file **without the extension**. 7226b7802baSBartosz Kaszubowski * @param mimeType The MIME type of new file. 7236b7802baSBartosz Kaszubowski * @return A Promise that resolves to a [SAF URI](#saf-uri) to the created file. 7246b7802baSBartosz Kaszubowski */ 725400de723SŁukasz Kosmaty export async function createFileAsync( 726400de723SŁukasz Kosmaty parentUri: string, 727400de723SŁukasz Kosmaty fileName: string, 728400de723SŁukasz Kosmaty mimeType: string 729400de723SŁukasz Kosmaty ): Promise<string> { 730400de723SŁukasz Kosmaty if (!ExponentFileSystem.createSAFFileAsync) { 731400de723SŁukasz Kosmaty throw new UnavailabilityError('expo-file-system', 'StorageAccessFramework.createFileAsync'); 732400de723SŁukasz Kosmaty } 733400de723SŁukasz Kosmaty return await ExponentFileSystem.createSAFFileAsync(parentUri, fileName, mimeType); 734400de723SŁukasz Kosmaty } 735400de723SŁukasz Kosmaty 7366b7802baSBartosz Kaszubowski /** 7376b7802baSBartosz Kaszubowski * Alias for [`writeAsStringAsync`](#filesystemwriteasstringasyncfileuri-contents-options) method. 7386b7802baSBartosz Kaszubowski */ 739400de723SŁukasz Kosmaty export const writeAsStringAsync = baseWriteAsStringAsync; 7406b7802baSBartosz Kaszubowski /** 7416b7802baSBartosz Kaszubowski * Alias for [`readAsStringAsync`](#filesystemreadasstringasyncfileuri-options) method. 7426b7802baSBartosz Kaszubowski */ 743400de723SŁukasz Kosmaty export const readAsStringAsync = baseReadAsStringAsync; 7446b7802baSBartosz Kaszubowski /** 7456b7802baSBartosz Kaszubowski * Alias for [`deleteAsync`](#filesystemdeleteasyncfileuri-options) method. 7466b7802baSBartosz Kaszubowski */ 747400de723SŁukasz Kosmaty export const deleteAsync = baseDeleteAsync; 7486b7802baSBartosz Kaszubowski /** 7496b7802baSBartosz Kaszubowski * Alias for [`moveAsync`](#filesystemmoveasyncoptions) method. 7506b7802baSBartosz Kaszubowski */ 751400de723SŁukasz Kosmaty export const moveAsync = baseMoveAsync; 7526b7802baSBartosz Kaszubowski /** 753a6c9ed0fSLeandro Alberti * Alias for [`copyAsync`](#filesystemcopyasyncoptions) method. 7546b7802baSBartosz Kaszubowski */ 755400de723SŁukasz Kosmaty export const copyAsync = baseCopyAsync; 756400de723SŁukasz Kosmaty} 757