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  _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        {this.state.downloadProgress ? (
265          <Text style={{ paddingVertical: 15 }}>
266            Download progress: {this.state.downloadProgress * 100}%
267          </Text>
268        ) : null}
269        {/* Add back progress bar once deprecation warnings from reanimated 2 are resolved */}
270        {/* <Progress.Bar style={styles.progress} isAnimated progress={this.state.downloadProgress} /> */}
271        <ListButton onPress={this._pause} title="Pause Download" />
272        <ListButton onPress={this._resume} title="Resume Download" />
273        <ListButton onPress={this._getInfo} title="Get Info" />
274        <ListButton onPress={this._readAsset} title="Read Asset" />
275        <ListButton onPress={this._getInfoAsset} title="Get Info Asset" />
276        <ListButton onPress={this._copyAndReadAsset} title="Copy and Read Asset" />
277        <ListButton onPress={this._alertFreeSpace} title="Alert free space" />
278        {Platform.OS === 'android' && (
279          <>
280            <HeadingText>Storage Access Framework</HeadingText>
281            <ListButton
282              onPress={this._askForDirPermissions}
283              title="Ask for directory permissions"
284            />
285            {this.state.permittedURI && (
286              <>
287                <SimpleActionDemo title="Read directory" action={this._readSAFDirAsync} />
288                <SimpleActionDemo title="Create a file" action={this._creatSAFFileAsync} />
289
290                {this.state.createdFileURI && (
291                  <>
292                    <SimpleActionDemo
293                      title="Write to created file"
294                      action={this._writeToSAFFileAsync}
295                    />
296                    <SimpleActionDemo
297                      title="Read from created file"
298                      action={this._readSAFFileAsync}
299                    />
300                    <ListButton title="Delete created file" onPress={this._deleteSAFFileAsync} />
301                    <SimpleActionDemo
302                      title="Copy file to internal storage"
303                      action={this._copySAFFileToInternalStorageAsync}
304                    />
305                    <ListButton
306                      title="Move file to internal storage"
307                      onPress={this._moveSAFFileToInternalStorageAsync}
308                    />
309                  </>
310                )}
311              </>
312            )}
313          </>
314        )}
315      </ScrollView>
316    );
317  }
318}
319