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