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