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