1import AsyncStorage from '@react-native-community/async-storage';
2import { Asset } from 'expo-asset';
3import * as FileSystem from 'expo-file-system';
4import * as Progress from 'expo-progress';
5import React from 'react';
6import { Alert, ScrollView, StyleSheet, 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        <ListButton onPress={this._pause} title="Pause Download" />
265        <ListButton onPress={this._resume} title="Resume Download" />
266        <ListButton onPress={this._getInfo} title="Get Info" />
267        <Progress.Bar style={styles.progress} isAnimated progress={this.state.downloadProgress} />
268        <ListButton onPress={this._readAsset} title="Read Asset" />
269        <ListButton onPress={this._getInfoAsset} title="Get Info Asset" />
270        <ListButton onPress={this._copyAndReadAsset} title="Copy and Read Asset" />
271        <ListButton onPress={this._alertFreeSpace} title="Alert free space" />
272        {Platform.OS === 'android' && (
273          <>
274            <HeadingText>Storage Access Framework</HeadingText>
275            <ListButton
276              onPress={this._askForDirPermissions}
277              title="Ask for directory permissions"
278            />
279            {this.state.permittedURI && (
280              <>
281                <SimpleActionDemo title="Read directory" action={this._readSAFDirAsync} />
282                <SimpleActionDemo title="Create a file" action={this._creatSAFFileAsync} />
283
284                {this.state.createdFileURI && (
285                  <>
286                    <SimpleActionDemo
287                      title="Write to created file"
288                      action={this._writeToSAFFileAsync}
289                    />
290                    <SimpleActionDemo
291                      title="Read from created file"
292                      action={this._readSAFFileAsync}
293                    />
294                    <ListButton title="Delete created file" onPress={this._deleteSAFFileAsync} />
295                    <SimpleActionDemo
296                      title="Copy file to internal storage"
297                      action={this._copySAFFileToInternalStorageAsync}
298                    />
299                    <ListButton
300                      title="Move file to internal storage"
301                      onPress={this._moveSAFFileToInternalStorageAsync}
302                    />
303                  </>
304                )}
305              </>
306            )}
307          </>
308        )}
309      </ScrollView>
310    );
311  }
312}
313
314const styles = StyleSheet.create({
315  progress: {
316    marginHorizontal: 10,
317    marginVertical: 32,
318  },
319});
320