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 React from 'react'; 6import { Alert, ScrollView, Text, Platform } from 'react-native'; 7 8import HeadingText from '../components/HeadingText'; 9import ListButton from '../components/ListButton'; 10import SimpleActionDemo from '../components/SimpleActionDemo'; 11 12const { StorageAccessFramework } = FileSystem; 13 14interface State { 15 downloadProgress: number; 16 permittedURI: string | null; 17 createdFileURI: string | null; 18} 19 20// See: https://github.com/expo/expo/pull/10229#discussion_r490961694 21// eslint-disable-next-line @typescript-eslint/ban-types 22export default class FileSystemScreen extends React.Component<{}, State> { 23 static navigationOptions = { 24 title: 'FileSystem', 25 }; 26 27 readonly state: State = { 28 downloadProgress: 0, 29 permittedURI: null, 30 createdFileURI: null, 31 }; 32 33 download?: FileSystem.DownloadResumable; 34 35 _download = async () => { 36 const url = 'http://ipv4.download.thinkbroadband.com/256KB.zip'; 37 await FileSystem.downloadAsync(url, FileSystem.documentDirectory + '256KB.zip'); 38 alert('Download complete!'); 39 }; 40 41 _startDownloading = async () => { 42 const url = 'http://ipv4.download.thinkbroadband.com/5MB.zip'; 43 const fileUri = FileSystem.documentDirectory + '5MB.zip'; 44 const callback: FileSystem.DownloadProgressCallback = downloadProgress => { 45 const progress = 46 downloadProgress.totalBytesWritten / downloadProgress.totalBytesExpectedToWrite; 47 this.setState({ 48 downloadProgress: progress, 49 }); 50 }; 51 const options = { md5: true }; 52 this.download = FileSystem.createDownloadResumable(url, fileUri, options, callback); 53 54 try { 55 const result = await this.download.downloadAsync(); 56 if (result) { 57 this._downloadComplete(); 58 } 59 } catch (e) { 60 console.log(e); 61 } 62 }; 63 64 _pause = async () => { 65 if (!this.download) { 66 alert('Initiate a download first!'); 67 return; 68 } 69 try { 70 const downloadSnapshot = await this.download.pauseAsync(); 71 await AsyncStorage.setItem('pausedDownload', JSON.stringify(downloadSnapshot)); 72 alert('Download paused...'); 73 } catch (e) { 74 console.log(e); 75 } 76 }; 77 78 _resume = async () => { 79 try { 80 if (this.download) { 81 const result = await this.download.resumeAsync(); 82 if (result) { 83 this._downloadComplete(); 84 } 85 } else { 86 this._fetchDownload(); 87 } 88 } catch (e) { 89 console.log(e); 90 } 91 }; 92 93 _downloadComplete = () => { 94 if (this.state.downloadProgress !== 1) { 95 this.setState({ 96 downloadProgress: 1, 97 }); 98 } 99 alert('Download complete!'); 100 }; 101 102 _fetchDownload = async () => { 103 try { 104 const downloadJson = await AsyncStorage.getItem('pausedDownload'); 105 if (downloadJson !== null) { 106 const downloadFromStore = JSON.parse(downloadJson); 107 const callback: FileSystem.DownloadProgressCallback = downloadProgress => { 108 const progress = 109 downloadProgress.totalBytesWritten / downloadProgress.totalBytesExpectedToWrite; 110 this.setState({ 111 downloadProgress: progress, 112 }); 113 }; 114 this.download = new FileSystem.DownloadResumable( 115 downloadFromStore.url, 116 downloadFromStore.fileUri, 117 downloadFromStore.options, 118 callback, 119 downloadFromStore.resumeData 120 ); 121 await this.download.resumeAsync(); 122 if (this.state.downloadProgress === 1) { 123 alert('Download complete!'); 124 } 125 } else { 126 alert('Initiate a download first!'); 127 return; 128 } 129 } catch (e) { 130 console.log(e); 131 } 132 }; 133 134 _getInfo = async () => { 135 if (!this.download) { 136 alert('Initiate a download first!'); 137 return; 138 } 139 try { 140 const info = await FileSystem.getInfoAsync(this.download._fileUri); 141 Alert.alert('File Info:', JSON.stringify(info), [{ text: 'OK', onPress: () => {} }]); 142 } catch (e) { 143 console.log(e); 144 } 145 }; 146 147 _readAsset = async () => { 148 const asset = Asset.fromModule(require('../../assets/index.html')); 149 await asset.downloadAsync(); 150 try { 151 const result = await FileSystem.readAsStringAsync(asset.localUri!); 152 Alert.alert('Result', result); 153 } catch (e) { 154 Alert.alert('Error', e.message); 155 } 156 }; 157 158 _getInfoAsset = async () => { 159 const asset = Asset.fromModule(require('../../assets/index.html')); 160 await asset.downloadAsync(); 161 try { 162 const result = await FileSystem.getInfoAsync(asset.localUri!); 163 Alert.alert('Result', JSON.stringify(result, null, 2)); 164 } catch (e) { 165 Alert.alert('Error', e.message); 166 } 167 }; 168 169 _copyAndReadAsset = async () => { 170 const asset = Asset.fromModule(require('../../assets/index.html')); 171 await asset.downloadAsync(); 172 const tmpFile = FileSystem.cacheDirectory + 'test.html'; 173 try { 174 await FileSystem.copyAsync({ from: asset.localUri!, to: tmpFile }); 175 const result = await FileSystem.readAsStringAsync(tmpFile); 176 Alert.alert('Result', result); 177 } catch (e) { 178 Alert.alert('Error', e.message); 179 } 180 }; 181 182 _alertFreeSpace = async () => { 183 const freeBytes = await FileSystem.getFreeDiskStorageAsync(); 184 alert(`${Math.round(freeBytes / 1024 / 1024)} MB available`); 185 }; 186 187 _askForDirPermissions = async () => { 188 const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync(); 189 if (permissions.granted) { 190 const uri = permissions.directoryUri; 191 this.setState({ 192 permittedURI: uri, 193 }); 194 alert(`You selected: ${uri}`); 195 } 196 }; 197 198 _readSAFDirAsync = async () => { 199 return await StorageAccessFramework.readDirectoryAsync(this.state.permittedURI!); 200 }; 201 202 _creatSAFFileAsync = async () => { 203 const createdFile = await StorageAccessFramework.createFileAsync( 204 // eslint-disable-next-line react/no-access-state-in-setstate 205 this.state.permittedURI!, 206 'test', 207 'text/plain' 208 ); 209 210 this.setState({ 211 createdFileURI: createdFile, 212 }); 213 214 return createdFile; 215 }; 216 217 _writeToSAFFileAsync = async () => { 218 await StorageAccessFramework.writeAsStringAsync( 219 this.state.createdFileURI!, 220 'Expo is awesome ' 221 ); 222 223 return 'Done '; 224 }; 225 226 _readSAFFileAsync = async () => { 227 return await StorageAccessFramework.readAsStringAsync(this.state.createdFileURI!); 228 }; 229 230 _deleteSAFFileAsync = async () => { 231 await StorageAccessFramework.deleteAsync(this.state.createdFileURI!); 232 233 this.setState({ 234 createdFileURI: null, 235 }); 236 }; 237 238 _copySAFFileToInternalStorageAsync = async () => { 239 const outputDir = FileSystem.cacheDirectory! + '/SAFTest'; 240 await StorageAccessFramework.copyAsync({ 241 from: this.state.createdFileURI!, 242 to: outputDir, 243 }); 244 245 return await FileSystem.readDirectoryAsync(outputDir); 246 }; 247 248 _moveSAFFileToInternalStorageAsync = async () => { 249 await StorageAccessFramework.moveAsync({ 250 from: this.state.createdFileURI!, 251 to: FileSystem.cacheDirectory!, 252 }); 253 254 this.setState({ 255 createdFileURI: null, 256 }); 257 }; 258 259 render() { 260 return ( 261 <ScrollView style={{ padding: 10 }}> 262 <ListButton onPress={this._download} title="Download file (512KB)" /> 263 <ListButton onPress={this._startDownloading} title="Start Downloading file (5MB)" /> 264 {this.state.downloadProgress ? ( 265 <Text style={{ paddingVertical: 15 }}> 266 Download progress: {this.state.downloadProgress * 100}% 267 </Text> 268 ) : null} 269 {/* Add back progress bar once deprecation warnings from reanimated 2 are resolved */} 270 {/* <Progress.Bar style={styles.progress} isAnimated progress={this.state.downloadProgress} /> */} 271 <ListButton onPress={this._pause} title="Pause Download" /> 272 <ListButton onPress={this._resume} title="Resume Download" /> 273 <ListButton onPress={this._getInfo} title="Get Info" /> 274 <ListButton onPress={this._readAsset} title="Read Asset" /> 275 <ListButton onPress={this._getInfoAsset} title="Get Info Asset" /> 276 <ListButton onPress={this._copyAndReadAsset} title="Copy and Read Asset" /> 277 <ListButton onPress={this._alertFreeSpace} title="Alert free space" /> 278 {Platform.OS === 'android' && ( 279 <> 280 <HeadingText>Storage Access Framework</HeadingText> 281 <ListButton 282 onPress={this._askForDirPermissions} 283 title="Ask for directory permissions" 284 /> 285 {this.state.permittedURI && ( 286 <> 287 <SimpleActionDemo title="Read directory" action={this._readSAFDirAsync} /> 288 <SimpleActionDemo title="Create a file" action={this._creatSAFFileAsync} /> 289 290 {this.state.createdFileURI && ( 291 <> 292 <SimpleActionDemo 293 title="Write to created file" 294 action={this._writeToSAFFileAsync} 295 /> 296 <SimpleActionDemo 297 title="Read from created file" 298 action={this._readSAFFileAsync} 299 /> 300 <ListButton title="Delete created file" onPress={this._deleteSAFFileAsync} /> 301 <SimpleActionDemo 302 title="Copy file to internal storage" 303 action={this._copySAFFileToInternalStorageAsync} 304 /> 305 <ListButton 306 title="Move file to internal storage" 307 onPress={this._moveSAFFileToInternalStorageAsync} 308 /> 309 </> 310 )} 311 </> 312 )} 313 </> 314 )} 315 </ScrollView> 316 ); 317 } 318} 319