1import AsyncStorage from '@react-native-async-storage/async-storage'; 2import { Asset } from 'expo-asset'; 3import * as FileSystem from 'expo-file-system'; 4// import * as Progress from 'expo-progress'; 5import type { 6 DownloadProgressData, 7 DownloadResumable, 8 FileSystemNetworkTaskProgressCallback, 9 UploadProgressData, 10 UploadTask, 11} from 'expo-file-system'; 12import React from 'react'; 13import { Alert, ScrollView, Text, Platform } from 'react-native'; 14 15import HeadingText from '../components/HeadingText'; 16import ListButton from '../components/ListButton'; 17import SimpleActionDemo from '../components/SimpleActionDemo'; 18 19const { StorageAccessFramework } = FileSystem; 20 21interface State { 22 downloadProgress: number; 23 uploadProgress: number; 24 permittedURI: string | null; 25 createdFileURI: string | null; 26} 27 28export default class FileSystemScreen extends React.Component<object, State> { 29 static navigationOptions = { 30 title: 'FileSystem', 31 }; 32 33 readonly state: State = { 34 downloadProgress: 0, 35 uploadProgress: 0, 36 permittedURI: null, 37 createdFileURI: null, 38 }; 39 40 download?: DownloadResumable; 41 upload?: UploadTask; 42 43 _download = async () => { 44 const url = 'http://ipv4.download.thinkbroadband.com/256KB.zip'; 45 await FileSystem.downloadAsync(url, FileSystem.documentDirectory + '256KB.zip'); 46 alert('Download complete!'); 47 }; 48 49 _startDownloading = async () => { 50 const url = 'http://ipv4.download.thinkbroadband.com/5MB.zip'; 51 const fileUri = FileSystem.documentDirectory + '5MB.zip'; 52 const callback: FileSystemNetworkTaskProgressCallback<DownloadProgressData> = ( 53 downloadProgress 54 ) => { 55 const progress = 56 downloadProgress.totalBytesWritten / downloadProgress.totalBytesExpectedToWrite; 57 this.setState({ 58 downloadProgress: progress, 59 }); 60 }; 61 const options = { md5: true }; 62 this.download = FileSystem.createDownloadResumable(url, fileUri, options, callback); 63 64 try { 65 const result = await this.download.downloadAsync(); 66 if (result) { 67 this._downloadComplete(); 68 } 69 } catch (e) { 70 console.log(e); 71 } 72 }; 73 74 _pause = async () => { 75 if (!this.download) { 76 alert('Initiate a download first!'); 77 return; 78 } 79 try { 80 const downloadSnapshot = await this.download.pauseAsync(); 81 await AsyncStorage.setItem('pausedDownload', JSON.stringify(downloadSnapshot)); 82 alert('Download paused...'); 83 } catch (e) { 84 console.log(e); 85 } 86 }; 87 88 _resume = async () => { 89 try { 90 if (this.download) { 91 const result = await this.download.resumeAsync(); 92 if (result) { 93 this._downloadComplete(); 94 } 95 } else { 96 this._fetchDownload(); 97 } 98 } catch (e) { 99 console.log(e); 100 } 101 }; 102 103 _cancel = async () => { 104 if (!this.download) { 105 alert('Initiate a download first!'); 106 return; 107 } 108 109 try { 110 await this.download.cancelAsync(); 111 delete this.download; 112 await AsyncStorage.removeItem('pausedDownload'); 113 this.setState({ 114 downloadProgress: 0, 115 }); 116 } catch (e) { 117 console.log(e); 118 } 119 }; 120 121 _downloadComplete = () => { 122 if (this.state.downloadProgress !== 1) { 123 this.setState({ 124 downloadProgress: 1, 125 }); 126 } 127 alert('Download complete!'); 128 }; 129 130 _fetchDownload = async () => { 131 try { 132 const downloadJson = await AsyncStorage.getItem('pausedDownload'); 133 if (downloadJson !== null) { 134 const downloadFromStore = JSON.parse(downloadJson); 135 const callback: FileSystemNetworkTaskProgressCallback<DownloadProgressData> = ( 136 downloadProgress 137 ) => { 138 const progress = 139 downloadProgress.totalBytesWritten / downloadProgress.totalBytesExpectedToWrite; 140 this.setState({ 141 downloadProgress: progress, 142 }); 143 }; 144 this.download = new FileSystem.DownloadResumable( 145 downloadFromStore.url, 146 downloadFromStore.fileUri, 147 downloadFromStore.options, 148 callback, 149 downloadFromStore.resumeData 150 ); 151 await this.download.resumeAsync(); 152 if (this.state.downloadProgress === 1) { 153 alert('Download complete!'); 154 } 155 } else { 156 alert('Initiate a download first!'); 157 return; 158 } 159 } catch (e) { 160 console.log(e); 161 } 162 }; 163 164 _upload = async () => { 165 try { 166 const fileUri = FileSystem.documentDirectory + '5MB.zip'; 167 const downloadUrl = 'https://xcal1.vodafone.co.uk/5MB.zip'; 168 await FileSystem.downloadAsync(downloadUrl, fileUri); 169 170 const callback: FileSystemNetworkTaskProgressCallback<UploadProgressData> = ( 171 uploadProgress 172 ) => { 173 // intentionally use deprecated property to test warning 174 const progress = uploadProgress.totalByteSent / uploadProgress.totalBytesExpectedToSend; 175 this.setState({ 176 uploadProgress: progress, 177 }); 178 }; 179 const uploadUrl = 'http://httpbin.org/post'; 180 this.upload = FileSystem.createUploadTask(uploadUrl, fileUri, {}, callback); 181 182 await this.upload.uploadAsync(); 183 } catch (e) { 184 console.log(e); 185 } 186 }; 187 188 _getInfo = async () => { 189 if (!this.download) { 190 alert('Initiate a download first!'); 191 return; 192 } 193 try { 194 const info = await FileSystem.getInfoAsync(this.download.fileUri); 195 Alert.alert('File Info:', JSON.stringify(info), [{ text: 'OK', onPress: () => {} }]); 196 } catch (e) { 197 console.log(e); 198 } 199 }; 200 201 _readAsset = async () => { 202 const asset = Asset.fromModule(require('../../assets/index.html')); 203 await asset.downloadAsync(); 204 try { 205 const result = await FileSystem.readAsStringAsync(asset.localUri!); 206 Alert.alert('Result', result); 207 } catch (e) { 208 Alert.alert('Error', e.message); 209 } 210 }; 211 212 _getInfoAsset = async () => { 213 const asset = Asset.fromModule(require('../../assets/index.html')); 214 await asset.downloadAsync(); 215 try { 216 const result = await FileSystem.getInfoAsync(asset.localUri!); 217 Alert.alert('Result', JSON.stringify(result, null, 2)); 218 } catch (e) { 219 Alert.alert('Error', e.message); 220 } 221 }; 222 223 _copyAndReadAsset = async () => { 224 const asset = Asset.fromModule(require('../../assets/index.html')); 225 await asset.downloadAsync(); 226 const tmpFile = FileSystem.cacheDirectory + 'test.html'; 227 try { 228 await FileSystem.copyAsync({ from: asset.localUri!, to: tmpFile }); 229 const result = await FileSystem.readAsStringAsync(tmpFile); 230 Alert.alert('Result', result); 231 } catch (e) { 232 Alert.alert('Error', e.message); 233 } 234 }; 235 236 _createUTF8Uri = async () => { 237 const folderName = '中文'; 238 const folderUri = FileSystem.documentDirectory + folderName; 239 240 const dirInfo = await FileSystem.getInfoAsync(folderUri); 241 if (dirInfo.exists) { 242 await FileSystem.deleteAsync(folderUri); 243 } 244 245 await FileSystem.makeDirectoryAsync(folderUri); 246 const newDirInfo = await FileSystem.getInfoAsync(folderUri); 247 248 if (newDirInfo.exists && newDirInfo.isDirectory) { 249 alert(`${folderName} directory was successfully created`); 250 } 251 }; 252 253 _alertFreeSpace = async () => { 254 const freeBytes = await FileSystem.getFreeDiskStorageAsync(); 255 alert(`${Math.round(freeBytes / 1024 / 1024)} MB available`); 256 }; 257 258 _askForDirPermissions = async () => { 259 const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync(); 260 if (permissions.granted) { 261 const url = permissions.directoryUri; 262 this.setState({ 263 permittedURI: url, 264 }); 265 alert(`You selected: ${url}`); 266 } 267 }; 268 269 _readSAFDirAsync = async () => { 270 return await StorageAccessFramework.readDirectoryAsync(this.state.permittedURI!); 271 }; 272 273 _creatSAFFileAsync = async () => { 274 const createdFile = await StorageAccessFramework.createFileAsync( 275 // eslint-disable-next-line react/no-access-state-in-setstate 276 this.state.permittedURI!, 277 'test', 278 'text/plain' 279 ); 280 281 this.setState({ 282 createdFileURI: createdFile, 283 }); 284 285 return createdFile; 286 }; 287 288 _writeToSAFFileAsync = async () => { 289 await StorageAccessFramework.writeAsStringAsync( 290 this.state.createdFileURI!, 291 'Expo is awesome ' 292 ); 293 294 return 'Done '; 295 }; 296 297 _readSAFFileAsync = async () => { 298 return await StorageAccessFramework.readAsStringAsync(this.state.createdFileURI!); 299 }; 300 301 _deleteSAFFileAsync = async () => { 302 await StorageAccessFramework.deleteAsync(this.state.createdFileURI!); 303 304 this.setState({ 305 createdFileURI: null, 306 }); 307 }; 308 309 _copySAFFileToInternalStorageAsync = async () => { 310 const outputDir = FileSystem.cacheDirectory! + '/SAFTest'; 311 await StorageAccessFramework.copyAsync({ 312 from: this.state.createdFileURI!, 313 to: outputDir, 314 }); 315 316 return await FileSystem.readDirectoryAsync(outputDir); 317 }; 318 319 _moveSAFFileToInternalStorageAsync = async () => { 320 await StorageAccessFramework.moveAsync({ 321 from: this.state.createdFileURI!, 322 to: FileSystem.cacheDirectory!, 323 }); 324 325 this.setState({ 326 createdFileURI: null, 327 }); 328 }; 329 330 render() { 331 return ( 332 <ScrollView style={{ padding: 10 }}> 333 <ListButton onPress={this._download} title="Download file (512KB)" /> 334 <ListButton onPress={this._startDownloading} title="Start Downloading file (5MB)" /> 335 {this.state.downloadProgress ? ( 336 <Text style={{ paddingVertical: 15 }}> 337 Download progress: {this.state.downloadProgress * 100}% 338 </Text> 339 ) : null} 340 {/* Add back progress bar once deprecation warnings from reanimated 2 are resolved */} 341 {/* <Progress.Bar style={styles.progress} isAnimated progress={this.state.downloadProgress} /> */} 342 <ListButton onPress={this._pause} title="Pause Download" /> 343 <ListButton onPress={this._resume} title="Resume Download" /> 344 <ListButton onPress={this._cancel} title="Cancel Download" /> 345 <ListButton onPress={this._upload} title="Download & Upload file (5MB)" /> 346 {this.state.uploadProgress ? ( 347 <Text style={{ paddingVertical: 15 }}> 348 Upload progress: {this.state.uploadProgress * 100}% 349 </Text> 350 ) : null} 351 <ListButton onPress={this._getInfo} title="Get Info" /> 352 <ListButton onPress={this._readAsset} title="Read Asset" /> 353 <ListButton onPress={this._getInfoAsset} title="Get Info Asset" /> 354 <ListButton onPress={this._copyAndReadAsset} title="Copy and Read Asset" /> 355 <ListButton onPress={this._createUTF8Uri} title="Create UTF-8 uri" /> 356 <ListButton onPress={this._alertFreeSpace} title="Alert free space" /> 357 {Platform.OS === 'android' && ( 358 <> 359 <HeadingText>Storage Access Framework</HeadingText> 360 <ListButton 361 onPress={this._askForDirPermissions} 362 title="Ask for directory permissions" 363 /> 364 {this.state.permittedURI && ( 365 <> 366 <SimpleActionDemo title="Read directory" action={this._readSAFDirAsync} /> 367 <SimpleActionDemo title="Create a file" action={this._creatSAFFileAsync} /> 368 369 {this.state.createdFileURI && ( 370 <> 371 <SimpleActionDemo 372 title="Write to created file" 373 action={this._writeToSAFFileAsync} 374 /> 375 <SimpleActionDemo 376 title="Read from created file" 377 action={this._readSAFFileAsync} 378 /> 379 <ListButton title="Delete created file" onPress={this._deleteSAFFileAsync} /> 380 <SimpleActionDemo 381 title="Copy file to internal storage" 382 action={this._copySAFFileToInternalStorageAsync} 383 /> 384 <ListButton 385 title="Move file to internal storage" 386 onPress={this._moveSAFFileToInternalStorageAsync} 387 /> 388 </> 389 )} 390 </> 391 )} 392 </> 393 )} 394 </ScrollView> 395 ); 396 } 397} 398