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 const progress = uploadProgress.totalBytesSent / uploadProgress.totalBytesExpectedToSend; 174 this.setState({ 175 uploadProgress: progress, 176 }); 177 }; 178 const uploadUrl = 'http://httpbin.org/post'; 179 this.upload = FileSystem.createUploadTask(uploadUrl, fileUri, {}, callback); 180 181 await this.upload.uploadAsync(); 182 } catch (e) { 183 console.log(e); 184 } 185 }; 186 187 _getInfo = async () => { 188 if (!this.download) { 189 alert('Initiate a download first!'); 190 return; 191 } 192 try { 193 const info = await FileSystem.getInfoAsync(this.download.fileUri); 194 Alert.alert('File Info:', JSON.stringify(info), [{ text: 'OK', onPress: () => {} }]); 195 } catch (e) { 196 console.log(e); 197 } 198 }; 199 200 _readAsset = async () => { 201 const asset = Asset.fromModule(require('../../assets/index.html')); 202 await asset.downloadAsync(); 203 try { 204 const result = await FileSystem.readAsStringAsync(asset.localUri!); 205 Alert.alert('Result', result); 206 } catch (e) { 207 Alert.alert('Error', e.message); 208 } 209 }; 210 211 _getInfoAsset = async () => { 212 const asset = Asset.fromModule(require('../../assets/index.html')); 213 await asset.downloadAsync(); 214 try { 215 const result = await FileSystem.getInfoAsync(asset.localUri!); 216 Alert.alert('Result', JSON.stringify(result, null, 2)); 217 } catch (e) { 218 Alert.alert('Error', e.message); 219 } 220 }; 221 222 _copyAndReadAsset = async () => { 223 const asset = Asset.fromModule(require('../../assets/index.html')); 224 await asset.downloadAsync(); 225 const tmpFile = FileSystem.cacheDirectory + 'test.html'; 226 try { 227 await FileSystem.copyAsync({ from: asset.localUri!, to: tmpFile }); 228 const result = await FileSystem.readAsStringAsync(tmpFile); 229 Alert.alert('Result', result); 230 } catch (e) { 231 Alert.alert('Error', e.message); 232 } 233 }; 234 235 _alertFreeSpace = async () => { 236 const freeBytes = await FileSystem.getFreeDiskStorageAsync(); 237 alert(`${Math.round(freeBytes / 1024 / 1024)} MB available`); 238 }; 239 240 _askForDirPermissions = async () => { 241 const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync(); 242 if (permissions.granted) { 243 const url = permissions.directoryUri; 244 this.setState({ 245 permittedURI: url, 246 }); 247 alert(`You selected: ${url}`); 248 } 249 }; 250 251 _readSAFDirAsync = async () => { 252 return await StorageAccessFramework.readDirectoryAsync(this.state.permittedURI!); 253 }; 254 255 _creatSAFFileAsync = async () => { 256 const createdFile = await StorageAccessFramework.createFileAsync( 257 // eslint-disable-next-line react/no-access-state-in-setstate 258 this.state.permittedURI!, 259 'test', 260 'text/plain' 261 ); 262 263 this.setState({ 264 createdFileURI: createdFile, 265 }); 266 267 return createdFile; 268 }; 269 270 _writeToSAFFileAsync = async () => { 271 await StorageAccessFramework.writeAsStringAsync( 272 this.state.createdFileURI!, 273 'Expo is awesome ' 274 ); 275 276 return 'Done '; 277 }; 278 279 _readSAFFileAsync = async () => { 280 return await StorageAccessFramework.readAsStringAsync(this.state.createdFileURI!); 281 }; 282 283 _deleteSAFFileAsync = async () => { 284 await StorageAccessFramework.deleteAsync(this.state.createdFileURI!); 285 286 this.setState({ 287 createdFileURI: null, 288 }); 289 }; 290 291 _copySAFFileToInternalStorageAsync = async () => { 292 const outputDir = FileSystem.cacheDirectory! + '/SAFTest'; 293 await StorageAccessFramework.copyAsync({ 294 from: this.state.createdFileURI!, 295 to: outputDir, 296 }); 297 298 return await FileSystem.readDirectoryAsync(outputDir); 299 }; 300 301 _moveSAFFileToInternalStorageAsync = async () => { 302 await StorageAccessFramework.moveAsync({ 303 from: this.state.createdFileURI!, 304 to: FileSystem.cacheDirectory!, 305 }); 306 307 this.setState({ 308 createdFileURI: null, 309 }); 310 }; 311 312 render() { 313 return ( 314 <ScrollView style={{ padding: 10 }}> 315 <ListButton onPress={this._download} title="Download file (512KB)" /> 316 <ListButton onPress={this._startDownloading} title="Start Downloading file (5MB)" /> 317 {this.state.downloadProgress ? ( 318 <Text style={{ paddingVertical: 15 }}> 319 Download progress: {this.state.downloadProgress * 100}% 320 </Text> 321 ) : null} 322 {/* Add back progress bar once deprecation warnings from reanimated 2 are resolved */} 323 {/* <Progress.Bar style={styles.progress} isAnimated progress={this.state.downloadProgress} /> */} 324 <ListButton onPress={this._pause} title="Pause Download" /> 325 <ListButton onPress={this._resume} title="Resume Download" /> 326 <ListButton onPress={this._cancel} title="Cancel Download" /> 327 <ListButton onPress={this._upload} title="Download & Upload file (5MB)" /> 328 {this.state.uploadProgress ? ( 329 <Text style={{ paddingVertical: 15 }}> 330 Upload progress: {this.state.uploadProgress * 100}% 331 </Text> 332 ) : null} 333 <ListButton onPress={this._getInfo} title="Get Info" /> 334 <ListButton onPress={this._readAsset} title="Read Asset" /> 335 <ListButton onPress={this._getInfoAsset} title="Get Info Asset" /> 336 <ListButton onPress={this._copyAndReadAsset} title="Copy and Read Asset" /> 337 <ListButton onPress={this._alertFreeSpace} title="Alert free space" /> 338 {Platform.OS === 'android' && ( 339 <> 340 <HeadingText>Storage Access Framework</HeadingText> 341 <ListButton 342 onPress={this._askForDirPermissions} 343 title="Ask for directory permissions" 344 /> 345 {this.state.permittedURI && ( 346 <> 347 <SimpleActionDemo title="Read directory" action={this._readSAFDirAsync} /> 348 <SimpleActionDemo title="Create a file" action={this._creatSAFFileAsync} /> 349 350 {this.state.createdFileURI && ( 351 <> 352 <SimpleActionDemo 353 title="Write to created file" 354 action={this._writeToSAFFileAsync} 355 /> 356 <SimpleActionDemo 357 title="Read from created file" 358 action={this._readSAFFileAsync} 359 /> 360 <ListButton title="Delete created file" onPress={this._deleteSAFFileAsync} /> 361 <SimpleActionDemo 362 title="Copy file to internal storage" 363 action={this._copySAFFileToInternalStorageAsync} 364 /> 365 <ListButton 366 title="Move file to internal storage" 367 onPress={this._moveSAFFileToInternalStorageAsync} 368 /> 369 </> 370 )} 371 </> 372 )} 373 </> 374 )} 375 </ScrollView> 376 ); 377 } 378} 379