create-expo-cljs-app
Version:
Create a react native application with Expo and Shadow-CLJS!
493 lines (427 loc) • 14.5 kB
text/typescript
import { EventEmitter, Subscription, UnavailabilityError } from 'expo-modules-core';
import { Platform } from 'react-native';
import { v4 as uuidv4 } from 'uuid';
import ExponentFileSystem from './ExponentFileSystem';
import {
DownloadOptions,
DownloadPauseState,
DownloadProgressCallback,
FileSystemNetworkTaskProgressCallback,
DownloadProgressData,
UploadProgressData,
DownloadResult,
EncodingType,
FileInfo,
FileSystemAcceptedUploadHttpMethod,
FileSystemDownloadResult,
FileSystemRequestDirectoryPermissionsResult,
FileSystemSessionType,
FileSystemUploadOptions,
FileSystemUploadResult,
FileSystemUploadType,
ProgressEvent,
ReadingOptions,
WritingOptions,
} from './FileSystem.types';
if (!ExponentFileSystem) {
console.warn(
"No native ExponentFileSystem module found, are you sure the expo-file-system's module is linked properly?"
);
}
// Prevent webpack from pruning this.
const _unused = new EventEmitter(ExponentFileSystem); // eslint-disable-line
export {
DownloadOptions,
DownloadPauseState,
DownloadProgressCallback,
DownloadProgressData,
DownloadResult,
EncodingType,
FileInfo,
FileSystemDownloadResult,
FileSystemRequestDirectoryPermissionsResult,
FileSystemAcceptedUploadHttpMethod,
FileSystemSessionType,
FileSystemUploadOptions,
FileSystemUploadResult,
FileSystemUploadType,
FileSystemNetworkTaskProgressCallback,
ReadingOptions,
WritingOptions,
};
function normalizeEndingSlash(p: string | null): string | null {
if (p != null) {
return p.replace(/\/*$/, '') + '/';
}
return null;
}
export const documentDirectory = normalizeEndingSlash(ExponentFileSystem.documentDirectory);
export const cacheDirectory = normalizeEndingSlash(ExponentFileSystem.cacheDirectory);
export const { bundledAssets, bundleDirectory } = ExponentFileSystem;
export async function getInfoAsync(
fileUri: string,
options: { md5?: boolean; size?: boolean } = {}
): Promise<FileInfo> {
if (!ExponentFileSystem.getInfoAsync) {
throw new UnavailabilityError('expo-file-system', 'getInfoAsync');
}
return await ExponentFileSystem.getInfoAsync(fileUri, options);
}
export async function readAsStringAsync(
fileUri: string,
options?: ReadingOptions
): Promise<string> {
if (!ExponentFileSystem.readAsStringAsync) {
throw new UnavailabilityError('expo-file-system', 'readAsStringAsync');
}
return await ExponentFileSystem.readAsStringAsync(fileUri, options || {});
}
export async function getContentUriAsync(fileUri: string): Promise<string> {
if (Platform.OS === 'android') {
if (!ExponentFileSystem.getContentUriAsync) {
throw new UnavailabilityError('expo-file-system', 'getContentUriAsync');
}
return await ExponentFileSystem.getContentUriAsync(fileUri);
} else {
return new Promise(function (resolve, reject) {
resolve(fileUri);
});
}
}
export async function writeAsStringAsync(
fileUri: string,
contents: string,
options: WritingOptions = {}
): Promise<void> {
if (!ExponentFileSystem.writeAsStringAsync) {
throw new UnavailabilityError('expo-file-system', 'writeAsStringAsync');
}
return await ExponentFileSystem.writeAsStringAsync(fileUri, contents, options);
}
export async function deleteAsync(
fileUri: string,
options: { idempotent?: boolean } = {}
): Promise<void> {
if (!ExponentFileSystem.deleteAsync) {
throw new UnavailabilityError('expo-file-system', 'deleteAsync');
}
return await ExponentFileSystem.deleteAsync(fileUri, options);
}
export async function deleteLegacyDocumentDirectoryAndroid(): Promise<void> {
if (Platform.OS !== 'android' || documentDirectory == null) {
return;
}
const legacyDocumentDirectory = `${documentDirectory}ExperienceData/`;
return await deleteAsync(legacyDocumentDirectory, { idempotent: true });
}
export async function moveAsync(options: { from: string; to: string }): Promise<void> {
if (!ExponentFileSystem.moveAsync) {
throw new UnavailabilityError('expo-file-system', 'moveAsync');
}
return await ExponentFileSystem.moveAsync(options);
}
export async function copyAsync(options: { from: string; to: string }): Promise<void> {
if (!ExponentFileSystem.copyAsync) {
throw new UnavailabilityError('expo-file-system', 'copyAsync');
}
return await ExponentFileSystem.copyAsync(options);
}
export async function makeDirectoryAsync(
fileUri: string,
options: { intermediates?: boolean } = {}
): Promise<void> {
if (!ExponentFileSystem.makeDirectoryAsync) {
throw new UnavailabilityError('expo-file-system', 'makeDirectoryAsync');
}
return await ExponentFileSystem.makeDirectoryAsync(fileUri, options);
}
export async function readDirectoryAsync(fileUri: string): Promise<string[]> {
if (!ExponentFileSystem.readDirectoryAsync) {
throw new UnavailabilityError('expo-file-system', 'readDirectoryAsync');
}
return await ExponentFileSystem.readDirectoryAsync(fileUri, {});
}
export async function getFreeDiskStorageAsync(): Promise<number> {
if (!ExponentFileSystem.getFreeDiskStorageAsync) {
throw new UnavailabilityError('expo-file-system', 'getFreeDiskStorageAsync');
}
return await ExponentFileSystem.getFreeDiskStorageAsync();
}
export async function getTotalDiskCapacityAsync(): Promise<number> {
if (!ExponentFileSystem.getTotalDiskCapacityAsync) {
throw new UnavailabilityError('expo-file-system', 'getTotalDiskCapacityAsync');
}
return await ExponentFileSystem.getTotalDiskCapacityAsync();
}
export async function downloadAsync(
uri: string,
fileUri: string,
options: DownloadOptions = {}
): Promise<FileSystemDownloadResult> {
if (!ExponentFileSystem.downloadAsync) {
throw new UnavailabilityError('expo-file-system', 'downloadAsync');
}
return await ExponentFileSystem.downloadAsync(uri, fileUri, {
sessionType: FileSystemSessionType.BACKGROUND,
...options,
});
}
export async function uploadAsync(
url: string,
fileUri: string,
options: FileSystemUploadOptions = {}
): Promise<FileSystemUploadResult> {
if (!ExponentFileSystem.uploadAsync) {
throw new UnavailabilityError('expo-file-system', 'uploadAsync');
}
return await ExponentFileSystem.uploadAsync(url, fileUri, {
sessionType: FileSystemSessionType.BACKGROUND,
uploadType: FileSystemUploadType.BINARY_CONTENT,
...options,
httpMethod: (options.httpMethod || 'POST').toUpperCase(),
});
}
export function createDownloadResumable(
uri: string,
fileUri: string,
options?: DownloadOptions,
callback?: FileSystemNetworkTaskProgressCallback<DownloadProgressData>,
resumeData?: string
): DownloadResumable {
return new DownloadResumable(uri, fileUri, options, callback, resumeData);
}
export function createUploadTask(
url: string,
fileUri: string,
options?: FileSystemUploadOptions,
callback?: FileSystemNetworkTaskProgressCallback<UploadProgressData>
): UploadTask {
return new UploadTask(url, fileUri, options, callback);
}
export abstract class FileSystemCancellableNetworkTask<
T extends DownloadProgressData | UploadProgressData
> {
private _uuid = uuidv4();
protected taskWasCanceled = false;
private emitter = new EventEmitter(ExponentFileSystem);
private subscription?: Subscription | null;
public async cancelAsync(): Promise<void> {
if (!ExponentFileSystem.networkTaskCancelAsync) {
throw new UnavailabilityError('expo-file-system', 'networkTaskCancelAsync');
}
this.removeSubscription();
this.taskWasCanceled = true;
return await ExponentFileSystem.networkTaskCancelAsync(this.uuid);
}
protected isTaskCancelled(): boolean {
if (this.taskWasCanceled) {
console.warn('This task was already canceled.');
return true;
}
return false;
}
protected get uuid(): string {
return this._uuid;
}
protected abstract getEventName(): string;
protected abstract getCallback(): FileSystemNetworkTaskProgressCallback<T> | undefined;
protected addSubscription() {
if (this.subscription) {
return;
}
this.subscription = this.emitter.addListener(this.getEventName(), (event: ProgressEvent<T>) => {
if (event.uuid === this.uuid) {
const callback = this.getCallback();
if (callback) {
callback(event.data);
}
}
});
}
protected removeSubscription() {
if (!this.subscription) {
return;
}
this.emitter.removeSubscription(this.subscription);
this.subscription = null;
}
}
export class UploadTask extends FileSystemCancellableNetworkTask<UploadProgressData> {
private options: FileSystemUploadOptions;
constructor(
private url: string,
private fileUri: string,
options?: FileSystemUploadOptions,
private callback?: FileSystemNetworkTaskProgressCallback<UploadProgressData>
) {
super();
const httpMethod = (options?.httpMethod?.toUpperCase ||
'POST') as FileSystemAcceptedUploadHttpMethod;
this.options = {
sessionType: FileSystemSessionType.BACKGROUND,
uploadType: FileSystemUploadType.BINARY_CONTENT,
...options,
httpMethod,
};
}
protected getEventName(): string {
return 'expo-file-system.uploadProgress';
}
protected getCallback(): FileSystemNetworkTaskProgressCallback<UploadProgressData> | undefined {
return this.callback;
}
public async uploadAsync(): Promise<FileSystemUploadResult | undefined> {
if (!ExponentFileSystem.uploadTaskStartAsync) {
throw new UnavailabilityError('expo-file-system', 'uploadTaskStartAsync');
}
if (this.isTaskCancelled()) {
return;
}
this.addSubscription();
const result = await ExponentFileSystem.uploadTaskStartAsync(
this.url,
this.fileUri,
this.uuid,
this.options
);
this.removeSubscription();
return result;
}
}
export class DownloadResumable extends FileSystemCancellableNetworkTask<DownloadProgressData> {
constructor(
private url: string,
private _fileUri: string,
private options: DownloadOptions = {},
private callback?: FileSystemNetworkTaskProgressCallback<DownloadProgressData>,
private resumeData?: string
) {
super();
}
public get fileUri(): string {
return this._fileUri;
}
protected getEventName(): string {
return 'expo-file-system.downloadProgress';
}
protected getCallback(): FileSystemNetworkTaskProgressCallback<DownloadProgressData> | undefined {
return this.callback;
}
async downloadAsync(): Promise<FileSystemDownloadResult | undefined> {
if (!ExponentFileSystem.downloadResumableStartAsync) {
throw new UnavailabilityError('expo-file-system', 'downloadResumableStartAsync');
}
if (this.isTaskCancelled()) {
return;
}
this.addSubscription();
return await ExponentFileSystem.downloadResumableStartAsync(
this.url,
this._fileUri,
this.uuid,
this.options,
this.resumeData
);
}
async pauseAsync(): Promise<DownloadPauseState> {
if (!ExponentFileSystem.downloadResumablePauseAsync) {
throw new UnavailabilityError('expo-file-system', 'downloadResumablePauseAsync');
}
if (this.isTaskCancelled()) {
return {
fileUri: this._fileUri,
options: this.options,
url: this.url,
};
}
const pauseResult = await ExponentFileSystem.downloadResumablePauseAsync(this.uuid);
this.removeSubscription();
if (pauseResult) {
this.resumeData = pauseResult.resumeData;
return this.savable();
} else {
throw new Error('Unable to generate a savable pause state');
}
}
async resumeAsync(): Promise<FileSystemDownloadResult | undefined> {
if (!ExponentFileSystem.downloadResumableStartAsync) {
throw new UnavailabilityError('expo-file-system', 'downloadResumableStartAsync');
}
if (this.isTaskCancelled()) {
return;
}
this.addSubscription();
return await ExponentFileSystem.downloadResumableStartAsync(
this.url,
this.fileUri,
this.uuid,
this.options,
this.resumeData
);
}
savable(): DownloadPauseState {
return {
url: this.url,
fileUri: this.fileUri,
options: this.options,
resumeData: this.resumeData,
};
}
}
const baseReadAsStringAsync = readAsStringAsync;
const baseWriteAsStringAsync = writeAsStringAsync;
const baseDeleteAsync = deleteAsync;
const baseMoveAsync = moveAsync;
const baseCopyAsync = copyAsync;
/**
* Android only
*/
export namespace StorageAccessFramework {
export function getUriForDirectoryInRoot(folderName: string) {
return `content://com.android.externalstorage.documents/tree/primary:${folderName}/document/primary:${folderName}`;
}
export async function requestDirectoryPermissionsAsync(
initialFileUrl: string | null = null
): Promise<FileSystemRequestDirectoryPermissionsResult> {
if (!ExponentFileSystem.requestDirectoryPermissionsAsync) {
throw new UnavailabilityError(
'expo-file-system',
'StorageAccessFramework.requestDirectoryPermissionsAsync'
);
}
return await ExponentFileSystem.requestDirectoryPermissionsAsync(initialFileUrl);
}
export async function readDirectoryAsync(dirUri: string): Promise<string[]> {
if (!ExponentFileSystem.readSAFDirectoryAsync) {
throw new UnavailabilityError(
'expo-file-system',
'StorageAccessFramework.readDirectoryAsync'
);
}
return await ExponentFileSystem.readSAFDirectoryAsync(dirUri, {});
}
export async function makeDirectoryAsync(parentUri: string, dirName: string): Promise<string> {
if (!ExponentFileSystem.makeSAFDirectoryAsync) {
throw new UnavailabilityError(
'expo-file-system',
'StorageAccessFramework.makeDirectoryAsync'
);
}
return await ExponentFileSystem.makeSAFDirectoryAsync(parentUri, dirName);
}
export async function createFileAsync(
parentUri: string,
fileName: string,
mimeType: string
): Promise<string> {
if (!ExponentFileSystem.createSAFFileAsync) {
throw new UnavailabilityError('expo-file-system', 'StorageAccessFramework.createFileAsync');
}
return await ExponentFileSystem.createSAFFileAsync(parentUri, fileName, mimeType);
}
export const writeAsStringAsync = baseWriteAsStringAsync;
export const readAsStringAsync = baseReadAsStringAsync;
export const deleteAsync = baseDeleteAsync;
export const moveAsync = baseMoveAsync;
export const copyAsync = baseCopyAsync;
}