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 _cancel = async () => { 94 if (!this.download) { 95 alert('Initiate a download first!'); 96 return; 97 } 98 99 try { 100 await this.download.cancelAsync(); 101 delete this.download; 102 await AsyncStorage.removeItem('pausedDownload'); 103 this.setState({ 104 downloadProgress: 0, 105 }); 106 } catch (e) { 107 console.log(e); 108 } 109 }; 110 111 _downloadComplete = () => { 112 if (this.state.downloadProgress !== 1) { 113 this.setState({ 114 downloadProgress: 1, 115 }); 116 } 117 alert('Download complete!'); 118 }; 119 120 _fetchDownload = async () => { 121 try { 122 const downloadJson = await AsyncStorage.getItem('pausedDownload'); 123 if (downloadJson !== null) { 124 const downloadFromStore = JSON.parse(downloadJson); 125 const callback: FileSystem.DownloadProgressCallback = (downloadProgress) => { 126 const progress = 127 downloadProgress.totalBytesWritten / downloadProgress.totalBytesExpectedToWrite; 128 this.setState({ 129 downloadProgress: progress, 130 }); 131 }; 132 this.download = new FileSystem.DownloadResumable( 133 downloadFromStore.url, 134 downloadFromStore.fileUri, 135 downloadFromStore.options, 136 callback, 137 downloadFromStore.resumeData 138 ); 139 await this.download.resumeAsync(); 140 if (this.state.downloadProgress === 1) { 141 alert('Download complete!'); 142 } 143 } else { 144 alert('Initiate a download first!'); 145 return; 146 } 147 } catch (e) { 148 console.log(e); 149 } 150 }; 151 152 _getInfo = async () => { 153 if (!this.download) { 154 alert('Initiate a download first!'); 155 return; 156 } 157 try { 158 const info = await FileSystem.getInfoAsync(this.download.fileUri); 159 Alert.alert('File Info:', JSON.stringify(info), [{ text: 'OK', onPress: () => {} }]); 160 } catch (e) { 161 console.log(e); 162 } 163 }; 164 165 _readAsset = async () => { 166 const asset = Asset.fromModule(require('../../assets/index.html')); 167 await asset.downloadAsync(); 168 try { 169 const result = await FileSystem.readAsStringAsync(asset.localUri!); 170 Alert.alert('Result', result); 171 } catch (e) { 172 Alert.alert('Error', e.message); 173 } 174 }; 175 176 _getInfoAsset = async () => { 177 const asset = Asset.fromModule(require('../../assets/index.html')); 178 await asset.downloadAsync(); 179 try { 180 const result = await FileSystem.getInfoAsync(asset.localUri!); 181 Alert.alert('Result', JSON.stringify(result, null, 2)); 182 } catch (e) { 183 Alert.alert('Error', e.message); 184 } 185 }; 186 187 _copyAndReadAsset = async () => { 188 const asset = Asset.fromModule(require('../../assets/index.html')); 189 await asset.downloadAsync(); 190 const tmpFile = FileSystem.cacheDirectory + 'test.html'; 191 try { 192 await FileSystem.copyAsync({ from: asset.localUri!, to: tmpFile }); 193 const result = await FileSystem.readAsStringAsync(tmpFile); 194 Alert.alert('Result', result); 195 } catch (e) { 196 Alert.alert('Error', e.message); 197 } 198 }; 199 200 _alertFreeSpace = async () => { 201 const freeBytes = await FileSystem.getFreeDiskStorageAsync(); 202 alert(`${Math.round(freeBytes / 1024 / 1024)} MB available`); 203 }; 204 205 _askForDirPermissions = async () => { 206 const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync(); 207 if (permissions.granted) { 208 const uri = permissions.directoryUri; 209 this.setState({ 210 permittedURI: uri, 211 }); 212 alert(`You selected: ${uri}`); 213 } 214 }; 215 216 _readSAFDirAsync = async () => { 217 return await StorageAccessFramework.readDirectoryAsync(this.state.permittedURI!); 218 }; 219 220 _creatSAFFileAsync = async () => { 221 const createdFile = await StorageAccessFramework.createFileAsync( 222 // eslint-disable-next-line react/no-access-state-in-setstate 223 this.state.permittedURI!, 224 'test', 225 'text/plain' 226 ); 227 228 this.setState({ 229 createdFileURI: createdFile, 230 }); 231 232 return createdFile; 233 }; 234 235 _writeToSAFFileAsync = async () => { 236 await StorageAccessFramework.writeAsStringAsync( 237 this.state.createdFileURI!, 238 'Expo is awesome ' 239 ); 240 241 return 'Done '; 242 }; 243 244 _readSAFFileAsync = async () => { 245 return await StorageAccessFramework.readAsStringAsync(this.state.createdFileURI!); 246 }; 247 248 _deleteSAFFileAsync = async () => { 249 await StorageAccessFramework.deleteAsync(this.state.createdFileURI!); 250 251 this.setState({ 252 createdFileURI: null, 253 }); 254 }; 255 256 _copySAFFileToInternalStorageAsync = async () => { 257 const outputDir = FileSystem.cacheDirectory! + '/SAFTest'; 258 await StorageAccessFramework.copyAsync({ 259 from: this.state.createdFileURI!, 260 to: outputDir, 261 }); 262 263 return await FileSystem.readDirectoryAsync(outputDir); 264 }; 265 266 _moveSAFFileToInternalStorageAsync = async () => { 267 await StorageAccessFramework.moveAsync({ 268 from: this.state.createdFileURI!, 269 to: FileSystem.cacheDirectory!, 270 }); 271 272 this.setState({ 273 createdFileURI: null, 274 }); 275 }; 276 277 render() { 278 return ( 279 <ScrollView style={{ padding: 10 }}> 280 <ListButton onPress={this._download} title="Download file (512KB)" /> 281 <ListButton onPress={this._startDownloading} title="Start Downloading file (5MB)" /> 282 {this.state.downloadProgress ? ( 283 <Text style={{ paddingVertical: 15 }}> 284 Download progress: {this.state.downloadProgress * 100}% 285 </Text> 286 ) : null} 287 {/* Add back progress bar once deprecation warnings from reanimated 2 are resolved */} 288 {/* <Progress.Bar style={styles.progress} isAnimated progress={this.state.downloadProgress} /> */} 289 <ListButton onPress={this._pause} title="Pause Download" /> 290 <ListButton onPress={this._resume} title="Resume Download" /> 291 <ListButton onPress={this._cancel} title="Cancel Download" /> 292 <ListButton onPress={this._getInfo} title="Get Info" /> 293 <ListButton onPress={this._readAsset} title="Read Asset" /> 294 <ListButton onPress={this._getInfoAsset} title="Get Info Asset" /> 295 <ListButton onPress={this._copyAndReadAsset} title="Copy and Read Asset" /> 296 <ListButton onPress={this._alertFreeSpace} title="Alert free space" /> 297 {Platform.OS === 'android' && ( 298 <> 299 <HeadingText>Storage Access Framework</HeadingText> 300 <ListButton 301 onPress={this._askForDirPermissions} 302 title="Ask for directory permissions" 303 /> 304 {this.state.permittedURI && ( 305 <> 306 <SimpleActionDemo title="Read directory" action={this._readSAFDirAsync} /> 307 <SimpleActionDemo title="Create a file" action={this._creatSAFFileAsync} /> 308 309 {this.state.createdFileURI && ( 310 <> 311 <SimpleActionDemo 312 title="Write to created file" 313 action={this._writeToSAFFileAsync} 314 /> 315 <SimpleActionDemo 316 title="Read from created file" 317 action={this._readSAFFileAsync} 318 /> 319 <ListButton title="Delete created file" onPress={this._deleteSAFFileAsync} /> 320 <SimpleActionDemo 321 title="Copy file to internal storage" 322 action={this._copySAFFileToInternalStorageAsync} 323 /> 324 <ListButton 325 title="Move file to internal storage" 326 onPress={this._moveSAFFileToInternalStorageAsync} 327 /> 328 </> 329 )} 330 </> 331 )} 332 </> 333 )} 334 </ScrollView> 335 ); 336 } 337} 338