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