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