1import { EventEmitter, Subscription, UnavailabilityError } from '@unimodules/core'; 2import { Platform } from 'react-native'; 3import uuidv4 from 'uuid/v4'; 4 5import ExponentFileSystem from './ExponentFileSystem'; 6import { 7 DownloadOptions, 8 DownloadPauseState, 9 DownloadProgressCallback, 10 DownloadProgressData, 11 DownloadResult, 12 EncodingType, 13 FileInfo, 14 FileSystemDownloadResult, 15 FileSystemAcceptedUploadHttpMethod, 16 FileSystemSessionType, 17 FileSystemUploadOptions, 18 FileSystemUploadResult, 19 ProgressEvent, 20 ReadingOptions, 21 WritingOptions, 22} from './FileSystem.types'; 23 24if (!ExponentFileSystem) { 25 console.warn( 26 "No native ExponentFileSystem module found, are you sure the expo-file-system's module is linked properly?" 27 ); 28} 29// Prevent webpack from pruning this. 30const _unused = new EventEmitter(ExponentFileSystem); // eslint-disable-line 31 32export { 33 DownloadOptions, 34 DownloadPauseState, 35 DownloadProgressCallback, 36 DownloadProgressData, 37 DownloadResult, 38 EncodingType, 39 FileInfo, 40 FileSystemDownloadResult, 41 FileSystemAcceptedUploadHttpMethod, 42 FileSystemSessionType, 43 FileSystemUploadOptions, 44 FileSystemUploadResult, 45 ProgressEvent, 46 ReadingOptions, 47 WritingOptions, 48}; 49 50function normalizeEndingSlash(p: string | null): string | null { 51 if (p != null) { 52 return p.replace(/\/*$/, '') + '/'; 53 } 54 return null; 55} 56 57export const documentDirectory = normalizeEndingSlash(ExponentFileSystem.documentDirectory); 58export const cacheDirectory = normalizeEndingSlash(ExponentFileSystem.cacheDirectory); 59 60export const { bundledAssets, bundleDirectory } = ExponentFileSystem; 61 62export async function getInfoAsync( 63 fileUri: string, 64 options: { md5?: boolean; size?: boolean } = {} 65): Promise<FileInfo> { 66 if (!ExponentFileSystem.getInfoAsync) { 67 throw new UnavailabilityError('expo-file-system', 'getInfoAsync'); 68 } 69 return await ExponentFileSystem.getInfoAsync(fileUri, options); 70} 71 72export async function readAsStringAsync( 73 fileUri: string, 74 options?: ReadingOptions 75): Promise<string> { 76 if (!ExponentFileSystem.readAsStringAsync) { 77 throw new UnavailabilityError('expo-file-system', 'readAsStringAsync'); 78 } 79 return await ExponentFileSystem.readAsStringAsync(fileUri, options || {}); 80} 81 82export async function getContentUriAsync(fileUri: string): Promise<string> { 83 if (Platform.OS === 'android') { 84 if (!ExponentFileSystem.getContentUriAsync) { 85 throw new UnavailabilityError('expo-file-system', 'getContentUriAsync'); 86 } 87 return await ExponentFileSystem.getContentUriAsync(fileUri); 88 } else { 89 return new Promise(function(resolve, reject) { 90 resolve(fileUri); 91 }); 92 } 93} 94 95export async function writeAsStringAsync( 96 fileUri: string, 97 contents: string, 98 options: WritingOptions = {} 99): Promise<void> { 100 if (!ExponentFileSystem.writeAsStringAsync) { 101 throw new UnavailabilityError('expo-file-system', 'writeAsStringAsync'); 102 } 103 return await ExponentFileSystem.writeAsStringAsync(fileUri, contents, options); 104} 105 106export async function deleteAsync( 107 fileUri: string, 108 options: { idempotent?: boolean } = {} 109): Promise<void> { 110 if (!ExponentFileSystem.deleteAsync) { 111 throw new UnavailabilityError('expo-file-system', 'deleteAsync'); 112 } 113 return await ExponentFileSystem.deleteAsync(fileUri, options); 114} 115 116export async function deleteLegacyDocumentDirectoryAndroid(): Promise<void> { 117 if (Platform.OS !== 'android' || documentDirectory == null) { 118 return; 119 } 120 const legacyDocumentDirectory = `${documentDirectory}ExperienceData/`; 121 return await deleteAsync(legacyDocumentDirectory, { idempotent: true }); 122} 123 124export async function moveAsync(options: { from: string; to: string }): Promise<void> { 125 if (!ExponentFileSystem.moveAsync) { 126 throw new UnavailabilityError('expo-file-system', 'moveAsync'); 127 } 128 return await ExponentFileSystem.moveAsync(options); 129} 130 131export async function copyAsync(options: { from: string; to: string }): Promise<void> { 132 if (!ExponentFileSystem.copyAsync) { 133 throw new UnavailabilityError('expo-file-system', 'copyAsync'); 134 } 135 return await ExponentFileSystem.copyAsync(options); 136} 137 138export async function makeDirectoryAsync( 139 fileUri: string, 140 options: { intermediates?: boolean } = {} 141): Promise<void> { 142 if (!ExponentFileSystem.makeDirectoryAsync) { 143 throw new UnavailabilityError('expo-file-system', 'makeDirectoryAsync'); 144 } 145 return await ExponentFileSystem.makeDirectoryAsync(fileUri, options); 146} 147 148export async function readDirectoryAsync(fileUri: string): Promise<string[]> { 149 if (!ExponentFileSystem.readDirectoryAsync) { 150 throw new UnavailabilityError('expo-file-system', 'readDirectoryAsync'); 151 } 152 return await ExponentFileSystem.readDirectoryAsync(fileUri, {}); 153} 154 155export async function getFreeDiskStorageAsync(): Promise<number> { 156 if (!ExponentFileSystem.getFreeDiskStorageAsync) { 157 throw new UnavailabilityError('expo-file-system', 'getFreeDiskStorageAsync'); 158 } 159 return await ExponentFileSystem.getFreeDiskStorageAsync(); 160} 161 162export async function getTotalDiskCapacityAsync(): Promise<number> { 163 if (!ExponentFileSystem.getTotalDiskCapacityAsync) { 164 throw new UnavailabilityError('expo-file-system', 'getTotalDiskCapacityAsync'); 165 } 166 return await ExponentFileSystem.getTotalDiskCapacityAsync(); 167} 168 169export async function downloadAsync( 170 uri: string, 171 fileUri: string, 172 options: DownloadOptions = {} 173): Promise<FileSystemDownloadResult> { 174 if (!ExponentFileSystem.downloadAsync) { 175 throw new UnavailabilityError('expo-file-system', 'downloadAsync'); 176 } 177 178 return await ExponentFileSystem.downloadAsync(uri, fileUri, { 179 sessionType: FileSystemSessionType.BACKGROUND, 180 ...options, 181 }); 182} 183 184export async function uploadAsync( 185 url: string, 186 fileUri: string, 187 options: FileSystemUploadOptions = {} 188): Promise<FileSystemUploadResult> { 189 if (!ExponentFileSystem.uploadAsync) { 190 throw new UnavailabilityError('expo-file-system', 'uploadAsync'); 191 } 192 193 return await ExponentFileSystem.uploadAsync(url, fileUri, { 194 sessionType: FileSystemSessionType.BACKGROUND, 195 ...options, 196 httpMethod: (options.httpMethod || 'POST').toUpperCase(), 197 }); 198} 199 200export function createDownloadResumable( 201 uri: string, 202 fileUri: string, 203 options?: DownloadOptions, 204 callback?: DownloadProgressCallback, 205 resumeData?: string 206): DownloadResumable { 207 return new DownloadResumable(uri, fileUri, options, callback, resumeData); 208} 209 210export class DownloadResumable { 211 _uuid: string; 212 _url: string; 213 _fileUri: string; 214 _options: DownloadOptions; 215 _resumeData?: string; 216 _callback?: DownloadProgressCallback; 217 _subscription?: Subscription | null; 218 _emitter: EventEmitter; 219 220 constructor( 221 url: string, 222 fileUri: string, 223 options: DownloadOptions = {}, 224 callback?: DownloadProgressCallback, 225 resumeData?: string 226 ) { 227 this._uuid = uuidv4(); 228 this._url = url; 229 this._fileUri = fileUri; 230 this._options = options; 231 this._resumeData = resumeData; 232 this._callback = callback; 233 this._subscription = null; 234 this._emitter = new EventEmitter(ExponentFileSystem); 235 } 236 237 async downloadAsync(): Promise<FileSystemDownloadResult | undefined> { 238 if (!ExponentFileSystem.downloadResumableStartAsync) { 239 throw new UnavailabilityError('expo-file-system', 'downloadResumableStartAsync'); 240 } 241 this._addSubscription(); 242 return await ExponentFileSystem.downloadResumableStartAsync( 243 this._url, 244 this._fileUri, 245 this._uuid, 246 this._options, 247 this._resumeData 248 ); 249 } 250 251 async pauseAsync(): Promise<DownloadPauseState> { 252 if (!ExponentFileSystem.downloadResumablePauseAsync) { 253 throw new UnavailabilityError('expo-file-system', 'downloadResumablePauseAsync'); 254 } 255 const pauseResult = await ExponentFileSystem.downloadResumablePauseAsync(this._uuid); 256 this._removeSubscription(); 257 if (pauseResult) { 258 this._resumeData = pauseResult.resumeData; 259 return this.savable(); 260 } else { 261 throw new Error('Unable to generate a savable pause state'); 262 } 263 } 264 265 async resumeAsync(): Promise<FileSystemDownloadResult | undefined> { 266 if (!ExponentFileSystem.downloadResumableStartAsync) { 267 throw new UnavailabilityError('expo-file-system', 'downloadResumableStartAsync'); 268 } 269 this._addSubscription(); 270 return await ExponentFileSystem.downloadResumableStartAsync( 271 this._url, 272 this._fileUri, 273 this._uuid, 274 this._options, 275 this._resumeData 276 ); 277 } 278 279 savable(): DownloadPauseState { 280 return { 281 url: this._url, 282 fileUri: this._fileUri, 283 options: this._options, 284 resumeData: this._resumeData, 285 }; 286 } 287 288 _addSubscription(): void { 289 if (this._subscription) { 290 return; 291 } 292 this._subscription = this._emitter.addListener( 293 'expo-file-system.downloadProgress', 294 (event: ProgressEvent) => { 295 if (event.uuid === this._uuid) { 296 const callback = this._callback; 297 if (callback) { 298 callback(event.data); 299 } 300 } 301 } 302 ); 303 } 304 305 _removeSubscription(): void { 306 if (!this._subscription) { 307 return; 308 } 309 this._emitter.removeSubscription(this._subscription); 310 this._subscription = null; 311 } 312} 313