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