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