1import { UnavailabilityError } from 'expo-errors'; 2import { EventEmitter, Subscription } from 'expo-core'; 3import UUID from 'uuid-js'; 4import ExponentFileSystem from './ExponentFileSystem'; 5 6import { 7 DownloadOptions, 8 DownloadResult, 9 DownloadProgressCallback, 10 DownloadProgressData, 11 DownloadPauseState, 12 FileInfo, 13 EncodingType, 14 ReadingOptions, 15 WritingOptions, 16 ProgressEvent, 17} from './FileSystem.types'; 18 19if (!ExponentFileSystem) { 20 console.warn( 21 "No native ExponentFileSystem module found, are you sure the expo-file-system's module is linked properly?" 22 ); 23} 24// Prevent webpack from pruning this. 25const _unused = new EventEmitter(ExponentFileSystem); 26 27export { 28 DownloadOptions, 29 DownloadResult, 30 DownloadProgressCallback, 31 DownloadProgressData, 32 DownloadPauseState, 33 FileInfo, 34 EncodingType, 35 ReadingOptions, 36 WritingOptions, 37 ProgressEvent, 38}; 39 40function normalizeEndingSlash(p: string | null): string | null { 41 if (p != null) { 42 return p.replace(/\/*$/, '') + '/'; 43 } 44 return null; 45} 46 47export const documentDirectory = normalizeEndingSlash(ExponentFileSystem.documentDirectory); 48export const cacheDirectory = normalizeEndingSlash(ExponentFileSystem.cacheDirectory); 49 50export const { bundledAssets, bundleDirectory } = ExponentFileSystem; 51 52export async function getInfoAsync( 53 fileUri: string, 54 options: { md5?: boolean; cache?: boolean } = {} 55): Promise<FileInfo> { 56 if (!ExponentFileSystem.getInfoAsync) { 57 throw new UnavailabilityError('expo-file-system', 'getInfoAsync'); 58 } 59 return await ExponentFileSystem.getInfoAsync(fileUri, options); 60} 61 62export async function readAsStringAsync( 63 fileUri: string, 64 options?: ReadingOptions 65): Promise<string> { 66 if (!ExponentFileSystem.readAsStringAsync) { 67 throw new UnavailabilityError('expo-file-system', 'readAsStringAsync'); 68 } 69 return await ExponentFileSystem.readAsStringAsync(fileUri, options || {}); 70} 71 72export async function writeAsStringAsync( 73 fileUri: string, 74 contents: string, 75 options: WritingOptions = {} 76): Promise<void> { 77 if (!ExponentFileSystem.writeAsStringAsync) { 78 throw new UnavailabilityError('expo-file-system', 'writeAsStringAsync'); 79 } 80 return await ExponentFileSystem.writeAsStringAsync(fileUri, contents, options); 81} 82 83export async function deleteAsync( 84 fileUri: string, 85 options: { idempotent?: boolean } = {} 86): Promise<void> { 87 if (!ExponentFileSystem.deleteAsync) { 88 throw new UnavailabilityError('expo-file-system', 'deleteAsync'); 89 } 90 return await ExponentFileSystem.deleteAsync(fileUri, options); 91} 92 93export async function moveAsync(options: { from: string; to: string }): Promise<void> { 94 if (!ExponentFileSystem.moveAsync) { 95 throw new UnavailabilityError('expo-file-system', 'moveAsync'); 96 } 97 return await ExponentFileSystem.moveAsync(options); 98} 99 100export async function copyAsync(options: { from: string; to: string }): Promise<void> { 101 if (!ExponentFileSystem.copyAsync) { 102 throw new UnavailabilityError('expo-file-system', 'copyAsync'); 103 } 104 return await ExponentFileSystem.copyAsync(options); 105} 106 107export async function makeDirectoryAsync( 108 fileUri: string, 109 options: { intermediates?: boolean } = {} 110): Promise<void> { 111 if (!ExponentFileSystem.makeDirectoryAsync) { 112 throw new UnavailabilityError('expo-file-system', 'makeDirectoryAsync'); 113 } 114 return await ExponentFileSystem.makeDirectoryAsync(fileUri, options); 115} 116 117export async function readDirectoryAsync(fileUri: string): Promise<string[]> { 118 if (!ExponentFileSystem.readDirectoryAsync) { 119 throw new UnavailabilityError('expo-file-system', 'readDirectoryAsync'); 120 } 121 return await ExponentFileSystem.readDirectoryAsync(fileUri, {}); 122} 123 124export async function downloadAsync( 125 uri: string, 126 fileUri: string, 127 options: DownloadOptions = {} 128): Promise<DownloadResult> { 129 if (!ExponentFileSystem.downloadAsync) { 130 throw new UnavailabilityError('expo-file-system', 'downloadAsync'); 131 } 132 return await ExponentFileSystem.downloadAsync(uri, fileUri, options); 133} 134 135export function createDownloadResumable( 136 uri: string, 137 fileUri: string, 138 options?: DownloadOptions, 139 callback?: DownloadProgressCallback, 140 resumeData?: string 141): DownloadResumable { 142 return new DownloadResumable(uri, fileUri, options, callback, resumeData); 143} 144 145export class DownloadResumable { 146 _uuid: string; 147 _url: string; 148 _fileUri: string; 149 _options: DownloadOptions; 150 _resumeData?: string; 151 _callback?: DownloadProgressCallback; 152 _subscription?: Subscription | null; 153 _emitter: EventEmitter; 154 155 constructor( 156 url: string, 157 fileUri: string, 158 options: DownloadOptions = {}, 159 callback?: DownloadProgressCallback, 160 resumeData?: string 161 ) { 162 this._uuid = UUID.create(4).toString(); 163 this._url = url; 164 this._fileUri = fileUri; 165 this._options = options; 166 this._resumeData = resumeData; 167 this._callback = callback; 168 this._subscription = null; 169 this._emitter = new EventEmitter(ExponentFileSystem); 170 } 171 172 async downloadAsync(): Promise<DownloadResult | undefined> { 173 if (!ExponentFileSystem.downloadResumableStartAsync) { 174 throw new UnavailabilityError('expo-file-system', 'downloadResumableStartAsync'); 175 } 176 this._addSubscription(); 177 return await ExponentFileSystem.downloadResumableStartAsync( 178 this._url, 179 this._fileUri, 180 this._uuid, 181 this._options, 182 this._resumeData 183 ); 184 } 185 186 async pauseAsync(): Promise<DownloadPauseState> { 187 if (!ExponentFileSystem.downloadResumablePauseAsync) { 188 throw new UnavailabilityError('expo-file-system', 'downloadResumablePauseAsync'); 189 } 190 const pauseResult = await ExponentFileSystem.downloadResumablePauseAsync(this._uuid); 191 this._removeSubscription(); 192 if (pauseResult) { 193 this._resumeData = pauseResult.resumeData; 194 return this.savable(); 195 } else { 196 throw new Error('Unable to generate a savable pause state'); 197 } 198 } 199 200 async resumeAsync(): Promise<DownloadResult | undefined> { 201 if (!ExponentFileSystem.downloadResumableStartAsync) { 202 throw new UnavailabilityError('expo-file-system', 'downloadResumableStartAsync'); 203 } 204 this._addSubscription(); 205 return await ExponentFileSystem.downloadResumableStartAsync( 206 this._url, 207 this._fileUri, 208 this._uuid, 209 this._options, 210 this._resumeData 211 ); 212 } 213 214 savable(): DownloadPauseState { 215 return { 216 url: this._url, 217 fileUri: this._fileUri, 218 options: this._options, 219 resumeData: this._resumeData, 220 }; 221 } 222 223 _addSubscription(): void { 224 if (this._subscription) { 225 return; 226 } 227 this._subscription = this._emitter.addListener( 228 'Exponent.downloadProgress', 229 (event: ProgressEvent) => { 230 if (event.uuid === this._uuid) { 231 const callback = this._callback; 232 if (callback) { 233 callback(event.data); 234 } 235 } 236 } 237 ); 238 } 239 240 _removeSubscription(): void { 241 if (!this._subscription) { 242 return; 243 } 244 this._emitter.removeSubscription(this._subscription); 245 this._subscription = null; 246 } 247} 248