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