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 _alertFreeSpace = async () => { 237 const freeBytes = await FileSystem.getFreeDiskStorageAsync(); 238 alert(`${Math.round(freeBytes / 1024 / 1024)} MB available`); 239 }; 240 241 _askForDirPermissions = async () => { 242 const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync(); 243 if (permissions.granted) { 244 const url = permissions.directoryUri; 245 this.setState({ 246 permittedURI: url, 247 }); 248 alert(`You selected: ${url}`); 249 } 250 }; 251 252 _readSAFDirAsync = async () => { 253 return await StorageAccessFramework.readDirectoryAsync(this.state.permittedURI!); 254 }; 255 256 _creatSAFFileAsync = async () => { 257 const createdFile = await StorageAccessFramework.createFileAsync( 258 // eslint-disable-next-line react/no-access-state-in-setstate 259 this.state.permittedURI!, 260 'test', 261 'text/plain' 262 ); 263 264 this.setState({ 265 createdFileURI: createdFile, 266 }); 267 268 return createdFile; 269 }; 270 271 _writeToSAFFileAsync = async () => { 272 await StorageAccessFramework.writeAsStringAsync( 273 this.state.createdFileURI!, 274 'Expo is awesome ' 275 ); 276 277 return 'Done '; 278 }; 279 280 _readSAFFileAsync = async () => { 281 return await StorageAccessFramework.readAsStringAsync(this.state.createdFileURI!); 282 }; 283 284 _deleteSAFFileAsync = async () => { 285 await StorageAccessFramework.deleteAsync(this.state.createdFileURI!); 286 287 this.setState({ 288 createdFileURI: null, 289 }); 290 }; 291 292 _copySAFFileToInternalStorageAsync = async () => { 293 const outputDir = FileSystem.cacheDirectory! + '/SAFTest'; 294 await StorageAccessFramework.copyAsync({ 295 from: this.state.createdFileURI!, 296 to: outputDir, 297 }); 298 299 return await FileSystem.readDirectoryAsync(outputDir); 300 }; 301 302 _moveSAFFileToInternalStorageAsync = async () => { 303 await StorageAccessFramework.moveAsync({ 304 from: this.state.createdFileURI!, 305 to: FileSystem.cacheDirectory!, 306 }); 307 308 this.setState({ 309 createdFileURI: null, 310 }); 311 }; 312 313 render() { 314 return ( 315 <ScrollView style={{ padding: 10 }}> 316 <ListButton onPress={this._download} title="Download file (512KB)" /> 317 <ListButton onPress={this._startDownloading} title="Start Downloading file (5MB)" /> 318 {this.state.downloadProgress ? ( 319 <Text style={{ paddingVertical: 15 }}> 320 Download progress: {this.state.downloadProgress * 100}% 321 </Text> 322 ) : null} 323 {/* Add back progress bar once deprecation warnings from reanimated 2 are resolved */} 324 {/* <Progress.Bar style={styles.progress} isAnimated progress={this.state.downloadProgress} /> */} 325 <ListButton onPress={this._pause} title="Pause Download" /> 326 <ListButton onPress={this._resume} title="Resume Download" /> 327 <ListButton onPress={this._cancel} title="Cancel Download" /> 328 <ListButton onPress={this._upload} title="Download & Upload file (5MB)" /> 329 {this.state.uploadProgress ? ( 330 <Text style={{ paddingVertical: 15 }}> 331 Upload progress: {this.state.uploadProgress * 100}% 332 </Text> 333 ) : null} 334 <ListButton onPress={this._getInfo} title="Get Info" /> 335 <ListButton onPress={this._readAsset} title="Read Asset" /> 336 <ListButton onPress={this._getInfoAsset} title="Get Info Asset" /> 337 <ListButton onPress={this._copyAndReadAsset} title="Copy and Read Asset" /> 338 <ListButton onPress={this._alertFreeSpace} title="Alert free space" /> 339 {Platform.OS === 'android' && ( 340 <> 341 <HeadingText>Storage Access Framework</HeadingText> 342 <ListButton 343 onPress={this._askForDirPermissions} 344 title="Ask for directory permissions" 345 /> 346 {this.state.permittedURI && ( 347 <> 348 <SimpleActionDemo title="Read directory" action={this._readSAFDirAsync} /> 349 <SimpleActionDemo title="Create a file" action={this._creatSAFFileAsync} /> 350 351 {this.state.createdFileURI && ( 352 <> 353 <SimpleActionDemo 354 title="Write to created file" 355 action={this._writeToSAFFileAsync} 356 /> 357 <SimpleActionDemo 358 title="Read from created file" 359 action={this._readSAFFileAsync} 360 /> 361 <ListButton title="Delete created file" onPress={this._deleteSAFFileAsync} /> 362 <SimpleActionDemo 363 title="Copy file to internal storage" 364 action={this._copySAFFileToInternalStorageAsync} 365 /> 366 <ListButton 367 title="Move file to internal storage" 368 onPress={this._moveSAFFileToInternalStorageAsync} 369 /> 370 </> 371 )} 372 </> 373 )} 374 </> 375 )} 376 </ScrollView> 377 ); 378 } 379} 380