first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

2
node_modules/expo-file-system/.eslintrc.js generated vendored Normal file
View File

@@ -0,0 +1,2 @@
// @generated by expo-module-scripts
module.exports = require('expo-module-scripts/eslintrc.base.js');

771
node_modules/expo-file-system/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,771 @@
# Changelog
## Unpublished
### 🛠 Breaking changes
### 🎉 New features
### 🐛 Bug fixes
### 💡 Others
## 55.0.10 — 2026-02-26
_This version does not introduce any user-facing changes._
## 55.0.9 — 2026-02-25
### 🐛 Bug fixes
- Fix incorrect return types for `File` and `Directory` static methods. ([#43282](https://github.com/expo/expo/pull/43282) by [@alanjhughes](https://github.com/alanjhughes))
## 55.0.8 — 2026-02-20
_This version does not introduce any user-facing changes._
## 55.0.7 — 2026-02-20
### 🐛 Bug fixes
- Fix Jest mock to support new `File`/`Directory`/`Paths` API. ([#43005](https://github.com/expo/expo/pull/43005) by [@aleqsio](https://github.com/aleqsio))
## 55.0.6 — 2026-02-16
### 🐛 Bug fixes
- [Android] Fix bundle directory listing returning incorrect names, URIs and trailing slashes for files. ([#42882](https://github.com/expo/expo/pull/42882) by [@aleqsio](https://github.com/aleqsio))
## 55.0.5 — 2026-02-08
### 🎉 New features
- Add `append` option to write methods. ([#42778](https://github.com/expo/expo/pull/42778) by [@aleqsio](https://github.com/aleqsio))
## 55.0.4 — 2026-02-03
_This version does not introduce any user-facing changes._
## 55.0.3 — 2026-01-27
_This version does not introduce any user-facing changes._
## 55.0.2 — 2026-01-26
_This version does not introduce any user-facing changes._
## 55.0.1 — 2026-01-22
_This version does not introduce any user-facing changes._
## 55.0.0 — 2026-01-21
### 🐛 Bug fixes
- [file-system] Fix package exports for Typescript projects ([#39543](https://github.com/expo/expo/pull/39543) by [@smoores-dev](https://github.com/smoores-dev))
### 💡 Others
- [Android] Add `android:maxSdkVersion` annotation to `READ_EXTERNAL_STORAGE` and `WRITE_EXTERNAL_STORAGE` permissions. ([#40976](https://github.com/expo/expo/pull/40976) by [@behenate](https://github.com/behenate))
- [Android] Removed references to legacy native modules API. ([#41791](https://github.com/expo/expo/pull/41791) by [@lukmccall](https://github.com/lukmccall))
## 19.0.20 - 2025-12-05
_This version does not introduce any user-facing changes._
## 19.0.19 - 2025-11-18
### 🎉 New features
- [Android] Add contentUri property. ([#40002](https://github.com/expo/expo/pull/40002) by [@aleqsio](https://github.com/aleqsio))
### 🐛 Bug fixes
- [iOS] Add missing `createFile` and `createDirectory` methods. ([#40314](https://github.com/expo/expo/pull/40314) by [@jakex7](https://github.com/jakex7))
## 19.0.18 - 2025-11-17
_This version does not introduce any user-facing changes._
## 19.0.17 - 2025-10-09
### 🐛 Bug fixes
- [Android] Fix recursive file deletion. ([#40248](https://github.com/expo/expo/pull/40248) by [@aleqsio](https://github.com/aleqsio))
## 19.0.16 - 2025-10-01
### 🎉 New features
- Add write options for base64 encoded bytes. ([#39963](https://github.com/expo/expo/pull/39963) by [@aleqsio](https://github.com/aleqsio))
- [iOS] Add file sharing config options ([#39286](https://github.com/expo/expo/pull/39286) by [@kosmydel](https://github.com/kosmydel))
### 🐛 Bug fixes
- Fix typedoc in the File class. ([#40064](https://github.com/expo/expo/pull/40064) by [@aleqsio](https://github.com/aleqsio))
- [Android] Fix getContentUri. ([#40001](https://github.com/expo/expo/pull/40001) by [@aleqsio](https://github.com/aleqsio))
## 19.0.15 - 2025-09-22
### 🎉 New features
- add `idempotent` option to `downloadFileAsync` ([#39681](https://github.com/expo/expo/pull/39681) by [@vonovak](https://github.com/vonovak))
## 19.0.14 — 2025-09-13
_This version does not introduce any user-facing changes._
## 19.0.13 — 2025-09-12
### 🐛 Bug fixes
- [Android] Fix incorrect AndroidManifest.xml location. ([#39134](https://github.com/expo/expo/pull/39134) by [@robertying](https://github.com/robertying))
## 19.0.12 — 2025-09-11
_This version does not introduce any user-facing changes._
## 19.0.11 — 2025-09-10
### 🎉 New features
- [iOS] Add `pickDirectoryAsync` support ([#39210](https://github.com/expo/expo/pull/39210) by [@kosmydel](https://github.com/kosmydel))
### 🐛 Bug fixes
- Add minimal web stub to fix broken imports on web ([#39400](https://github.com/expo/expo/pull/39400) by [@LeonDvlpmnt](https://github.com/LeonDvlpmnt))
## 19.0.10 — 2025-09-08
_This version does not introduce any user-facing changes._
## 19.0.9 — 2025-09-03
### 🎉 New features
- Add stubs for legacy methods imported from "expo-file-system". ([#39244](https://github.com/expo/expo/pull/39244) by [@aleqsio](https://github.com/aleqsio))
### 🐛 Bug fixes
- [iOS] Fix tvOS compile error. ([#39345](https://github.com/expo/expo/pull/39345) by [@douglowder](https://github.com/douglowder))
## 19.0.8 — 2025-09-02
### 🎉 New features
- [iOS] Add `pickFileAsync` support ([#39173](https://github.com/expo/expo/pull/39173) by [@kosmydel](https://github.com/kosmydel))
## 19.0.7 — 2025-08-31
### 🎉 New features
- Add `rename` method for files and directories ([#39138](https://github.com/expo/expo/pull/39138) by [@kosmydel](https://github.com/kosmydel))
- Add `idempotent` option for creating directories ([#39121](https://github.com/expo/expo/pull/39121) by [@kosmydel](https://github.com/kosmydel))
## 19.0.6 — 2025-08-26
_This version does not introduce any user-facing changes._
## 19.0.5 — 2025-08-25
_This version does not introduce any user-facing changes._
## 19.0.4 — 2025-08-21
_This version does not introduce any user-facing changes._
## 19.0.3 — 2025-08-18
### 🐛 Bug fixes
- Fixed type definition for `textSync()` to return `string` instead of `Promise<string>` ([#38898](https://github.com/expo/expo/pull/38898) by [@LeonDvlpmnt](https://github.com/LeonDvlpmnt))
## 19.0.2 — 2025-08-16
_This version does not introduce any user-facing changes._
## 19.0.1 — 2025-08-15
### 💡 Others
- Update typings for `typescript@5.9` ([#38833](https://github.com/expo/expo/pull/38833) by [@kitten](https://github.com/kitten))
- Change Constants to Constant/Property. ([#38926](https://github.com/expo/expo/pull/38926) by [@jakex7](https://github.com/jakex7))
## 19.0.0 — 2025-08-13
### 🛠 Breaking changes
- Make the modern filesystem API the default, move previous one to `expo-file-system/legacy`. ([#38404](https://github.com/expo/expo/pull/38404) by [@aleqsio](https://github.com/aleqsio))
### 🎉 New features
- [android] Add file and directory pickers. ([#38455](https://github.com/expo/expo/pull/38455) by [@aleqsio](https://github.com/aleqsio))
- Add support for asset uris. ([#38785](https://github.com/expo/expo/pull/38785) by [@aleqsio](https://github.com/aleqsio))
- Make file implement blob interface directly. ([#38160](https://github.com/expo/expo/pull/38160) by [@aleqsio](https://github.com/aleqsio))
- Add directory info function ([#37910](https://github.com/expo/expo/pull/37910) by [@Wenszel](https://github.com/Wenszel))
- Add total and available sizes, directory sizes. ([#37594](https://github.com/expo/expo/pull/37594) by [@aleqsio](https://github.com/aleqsio))
- Add info method, modificationTime and creationTime properties to file-system/next. ([#37505](https://github.com/expo/expo/pull/37505) by [@Wenszel](https://github.com/Wenszel))
- Add support for custom headers in downloadFileAsync ([#36108](https://github.com/expo/expo/pull/36108) by [@leonhh](https://github.com/leonhh))
- [next] Add limited support for SAF Uris. ([#38075](https://github.com/expo/expo/pull/38075) by [@aleqsio](https://github.com/aleqsio))
- [next] Add full support for SAF Uris. ([#38075](https://github.com/expo/expo/pull/38075) by [@aleqsio](https://github.com/aleqsio))
### 🐛 Bug fixes
- Update exists logic to align with documentation ([#37692](https://github.com/expo/expo/pull/37692) by [@Wenszel](https://github.com/Wenszel))
- Fix memory usage issue in getInfoAsync ([#37417](https://github.com/expo/expo/pull/37417) by [@Wenszel](https://github.com/Wenszel))
- Improved type safety in the FileSystem module to support tsconfig setups with stricter rules than the default. ([#37107](https://github.com/expo/expo/pull/37107) by [@hirbod](https://github.com/hirbod))
- Added required modifiers in the FileSystem module to support tsconfig setups with stricter rules than the default. ([#37467](https://github.com/expo/expo/pull/37467)
## 18.1.11 - 2025-07-01
### 💡 Others
- Remove "Please" from warnings and errors ([#36862](https://github.com/expo/expo/pull/36862) by [@brentvatne](https://github.com/brentvatne))
## 18.1.10 — 2025-05-08
### 🐛 Bug fixes
- Fix the `UploadTask.uploadAsync` method's return type did not indicate that the method could resolve to `null`. ([#36476](https://github.com/expo/expo/pull/36476) by [@DoctorJohn](https://github.com/DoctorJohn))
## 18.1.9 — 2025-05-03
_This version does not introduce any user-facing changes._
## 18.1.8 — 2025-04-30
_This version does not introduce any user-facing changes._
## 18.1.7 — 2025-04-28
### 💡 Others
- Remove `web-streams-polyfill` in favor of `expo` support. ([#36407](https://github.com/expo/expo/pull/36407) by [@EvanBacon](https://github.com/EvanBacon))
## 18.1.6 — 2025-04-25
_This version does not introduce any user-facing changes._
## 18.1.5 — 2025-04-21
_This version does not introduce any user-facing changes._
## 18.1.4 — 2025-04-14
_This version does not introduce any user-facing changes._
## 18.1.3 — 2025-04-11
_This version does not introduce any user-facing changes._
## 18.1.2 — 2025-04-11
_This version does not introduce any user-facing changes._
## 18.1.1 — 2025-04-09
_This version does not introduce any user-facing changes._
## 18.1.0 — 2025-04-04
### 🛠 Breaking changes
- Bump minimum macOS version to 11.0. ([#34980](https://github.com/expo/expo/pull/34980) by [@gabrieldonadel](https://github.com/gabrieldonadel))
### 🎉 New features
- [expo-file-system][next] Add options to the create function. ([#32909](https://github.com/expo/expo/pull/32909) by [@aleqsio](https://github.com/aleqsio))
### 🐛 Bug fixes
- [next] Fix inconsistent behavior when using special chars in filenames. ([#35801](https://github.com/expo/expo/pull/35801) by [@aleqsio](https://github.com/aleqsio))
- Fix expo-updates breaking filesystem on Android API 24 and 25. ([#33694](https://github.com/expo/expo/pull/33694) by [@aleqsio](https://github.com/aleqsio))
### 💡 Others
- [Android] Started using expo modules gradle plugin. ([#34176](https://github.com/expo/expo/pull/34176) by [@lukmccall](https://github.com/lukmccall))
- [apple] Migrate remaining `expo-module.config.json` to unified platform syntax. ([#34445](https://github.com/expo/expo/pull/34445) by [@reichhartd](https://github.com/reichhartd))
- [iOS] Fix warnings which will become errors in Swift 6. ([#35288](https://github.com/expo/expo/pull/35288) by [@behenate](https://github.com/behenate))
## 18.0.12 - 2025-03-20
_This version does not introduce any user-facing changes._
## 18.0.11 - 2025-02-19
_This version does not introduce any user-facing changes._
## 18.0.10 - 2025-02-06
_This version does not introduce any user-facing changes._
## 18.0.9 - 2025-02-03
_This version does not introduce any user-facing changes._
## 18.0.8 - 2025-01-31
### 🐛 Bug fixes
- Fix types that are erroring in default template. ([#34520](https://github.com/expo/expo/pull/34520) by [@aleqsio](https://github.com/aleqsio))
- [Android] fixed issue with deleting a directory with children ([#34550](https://github.com/expo/expo/pull/34550) by [@chrfalch](https://github.com/chrfalch))
## 18.0.7 - 2025-01-10
_This version does not introduce any user-facing changes._
## 18.0.6 - 2024-12-16
### 🎉 New features
- [next] Add blob support and `.blob()` function. ([#33152](https://github.com/expo/expo/pull/33152) by [@aleqsio](https://github.com/aleqsio))
## 18.0.5 - 2024-12-10
### 🐛 Bug fixes
- [macOS][next]: Add availability checks ([#33504](https://github.com/expo/expo/pull/33504) by [@hassankhan](https://github.com/hassankhan))
## 18.0.4 — 2024-11-19
### 🎉 New features
- [next] Added `.bytes()` and writing a `Uint8Array`. ([#33020](https://github.com/expo/expo/pull/33020) by [@aleqsio](https://github.com/aleqsio))
## 18.0.3 — 2024-11-13
### 🎉 New features
- [next] Add file handles. ([#31738](https://github.com/expo/expo/pull/31738) by [@aleqsio](https://github.com/aleqsio))
## 18.0.2 — 2024-11-11
_This version does not introduce any user-facing changes._
## 18.0.1 — 2024-11-07
### 🐛 Bug fixes
- [expo-file-system][next] Fix download function throwing an unexpected error if destination already exists. ([#32626](https://github.com/expo/expo/pull/32626) by [@aleqsio](https://github.com/aleqsio))
## 18.0.0 — 2024-10-22
### 🛠 Breaking changes
- [next] Disable module in Expo Go. ([#31938](https://github.com/expo/expo/pull/31938) by [@aleqsio](https://github.com/aleqsio))
- Bumped iOS and tvOS deployment target to 15.1. ([#30840](https://github.com/expo/expo/pull/30840) by [@tsapeta](https://github.com/tsapeta))
### 🎉 New features
- [expo-file-system][next] Add file permissions for expo go ([#31594](https://github.com/expo/expo/pull/31594) by [@aleqsio](https://github.com/aleqsio))
- [expo-file-system][next] Add better error handling to delete ([#31683](https://github.com/expo/expo/pull/31683) by [@aleqsio](https://github.com/aleqsio))
- [expo-file-system][next] Adjust copy/move functions to allow renaming folders. ([#31636](https://github.com/expo/expo/pull/31636) by [@aleqsio](https://github.com/aleqsio))
- [expo-file-system][next] Add name property to files and directories. ([#31545](https://github.com/expo/expo/pull/31545) by [@aleqsio](https://github.com/aleqsio))
- Add path joining in `File` and `Directory` constructors. ([#31467](https://github.com/expo/expo/pull/31467) by [@aleqsio](https://github.com/aleqsio))
- [expo-file-system][next] Make move operation change file url. ([#31544](https://github.com/expo/expo/pull/31544) by [@aleqsio](https://github.com/aleqsio))
- Change `exists()` function to a property. ([#31522](https://github.com/expo/expo/pull/31522) by [@aleqsio](https://github.com/aleqsio))
- Add path utilities and `parentDirectory`, `extension` fields to the new file system module. ([#31333](https://github.com/expo/expo/pull/31333) by [@aleqsio](https://github.com/aleqsio))
- Add listing directory contents to the new file system module. ([#31121](https://github.com/expo/expo/pull/31121) by [@aleqsio](https://github.com/aleqsio))
- Add `base64()` new file system module. ([#31357](https://github.com/expo/expo/pull/31357) by [@aleqsio](https://github.com/aleqsio))
- Add size and md5 properties to the new file system module. ([#31301](https://github.com/expo/expo/pull/31301) by [@aleqsio](https://github.com/aleqsio))
- Add downloading to the new file system module. ([#30841](https://github.com/expo/expo/pull/30841) by [@aleqsio](https://github.com/aleqsio))
- Add copying and moving files to the new file system module. ([#30314](https://github.com/expo/expo/pull/30314) by [@aleqsio](https://github.com/aleqsio))
- Add new file system module. ([#29995](https://github.com/expo/expo/pull/29995) by [@aleqsio](https://github.com/aleqsio))
- [iOS] Added `Paths.appleSharedContainers` to get the paths to the Apple App Groups shared containers. ([#31525](https://github.com/expo/expo/pull/31525) by [@IgorKhramtsov](https://github.com/IgorKhramtsov) and [@kudo](https://github.com/kudo))
### 🐛 Bug fixes
- [iOS] fix: `getFreeDiskStorageAsync` returns result that's closer to the value reported by the system. ([#29732](https://github.com/expo/expo/pull/29732) by [@vonovak](https://github.com/vonovak))
- Add NULL check before dereferencing pointer to error pointer in `EXFileSystemAssetLibraryHandler`. ([#29091](https://github.com/expo/expo/pull/29091) by [@hakonk](https://github.com/hakonk))
- Add missing `react-native` peer dependencies for isolated modules. ([#30466](https://github.com/expo/expo/pull/30466) by [@byCedric](https://github.com/byCedric))
### 💡 Others
- [iOS] Validate folder/file type when operating on File/Folder instances in the new FS module. ([#31316](https://github.com/expo/expo/pull/31316) by [@aleqsio](https://github.com/aleqsio))
- Use the `src` folder as the Metro target. ([#31234](https://github.com/expo/expo/pull/31234) by [@aleqsio](https://github.com/aleqsio))
- Removed redundant usage of `EventEmitter` instance. ([#28946](https://github.com/expo/expo/pull/28946) by [@tsapeta](https://github.com/tsapeta))
## 17.0.1 — 2024-04-23
_This version does not introduce any user-facing changes._
## 17.0.0 — 2024-04-18
### 🐛 Bug fixes
- [Android] remove `CookieHandler` as it's no longer in the module registry and not necessary. ([#28145](https://github.com/expo/expo/pull/28145) by [@alanjhughes](https://github.com/alanjhughes))
### 💡 Others
- [iOS] Add privacy manifest describing required reason API usage. ([#27770](https://github.com/expo/expo/pull/27770) by [@aleqsio](https://github.com/aleqsio))
- drop unused web `name` property. ([#27437](https://github.com/expo/expo/pull/27437) by [@EvanBacon](https://github.com/EvanBacon))
- Removed deprecated backward compatible Gradle settings. ([#28083](https://github.com/expo/expo/pull/28083) by [@kudo](https://github.com/kudo))
## 16.0.8 - 2024-03-07
_This version does not introduce any user-facing changes._
## 16.0.7 - 2024-02-27
### 🐛 Bug fixes
- [iOS] Fix downloadAsync for local files. ([#27187](https://github.com/expo/expo/pull/27187) by [@gabrieldonadel](https://github.com/gabrieldonadel))
- On `iOS`, fix an issue with `copyAsync` where the copy fails if it is a photo library asset. ([#27208](https://github.com/expo/expo/pull/27208) by [@alanjhughes](https://github.com/alanjhughes))
- On `iOS`, resolve the promise manually after copying a PHAsset file. ([#27381](https://github.com/expo/expo/pull/27381) by [@alanjhughes](https://github.com/alanjhughes))
## 16.0.6 - 2024-02-06
### 🐛 Bug fixes
- On `iOS`, fix upload task requests. ([#26880](https://github.com/expo/expo/pull/26880) by [@alanjhughes](https://github.com/alanjhughes))
## 16.0.5 - 2024-01-23
### 🐛 Bug fixes
- On `iOS`, set `httpMethod` on upload requests. ([#26516](https://github.com/expo/expo/pull/26516) by [@alanjhughes](https://github.com/alanjhughes))
## 16.0.4 - 2024-01-18
_This version does not introduce any user-facing changes._
## 16.0.3 - 2024-01-10
### 🎉 New features
- Added support for macOS platform. ([#26253](https://github.com/expo/expo/pull/26253) by [@tsapeta](https://github.com/tsapeta))
## 16.0.2 - 2023-12-19
_This version does not introduce any user-facing changes._
## 16.0.1 — 2023-12-13
_This version does not introduce any user-facing changes._
## 16.0.0 — 2023-12-12
### 🐛 Bug fixes
- On `Android`, handle using files from `SAF` correctly. ([#25389](https://github.com/expo/expo/pull/25389) by [@alanjhughes](https://github.com/alanjhughes))
- Removed legacy `bundledAssets` constant that was used only in standalone apps. ([#25484](https://github.com/expo/expo/pull/25484) by [@tsapeta](https://github.com/tsapeta))
- [iOS] Added missing check for directory permissions in `deleteAsync` method. ([#25704](https://github.com/expo/expo/pull/25704) by [@tsapeta](https://github.com/tsapeta))
## 15.4.5 — 2023-11-20
### 🐛 Bug fixes
- On `Android`, use `addInterceptor` instead of `addNetworkInterceptor` in `downloadResumableStartAsync`. ([#24702](https://github.com/expo/expo/pull/24702) by [@alanhughes](https://github.com/alanjhughes))
## 15.9.0 — 2023-11-14
### 🛠 Breaking changes
- Bumped iOS deployment target to 13.4. ([#25063](https://github.com/expo/expo/pull/25063) by [@gabrieldonadel](https://github.com/gabrieldonadel))
- On `Android` bump `compileSdkVersion` and `targetSdkVersion` to `34`. ([#24708](https://github.com/expo/expo/pull/24708) by [@alanjhughes](https://github.com/alanjhughes))
## 15.8.0 — 2023-10-17
### 🛠 Breaking changes
- Dropped support for Android SDK 21 and 22. ([#24201](https://github.com/expo/expo/pull/24201) by [@behenate](https://github.com/behenate))
### 🐛 Bug fixes
- On `Android`, use `addInterceptor` instead of `addNetworkInterceptor` in `downloadResumableStartAsync`. ([#24702](https://github.com/expo/expo/pull/24702) by [@alanhughes](https://github.com/alanjhughes))
## 15.7.0 — 2023-09-15
### 🎉 New features
- Added support for Apple tvOS. ([#24329](https://github.com/expo/expo/pull/24329) by [@douglowder](https://github.com/douglowder))
### 💡 Others
- Migrated to Swift and Expo Modules API on iOS. ([#23943](https://github.com/expo/expo/pull/23943) by [@tsapeta](https://github.com/tsapeta))
- Throw the correct error when we can't find the permissions modules. ([#24464](https://github.com/expo/expo/pull/24464) by [@alanhughes](https://github.com/alanjhughes))
## 15.6.0 — 2023-09-04
### 🎉 New features
- Added support for React Native 0.73. ([#24018](https://github.com/expo/expo/pull/24018) by [@kudo](https://github.com/kudo))
## 15.4.4 - 2023-08-29
_This version does not introduce any user-facing changes._
## 15.4.3 - 2023-08-09
### 🐛 Bug fixes
- Fix regression in `copyAsync` on Android. ([#23892](https://github.com/expo/expo/pull/23892) by [@brentvatne](https://github.com/brentvatne))
## 15.5.1 — 2023-08-02
_This version does not introduce any user-facing changes._
## 15.5.0 — 2023-07-28
### 💡 Others
- Fork `uuid@3.4.0` and move into `expo-modules-core`. Remove the original dependency. ([#23249](https://github.com/expo/expo/pull/23249) by [@alanhughes](https://github.com/alanjhughes))
## 15.4.2 — 2023-06-28
_This version does not introduce any user-facing changes._
## 15.4.1 — 2023-06-27
### 🐛 Bug fixes
- Fixed hard crash on iOS when calling readDirectoryAsync. ([#23106](https://github.com/expo/expo/pull/23106) by [@aleqsio](https://github.com/aleqsio))
## 15.4.0 — 2023-06-13
### 🎉 New features
- Migrated Android codebase to use Expo modules API. ([#22728](https://github.com/expo/expo/pull/22728) by [@alanhughes](https://github.com/alanjhughes))
### 🐛 Bug fixes
- Fixed Android build warnings for Gradle version 8. ([#22537](https://github.com/expo/expo/pull/22537), [#22609](https://github.com/expo/expo/pull/22609) by [@kudo](https://github.com/kudo))
## 15.3.0 — 2023-05-08
### 🛠 Breaking changes
- Removed the deprecated `UploadProgressData.totalByteSent` field. ([#22277](https://github.com/expo/expo/pull/22277) by [@gabrieldonadel](https://github.com/gabrieldonadel))
### 🐛 Bug fixes
- Add UTF-8 URI support on iOS. ([#21196](https://github.com/expo/expo/pull/21196) by [@gabrieldonadel](https://github.com/gabrieldonadel))
### 💡 Others
- Android: Switch from deprecated `toLowerCase` to `lowercase` function ([#22225](https://github.com/expo/expo/pull/22225) by [@hbiede](https://github.com/hbiede))
## 15.2.2 — 2023-02-09
_This version does not introduce any user-facing changes._
## 15.2.1 — 2023-02-09
### 🐛 Bug fixes
- Add utf-8 uri support on iOS. ([#21098](https://github.com/expo/expo/pull/21098) by [@gabrieldonadel](https://github.com/gabrieldonadel))
## 15.2.0 — 2023-02-03
### 💡 Others
- Extract nested object definitions to the separate types, which adds: `DeletingOptions`, `InfoOptions`, `RelocatingOptions` and `MakeDirectoryOptions` types. ([#20103](https://github.com/expo/expo/pull/20103) by [@Simek](https://github.com/Simek))
- Simplify the way in which types are exported from the package. ([#20103](https://github.com/expo/expo/pull/20103) by [@Simek](https://github.com/Simek))
- Rename `UploadProgressData` `totalByteSent` field to `totalBytesSent`. ([#20804](https://github.com/expo/expo/pull/20804) by [@gabrieldonadel](https://github.com/gabrieldonadel))
- On Android bump `compileSdkVersion` and `targetSdkVersion` to `33`. ([#20721](https://github.com/expo/expo/pull/20721) by [@lukmccall](https://github.com/lukmccall))
## 15.1.1 — 2022-10-28
_This version does not introduce any user-facing changes._
## 15.1.0 — 2022-10-25
### 🎉 New features
- Added `DirectoriesModule` to expo-file-system on Android as a temporary solution to fix cache directories being incorrect in new Sweet API modules. ([#19205](https://github.com/expo/expo/pull/19205) by [@aleqsio](https://github.com/aleqsio))
## 15.0.0 — 2022-10-06
### 🛠 Breaking changes
- Bumped iOS deployment target to 13.0 and deprecated support for iOS 12. ([#18873](https://github.com/expo/expo/pull/18873) by [@tsapeta](https://github.com/tsapeta))
### 💡 Others
- [plugin] Migrate import from @expo/config-plugins to expo/config-plugins and @expo/config-types to expo/config. ([#18855](https://github.com/expo/expo/pull/18855) by [@brentvatne](https://github.com/brentvatne))
- Drop `@expo/config-plugins` dependency in favor of peer dependency on `expo`. ([#18595](https://github.com/expo/expo/pull/18595) by [@EvanBacon](https://github.com/EvanBacon))
## 14.1.0 — 2022-07-07
_This version does not introduce any user-facing changes._
## 14.0.0 — 2022-04-18
### 🛠 Breaking changes
- Remove okhttp and okio backward compatible workaround and drop react-native 0.64 support. ([#16446](https://github.com/expo/expo/pull/16446) by [@kudo](https://github.com/kudo))
### 🐛 Bug fixes
- Fixed failing download on Android when using `createDownloadResumable()`, because of an invalid Range header. ([#15934](https://github.com/expo/expo/pull/15934) by [@johanpoirier](https://github.com/johanpoirier))
- Fix `Plugin with id 'maven' not found` build error from Android Gradle 7. ([#16080](https://github.com/expo/expo/pull/16080) by [@kudo](https://github.com/kudo))
- Fix URL scheme differences between iOS and Android. ([#16352](https://github.com/expo/expo/pull/16352) by [@hbiede](https://github.com/hbiede))
### ⚠️ Notices
- On Android bump `compileSdkVersion` to `31`, `targetSdkVersion` to `31` and `Java` version to `11`. ([#16941](https://github.com/expo/expo/pull/16941) by [@bbarthec](https://github.com/bbarthec))
## 13.2.1 — 2022-01-20
### 🐛 Bug fixes
- Fix build errors on React Native 0.66 caused by `okio` and `okhttp`. ([#15632](https://github.com/expo/expo/pull/15632) by [@kudo](https://github.com/kudo))
## 13.2.0 — 2021-12-22
### 🐛 Bug fixes
- Fixed runtime crash due to `.toUpperCase` not being invoked as a function, it was missing `()`. ([#15615](https://github.com/expo/expo/pull/15615) by [@lukebrandonfarrell](https://github.com/lukebrandonfarrell))
- Fixed `totalByteSent` in upload progress callback incorrectly sending `bytesSent` on iOS. ([#15615](https://github.com/expo/expo/pull/15615) by [@lukebrandonfarrell](https://github.com/lukebrandonfarrell))
- Fixed simulator runtime crash on arm64 devices caused by `CFRelease(NULL)`. ([#15496](https://github.com/expo/expo/pull/15496) by [@daxaxelrod](https://github.com/daxaxelrod))
### 💡 Others
- Updated `@expo/config-plugins` from `4.0.2` to `4.0.14` ([#15621](https://github.com/expo/expo/pull/15621) by [@EvanBacon](https://github.com/EvanBacon))
## 13.1.4 — 2022-02-10
_This version does not introduce any user-facing changes._
## 13.1.3 — 2022-02-01
### 🐛 Bug fixes
- Fix `Plugin with id 'maven' not found` build error from Android Gradle 7. ([#16080](https://github.com/expo/expo/pull/16080) by [@kudo](https://github.com/kudo))
## 13.1.2 — 2022-01-22
### 🐛 Bug fixes
- Fixed runtime crash due to `.toUpperCase` not being invoked as a function, it was missing `()`. ([#15615](https://github.com/expo/expo/pull/15615) by [@lukebrandonfarrell](https://github.com/lukebrandonfarrell))
- Fixed `totalByteSent` in upload progress callback incorrectly sending `bytesSent` on iOS. ([#15615](https://github.com/expo/expo/pull/15615) by [@lukebrandonfarrell](https://github.com/lukebrandonfarrell))
- Fixed simulator runtime crash on arm64 devices caused by `CFRelease(NULL)`. ([#15496](https://github.com/expo/expo/pull/15496) by [@daxaxelrod](https://github.com/daxaxelrod))
## 13.1.1 — 2022-01-20
### 🐛 Bug fixes
- Fix build errors on React Native 0.66 caused by `okio` and `okhttp`. ([#15632](https://github.com/expo/expo/pull/15632) by [@kudo](https://github.com/kudo))
## 13.1.0 — 2021-11-17
### 🐛 Bug fixes
- Fixed `uploadAsync` failing to resolve when using `BINARY_CONTENT`. ([#14764](https://github.com/expo/expo/pull/14764) by [@cruzach](https://github.com/cruzach))
- Fix `okio` library build error for `react-native@0.65` or above. ([#14761](https://github.com/expo/expo/pull/14761) by [@kudo](https://github.com/kudo))
## 13.0.1 — 2021-10-01
_This version does not introduce any user-facing changes._
## 13.0.0 — 2021-09-28
### 🛠 Breaking changes
- Dropped support for iOS 11.0 ([#14383](https://github.com/expo/expo/pull/14383) by [@cruzach](https://github.com/cruzach))
### 🐛 Bug fixes
- `getFreeDiskStorageAsync` now correctly reports free disk space on iOS. ([#14279](https://github.com/expo/expo/pull/14279) by [mickmaccallum](https://github.com/mickmaccallum))
- Fix building errors from use_frameworks! in Podfile. ([#14523](https://github.com/expo/expo/pull/14523) by [@kudo](https://github.com/kudo))
### 💡 Others
- Updated `@expo/config-plugins` ([#14443](https://github.com/expo/expo/pull/14443) by [@EvanBacon](https://github.com/EvanBacon))
- Rewritten module to Kotlin. ([#14549](https://github.com/expo/expo/pull/14549) by [@mstach60161](https://github.com/mstach60161))
## 12.0.0 — 2021-09-08
### 🛠 Breaking changes
- Added `AndroidManifest.xml` queries for intent handling. ([#13388](https://github.com/expo/expo/pull/13388) by [@EvanBacon](https://github.com/EvanBacon))
### 💡 Others
- Migrated from `@unimodules/core` to `expo-modules-core`. ([#13749](https://github.com/expo/expo/pull/13749) by [@tsapeta](https://github.com/tsapeta))
## 11.1.0 — 2021-06-16
### 🐛 Bug fixes
- Enable kotlin in all modules. ([#12716](https://github.com/expo/expo/pull/12716) by [@wschurman](https://github.com/wschurman))
- Fixed crash of file system when try to read cache dir file on android. ([#12716](https://github.com/expo/expo/pull/13232) by [@nomi9995](https://github.com/nomi9995))
### 💡 Others
- Migrated from `unimodules-file-system-interface` to `expo-modules-core`.
- Build Android code using Java 8 to fix Android instrumented test build error. ([#12939](https://github.com/expo/expo/pull/12939) by [@kudo](https://github.com/kudo))
- Refactored uuid imports to v7 style. ([#13037](https://github.com/expo/expo/pull/13037) by [@giautm](https://github.com/giautm))
## 11.0.2 — 2021-04-13
_This version does not introduce any user-facing changes._
## 11.0.1 — 2021-04-09
_This version does not introduce any user-facing changes._
## 11.0.0 — 2021-03-10
### 🎉 New features
- Converted plugin to TypeScript. ([#11715](https://github.com/expo/expo/pull/11715) by [@EvanBacon](https://github.com/EvanBacon))
- Updated Android build configuration to target Android 11 (added support for Android SDK 30). ([#11647](https://github.com/expo/expo/pull/11647) by [@bbarthec](https://github.com/bbarthec))
- Added support for Storage Access Framework (**Android only**). ([#12032](https://github.com/expo/expo/pull/12032) by [@lukmccall](https://github.com/lukmccall))
### 🐛 Bug fixes
- Fixed copying movies from assets not working on iOS. ([#11749](https://github.com/expo/expo/pull/11749) by [@lukmccall](https://github.com/lukmccall))
- Remove peerDependencies and unimodulePeerDependencies from Expo modules. ([#11980](https://github.com/expo/expo/pull/11980) by [@brentvatne](https://github.com/brentvatne))
## 10.0.0 — 2021-01-15
### ⚠️ Notices
- The package is now shipped with prebuilt binaries on iOS. You can read more about it on [expo.fyi/prebuilt-modules](https://expo.fyi/prebuilt-modules). ([#11224](https://github.com/expo/expo/pull/11224) by [@tsapeta](https://github.com/tsapeta))
### 🛠 Breaking changes
- Dropped support for iOS 10.0 ([#11344](https://github.com/expo/expo/pull/11344) by [@tsapeta](https://github.com/tsapeta))
### 🎉 New features
- Created config plugins ([#11538](https://github.com/expo/expo/pull/11538) by [@EvanBacon](https://github.com/EvanBacon))
## 9.3.0 — 2020-11-17
_This version does not introduce any user-facing changes._
## 9.2.0 — 2020-08-18
### 🐛 Bug fixes
- Added docs about Android permissions and removed old storage permission. ([#9447](https://github.com/expo/expo/pull/9447) by [@bycedric](https://github.com/bycedric))
## 9.1.0 — 2020-07-27
### 🐛 Bug fixes
- Fix background URL session completion handler not being called. ([#8599](https://github.com/expo/expo/pull/8599) by [@lukmccall](https://github.com/lukmccall))
- Fix compilation error on macOS Catalyst ([#9055](https://github.com/expo/expo/pull/9055) by [@andymatuschak](https://github.com/andymatuschak))
- Fixed `uploadAsync` native signature on Android. ([#9076](https://github.com/expo/expo/pull/9076) by [@lukmccall](https://github.com/lukmccall))
- Fixed `uploadAsync` throwing `Double cannot be cast to Integer` on Android. ([#9076](https://github.com/expo/expo/pull/9076) by [@lukmccall](https://github.com/lukmccall))
- Fixed `getInfo` returning incorrect size when provided path points to a folder. ([#9063](https://github.com/expo/expo/pull/9063) by [@lukmccall](https://github.com/lukmccall))
- Fixed `uploadAsync()` returning empty response on iOS. ([#9166](https://github.com/expo/expo/pull/9166) by [@barthap](https://github.com/barthap))
## 9.0.1 — 2020-05-29
_This version does not introduce any user-facing changes._
## 9.0.0 — 2020-05-27
### 🛠 Breaking changes
- `FileSystem.downloadAsync` and `FileSystem.DownloadResumable` work by default when the app is in background too — they won't reject when the application is backgrounded. ([#7380](https://github.com/expo/expo/pull/7380) by [@lukmccall](https://github.com/lukmccall))
- `FileSystem.downloadAsync` and `FileSystem.DownloadResumable` will reject when invalid headers dictionary is provided. These methods accept only `Record<string, string>`. ([#7380](https://github.com/expo/expo/pull/7380) by [@lukmccall](https://github.com/lukmccall))
- `FileSystem.getContentUriAsync` now returns a string. ([#7192](https://github.com/expo/expo/pull/7192) by [@lukmccall](https://github.com/lukmccall))
### 🎉 New features
- Add `FileSystem.uploadAsync` method. ([#7380](https://github.com/expo/expo/pull/7380) by [@lukmccall](https://github.com/lukmccall))
- Add ability to read Android `raw` and `drawable` resources in `FileSystem.getInfoAsync`, `FileSystem.readAsStringAsync`, and `FileSystem.copyAsync`. ([#8104](https://github.com/expo/expo/pull/8104) by [@esamelson](https://github.com/esamelson))

42
node_modules/expo-file-system/README.md generated vendored Normal file
View File

@@ -0,0 +1,42 @@
<p>
<a href="https://docs.expo.dev/versions/latest/sdk/filesystem/">
<img
src="../../.github/resources/expo-file-system.svg"
alt="expo-file-system"
height="64" />
</a>
</p>
Provides access to the local file system on the device.
# API documentation
- [Documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/filesystem/)
- [Documentation for the main branch](https://docs.expo.dev/versions/unversioned/sdk/filesystem/)
# Installation in managed Expo projects
For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/filesystem/).
# Installation in bare React Native projects
For bare React Native projects, you must ensure that you have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/) before continuing.
## Installation in bare Android React Native project
This module requires permissions to interact with the filesystem and create resumable downloads. The `READ_EXTERNAL_STORAGE`, `WRITE_EXTERNAL_STORAGE` and `INTERNET` permissions are automatically added.
```xml
<!-- Added permissions -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
```
## Installation in bare iOS React Native project
No additional set up necessary.
# Contributing
Contributions are very welcome! Please refer to guidelines described in the [contributing guide](https://github.com/expo/expo#contributing).

25
node_modules/expo-file-system/android/build.gradle generated vendored Normal file
View File

@@ -0,0 +1,25 @@
plugins {
id 'com.android.library'
id 'expo-module-gradle-plugin'
}
group = 'host.exp.exponent'
version = '55.0.10'
android {
namespace "expo.modules.filesystem"
defaultConfig {
versionCode 30
versionName "55.0.10"
}
}
dependencies {
api 'commons-codec:commons-codec:1.10'
api 'commons-io:commons-io:1.4'
api 'com.squareup.okhttp3:okhttp:4.9.2'
api 'com.squareup.okhttp3:okhttp-urlconnection:4.9.2'
api 'com.squareup.okio:okio:2.9.0'
api "androidx.legacy:legacy-support-v4:1.0.0"
api "androidx.documentfile:documentfile:1.1.0"
}

View File

@@ -0,0 +1,17 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<application>
<provider tools:replace="android:authorities" android:name=".FileSystemFileProvider" android:authorities="${applicationId}.FileSystemFileProvider" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_system_provider_paths" />
</provider>
</application>
<queries>
<!-- Query open documents -->
<intent>
<action android:name="android.intent.action.OPEN_DOCUMENT_TREE" />
</intent>
</queries>
</manifest>

View File

@@ -0,0 +1,71 @@
package expo.modules.filesystem
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.DocumentsContract
import expo.modules.kotlin.activityresult.AppContextActivityResultContract
import expo.modules.kotlin.providers.AppContextProvider
import java.io.Serializable
@SuppressLint("WrongConstant")
internal class FilePickerContract(private val appContextProvider: AppContextProvider) : AppContextActivityResultContract<FilePickerContractOptions, FilePickerContractResult> {
private val contentResolver: ContentResolver
get() = requireNotNull(appContextProvider.appContext.reactContext) {
"React Application Context is null"
}.contentResolver
override fun createIntent(context: Context, input: FilePickerContractOptions): Intent =
if (input.pickerType == PickerType.FILE) {
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
// if no type is set no intent handler is found just android things
type = input.mimeType ?: "*/*"
}
} else {
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
}.also { intent ->
// intent.addCategory(Intent.CATEGORY_OPENABLE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
input.initialUri.let { intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, it) }
}
}
override fun parseResult(input: FilePickerContractOptions, resultCode: Int, intent: Intent?): FilePickerContractResult =
if (resultCode == Activity.RESULT_CANCELED || intent == null) {
FilePickerContractResult.Cancelled
} else {
val uri = intent.data
val takeFlags = (intent.flags.and((Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)))
uri?.let {
contentResolver.takePersistableUriPermission(it, takeFlags)
}
when (input.pickerType) {
PickerType.DIRECTORY -> FilePickerContractResult.Success(
FileSystemDirectory(
uri
?: Uri.EMPTY
)
)
PickerType.FILE -> {
FilePickerContractResult.Success(FileSystemFile(uri ?: Uri.EMPTY))
}
}
}
}
internal data class FilePickerContractOptions(val initialUri: Uri?, val mimeType: String? = null, val pickerType: PickerType = PickerType.FILE) : Serializable
internal enum class PickerType {
FILE,
DIRECTORY
}
internal sealed class FilePickerContractResult {
class Success(val path: FileSystemPath) : FilePickerContractResult()
object Cancelled : FilePickerContractResult()
}

View File

@@ -0,0 +1,118 @@
package expo.modules.filesystem
import android.net.Uri
import expo.modules.kotlin.services.FilePermissionService
class FileSystemDirectory(uri: Uri) : FileSystemPath(uri) {
fun validatePath() {
// Kept empty for now, but can be used to validate if the path is a valid directory path.
}
override fun validateType() {
if (file.exists() && !file.isDirectory()) {
throw InvalidTypeFolderException()
}
}
val exists: Boolean get() {
return if (checkPermission(FilePermissionService.Permission.READ)) {
file.isDirectory()
} else {
false
}
}
val size: Long get() {
validatePermission(FilePermissionService.Permission.READ)
validateType()
return file.walkTopDown().filter { it.isFile() }.map { it.length() }.sum()
}
fun info(): DirectoryInfo {
validateType()
validatePermission(FilePermissionService.Permission.READ)
if (!file.exists()) {
val directoryInfo = DirectoryInfo(
exists = false,
uri = slashifyFilePath(file.uri.toString())
)
return directoryInfo
}
val directoryInfo = DirectoryInfo(
exists = true,
uri = slashifyFilePath(file.uri.toString()),
files = file.listFilesAsUnified().mapNotNull { i -> i.fileName },
modificationTime = modificationTime,
creationTime = creationTime,
size = size
)
return directoryInfo
}
fun create(options: CreateOptions = CreateOptions()) {
validateType()
validatePermission(FilePermissionService.Permission.WRITE)
if (!needsCreation(options)) {
return
}
if (uri.isContentUri) {
throw UnableToCreateException("create function does not work with SAF Uris, use `createDirectory` and `createFile` instead")
}
validateCanCreate(options)
if (options.overwrite && file.exists()) {
file.delete()
}
val created = if (options.intermediates) {
javaFile.mkdirs()
} else {
javaFile.mkdir()
}
if (!created) {
throw UnableToCreateException("directory already exists or could not be created")
}
}
fun createFile(mimeType: String?, fileName: String): FileSystemFile {
validateType()
validatePermission(FilePermissionService.Permission.WRITE)
val newFile = file.createFile(mimeType ?: "text/plain", fileName)
?: throw UnableToCreateException("file could not be created")
return FileSystemFile(newFile.uri)
}
fun createDirectory(fileName: String): FileSystemDirectory {
validateType()
validatePermission(FilePermissionService.Permission.WRITE)
val newDirectory = file.createDirectory(fileName)
?: throw UnableToCreateException("directory could not be created")
return FileSystemDirectory(newDirectory.uri)
}
// this function is internal and will be removed in the future (when returning arrays of shared objects is supported)
fun listAsRecords(): List<Map<String, Any>> {
validateType()
validatePermission(FilePermissionService.Permission.READ)
return file.listFilesAsUnified().map {
val uriString = it.uri.toString()
val isDir = it.isDirectory()
mapOf(
"isDirectory" to isDir,
"uri" to if (isDir) {
if (uriString.endsWith("/")) uriString else "$uriString/"
} else {
uriString
}
)
}
}
fun asString(): String {
val uriString = file.uri.toString()
return if (uriString.endsWith("/")) uriString else "$uriString/"
}
fun needsCreation(options: CreateOptions): Boolean {
return !file.exists() || !options.idempotent
}
}

View File

@@ -0,0 +1,58 @@
package expo.modules.filesystem
import expo.modules.kotlin.exception.CodedException
import expo.modules.kotlin.services.FilePermissionService
internal class CopyOrMoveDirectoryToFileException :
CodedException("Unable to copy or move a folder to a file")
internal class InvalidTypeFolderException :
CodedException("A file with the same name already exists in the folder location")
internal class InvalidTypeFileException :
CodedException("A folder with the same name already exists in the file location")
internal class DestinationDoesNotExistException :
CodedException("The destination path does not exist")
internal class UnableToDownloadException(reason: String) :
CodedException(
"Unable to download a file: $reason"
)
internal class UnableToDeleteException(reason: String) :
CodedException(
"Unable to delete file or directory: $reason"
)
internal class UnableToCreateException(reason: String) :
CodedException(
"Unable to create file or directory: $reason"
)
internal class InvalidPermissionException(permission: FilePermissionService.Permission) :
CodedException(
"Missing '${permission.name}' permission for accessing the file."
)
internal class UnableToReadHandleException(reason: String) :
CodedException(
"Unable to read from a file handle: '$reason'"
)
internal class UnableToWriteHandleException(reason: String) :
CodedException(
"Unable to write to a file handle: '$reason'"
)
internal class MissingAppContextException :
CodedException(
"The app context is missing."
)
internal class PickerCancelledException :
CodedException("The file picker was cancelled by the user")
internal class DestinationAlreadyExistsException :
CodedException(
"Destination already exists"
)

View File

@@ -0,0 +1,177 @@
package expo.modules.filesystem
import android.net.Uri
import android.util.Base64
import expo.modules.kotlin.services.FilePermissionService
import expo.modules.kotlin.typedarray.TypedArray
import java.io.FileOutputStream
import java.security.MessageDigest
class FileSystemFile(uri: Uri) : FileSystemPath(uri) {
// Kept empty for now, but can be used to validate if the uri is a valid file uri. // TODO: Move to the constructor once also moved on iOS
fun validatePath() {
}
// This makes sure that if a file already exists at a location, it is the correct type so that all available operations perform as expected.
// After calling this function, we can use the `isDirectory` and `isFile` functions safely as they will match the shared class used.
override fun validateType() {
validatePermission(FilePermissionService.Permission.READ)
if (file.exists() && file.isDirectory()) {
throw InvalidTypeFileException()
}
}
val exists: Boolean get() {
return if (checkPermission(FilePermissionService.Permission.READ)) {
file.isFile()
} else {
false
}
}
fun create(options: CreateOptions = CreateOptions()) {
validateType()
validatePermission(FilePermissionService.Permission.WRITE)
validateCanCreate(options)
if (uri.isContentUri) {
throw UnableToCreateException("create function does not work with SAF Uris, use `createDirectory` and `createFile` instead")
}
if (options.overwrite && exists) {
javaFile.delete()
}
if (options.intermediates) {
javaFile.parentFile?.mkdirs()
}
val created = javaFile.createNewFile()
if (!created) {
throw UnableToCreateException("file already exists or could not be created")
}
}
fun write(content: String, append: Boolean = false) {
validateType()
validatePermission(FilePermissionService.Permission.WRITE)
if (!exists) {
create()
}
file.outputStream(append).use { outputStream ->
outputStream.write(content.toByteArray())
}
}
fun write(content: TypedArray, append: Boolean = false) {
validateType()
validatePermission(FilePermissionService.Permission.WRITE)
if (!exists) {
create()
}
if (uri.isContentUri) {
file.outputStream(append).use { outputStream ->
val array = ByteArray(content.length)
content.toDirectBuffer().get(array)
outputStream.write(array)
}
} else {
FileOutputStream(javaFile, append).use {
it.channel.write(content.toDirectBuffer())
}
}
}
fun write(content: ByteArray, append: Boolean = false) {
validateType()
validatePermission(FilePermissionService.Permission.WRITE)
if (!exists) {
create()
}
if (uri.isContentUri) {
file.outputStream(append).use { outputStream ->
outputStream.write(content)
}
} else {
FileOutputStream(javaFile, append).use {
it.write(content)
}
}
}
fun asString(): String {
val uriString = file.uri.toString()
return if (uriString.endsWith("/")) uriString.dropLast(1) else uriString
}
fun text(): String {
validateType()
validatePermission(FilePermissionService.Permission.READ)
return file.inputStream().use { inputStream ->
inputStream.bufferedReader().use { it.readText() }
}
}
fun base64(): String {
validateType()
validatePermission(FilePermissionService.Permission.READ)
file.inputStream().use {
return Base64.encodeToString(it.readBytes(), Base64.NO_WRAP)
}
}
fun bytes(): ByteArray {
validateType()
validatePermission(FilePermissionService.Permission.READ)
file.inputStream().use {
return it.readBytes()
}
}
fun asContentUri(): Uri {
validateType()
validatePermission(FilePermissionService.Permission.READ)
return file.getContentUri(appContext ?: throw MissingAppContextException())
}
@OptIn(ExperimentalStdlibApi::class)
val md5: String get() {
validatePermission(FilePermissionService.Permission.READ)
val md = MessageDigest.getInstance("MD5")
file.inputStream().use {
val digest = md.digest(it.readBytes())
return digest.toHexString()
}
}
val size: Long? get() {
return if (file.exists()) {
file.length()
} else {
null
}
}
val type: String? get() {
return file.type
}
fun info(options: InfoOptions?): FileInfo {
validateType()
validatePermission(FilePermissionService.Permission.READ)
if (!file.exists()) {
val fileInfo = FileInfo(
exists = false,
uri = slashifyFilePath(file.uri.toString())
)
return fileInfo
}
val fileInfo = FileInfo(
exists = true,
uri = slashifyFilePath(file.uri.toString()),
size = size,
modificationTime = modificationTime,
creationTime = creationTime
)
if (options != null && options.md5 == true) {
fileInfo.md5 = md5
}
return fileInfo
}
}

View File

@@ -0,0 +1,85 @@
package expo.modules.filesystem
import expo.modules.kotlin.sharedobjects.SharedRef
import java.io.RandomAccessFile
import java.nio.ByteBuffer
import java.nio.channels.FileChannel
import kotlin.math.min
class FileSystemFileHandle(file: FileSystemFile) : SharedRef<FileChannel>(RandomAccessFile(file.javaFile, "rw").channel), AutoCloseable {
private val fileChannel: FileChannel = ref
private fun ensureIsOpen() {
if (!fileChannel.isOpen) {
throw UnableToReadHandleException("file handle is closed")
}
}
override fun sharedObjectDidRelease() {
close()
}
override fun close() {
fileChannel.close()
}
fun read(length: Long): ByteArray {
ensureIsOpen()
try {
val currentPosition = fileChannel.position()
val totalSize = fileChannel.size()
val available = totalSize - currentPosition
val readAmount = min(length, available).coerceAtMost(Int.MAX_VALUE.toLong()).toInt()
if (readAmount <= 0) {
return ByteArray(0)
}
val buffer = ByteBuffer.allocate(readAmount)
var bytesRead = 0
while (bytesRead < readAmount) {
val result = fileChannel.read(buffer)
if (result == -1) break
bytesRead += result
}
return buffer.array()
} catch (e: Exception) {
throw UnableToReadHandleException(e.message ?: "unknown error")
}
}
fun write(data: ByteArray) {
ensureIsOpen()
try {
val buffer = ByteBuffer.wrap(data)
while (buffer.hasRemaining()) {
fileChannel.write(buffer)
}
} catch (e: Exception) {
throw UnableToWriteHandleException(e.message ?: "unknown error")
}
}
var offset: Long?
get() {
return try {
fileChannel.position()
} catch (e: Exception) {
null
}
}
set(value) {
if (value == null) return
fileChannel.position(value)
}
val size: Long?
get() {
return try {
fileChannel.size()
} catch (e: Exception) {
null
}
}
}

View File

@@ -0,0 +1,5 @@
package expo.modules.filesystem
import androidx.core.content.FileProvider
class FileSystemFileProvider : FileProvider()

View File

@@ -0,0 +1,335 @@
package expo.modules.filesystem
import android.content.Context
import android.net.Uri
import android.os.Build
import android.util.Base64
import android.webkit.URLUtil
import androidx.annotation.RequiresApi
import expo.modules.kotlin.activityresult.AppContextActivityResultLauncher
import expo.modules.kotlin.devtools.await
import expo.modules.kotlin.exception.Exceptions
import expo.modules.kotlin.functions.Coroutine
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import expo.modules.kotlin.services.FilePermissionService
import expo.modules.kotlin.typedarray.TypedArray
import expo.modules.kotlin.types.Either
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.File
import java.io.FileOutputStream
import java.net.URI
class FileSystemModule : Module() {
private val context: Context
get() = appContext.reactContext ?: throw Exceptions.AppContextLost()
@RequiresApi(Build.VERSION_CODES.O)
override fun definition() = ModuleDefinition {
Name("FileSystem")
Constant("documentDirectory") {
Uri.fromFile(context.filesDir).toString() + "/"
}
Constant("cacheDirectory") {
Uri.fromFile(context.cacheDir).toString() + "/"
}
Constant("bundleDirectory") {
"asset://"
}
Property("totalDiskSpace") {
File(context.filesDir.path).totalSpace
}
Property("availableDiskSpace") {
File(context.filesDir.path).freeSpace
}
AsyncFunction("downloadFileAsync") Coroutine { url: URI, to: FileSystemPath, options: DownloadOptions? ->
to.validatePermission(FilePermissionService.Permission.WRITE)
val requestBuilder = Request.Builder().url(url.toURL())
options?.headers?.forEach { (key, value) ->
requestBuilder.addHeader(key, value)
}
val request = requestBuilder.build()
val client = OkHttpClient()
val response = request.await(client)
if (!response.isSuccessful) {
throw UnableToDownloadException("response has status: ${response.code}")
}
val contentDisposition = response.headers["content-disposition"]
val contentType = response.headers["content-type"]
val fileName = URLUtil.guessFileName(url.toString(), contentDisposition, contentType)
val destination = if (to is FileSystemDirectory) {
File(to.javaFile, fileName)
} else {
to.javaFile
}
if (options?.idempotent != true && destination.exists()) {
throw DestinationAlreadyExistsException()
}
val body = response.body ?: throw UnableToDownloadException("response body is null")
body.byteStream().use { input ->
FileOutputStream(destination).use { output ->
input.copyTo(output)
}
}
return@Coroutine destination.toURI()
}
lateinit var filePickerLauncher: AppContextActivityResultLauncher<FilePickerContractOptions, FilePickerContractResult>
RegisterActivityContracts {
filePickerLauncher = registerForActivityResult(
FilePickerContract(this@FileSystemModule)
)
}
AsyncFunction("pickDirectoryAsync") Coroutine { initialUri: Uri? ->
val result = filePickerLauncher.launch(
FilePickerContractOptions(initialUri, null, PickerType.DIRECTORY)
)
when (result) {
is FilePickerContractResult.Success -> result.path as FileSystemDirectory
is FilePickerContractResult.Cancelled -> throw PickerCancelledException()
}
}
AsyncFunction("pickFileAsync") Coroutine { initialUri: Uri?, mimeType: String? ->
val result = filePickerLauncher.launch(
FilePickerContractOptions(initialUri, mimeType, PickerType.FILE)
)
when (result) {
is FilePickerContractResult.Success -> result.path as FileSystemFile
is FilePickerContractResult.Cancelled -> throw PickerCancelledException()
}
}
Function("info") { url: URI ->
val file = File(url)
val permissions = appContext
.filePermission
.getPathPermissions(
appContext.reactContext ?: throw Exceptions.ReactContextLost(),
file.path
)
if (permissions.contains(FilePermissionService.Permission.READ) && file.exists()) {
PathInfo(exists = file.exists(), isDirectory = file.isDirectory)
} else {
PathInfo(exists = false, isDirectory = null)
}
}
Class(FileSystemFile::class) {
Constructor { uri: Uri ->
FileSystemFile(uri)
}
Function("delete") { file: FileSystemFile ->
file.delete()
}
Function("validatePath") { file: FileSystemFile ->
file.validatePath()
}
Function("create") { file: FileSystemFile, options: CreateOptions? ->
file.create(options ?: CreateOptions())
}
Function("write") { file: FileSystemFile, content: Either<String, TypedArray>, options: WriteOptions? ->
val append = options?.append ?: false
if (content.`is`(String::class)) {
content.get(String::class).let {
if (options?.encoding == EncodingType.BASE64) {
file.write(Base64.decode(it, Base64.DEFAULT), append)
} else {
file.write(it, append)
}
}
}
if (content.`is`(TypedArray::class)) {
content.get(TypedArray::class).let {
file.write(it, append)
}
}
}
AsyncFunction("text") { file: FileSystemFile ->
file.text()
}
Function("textSync") { file: FileSystemFile ->
file.text()
}
AsyncFunction("base64") { file: FileSystemFile ->
file.base64()
}
Function("base64Sync") { file: FileSystemFile ->
file.base64()
}
AsyncFunction("bytes") { file: FileSystemFile ->
file.bytes()
}
Function("bytesSync") { file: FileSystemFile ->
file.bytes()
}
Function("info") { file: FileSystemFile, options: InfoOptions? ->
file.info(options)
}
Property("exists") { file: FileSystemFile ->
file.exists
}
Property("modificationTime") { file: FileSystemFile ->
file.modificationTime
}
Property("creationTime") { file: FileSystemFile ->
file.creationTime
}
Function("copy") { file: FileSystemFile, destination: FileSystemPath ->
file.copy(destination)
}
Function("move") { file: FileSystemFile, destination: FileSystemPath ->
file.move(destination)
}
Function("rename") { file: FileSystemFile, newName: String ->
file.rename(newName)
}
Property("uri") { file ->
file.asString()
}
Property("contentUri") { file ->
file.asContentUri()
}
Property("md5") { file ->
try {
file.md5
} catch (e: Exception) {
null
}
}
Property("size") { file ->
try {
file.size
} catch (e: Exception) {
null
}
}
Property("type") { file ->
file.type
}
Function("open") { file: FileSystemFile ->
FileSystemFileHandle(file)
}
}
Class(FileSystemFileHandle::class) {
Constructor { file: FileSystemFile ->
FileSystemFileHandle(file)
}
Function("readBytes") { fileHandle: FileSystemFileHandle, bytes: Long ->
fileHandle.read(bytes)
}
Function("writeBytes") { fileHandle: FileSystemFileHandle, data: ByteArray ->
fileHandle.write(data)
}
Function("close") { fileHandle: FileSystemFileHandle ->
fileHandle.close()
}
Property("offset") { fileHandle: FileSystemFileHandle ->
fileHandle.offset
}.set { fileHandle: FileSystemFileHandle, offset: Long ->
fileHandle.offset = offset
}
Property("size") { fileHandle: FileSystemFileHandle ->
fileHandle.size
}
}
Class(FileSystemDirectory::class) {
Constructor { uri: Uri ->
FileSystemDirectory(uri)
}
Function("info") { directory: FileSystemDirectory ->
directory.info()
}
Function("delete") { directory: FileSystemDirectory ->
directory.delete()
}
Function("create") { directory: FileSystemDirectory, options: CreateOptions? ->
directory.create(options ?: CreateOptions())
}
Function("createDirectory") { file: FileSystemDirectory, name: String ->
return@Function file.createDirectory(name)
}
Function("createFile") { file: FileSystemDirectory, name: String, mimeType: String? ->
return@Function file.createFile(mimeType, name)
}
Property("exists") { directory: FileSystemDirectory ->
directory.exists
}
Function("validatePath") { directory: FileSystemDirectory ->
directory.validatePath()
}
Function("copy") { directory: FileSystemDirectory, destination: FileSystemPath ->
directory.copy(destination)
}
Function("move") { directory: FileSystemDirectory, destination: FileSystemPath ->
directory.move(destination)
}
Function("rename") { directory: FileSystemDirectory, newName: String ->
directory.rename(newName)
}
Property("uri") { directory ->
directory.asString()
}
Property("size") { directory ->
directory.size
}
// this function is internal and will be removed in the future (when returning arrays of shared objects is supported)
Function("listAsRecords") { directory: FileSystemDirectory ->
directory.listAsRecords()
}
}
}
}

View File

@@ -0,0 +1,62 @@
package expo.modules.filesystem
import expo.modules.kotlin.records.Field
import expo.modules.kotlin.records.Record
import expo.modules.kotlin.types.Enumerable
data class InfoOptions(
@Field
val md5: Boolean?
) : Record
data class CreateOptions(
@Field
val intermediates: Boolean = false,
@Field
val overwrite: Boolean = false,
@Field
val idempotent: Boolean = false
) : Record
enum class EncodingType(val value: String) : Enumerable {
UTF8("utf8"),
BASE64("base64")
}
data class WriteOptions(
@Field
val encoding: EncodingType = EncodingType.UTF8,
@Field
val append: Boolean = false
) : Record
data class DownloadOptions(
@Field
val headers: Map<String, String> = emptyMap(),
@Field
val idempotent: Boolean = false
) : Record
data class FileInfo(
@Field var exists: Boolean,
@Field var uri: String?,
@Field var md5: String? = null,
@Field var size: Long? = null,
@Field var modificationTime: Long? = null,
@Field var creationTime: Long? = null
) : Record
data class PathInfo(
@Field var exists: Boolean,
@Field var isDirectory: Boolean?
) : Record
data class DirectoryInfo(
@Field var exists: Boolean,
@Field var uri: String?,
@Field var files: List<String>? = null,
@Field var md5: String? = null,
@Field var size: Long? = null,
@Field var modificationTime: Long? = null,
@Field var creationTime: Long? = null
) : Record

View File

@@ -0,0 +1,188 @@
package expo.modules.filesystem
import android.net.Uri
import android.os.Build
import androidx.core.net.toUri
import expo.modules.filesystem.unifiedfile.AssetFile
import expo.modules.filesystem.unifiedfile.JavaFile
import expo.modules.filesystem.unifiedfile.SAFDocumentFile
import expo.modules.filesystem.unifiedfile.UnifiedFileInterface
import expo.modules.kotlin.exception.Exceptions
import expo.modules.kotlin.services.FilePermissionService
import expo.modules.kotlin.sharedobjects.SharedObject
import java.io.File
import java.util.EnumSet
import java.util.regex.Pattern
import kotlin.io.path.moveTo
val Uri.isContentUri
get(): Boolean {
return scheme == "content"
}
val Uri.isAssetUri
get(): Boolean {
return scheme == "asset"
}
fun slashifyFilePath(path: String?): String? {
return if (path == null) {
null
} else if (path.startsWith("file:///")) {
path
} else {
// Ensure leading schema with a triple slash
Pattern.compile("^file:/*").matcher(path).replaceAll("file:///")
}
}
abstract class FileSystemPath(var uri: Uri) : SharedObject() {
private var fileAdapter: UnifiedFileInterface? = null
val file: UnifiedFileInterface
get() {
val currentAdapter = fileAdapter
if (currentAdapter?.uri == uri) {
return currentAdapter
}
val newAdapter = if (uri.isContentUri) {
SAFDocumentFile(appContext?.reactContext ?: throw Exception("No context"), uri)
} else if (uri.isAssetUri) {
AssetFile(appContext?.reactContext ?: throw Exception("No context"), uri)
} else {
JavaFile(uri)
}
fileAdapter = newAdapter
return newAdapter
}
val javaFile: File
get() =
if (uri.isContentUri) {
throw Exception("This method cannot be used with content URIs: $uri")
} else {
(file as File)
}
fun delete() {
if (!file.exists()) {
throw UnableToDeleteException("uri '${file.uri}' does not exist")
}
if (file.isDirectory()) {
if (!file.deleteRecursively()) {
throw UnableToDeleteException("failed to delete '${file.uri}'")
}
} else {
if (!file.delete()) {
throw UnableToDeleteException("failed to delete '${file.uri}'")
}
}
}
abstract fun validateType()
fun getMoveOrCopyPath(destination: FileSystemPath): File {
if (destination is FileSystemDirectory) {
if (this is FileSystemFile) {
if (!destination.exists) {
throw DestinationDoesNotExistException()
}
return File(destination.javaFile, javaFile.name)
}
// this if FileSystemDirectory
// we match unix behavior https://askubuntu.com/a/763915
if (destination.exists) {
return File(destination.javaFile, javaFile.name)
}
if (destination.javaFile.parentFile?.exists() != true) {
throw DestinationDoesNotExistException()
}
return destination.javaFile
}
// destination is FileSystemFile
if (this !is FileSystemFile) {
throw CopyOrMoveDirectoryToFileException()
}
if (destination.javaFile.parentFile?.exists() != true) {
throw DestinationDoesNotExistException()
}
return destination.javaFile
}
fun validatePermission(permission: FilePermissionService.Permission) {
if (!checkPermission(permission)) {
throw InvalidPermissionException(permission)
}
}
fun checkPermission(permission: FilePermissionService.Permission): Boolean {
if (uri.isContentUri) {
// TODO: Consider adding a check for content URIs (not in legacy FS)
return true
}
if (uri.isAssetUri) {
// TODO: Consider adding a check for asset URIs this returns asset files of Expo Go (such as root-cert), but these are already freely available on apk mirrors ect.
return true
}
val permissions = appContext?.filePermission?.getPathPermissions(
appContext?.reactContext ?: throw Exceptions.ReactContextLost(), javaFile.path
) ?: EnumSet.noneOf(FilePermissionService.Permission::class.java)
return permissions.contains(permission)
}
fun validateCanCreate(options: CreateOptions) {
if (!options.overwrite && file.exists()) {
throw UnableToCreateException("it already exists")
}
}
fun copy(to: FileSystemPath) {
validateType()
to.validateType()
validatePermission(FilePermissionService.Permission.READ)
to.validatePermission(FilePermissionService.Permission.WRITE)
javaFile.copyRecursively(getMoveOrCopyPath(to))
}
fun move(to: FileSystemPath) {
validateType()
to.validateType()
validatePermission(FilePermissionService.Permission.WRITE)
to.validatePermission(FilePermissionService.Permission.WRITE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val destination = getMoveOrCopyPath(to)
javaFile.toPath().moveTo(destination.toPath())
uri = destination.toUri()
} else {
javaFile.copyTo(getMoveOrCopyPath(to))
javaFile.delete()
uri = getMoveOrCopyPath(to).toUri()
}
}
fun rename(newName: String) {
validateType()
validatePermission(FilePermissionService.Permission.WRITE)
val newFile = File(javaFile.parent, newName)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
javaFile.toPath().moveTo(newFile.toPath())
uri = newFile.toUri()
} else {
javaFile.copyTo(newFile)
javaFile.delete()
uri = newFile.toUri()
}
}
val modificationTime: Long?
get() {
validateType()
return file.lastModified()
}
val creationTime: Long?
get() {
return file.creationTime
}
}

View File

@@ -0,0 +1,52 @@
package expo.modules.filesystem.legacy
import okhttp3.RequestBody
import okio.Buffer
import okio.BufferedSink
import okio.ForwardingSink
import okio.Sink
import okio.buffer
import java.io.IOException
@FunctionalInterface
fun interface RequestBodyDecorator {
fun decorate(requestBody: RequestBody): RequestBody
}
@FunctionalInterface
interface CountingRequestListener {
fun onProgress(bytesWritten: Long, contentLength: Long)
}
private class CountingSink(
sink: Sink,
private val requestBody: RequestBody,
private val progressListener: CountingRequestListener
) : ForwardingSink(sink) {
private var bytesWritten = 0L
override fun write(source: Buffer, byteCount: Long) {
super.write(source, byteCount)
bytesWritten += byteCount
progressListener.onProgress(bytesWritten, requestBody.contentLength())
}
}
class CountingRequestBody(
private val requestBody: RequestBody,
private val progressListener: CountingRequestListener
) : RequestBody() {
override fun contentType() = requestBody.contentType()
@Throws(IOException::class)
override fun contentLength() = requestBody.contentLength()
override fun writeTo(sink: BufferedSink) {
val countingSink = CountingSink(sink, this, progressListener)
val bufferedSink = countingSink.buffer()
requestBody.writeTo(bufferedSink)
bufferedSink.flush()
}
}

View File

@@ -0,0 +1,48 @@
package expo.modules.filesystem.legacy
import android.net.Uri
import expo.modules.kotlin.exception.CodedException
internal class FileSystemOkHttpNullException :
CodedException("okHttpClient is null")
internal class FileSystemCannotReadDirectoryException(uri: Uri?) :
CodedException("Uri '$uri' doesn't exist or isn't a directory")
internal class FileSystemCannotCreateDirectoryException(uri: Uri?) :
CodedException(
uri?.let {
"Directory '$it' could not be created or already exists"
} ?: "Unknown error"
)
internal class FileSystemUnreadableDirectoryException(uri: String) :
CodedException("No readable files with the uri '$uri'. Please use other uri")
internal class FileSystemCannotCreateFileException(uri: Uri?) :
CodedException(
uri?.let {
"Provided uri '$it' is not pointing to a directory"
} ?: "Unknown error"
)
internal class FileSystemFileNotFoundException(uri: Uri?) :
CodedException("File '$uri' could not be deleted because it could not be found")
internal class FileSystemPendingPermissionsRequestException :
CodedException("You have an unfinished permission request")
internal class FileSystemCannotMoveFileException(fromUri: Uri, toUri: Uri) :
CodedException("File '$fromUri' could not be moved to '$toUri'")
internal class FileSystemUnsupportedSchemeException :
CodedException("Can't read Storage Access Framework directory, use StorageAccessFramework.readDirectoryAsync() instead")
internal class FileSystemCannotFindTaskException :
CodedException("Cannot find task")
internal class FileSystemCopyFailedException(uri: Uri?) :
CodedException("File '$uri' could not be copied because it could not be found")
internal class CookieHandlerNotFoundException :
CodedException("Failed to find CookieHandler")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,92 @@
package expo.modules.filesystem.legacy
import expo.modules.kotlin.records.Field
import expo.modules.kotlin.records.Record
import expo.modules.kotlin.types.Enumerable
data class InfoOptionsLegacy(
@Field
val md5: Boolean?
) : Record
data class DeletingOptions(
@Field
val idempotent: Boolean = false
) : Record
data class ReadingOptions(
@Field
val encoding: EncodingType = EncodingType.UTF8,
@Field
val position: Int?,
@Field
val length: Int?
) : Record
enum class EncodingType(val value: String) : Enumerable {
UTF8("utf8"),
BASE64("base64")
}
enum class SessionType(val value: Int) : Enumerable {
BACKGROUND(0),
FOREGROUND(1)
}
enum class FileSystemUploadType(val value: Int) : Enumerable {
BINARY_CONTENT(0),
MULTIPART(1)
}
data class MakeDirectoryOptions(
@Field
val intermediates: Boolean = false
) : Record
data class RelocatingOptions(
@Field
val from: String,
@Field
val to: String
) : Record
data class DownloadOptionsLegacy(
@Field
val md5: Boolean = false,
@Field
val cache: Boolean?,
@Field
val headers: Map<String, String>?,
@Field
val sessionType: SessionType = SessionType.BACKGROUND
) : Record
data class WritingOptions(
@Field
val encoding: EncodingType = EncodingType.UTF8,
@Field
val append: Boolean = false
) : Record
data class FileSystemUploadOptions(
@Field
val headers: Map<String, String>?,
@Field
val httpMethod: HttpMethod = HttpMethod.POST,
@Field
val sessionType: SessionType = SessionType.BACKGROUND,
@Field
val uploadType: FileSystemUploadType,
@Field
val fieldName: String?,
@Field
val mimeType: String?,
@Field
val parameters: Map<String, String>?
) : Record
enum class HttpMethod(val value: String) : Enumerable {
POST("POST"),
PUT("PUT"),
PATCH("PATCH")
}

View File

@@ -0,0 +1,143 @@
package expo.modules.filesystem.unifiedfile
import android.content.Context
import android.net.Uri
import android.webkit.MimeTypeMap
import androidx.core.net.toUri
import expo.modules.kotlin.AppContext
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
class AssetFile(private val context: Context, override val uri: Uri) : UnifiedFileInterface {
val path: String = uri.path?.trimStart('/') ?: throw IllegalArgumentException("Invalid asset URI: $uri")
override fun exists(): Boolean = isDirectory() || isFile()
override fun isDirectory(): Boolean {
return context.assets.list(path)?.isNotEmpty() == true
}
override fun isFile(): Boolean {
return runCatching {
context.assets.open(path).use { true }
}.getOrElse {
false
}
}
// Cache the content URI to avoid redundant file writes assets are read-only
var contentUri: Uri? = null
override fun getContentUri(appContext: AppContext): Uri {
inputStream().use { inputStream ->
val outputFile = File(context.cacheDir, "expo_shared_assets/$fileName")
outputFile.parentFile?.mkdirs() // Create directories if needed
FileOutputStream(outputFile).use { outputStream ->
inputStream.copyTo(outputStream)
}
val newContentUri = JavaFile(outputFile.toUri()).getContentUri(appContext)
contentUri = newContentUri
return newContentUri
}
}
override val parentFile: UnifiedFileInterface?
get() {
val currentPath = uri.path.orEmpty()
if (currentPath.isEmpty()) {
return null
}
val parentPath = currentPath.substringBeforeLast('/')
val parentUri = "asset:///$parentPath".toUri()
return AssetFile(context, parentUri)
}
override fun createFile(mimeType: String, displayName: String): UnifiedFileInterface? {
throw UnsupportedOperationException("Asset files are not writable and cannot be created")
}
override fun createDirectory(displayName: String): UnifiedFileInterface? {
throw UnsupportedOperationException("Asset directories are not writable and cannot be created")
}
override fun delete(): Boolean = throw UnsupportedOperationException("Asset files are not writable and cannot be deleted")
override fun deleteRecursively(): Boolean = throw UnsupportedOperationException("Asset files are not writable and cannot be deleted")
override fun listFilesAsUnified(): List<UnifiedFileInterface> {
val list = context.assets.list(path)
return list?.map { name ->
val childPath = if (path.isEmpty()) name else "$path/$name"
AssetFile(context, "asset:///$childPath".toUri()) as UnifiedFileInterface
} ?: emptyList()
}
override val type: String?
get() {
val extension = MimeTypeMap.getFileExtensionFromUrl(uri.toString())
return if (extension.isNotEmpty()) {
MimeTypeMap
.getSingleton()
.getMimeTypeFromExtension(extension.lowercase())
} else {
null
}
}
override fun lastModified(): Long? {
return null
}
override val fileName: String?
get() = uri.lastPathSegment
override val creationTime: Long? get() {
return null
}
override fun outputStream(append: Boolean): OutputStream {
throw UnsupportedOperationException("Asset files are not writable")
}
override fun inputStream(): InputStream {
return context.assets.open(path)
}
override fun length(): Long {
runCatching {
context.assets.openFd(path).use { assetFileDescriptor ->
val length = assetFileDescriptor.length
if (length > 0) {
return length
}
}
}
runCatching {
var size: Long = 0
context.assets.open(path).use { inputStream ->
val buffer = ByteArray(8192)
var read: Int
while (inputStream.read(buffer).also { read = it } != -1) {
size += read
}
}
return size
}
return 0
}
override fun walkTopDown(): Sequence<AssetFile> = sequence {
yield(this@AssetFile)
if (isDirectory()) {
val assets = context.assets.list(path)
assets?.forEach { assetName ->
val childPath = if (path.isEmpty()) assetName else "$path/$assetName"
val childFile = AssetFile(context, "asset:///$childPath".toUri())
yieldAll(childFile.walkTopDown())
}
}
}
}

View File

@@ -0,0 +1,82 @@
package expo.modules.filesystem.unifiedfile
import android.net.Uri
import android.os.Build
import android.webkit.MimeTypeMap
import androidx.core.content.FileProvider
import androidx.core.net.toUri
import expo.modules.kotlin.AppContext
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.net.URI
import java.nio.file.attribute.BasicFileAttributes
import kotlin.io.path.Path
import kotlin.io.path.readAttributes
import kotlin.time.Duration.Companion.milliseconds
class JavaFile(override val uri: Uri) : UnifiedFileInterface, File(URI.create(uri.toString())) {
override val parentFile: UnifiedFileInterface?
get() = super<File>.parentFile?.toUri()?.let { JavaFile(it) }
override fun createFile(mimeType: String, displayName: String): UnifiedFileInterface? {
val childFile = File(super<File>.parentFile, displayName)
childFile.createNewFile()
return JavaFile(childFile.toUri())
}
override fun createDirectory(displayName: String): UnifiedFileInterface? {
val childFile = File(super<File>.parentFile, displayName)
childFile.mkdir()
return JavaFile(childFile.toUri())
}
override fun deleteRecursively(): Boolean {
if (isDirectory) {
listFiles()?.forEach { it.deleteRecursively() }
}
return super<File>.delete()
}
override fun getContentUri(appContext: AppContext): Uri {
return FileProvider.getUriForFile(
appContext.throwingActivity.application,
"${appContext.throwingActivity.application.packageName}.FileSystemFileProvider",
this
)
}
override fun listFilesAsUnified(): List<UnifiedFileInterface> =
super<File>.listFiles()?.map { JavaFile(it.toUri()) } ?: emptyList()
override val type: String? get() {
return MimeTypeMap.getFileExtensionFromUrl(path)
?.run { MimeTypeMap.getSingleton().getMimeTypeFromExtension(lowercase()) }
}
override val fileName: String?
get() = super<File>.name
override val creationTime: Long? get() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val attributes = Path(path).readAttributes<BasicFileAttributes>()
return attributes.creationTime().toMillis().milliseconds.inWholeMilliseconds
} else {
return null
}
}
override fun outputStream(append: Boolean): OutputStream {
return FileOutputStream(this, append)
}
override fun inputStream(): InputStream {
return FileInputStream(this)
}
override fun walkTopDown(): Sequence<JavaFile> {
return walk(direction = FileWalkDirection.TOP_DOWN).map { JavaFile(it.toUri()) }
}
}

View File

@@ -0,0 +1,105 @@
package expo.modules.filesystem.unifiedfile
import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import expo.modules.kotlin.AppContext
import java.io.InputStream
import java.io.OutputStream
class SAFDocumentFile(private val context: Context, override val uri: Uri) : UnifiedFileInterface {
private val documentFile: DocumentFile?
get() {
// Relying on singleUri.isDirectory did not work, and there's no explicit method for this, so we check path
val pathSegment = uri.pathSegments.getOrNull(0) ?: "tree"
if (pathSegment == "document") {
// If the path starts with "document", we treat it as a raw file URI
return DocumentFile.fromSingleUri(context, uri)
} else {
// Otherwise, we treat it as a tree URI
return DocumentFile.fromTreeUri(context, uri)
}
}
override fun exists(): Boolean = documentFile?.exists() == true
override fun isDirectory(): Boolean {
return documentFile?.isDirectory == true
}
override fun isFile(): Boolean {
return documentFile?.isFile == true
}
override val parentFile: UnifiedFileInterface?
get() = documentFile?.parentFile?.uri?.let { SAFDocumentFile(context, it) }
override fun createFile(mimeType: String, displayName: String): UnifiedFileInterface? {
val documentFile = documentFile?.createFile(mimeType, displayName)
return documentFile?.uri?.let { SAFDocumentFile(context, it) }
}
override fun createDirectory(displayName: String): UnifiedFileInterface? {
val documentFile = documentFile?.createDirectory(displayName)
return documentFile?.uri?.let { SAFDocumentFile(context, it) }
}
override fun delete(): Boolean = documentFile?.delete() == true
override fun deleteRecursively(): Boolean = documentFile?.deleteRecursively() == true
override fun listFilesAsUnified(): List<UnifiedFileInterface> =
documentFile?.listFiles()?.map { SAFDocumentFile(context, it.uri) } ?: emptyList()
override val type: String?
get() = documentFile?.type
override fun lastModified(): Long? {
return documentFile?.lastModified()
}
override val fileName: String?
get() = documentFile?.name
override fun getContentUri(appContext: AppContext): Uri {
return uri
}
override val creationTime: Long? get() {
// It seems there's no way to get this
return null
}
override fun outputStream(append: Boolean): OutputStream {
val mode = if (append) "wa" else "w"
return context.contentResolver.openOutputStream(uri, mode)
?: throw IllegalStateException("Unable to open output stream for URI: $uri")
}
override fun inputStream(): InputStream {
return context.contentResolver.openInputStream(uri)
?: throw IllegalStateException("Unable to open input stream for URI: $uri")
}
override fun length(): Long {
return documentFile?.length() ?: 0
}
override fun walkTopDown(): Sequence<SAFDocumentFile> {
return sequence {
yield(this@SAFDocumentFile)
if (isDirectory()) {
documentFile?.listFiles()?.forEach { child ->
yieldAll(SAFDocumentFile(context, child.uri).walkTopDown())
}
}
}
}
}
fun DocumentFile.deleteRecursively(): Boolean {
if (isDirectory) {
listFiles().forEach { it.deleteRecursively() }
}
return delete()
}

View File

@@ -0,0 +1,26 @@
package expo.modules.filesystem.unifiedfile
import android.net.Uri
import expo.modules.kotlin.AppContext
interface UnifiedFileInterface {
fun exists(): Boolean
fun isDirectory(): Boolean
fun isFile(): Boolean
val parentFile: UnifiedFileInterface?
fun createFile(mimeType: String, displayName: String): UnifiedFileInterface?
fun createDirectory(displayName: String): UnifiedFileInterface?
fun delete(): Boolean
fun deleteRecursively(): Boolean
fun listFilesAsUnified(): List<UnifiedFileInterface>
val uri: Uri
val type: String?
fun lastModified(): Long?
val creationTime: Long?
val fileName: String?
fun getContentUri(appContext: AppContext): Uri
fun outputStream(append: Boolean = false): java.io.OutputStream
fun inputStream(): java.io.InputStream
fun length(): Long
fun walkTopDown(): Sequence<UnifiedFileInterface>
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="expo_files" path="." />
<cache-path name="cached_expo_files" path="." />
</paths>

1
node_modules/expo-file-system/app.plugin.js generated vendored Normal file
View File

@@ -0,0 +1 @@
module.exports = require('./plugin/build/withFileSystem');

View File

@@ -0,0 +1,19 @@
import { NativeModule } from 'expo-modules-core';
import type { Directory, File, DownloadOptions, PathInfo } from './ExpoFileSystem.types';
declare class ExpoFileSystemModule extends NativeModule {
FileSystemDirectory: typeof Directory;
FileSystemFile: typeof File;
downloadFileAsync(url: string, destination: File | Directory, options?: DownloadOptions): Promise<string>;
pickDirectoryAsync(initialUri?: string): Promise<Directory>;
pickFileAsync(initialUri?: string, mimeType?: string): Promise<File>;
info(uri: string): PathInfo;
totalDiskSpace: number;
availableDiskSpace: number;
documentDirectory: string;
cacheDirectory: string;
bundleDirectory: string;
appleSharedContainers?: Record<string, string>;
}
declare const _default: ExpoFileSystemModule;
export default _default;
//# sourceMappingURL=ExpoFileSystem.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoFileSystem.d.ts","sourceRoot":"","sources":["../src/ExpoFileSystem.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAEzF,OAAO,OAAO,oBAAqB,SAAQ,YAAY;IACrD,mBAAmB,EAAE,OAAO,SAAS,CAAC;IACtC,cAAc,EAAE,OAAO,IAAI,CAAC;IAC5B,iBAAiB,CACf,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,IAAI,GAAG,SAAS,EAC7B,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,MAAM,CAAC;IAClB,kBAAkB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAC3D,aAAa,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACpE,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,qBAAqB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChD;;AAED,wBAAuE"}

View File

@@ -0,0 +1,389 @@
export type FileCreateOptions = {
/**
* Whether to create intermediate directories if they do not exist.
* @default false
*/
intermediates?: boolean;
/**
* Whether to overwrite the file if it exists.
* @default false
*/
overwrite?: boolean;
};
export declare enum EncodingType {
/**
* Standard encoding format.
*/
UTF8 = "utf8",
/**
* Binary, radix-64 representation.
*/
Base64 = "base64"
}
export type FileWriteOptions = {
/**
* The encoding format to use when writing the file.
* @default FileSystem.EncodingType.UTF8
*/
encoding?: EncodingType | 'utf8' | 'base64';
/**
* Whether to append the contents to the end of the file or overwrite the existing file.
* @default false
*/
append?: boolean;
};
export type DirectoryCreateOptions = {
/**
* Whether to create intermediate directories if they do not exist.
* @default false
*/
intermediates?: boolean;
/**
* Whether to overwrite the directory if it exists.
* @default false
*/
overwrite?: boolean;
/**
* This flag controls whether the `create` operation is idempotent
* (safe to call multiple times without error).
*
* If `true`, creating a file or directory that already exists will succeed silently.
* If `false`, an error will be thrown when the target already exists.
*
* @default false
*/
idempotent?: boolean;
};
export declare class Directory {
/**
* Creates an instance of a directory.
* @param uris An array of: `file:///` string URIs, `File` instances, `Directory` instances representing an arbitrary location on the file system. The location does not need to exist, or it may already contain a file.
* @example
* ```ts
* const directory = new Directory("file:///path/to/directory");
* ```
*/
constructor(...uris: (string | File | Directory)[]);
/**
* Represents the directory URI. The field is read-only, but it may change as a result of calling some methods such as `move`.
*/
readonly uri: string;
/**
* Validates a directory path.
* @hidden This method is not meant to be used directly. It is called by the JS constructor.
*/
validatePath(): void;
/**
* Deletes a directory. Also deletes all files and directories inside the directory.
*
* @throws Error if the directory does not exist or cannot be deleted.
*/
delete(): void;
/**
* A boolean representing if a directory exists and can be accessed.
*/
exists: boolean;
/**
* Creates a directory that the current uri points to.
*
* @throws Error if the containing folder doesn't exist, the application has no read access to it or the directory (or a file with the same path) already exists (unless `idempotent` is `true`).
*/
create(options?: DirectoryCreateOptions): void;
createFile(name: string, mimeType: string | null): File;
createDirectory(name: string): Directory;
/**
* Copies a directory.
*/
copy(destination: Directory | File): void;
/**
* Moves a directory. Updates the `uri` property that now points to the new location.
*/
move(destination: Directory | File): void;
/**
* Renames a directory.
*/
rename(newName: string): void;
/**
* @hidden
* Lists the contents of a directory. Should not be used directly, as it returns a list of paths.
* This function is internal and will be removed in the future (when returning arrays of shared objects is supported).
*/
listAsRecords(): {
isDirectory: string;
uri: string;
}[];
/**
* Lists the contents of a directory.
*/
list(): (Directory | File)[];
/**
* Retrieves an object containing properties of a directory.
*
* @throws Error If the application does not have read access to the directory, or if the path does not point to a directory (e.g., it points to a file).
*
* @returns An object with directory metadata (for example, size, creation date, and so on).
*/
info(): DirectoryInfo;
/**
* A size of the directory in bytes. Null if the directory does not exist, or it cannot be read.
*/
size: number | null;
/**
* A static method that opens a file picker to select a directory.
*
* On iOS, the selected directory grants temporary read and write access for the current app session only. After the app restarts, you must prompt the user again to regain access.
*
* @param initialUri An optional uri pointing to an initial folder on which the directory picker is opened.
* @returns a `Directory` instance. On Android, the underlying uri will be a content URI.
*/
static pickDirectoryAsync(initialUri?: string): Promise<Directory>;
}
export type DownloadOptions = {
/**
* The headers to send with the request.
*/
headers?: {
[key: string]: string;
};
/**
* This flag controls whether the `download` operation is idempotent
* (safe to call multiple times without error).
*
* If `true`, downloading a file that already exists overwrites the previous one.
* If `false`, an error is thrown when the target file already exists.
*
* @default false
*/
idempotent?: boolean;
};
/**
* Represents a file on the file system.
*/
export declare class File {
/**
* Creates an instance of File.
*
* @param uris A `file:///` URI representing an arbitrary location on the file system. The location does not need to exist, or it may already contain a directory.
*/
constructor(...uris: (string | File | Directory)[]);
/**
* Represents the file URI. The field is read-only, but it may change as a result of calling some methods such as `move`.
*/
readonly uri: string;
/**
* @hidden This method is not meant to be used directly. It is called by the JS constructor.
* Validates a directory path.
*/
validatePath(): void;
/**
* Retrieves text from the file.
* @returns A promise that resolves with the contents of the file as string.
*/
text(): Promise<string>;
/**
* Retrieves text from the file.
* @returns The contents of the file as string.
*/
textSync(): string;
/**
* Retrieves content of the file as base64.
* @returns A promise that resolves with the contents of the file as a base64 string.
*/
base64(): Promise<string>;
/**
* Retrieves content of the file as base64.
* @returns The contents of the file as a base64 string.
*/
base64Sync(): string;
/**
* Retrieves byte content of the entire file.
* @returns A promise that resolves with the contents of the file as a `Uint8Array`.
*/
bytes(): Promise<Uint8Array<ArrayBuffer>>;
/**
* Retrieves byte content of the entire file.
* @returns The contents of the file as a `Uint8Array`.
*/
bytesSync(): Uint8Array;
/**
* Writes content to the file.
* @param content The content to write into the file.
*/
write(content: string | Uint8Array, options?: FileWriteOptions): void;
/**
* Deletes a file.
*
* @throws Error if the directory does not exist or cannot be deleted.
*/
delete(): void;
/**
* Retrieves an object containing properties of a file
* @throws Error If the application does not have read access to the file, or if the path does not point to a file (for example, it points to a directory).
* @returns An object with file metadata (for example, size, creation date, and so on).
*/
info(options?: InfoOptions): FileInfo;
/**
* A boolean representing if a file exists. `true` if the file exists, `false` otherwise.
* Also, `false` if the application does not have read access to the file.
*/
exists: boolean;
/**
* Creates a file.
*
* @throws Error if the containing folder doesn't exist, the application has no read access to it or the file (or directory with the same path) already exists.
*/
create(options?: FileCreateOptions): void;
/**
* Copies a file.
*/
copy(destination: Directory | File): void;
/**
* Moves a directory. Updates the `uri` property that now points to the new location.
*/
move(destination: Directory | File): void;
/**
* Renames a file.
*/
rename(newName: string): void;
/**
* Returns A `FileHandle` object that can be used to read and write data to the file.
* @throws Error if the file does not exist or cannot be opened.
*/
open(): FileHandle;
/**
* A static method that downloads a file from the network.
*
* On Android, the response body streams directly into the target file. If the download fails after
* it starts, a partially written file may remain at the destination. On iOS, the download first
* completes in a temporary location and the file is moved into place only after success, so no
* file is left behind when the request fails.
*
* @param url - The URL of the file to download.
* @param destination - The destination directory or file. If a directory is provided, the resulting filename will be determined based on the response headers.
* @param options - Download options. When the destination already contains a file, the promise rejects with a `DestinationAlreadyExists` error unless `options.idempotent` is set to `true`. With `idempotent: true`, the download overwrites the existing file instead of failing.
*
* @returns A promise that resolves to the downloaded file. When the server responds with
* a non-2xx HTTP status, the promise rejects with an `UnableToDownload` error whose
* message includes the status code. No file is created in that scenario.
*
* @example
* ```ts
* const file = await File.downloadFileAsync("https://example.com/image.png", new Directory(Paths.document));
* ```
*/
static downloadFileAsync(url: string, destination: Directory | File, options?: DownloadOptions): Promise<File>;
/**
* A static method that opens a file picker to select a single file of specified type. On iOS, it returns a temporary copy of the file leaving the original file untouched.
*
* Selecting multiple files is not supported yet.
*
* @param initialUri An optional URI pointing to an initial folder on which the file picker is opened.
* @param mimeType A mime type that is used to filter out files that can be picked out.
* @returns A `File` instance or an array of `File` instances.
*/
static pickFileAsync(initialUri?: string, mimeType?: string): Promise<File | File[]>;
/**
* A size of the file in bytes. 0 if the file does not exist, or it cannot be read.
*/
size: number;
/**
* A md5 hash of the file. Null if the file does not exist, or it cannot be read.
*/
md5: string | null;
/**
* A last modification time of the file expressed in milliseconds since epoch. Returns a Null if the file does not exist, or it cannot be read.
*/
modificationTime: number | null;
/**
* A creation time of the file expressed in milliseconds since epoch. Returns null if the file does not exist, cannot be read or the Android version is earlier than API 26.
*/
creationTime: number | null;
/**
* A mime type of the file. An empty string if the file does not exist, or it cannot be read.
*/
type: string;
/**
* A content URI to the file that can be shared to external applications.
* @platform android
*/
contentUri: string;
}
export declare class FileHandle {
close(): void;
readBytes(length: number): Uint8Array<ArrayBuffer>;
writeBytes(bytes: Uint8Array): void;
offset: number | null;
size: number | null;
}
export type FileInfo = {
/**
* Indicates whether the file exists.
*/
exists: boolean;
/**
* A URI pointing to the file. This is the same as the `fileUri` input parameter
* and preserves its scheme (for example, `file://` or `content://`).
*/
uri?: string;
/**
* The size of the file in bytes.
*/
size?: number;
/**
* The last modification time of the file expressed in milliseconds since epoch.
*/
modificationTime?: number;
/**
* A creation time of the file expressed in milliseconds since epoch. Returns null if the Android version is earlier than API 26.
*/
creationTime?: number;
/**
* Present if the `md5` option was truthy. Contains the MD5 hash of the file.
*/
md5?: string;
};
export type InfoOptions = {
/**
* Whether to return the MD5 hash of the file.
*
* @default false
*/
md5?: boolean;
};
export type PathInfo = {
/**
* Indicates whether the path exists. Returns true if it exists; false if the path does not exist or if there is no read permission.
*/
exists: boolean;
/**
* Indicates whether the path is a directory. Returns true or false if the path exists; otherwise, returns null.
*/
isDirectory: boolean | null;
};
export type DirectoryInfo = {
/**
* Indicates whether the directory exists.
*/
exists: boolean;
/**
* A `file://` URI pointing to the directory.
*/
uri?: string;
/**
* The size of the file in bytes.
*/
size?: number;
/**
* The last modification time of the directory expressed in milliseconds since epoch.
*/
modificationTime?: number;
/**
* A creation time of the directory expressed in milliseconds since epoch. Returns null if the Android version is earlier than API 26.
*/
creationTime?: number;
/**
* A list of file names contained within a directory.
*/
files?: string[];
};
//# sourceMappingURL=ExpoFileSystem.types.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoFileSystem.types.d.ts","sourceRoot":"","sources":["../src/ExpoFileSystem.types.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,20 @@
declare class FileSystemFile {
constructor();
}
declare class FileSystemDirectory {
constructor();
}
declare const _default: {
FileSystemDirectory: typeof FileSystemDirectory;
FileSystemFile: typeof FileSystemFile;
downloadFileAsync: () => Promise<void>;
pickDirectoryAsync: () => Promise<void>;
pickFileAsync: () => Promise<void>;
readonly totalDiskSpace: number;
readonly availableDiskSpace: number;
readonly documentDirectory: string;
readonly cacheDirectory: string;
readonly bundleDirectory: string;
};
export default _default;
//# sourceMappingURL=ExpoFileSystem.web.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoFileSystem.web.d.ts","sourceRoot":"","sources":["../src/ExpoFileSystem.web.ts"],"names":[],"mappings":"AAAA,cAAM,cAAc;;CAInB;AAED,cAAM,mBAAmB;;CAIxB;;;;;;;6BAiBuB,MAAM;iCAIF,MAAM;gCAIP,MAAM;6BAIT,MAAM;8BAIL,MAAM;;AA/B/B,wBAmCE"}

110
node_modules/expo-file-system/build/FileSystem.d.ts generated vendored Normal file
View File

@@ -0,0 +1,110 @@
import ExpoFileSystem from './ExpoFileSystem';
import type { DownloadOptions, PathInfo } from './ExpoFileSystem.types';
import { PathUtilities } from './pathUtilities';
export declare class Paths extends PathUtilities {
/**
* A property containing the cache directory a place to store files that can be deleted by the system when the device runs low on storage.
*/
static get cache(): Directory;
/**
* A property containing the bundle directory the directory where assets bundled with the application are stored.
*/
static get bundle(): Directory;
/**
* A property containing the document directory a place to store files that are safe from being deleted by the system.
*/
static get document(): Directory;
static get appleSharedContainers(): Record<string, Directory>;
/**
* A property that represents the total space on device's internal storage, represented in bytes.
*/
static get totalDiskSpace(): number;
/**
* A property that represents the available space on device's internal storage, represented in bytes.
*/
static get availableDiskSpace(): number;
/**
* Returns an object that indicates if the specified path represents a directory.
*/
static info(...uris: string[]): PathInfo;
}
/**
* Represents a file on the filesystem.
*
* A `File` instance can be created for any path, and does not need to exist on the filesystem during creation.
*
* The constructor accepts an array of strings that are joined to create the file URI. The first argument can also be a `Directory` instance (like `Paths.cache`) or a `File` instance (which creates a new reference to the same file).
* @example
* ```ts
* const file = new File(Paths.cache, "subdirName", "file.txt");
* ```
*/
export declare class File extends ExpoFileSystem.FileSystemFile implements Blob {
static downloadFileAsync: (url: string, destination: Directory | File, options?: DownloadOptions) => Promise<File>;
static pickFileAsync: (initialUri?: string, mimeType?: string) => Promise<File | File[]>;
/**
* Creates an instance of a file. It can be created for any path, and does not need to exist on the filesystem during creation.
*
* The constructor accepts an array of strings that are joined to create the file URI. The first argument can also be a `Directory` instance (like `Paths.cache`) or a `File` instance (which creates a new reference to the same file).
* @param uris An array of: `file:///` string URIs, `File` instances, and `Directory` instances representing an arbitrary location on the file system.
* @example
* ```ts
* const file = new File(Paths.cache, "subdirName", "file.txt");
* ```
*/
constructor(...uris: (string | File | Directory)[]);
get parentDirectory(): Directory;
/**
* File extension.
* @example '.png'
*/
get extension(): string;
/**
* File name. Includes the extension.
*/
get name(): string;
readableStream(): ReadableStream<Uint8Array<ArrayBuffer>>;
writableStream(): WritableStream<Uint8Array<ArrayBufferLike>>;
arrayBuffer(): Promise<ArrayBuffer>;
stream(): ReadableStream<Uint8Array<ArrayBuffer>>;
slice(start?: number, end?: number, contentType?: string): Blob;
}
/**
* Represents a directory on the filesystem.
*
* A `Directory` instance can be created for any path, and does not need to exist on the filesystem during creation.
*
* The constructor accepts an array of strings that are joined to create the directory URI. The first argument can also be a `Directory` instance (like `Paths.cache`).
* @example
* ```ts
* const directory = new Directory(Paths.cache, "subdirName");
* ```
*/
export declare class Directory extends ExpoFileSystem.FileSystemDirectory {
static pickDirectoryAsync: (initialUri?: string) => Promise<Directory>;
/**
* Creates an instance of a directory. It can be created for any path, and does not need to exist on the filesystem during creation.
*
* The constructor accepts an array of strings that are joined to create the directory URI. The first argument can also be a `Directory` instance (like `Paths.cache`).
* @param uris An array of: `file:///` string URIs, `File` instances, and `Directory` instances representing an arbitrary location on the file system.
* @example
* ```ts
* const directory = new Directory(Paths.cache, "subdirName");
* ```
*/
constructor(...uris: (string | File | Directory)[]);
get parentDirectory(): Directory;
/**
* Lists the contents of a directory.
* Calling this method if the parent directory does not exist will throw an error.
* @returns An array of `Directory` and `File` instances.
*/
list(): (Directory | File)[];
/**
* Directory name.
*/
get name(): string;
createFile(name: string, mimeType: string | null): File;
createDirectory(name: string): Directory;
}
//# sourceMappingURL=FileSystem.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"FileSystem.d.ts","sourceRoot":"","sources":["../src/FileSystem.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,qBAAa,KAAM,SAAQ,aAAa;IACtC;;OAEG;IACH,MAAM,KAAK,KAAK,cAEf;IAED;;OAEG;IACH,MAAM,KAAK,MAAM,cAEhB;IAED;;OAEG;IACH,MAAM,KAAK,QAAQ,cAElB;IACD,MAAM,KAAK,qBAAqB,8BAS/B;IAED;;OAEG;IACH,MAAM,KAAK,cAAc,WAExB;IAED;;OAEG;IACH,MAAM,KAAK,kBAAkB,WAE5B;IAED;;OAEG;IACH,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ;CAGzC;AAED;;;;;;;;;;GAUG;AACH,qBAAa,IAAK,SAAQ,cAAc,CAAC,cAAe,YAAW,IAAI;IACrE,MAAM,CAAC,iBAAiB,EAAE,CACxB,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,SAAS,GAAG,IAAI,EAC7B,OAAO,CAAC,EAAE,eAAe,KACtB,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnB,MAAM,CAAC,aAAa,EAAE,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC;IAEzF;;;;;;;;;OASG;gBACS,GAAG,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE;IAQlD,IAAI,eAAe,cAElB;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED;;OAEG;IACH,IAAI,IAAI,WAEP;IAED,cAAc;IAId,cAAc;IAIR,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC;IAKzC,MAAM,IAAI,cAAc,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAIjD,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI;CAGhE;AAiBD;;;;;;;;;;GAUG;AACH,qBAAa,SAAU,SAAQ,cAAc,CAAC,mBAAmB;IAC/D,MAAM,CAAC,kBAAkB,EAAE,CAAC,UAAU,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IAEvE;;;;;;;;;OASG;gBACS,GAAG,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE;IAQlD,IAAI,eAAe,cAElB;IAED;;;;OAIG;IACM,IAAI,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,EAAE;IAOrC;;OAEG;IACH,IAAI,IAAI,WAEP;IAED,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAKvD,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS;CAGzC"}

4
node_modules/expo-file-system/build/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,4 @@
export * from './FileSystem';
export { type FileCreateOptions, type DirectoryCreateOptions, type FileHandle, type FileInfo, type InfoOptions, type PathInfo, type DirectoryInfo, type DownloadOptions, } from './ExpoFileSystem.types';
export * from './legacyWarnings';
//# sourceMappingURL=index.d.ts.map

1
node_modules/expo-file-system/build/index.d.ts.map generated vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAE7B,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,wBAAwB,CAAC;AAEhC,cAAc,kBAAkB,CAAC"}

View File

@@ -0,0 +1,3 @@
declare const _default: any;
export default _default;
//# sourceMappingURL=ExponentFileSystem.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExponentFileSystem.d.ts","sourceRoot":"","sources":["../../src/legacy/ExponentFileSystem.ts"],"names":[],"mappings":";AAIA,wBAA2F"}

View File

@@ -0,0 +1,3 @@
import ExponentFileSystemShim from './ExponentFileSystemShim';
export default ExponentFileSystemShim;
//# sourceMappingURL=ExponentFileSystem.web.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExponentFileSystem.web.d.ts","sourceRoot":"","sources":["../../src/legacy/ExponentFileSystem.web.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,MAAM,0BAA0B,CAAC;AAC9D,eAAe,sBAAsB,CAAC"}

View File

@@ -0,0 +1,8 @@
import { NativeModule } from 'expo-modules-core';
import type { ExponentFileSystemModule, FileSystemEvents } from './types';
export default class FileSystemShim extends NativeModule<FileSystemEvents> implements ExponentFileSystemModule {
documentDirectory: null;
cacheDirectory: null;
bundleDirectory: null;
}
//# sourceMappingURL=ExponentFileSystemShim.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExponentFileSystemShim.d.ts","sourceRoot":"","sources":["../../src/legacy/ExponentFileSystemShim.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,KAAK,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE1E,MAAM,CAAC,OAAO,OAAO,cACnB,SAAQ,YAAY,CAAC,gBAAgB,CACrC,YAAW,wBAAwB;IAEnC,iBAAiB,OAAQ;IACzB,cAAc,OAAQ;IACtB,eAAe,OAAQ;CACxB"}

View File

@@ -0,0 +1,365 @@
import { DownloadOptions, DownloadPauseState, FileSystemNetworkTaskProgressCallback, DownloadProgressData, UploadProgressData, FileInfo, FileSystemDownloadResult, FileSystemRequestDirectoryPermissionsResult, FileSystemUploadOptions, FileSystemUploadResult, ReadingOptions, WritingOptions, DeletingOptions, InfoOptions, RelocatingOptions, MakeDirectoryOptions } from './FileSystem.types';
/**
* `file://` URI pointing to the directory where user documents for this app will be stored.
* Files stored here will remain until explicitly deleted by the app. Ends with a trailing `/`.
* Example uses are for files the user saves that they expect to see again.
*/
export declare const documentDirectory: string | null;
/**
* `file://` URI pointing to the directory where temporary files used by this app will be stored.
* Files stored here may be automatically deleted by the system when low on storage.
* Example uses are for downloaded or generated files that the app just needs for one-time usage.
*/
export declare const cacheDirectory: string | null;
/**
* URI to the directory where assets bundled with the application are stored.
*/
export declare const bundleDirectory: string | null;
/**
* Get metadata information about a file, directory or external content/asset.
* @param fileUri URI to the file or directory. See [supported URI schemes](#supported-uri-schemes).
* @param options A map of options represented by [`InfoOptions`](#infooptions) type.
* @return A Promise that resolves to a `FileInfo` object. If no item exists at this URI,
* the returned Promise resolves to `FileInfo` object in form of `{ exists: false, isDirectory: false }`.
*/
export declare function getInfoAsync(fileUri: string, options?: InfoOptions): Promise<FileInfo>;
/**
* Read the entire contents of a file as a string. Binary will be returned in raw format, you will need to append `data:image/png;base64,` to use it as Base64.
* @param fileUri `file://` or [SAF](#saf-uri) URI to the file or directory.
* @param options A map of read options represented by [`ReadingOptions`](#readingoptions) type.
* @return A Promise that resolves to a string containing the entire contents of the file.
*/
export declare function readAsStringAsync(fileUri: string, options?: ReadingOptions): Promise<string>;
/**
* Takes a `file://` URI and converts it into content URI (`content://`) so that it can be accessed by other applications outside of Expo.
* @param fileUri The local URI of the file. If there is no file at this URI, an exception will be thrown.
* @example
* ```js
* FileSystem.getContentUriAsync(uri).then(cUri => {
* console.log(cUri);
* IntentLauncher.startActivityAsync('android.intent.action.VIEW', {
* data: cUri,
* flags: 1,
* });
* });
* ```
* @return Returns a Promise that resolves to a `string` containing a `content://` URI pointing to the file.
* The URI is the same as the `fileUri` input parameter but in a different format.
* @platform android
*/
export declare function getContentUriAsync(fileUri: string): Promise<string>;
/**
* Write the entire contents of a file as a string.
* @param fileUri `file://` or [SAF](#saf-uri) URI to the file or directory.
* > Note: when you're using SAF URI the file needs to exist. You can't create a new file.
* @param contents The string to replace the contents of the file with.
* @param options A map of write options represented by [`WritingOptions`](#writingoptions) type.
*/
export declare function writeAsStringAsync(fileUri: string, contents: string, options?: WritingOptions): Promise<void>;
/**
* Delete a file or directory. If the URI points to a directory, the directory and all its contents are recursively deleted.
* @param fileUri `file://` or [SAF](#saf-uri) URI to the file or directory.
* @param options A map of write options represented by [`DeletingOptions`](#deletingoptions) type.
*/
export declare function deleteAsync(fileUri: string, options?: DeletingOptions): Promise<void>;
export declare function deleteLegacyDocumentDirectoryAndroid(): Promise<void>;
/**
* Move a file or directory to a new location.
* @param options A map of move options represented by [`RelocatingOptions`](#relocatingoptions) type.
*/
export declare function moveAsync(options: RelocatingOptions): Promise<void>;
/**
* Create a copy of a file or directory. Directories are recursively copied with all of their contents.
* It can be also used to copy content shared by other apps to local filesystem.
* @param options A map of move options represented by [`RelocatingOptions`](#relocatingoptions) type.
*/
export declare function copyAsync(options: RelocatingOptions): Promise<void>;
/**
* Create a new empty directory.
* @param fileUri `file://` URI to the new directory to create.
* @param options A map of create directory options represented by [`MakeDirectoryOptions`](#makedirectoryoptions) type.
*/
export declare function makeDirectoryAsync(fileUri: string, options?: MakeDirectoryOptions): Promise<void>;
/**
* Enumerate the contents of a directory.
* @param fileUri `file://` URI to the directory.
* @return A Promise that resolves to an array of strings, each containing the name of a file or directory contained in the directory at `fileUri`.
*/
export declare function readDirectoryAsync(fileUri: string): Promise<string[]>;
/**
* Gets the available internal disk storage size, in bytes. This returns the free space on the data partition that hosts all of the internal storage for all apps on the device.
* @return Returns a Promise that resolves to the number of bytes available on the internal disk.
*/
export declare function getFreeDiskStorageAsync(): Promise<number>;
/**
* Gets total internal disk storage size, in bytes. This is the total capacity of the data partition that hosts all the internal storage for all apps on the device.
* @return Returns a Promise that resolves to a number that specifies the total internal disk storage capacity in bytes.
*/
export declare function getTotalDiskCapacityAsync(): Promise<number>;
/**
* Download the contents at a remote URI to a file in the app's file system. The directory for a local file uri must exist prior to calling this function.
* @param uri The remote URI to download from.
* @param fileUri The local URI of the file to download to. If there is no file at this URI, a new one is created.
* If there is a file at this URI, its contents are replaced. The directory for the file must exist.
* @param options A map of download options represented by [`DownloadOptions`](#downloadoptions) type.
* @example
* ```js
* FileSystem.downloadAsync(
* 'http://techslides.com/demos/sample-videos/small.mp4',
* FileSystem.documentDirectory + 'small.mp4'
* )
* .then(({ uri }) => {
* console.log('Finished downloading to ', uri);
* })
* .catch(error => {
* console.error(error);
* });
* ```
* @return Returns a Promise that resolves to a `FileSystemDownloadResult` object.
*/
export declare function downloadAsync(uri: string, fileUri: string, options?: DownloadOptions): Promise<FileSystemDownloadResult>;
/**
* Upload the contents of the file pointed by `fileUri` to the remote url.
* @param url The remote URL, where the file will be sent.
* @param fileUri The local URI of the file to send. The file must exist.
* @param options A map of download options represented by [`FileSystemUploadOptions`](#filesystemuploadoptions) type.
* @example
* **Client**
*
* ```js
* import * as FileSystem from 'expo-file-system/legacy';
*
* try {
* const response = await FileSystem.uploadAsync(`http://192.168.0.1:1234/binary-upload`, fileUri, {
* fieldName: 'file',
* httpMethod: 'PATCH',
* uploadType: FileSystem.FileSystemUploadType.BINARY_CONTENT,
* });
* console.log(JSON.stringify(response, null, 4));
* } catch (error) {
* console.log(error);
* }
* ```
*
* **Server**
*
* Refer to the "[Server: Handling multipart requests](#server-handling-multipart-requests)" example - there is code for a simple Node.js server.
* @return Returns a Promise that resolves to `FileSystemUploadResult` object.
*/
export declare function uploadAsync(url: string, fileUri: string, options?: FileSystemUploadOptions): Promise<FileSystemUploadResult>;
/**
* Create a `DownloadResumable` object which can start, pause, and resume a download of contents at a remote URI to a file in the app's file system.
* > Note: You need to call `downloadAsync()`, on a `DownloadResumable` instance to initiate the download.
* The `DownloadResumable` object has a callback that provides download progress updates.
* Downloads can be resumed across app restarts by using `AsyncStorage` to store the `DownloadResumable.savable()` object for later retrieval.
* The `savable` object contains the arguments required to initialize a new `DownloadResumable` object to resume the download after an app restart.
* The directory for a local file uri must exist prior to calling this function.
* @param uri The remote URI to download from.
* @param fileUri The local URI of the file to download to. If there is no file at this URI, a new one is created.
* If there is a file at this URI, its contents are replaced. The directory for the file must exist.
* @param options A map of download options represented by [`DownloadOptions`](#downloadoptions) type.
* @param callback This function is called on each data write to update the download progress.
* > **Note**: When the app has been moved to the background, this callback won't be fired until it's moved to the foreground.
* @param resumeData The string which allows the api to resume a paused download. This is set on the `DownloadResumable` object automatically when a download is paused.
* When initializing a new `DownloadResumable` this should be `null`.
*/
export declare function createDownloadResumable(uri: string, fileUri: string, options?: DownloadOptions, callback?: FileSystemNetworkTaskProgressCallback<DownloadProgressData>, resumeData?: string): DownloadResumable;
export declare function createUploadTask(url: string, fileUri: string, options?: FileSystemUploadOptions, callback?: FileSystemNetworkTaskProgressCallback<UploadProgressData>): UploadTask;
export declare abstract class FileSystemCancellableNetworkTask<T extends DownloadProgressData | UploadProgressData> {
private _uuid;
protected taskWasCanceled: boolean;
private subscription?;
cancelAsync(): Promise<void>;
protected isTaskCancelled(): boolean;
protected get uuid(): string;
protected abstract getEventName(): string;
protected abstract getCallback(): FileSystemNetworkTaskProgressCallback<T> | undefined;
protected addSubscription(): void;
protected removeSubscription(): void;
}
export declare class UploadTask extends FileSystemCancellableNetworkTask<UploadProgressData> {
private url;
private fileUri;
private callback?;
private options;
constructor(url: string, fileUri: string, options?: FileSystemUploadOptions, callback?: FileSystemNetworkTaskProgressCallback<UploadProgressData> | undefined);
protected getEventName(): string;
protected getCallback(): FileSystemNetworkTaskProgressCallback<UploadProgressData> | undefined;
uploadAsync(): Promise<FileSystemUploadResult | undefined | null>;
}
export declare class DownloadResumable extends FileSystemCancellableNetworkTask<DownloadProgressData> {
private url;
private _fileUri;
private options;
private callback?;
private resumeData?;
constructor(url: string, _fileUri: string, options?: DownloadOptions, callback?: FileSystemNetworkTaskProgressCallback<DownloadProgressData> | undefined, resumeData?: string | undefined);
get fileUri(): string;
protected getEventName(): string;
protected getCallback(): FileSystemNetworkTaskProgressCallback<DownloadProgressData> | undefined;
/**
* Download the contents at a remote URI to a file in the app's file system.
* @return Returns a Promise that resolves to `FileSystemDownloadResult` object, or to `undefined` when task was cancelled.
*/
downloadAsync(): Promise<FileSystemDownloadResult | undefined>;
/**
* Pause the current download operation. `resumeData` is added to the `DownloadResumable` object after a successful pause operation.
* Returns an object that can be saved with `AsyncStorage` for future retrieval (the same object that is returned from calling `FileSystem.DownloadResumable.savable()`).
* @return Returns a Promise that resolves to `DownloadPauseState` object.
*/
pauseAsync(): Promise<DownloadPauseState>;
/**
* Resume a paused download operation.
* @return Returns a Promise that resolves to `FileSystemDownloadResult` object, or to `undefined` when task was cancelled.
*/
resumeAsync(): Promise<FileSystemDownloadResult | undefined>;
/**
* Method to get the object which can be saved with `AsyncStorage` for future retrieval.
* @returns Returns object in shape of `DownloadPauseState` type.
*/
savable(): DownloadPauseState;
}
/**
* The `StorageAccessFramework` is a namespace inside of the `expo-file-system` module, which encapsulates all functions which can be used with [SAF URIs](#saf-uri).
* You can read more about SAF in the [Android documentation](https://developer.android.com/guide/topics/providers/document-provider).
*
* @example
* # Basic Usage
*
* ```ts
* import { StorageAccessFramework } from 'expo-file-system';
*
* // Requests permissions for external directory
* const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync();
*
* if (permissions.granted) {
* // Gets SAF URI from response
* const uri = permissions.directoryUri;
*
* // Gets all files inside of selected directory
* const files = await StorageAccessFramework.readDirectoryAsync(uri);
* alert(`Files inside ${uri}:\n\n${JSON.stringify(files)}`);
* }
* ```
*
* # Migrating an album
*
* ```ts
* import * as MediaLibrary from 'expo-media-library';
* import * as FileSystem from 'expo-file-system/legacy';
* const { StorageAccessFramework } = FileSystem;
*
* async function migrateAlbum(albumName: string) {
* // Gets SAF URI to the album
* const albumUri = StorageAccessFramework.getUriForDirectoryInRoot(albumName);
*
* // Requests permissions
* const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync(albumUri);
* if (!permissions.granted) {
* return;
* }
*
* const permittedUri = permissions.directoryUri;
* // Checks if users selected the correct folder
* if (!permittedUri.includes(albumName)) {
* return;
* }
*
* const mediaLibraryPermissions = await MediaLibrary.requestPermissionsAsync();
* if (!mediaLibraryPermissions.granted) {
* return;
* }
*
* // Moves files from external storage to internal storage
* await StorageAccessFramework.moveAsync({
* from: permittedUri,
* to: FileSystem.documentDirectory!,
* });
*
* const outputDir = FileSystem.documentDirectory! + albumName;
* const migratedFiles = await FileSystem.readDirectoryAsync(outputDir);
*
* // Creates assets from local files
* const [newAlbumCreator, ...assets] = await Promise.all(
* migratedFiles.map<Promise<MediaLibrary.Asset>>(
* async fileName => await MediaLibrary.createAssetAsync(outputDir + '/' + fileName)
* )
* );
*
* // Album was empty
* if (!newAlbumCreator) {
* return;
* }
*
* // Creates a new album in the scoped directory
* const newAlbum = await MediaLibrary.createAlbumAsync(albumName, newAlbumCreator, false);
* if (assets.length) {
* await MediaLibrary.addAssetsToAlbumAsync(assets, newAlbum, false);
* }
* }
* ```
* @platform Android
*/
export declare namespace StorageAccessFramework {
/**
* Gets a [SAF URI](#saf-uri) pointing to a folder in the Android root directory. You can use this function to get URI for
* `StorageAccessFramework.requestDirectoryPermissionsAsync()` when you trying to migrate an album. In that case, the name of the album is the folder name.
* @param folderName The name of the folder which is located in the Android root directory.
* @return Returns a [SAF URI](#saf-uri) to a folder.
* @platform Android
*/
function getUriForDirectoryInRoot(folderName: string): string;
/**
* Allows users to select a specific directory, granting your app access to all of the files and sub-directories within that directory.
* @param initialFileUrl The [SAF URI](#saf-uri) of the directory that the file picker should display when it first loads.
* If URI is incorrect or points to a non-existing folder, it's ignored.
* @platform android 11+
* @return Returns a Promise that resolves to `FileSystemRequestDirectoryPermissionsResult` object.
*/
function requestDirectoryPermissionsAsync(initialFileUrl?: string | null): Promise<FileSystemRequestDirectoryPermissionsResult>;
/**
* Enumerate the contents of a directory.
* @param dirUri [SAF](#saf-uri) URI to the directory.
* @return A Promise that resolves to an array of strings, each containing the full [SAF URI](#saf-uri) of a file or directory contained in the directory at `fileUri`.
* @platform Android
*/
function readDirectoryAsync(dirUri: string): Promise<string[]>;
/**
* Creates a new empty directory.
* @param parentUri The [SAF](#saf-uri) URI to the parent directory.
* @param dirName The name of new directory.
* @return A Promise that resolves to a [SAF URI](#saf-uri) to the created directory.
* @platform Android
*/
function makeDirectoryAsync(parentUri: string, dirName: string): Promise<string>;
/**
* Creates a new empty file.
* @param parentUri The [SAF](#saf-uri) URI to the parent directory.
* @param fileName The name of new file **without the extension**.
* @param mimeType The MIME type of new file.
* @return A Promise that resolves to a [SAF URI](#saf-uri) to the created file.
* @platform Android
*/
function createFileAsync(parentUri: string, fileName: string, mimeType: string): Promise<string>;
/**
* Alias for [`writeAsStringAsync`](#filesystemwriteasstringasyncfileuri-contents-options) method.
*/
const writeAsStringAsync: typeof import("./FileSystem").writeAsStringAsync;
/**
* Alias for [`readAsStringAsync`](#filesystemreadasstringasyncfileuri-options) method.
*/
const readAsStringAsync: typeof import("./FileSystem").readAsStringAsync;
/**
* Alias for [`deleteAsync`](#filesystemdeleteasyncfileuri-options) method.
*/
const deleteAsync: typeof import("./FileSystem").deleteAsync;
/**
* Alias for [`moveAsync`](#filesystemmoveasyncoptions) method.
*/
const moveAsync: typeof import("./FileSystem").moveAsync;
/**
* Alias for [`copyAsync`](#filesystemcopyasyncoptions) method.
*/
const copyAsync: typeof import("./FileSystem").copyAsync;
}
//# sourceMappingURL=FileSystem.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"FileSystem.d.ts","sourceRoot":"","sources":["../../src/legacy/FileSystem.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,305 @@
/**
* These values can be used to define how sessions work on iOS.
* @platform ios
*/
export declare enum FileSystemSessionType {
/**
* Using this mode means that the downloading/uploading session on the native side will work even if the application is moved to background.
* If the task completes while the application is in background, the Promise will be either resolved immediately or (if the application execution has already been stopped) once the app is moved to foreground again.
* > Note: The background session doesn't fail if the server or your connection is down. Rather, it continues retrying until the task succeeds or is canceled manually.
*/
BACKGROUND = 0,
/**
* Using this mode means that downloading/uploading session on the native side will be terminated once the application becomes inactive (e.g. when it goes to background).
* Bringing the application to foreground again would trigger Promise rejection.
*/
FOREGROUND = 1
}
export declare enum FileSystemUploadType {
/**
* The file will be sent as a request's body. The request can't contain additional data.
*/
BINARY_CONTENT = 0,
/**
* An [RFC 2387-compliant](https://www.ietf.org/rfc/rfc2387.txt) request body. The provided file will be encoded into HTTP request.
* This request can contain additional data represented by [`UploadOptionsMultipart`](#uploadoptionsmultipart) type.
*/
MULTIPART = 1
}
export type DownloadOptions = {
/**
* If `true`, include the MD5 hash of the file in the returned object. Provided for convenience since it is common to check the integrity of a file immediately after downloading.
* @default false
*/
md5?: boolean;
cache?: boolean;
/**
* An object containing all the HTTP header fields and their values for the download network request. The keys and values of the object are the header names and values respectively.
*/
headers?: Record<string, string>;
/**
* A session type. Determines if tasks can be handled in the background. On Android, sessions always work in the background and you can't change it.
* @default FileSystemSessionType.BACKGROUND
* @platform ios
*/
sessionType?: FileSystemSessionType;
};
export type FileSystemHttpResult = {
/**
* An object containing all the HTTP response header fields and their values for the download network request.
* The keys and values of the object are the header names and values respectively.
*/
headers: Record<string, string>;
/**
* The HTTP response status code for the download network request.
*/
status: number;
mimeType: string | null;
};
export type FileSystemDownloadResult = FileSystemHttpResult & {
/**
* A `file://` URI pointing to the file. This is the same as the `fileUri` input parameter.
*/
uri: string;
/**
* Present if the `md5` option was truthy. Contains the MD5 hash of the file.
*/
md5?: string;
};
/**
* @deprecated Use `FileSystemDownloadResult` instead.
*/
export type DownloadResult = FileSystemDownloadResult;
export type FileSystemUploadOptions = (UploadOptionsBinary | UploadOptionsMultipart) & {
/**
* An object containing all the HTTP header fields and their values for the upload network request.
* The keys and values of the object are the header names and values respectively.
*/
headers?: Record<string, string>;
/**
* The request method.
* @default FileSystemAcceptedUploadHttpMethod.POST
*/
httpMethod?: FileSystemAcceptedUploadHttpMethod;
/**
* A session type. Determines if tasks can be handled in the background. On Android, sessions always work in the background and you can't change it.
* @default FileSystemSessionType.BACKGROUND
* @platform ios
*/
sessionType?: FileSystemSessionType;
};
/**
* Upload options when upload type is set to binary.
*/
export type UploadOptionsBinary = {
/**
* Upload type determines how the file will be sent to the server.
* Value will be `FileSystemUploadType.BINARY_CONTENT`.
*/
uploadType?: FileSystemUploadType;
};
/**
* Upload options when upload type is set to multipart.
*/
export type UploadOptionsMultipart = {
/**
* Upload type determines how the file will be sent to the server.
* Value will be `FileSystemUploadType.MULTIPART`.
*/
uploadType: FileSystemUploadType;
/**
* The name of the field which will hold uploaded file. Defaults to the file name without an extension.
*/
fieldName?: string;
/**
* The MIME type of the provided file. If not provided, the module will try to guess it based on the extension.
*/
mimeType?: string;
/**
* Additional form properties. They will be located in the request body.
*/
parameters?: Record<string, string>;
};
export type FileSystemUploadResult = FileSystemHttpResult & {
/**
* The body of the server response.
*/
body: string;
};
export type FileSystemNetworkTaskProgressCallback<T extends DownloadProgressData | UploadProgressData> = (data: T) => void;
/**
* @deprecated use `FileSystemNetworkTaskProgressCallback<DownloadProgressData>` instead.
*/
export type DownloadProgressCallback = FileSystemNetworkTaskProgressCallback<DownloadProgressData>;
export type DownloadProgressData = {
/**
* The total bytes written by the download operation.
*/
totalBytesWritten: number;
/**
* The total bytes expected to be written by the download operation. A value of `-1` means that the server did not return the `Content-Length` header
* and the total size is unknown. Without this header, you won't be able to track the download progress.
*/
totalBytesExpectedToWrite: number;
};
export type UploadProgressData = {
/**
* The total bytes sent by the upload operation.
*/
totalBytesSent: number;
/**
* The total bytes expected to be sent by the upload operation.
*/
totalBytesExpectedToSend: number;
};
export type DownloadPauseState = {
/**
* The remote URI to download from.
*/
url: string;
/**
* The local URI of the file to download to. If there is no file at this URI, a new one is created. If there is a file at this URI, its contents are replaced.
*/
fileUri: string;
/**
* Object representing the file download options.
*/
options: DownloadOptions;
/**
* The string which allows the API to resume a paused download.
*/
resumeData?: string;
};
export type FileInfo =
/**
* Object returned when file exist.
*/
{
/**
* Signifies that the requested file exist.
*/
exists: true;
/**
* A URI pointing to the file. This is the same as the `fileUri` input parameter
* and preserves its scheme (for example, `file://` or `content://`).
*/
uri: string;
/**
* The size of the file in bytes.
*/
size: number;
/**
* Boolean set to `true` if this is a directory and `false` if it is a file.
*/
isDirectory: boolean;
/**
* The last modification time of the file expressed in seconds since epoch.
*/
modificationTime: number;
/**
* Present if the `md5` option was truthy. Contains the MD5 hash of the file.
*/
md5?: string;
}
/**
* Object returned when file do not exist.
*/
| {
exists: false;
uri: string;
isDirectory: false;
};
/**
* These values can be used to define how file system data is read / written.
*/
export declare enum EncodingType {
/**
* Standard encoding format.
*/
UTF8 = "utf8",
/**
* Binary, radix-64 representation.
*/
Base64 = "base64"
}
export type FileSystemAcceptedUploadHttpMethod = 'POST' | 'PUT' | 'PATCH';
export type ReadingOptions = {
/**
* The encoding format to use when reading the file.
* @default EncodingType.UTF8
*/
encoding?: EncodingType | 'utf8' | 'base64';
/**
* Optional number of bytes to skip. This option is only used when `encoding: FileSystem.EncodingType.Base64` and `length` is defined.
* */
position?: number;
/**
* Optional number of bytes to read. This option is only used when `encoding: FileSystem.EncodingType.Base64` and `position` is defined.
*/
length?: number;
};
export type WritingOptions = {
/**
* The encoding format to use when writing the file.
* @default FileSystem.EncodingType.UTF8
*/
encoding?: EncodingType | 'utf8' | 'base64';
/**
* Whether to append the contents to the end of the file or overwrite the existing file.
* @default false
*/
append?: boolean;
};
export type DeletingOptions = {
/**
* If `true`, don't throw an error if there is no file or directory at this URI.
* @default false
*/
idempotent?: boolean;
};
export type InfoOptions = {
/**
* Whether to return the MD5 hash of the file.
* @default false
*/
md5?: boolean;
};
export type RelocatingOptions = {
/**
* URI or [SAF](#saf-uri) URI to the asset, file, or directory. See [supported URI schemes](#supported-uri-schemes).
*/
from: string;
/**
* `file://` URI to the file or directory which should be its new location.
*/
to: string;
};
export type MakeDirectoryOptions = {
/**
* If `true`, don't throw an error if there is no file or directory at this URI.
* @default false
*/
intermediates?: boolean;
};
export type ProgressEvent<T> = {
uuid: string;
data: T;
};
export type FileSystemRequestDirectoryPermissionsResult =
/**
* If the permissions were not granted.
*/
{
granted: false;
}
/**
* If the permissions were granted.
*/
| {
granted: true;
/**
* The [SAF URI](#saf-uri) to the user's selected directory. Available only if permissions were granted.
*/
directoryUri: string;
};
//# sourceMappingURL=FileSystem.types.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"FileSystem.types.d.ts","sourceRoot":"","sources":["../../src/legacy/FileSystem.types.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,3 @@
export * from './FileSystem';
export * from './FileSystem.types';
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/legacy/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC"}

38
node_modules/expo-file-system/build/legacy/types.d.ts generated vendored Normal file
View File

@@ -0,0 +1,38 @@
import { NativeModule } from 'expo-modules-core';
import { DownloadProgressData, ProgressEvent, UploadProgressData } from './FileSystem.types';
type PlatformMethod = (...args: any[]) => Promise<any>;
/**
* @hidden
*/
export type FileSystemEvents = {
'expo-file-system.downloadProgress'(event: ProgressEvent<DownloadProgressData>): void;
'expo-file-system.uploadProgress'(event: ProgressEvent<UploadProgressData>): void;
};
export declare class ExponentFileSystemModule extends NativeModule<FileSystemEvents> {
readonly documentDirectory: string | null;
readonly cacheDirectory: string | null;
readonly bundleDirectory: string | null;
readonly getInfoAsync?: PlatformMethod;
readonly readAsStringAsync?: PlatformMethod;
readonly writeAsStringAsync?: PlatformMethod;
readonly deleteAsync?: PlatformMethod;
readonly moveAsync?: PlatformMethod;
readonly copyAsync?: PlatformMethod;
readonly makeDirectoryAsync?: PlatformMethod;
readonly readDirectoryAsync?: PlatformMethod;
readonly downloadAsync?: PlatformMethod;
readonly uploadAsync?: PlatformMethod;
readonly downloadResumableStartAsync?: PlatformMethod;
readonly downloadResumablePauseAsync?: PlatformMethod;
readonly getContentUriAsync?: PlatformMethod;
readonly getFreeDiskStorageAsync?: PlatformMethod;
readonly getTotalDiskCapacityAsync?: PlatformMethod;
readonly requestDirectoryPermissionsAsync?: PlatformMethod;
readonly readSAFDirectoryAsync?: PlatformMethod;
readonly makeSAFDirectoryAsync?: PlatformMethod;
readonly createSAFFileAsync?: PlatformMethod;
readonly networkTaskCancelAsync?: PlatformMethod;
readonly uploadTaskStartAsync?: PlatformMethod;
}
export {};
//# sourceMappingURL=types.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/legacy/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7F,KAAK,cAAc,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;AAEvD;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,mCAAmC,CAAC,KAAK,EAAE,aAAa,CAAC,oBAAoB,CAAC,GAAG,IAAI,CAAC;IACtF,iCAAiC,CAAC,KAAK,EAAE,aAAa,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAAC;CACnF,CAAC;AAEF,MAAM,CAAC,OAAO,OAAO,wBAAyB,SAAQ,YAAY,CAAC,gBAAgB,CAAC;IAClF,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,QAAQ,CAAC,YAAY,CAAC,EAAE,cAAc,CAAC;IACvC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,cAAc,CAAC;IAC5C,QAAQ,CAAC,kBAAkB,CAAC,EAAE,cAAc,CAAC;IAC7C,QAAQ,CAAC,WAAW,CAAC,EAAE,cAAc,CAAC;IACtC,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,CAAC;IACpC,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,CAAC;IACpC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,cAAc,CAAC;IAC7C,QAAQ,CAAC,kBAAkB,CAAC,EAAE,cAAc,CAAC;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,cAAc,CAAC;IACxC,QAAQ,CAAC,WAAW,CAAC,EAAE,cAAc,CAAC;IACtC,QAAQ,CAAC,2BAA2B,CAAC,EAAE,cAAc,CAAC;IACtD,QAAQ,CAAC,2BAA2B,CAAC,EAAE,cAAc,CAAC;IACtD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,cAAc,CAAC;IAC7C,QAAQ,CAAC,uBAAuB,CAAC,EAAE,cAAc,CAAC;IAClD,QAAQ,CAAC,yBAAyB,CAAC,EAAE,cAAc,CAAC;IACpD,QAAQ,CAAC,gCAAgC,CAAC,EAAE,cAAc,CAAC;IAC3D,QAAQ,CAAC,qBAAqB,CAAC,EAAE,cAAc,CAAC;IAChD,QAAQ,CAAC,qBAAqB,CAAC,EAAE,cAAc,CAAC;IAChD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,cAAc,CAAC;IAC7C,QAAQ,CAAC,sBAAsB,CAAC,EAAE,cAAc,CAAC;IACjD,QAAQ,CAAC,oBAAoB,CAAC,EAAE,cAAc,CAAC;CAChD"}

View File

@@ -0,0 +1,66 @@
import type { DownloadOptions, FileSystemNetworkTaskProgressCallback, DownloadProgressData, UploadProgressData, FileInfo, FileSystemDownloadResult, FileSystemUploadOptions, FileSystemUploadResult, ReadingOptions, WritingOptions, DeletingOptions, InfoOptions, RelocatingOptions, MakeDirectoryOptions } from './legacy/FileSystem.types';
/**
* @deprecated Use `new File().info` or import this method from `expo-file-system/legacy`. This method will throw in runtime.
*/
export declare function getInfoAsync(fileUri: string, options?: InfoOptions): Promise<FileInfo>;
/**
* @deprecated Use `new File().text()` or import this method from `expo-file-system/legacy`. This method will throw in runtime.
*/
export declare function readAsStringAsync(fileUri: string, options?: ReadingOptions): Promise<string>;
/**
* @deprecated Import this method from `expo-file-system/legacy`. This method will throw in runtime.
*/
export declare function getContentUriAsync(fileUri: string): Promise<string>;
/**
* @deprecated Use `new File().write()` or import this method from `expo-file-system/legacy`. This method will throw in runtime.
*/
export declare function writeAsStringAsync(fileUri: string, contents: string, options?: WritingOptions): Promise<void>;
/**
* @deprecated Use `new File().delete()` or `new Directory().delete()` or import this method from `expo-file-system/legacy`. This method will throw in runtime.
*/
export declare function deleteAsync(fileUri: string, options?: DeletingOptions): Promise<void>;
/**
* @deprecated
*/
export declare function deleteLegacyDocumentDirectoryAndroid(): Promise<void>;
/**
* @deprecated Use `new File().move()` or import this method from `expo-file-system/legacy`. This method will throw in runtime.
*/
export declare function moveAsync(options: RelocatingOptions): Promise<void>;
/**
* @deprecated Use `new File().copy()` or import this method from `expo-file-system/legacy`. This method will throw in runtime.
*/
export declare function copyAsync(options: RelocatingOptions): Promise<void>;
/**
* @deprecated Use `new Directory().create()` or import this method from `expo-file-system/legacy`. This method will throw in runtime.
*/
export declare function makeDirectoryAsync(fileUri: string, options?: MakeDirectoryOptions): Promise<void>;
/**
* @deprecated Use `new Directory().list()` or import this method from `expo-file-system/legacy`. This method will throw in runtime.
*/
export declare function readDirectoryAsync(fileUri: string): Promise<string[]>;
/**
* @deprecated Use `Paths.availableDiskSpace` or import this method from `expo-file-system/legacy`. This method will throw in runtime.
*/
export declare function getFreeDiskStorageAsync(): Promise<number>;
/**
* @deprecated Use `Paths.totalDiskSpace` or import this method from `expo-file-system/legacy`. This method will throw in runtime.
*/
export declare function getTotalDiskCapacityAsync(): Promise<number>;
/**
* @deprecated Use `File.downloadFileAsync` or import this method from `expo-file-system/legacy`. This method will throw in runtime.
*/
export declare function downloadAsync(uri: string, fileUri: string, options?: DownloadOptions): Promise<FileSystemDownloadResult>;
/**
* @deprecated Use `@expo/fetch` or import this method from `expo-file-system/legacy`. This method will throw in runtime.
*/
export declare function uploadAsync(url: string, fileUri: string, options?: FileSystemUploadOptions): Promise<FileSystemUploadResult>;
/**
* @deprecated Import this method from `expo-file-system/legacy`. This method will throw in runtime.
*/
export declare function createDownloadResumable(uri: string, fileUri: string, options?: DownloadOptions, callback?: FileSystemNetworkTaskProgressCallback<DownloadProgressData>, resumeData?: string): any;
/**
* @deprecated Import this method from `expo-file-system/legacy`. This method will throw in runtime.
*/
export declare function createUploadTask(url: string, fileUri: string, options?: FileSystemUploadOptions, callback?: FileSystemNetworkTaskProgressCallback<UploadProgressData>): any;
//# sourceMappingURL=legacyWarnings.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"legacyWarnings.d.ts","sourceRoot":"","sources":["../src/legacyWarnings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,qCAAqC,EACrC,oBAAoB,EACpB,kBAAkB,EAClB,QAAQ,EACR,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,2BAA2B,CAAC;AAQnC;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAEhG;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEzE;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/F;AAED;;GAEG;AACH,wBAAsB,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC,CAE1E;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzE;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzE;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAE3E;AAED;;GAEG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,MAAM,CAAC,CAE/D;AAED;;GAEG;AACH,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,MAAM,CAAC,CAEjE;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,wBAAwB,CAAC,CAEnC;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,sBAAsB,CAAC,CAEjC;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,qCAAqC,CAAC,oBAAoB,CAAC,EACtE,UAAU,CAAC,EAAE,MAAM,GAClB,GAAG,CAEL;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,uBAAuB,EACjC,QAAQ,CAAC,EAAE,qCAAqC,CAAC,kBAAkB,CAAC,GACnE,GAAG,CAEL"}

View File

@@ -0,0 +1,60 @@
import type { Directory, File } from '../FileSystem';
export declare class PathUtilities {
/**
* Joins path segments into a single path.
* @param paths - An array of path segments.
* @returns A string representing the joined path.
*/
static join(...paths: (string | File | Directory)[]): string;
/**
* Resolves a relative path to an absolute path.
* @param from - The base path.
* @param to - The relative path.
* @returns A string representing the resolved path.
*/
static relative(from: string | File | Directory, to: string | File | Directory): string;
/**
* Checks if a path is absolute.
* @param path - The path to check.
* @returns `true` if the path is absolute, `false` otherwise.
*/
static isAbsolute(path: string | File | Directory): boolean;
/**
* Normalizes a path.
* @param path - The path to normalize.
* @returns A string representing the normalized path.
*/
static normalize(path: string | File | Directory): string;
/**
* Returns the directory name of a path.
* @param path - The path to get the directory name from.
* @returns A string representing the directory name.
*/
static dirname(path: string | File | Directory): string;
/**
* Returns the base name of a path.
* @param path - The path to get the base name from.
* @param ext - An optional file extension.
* @returns A string representing the base name.
*/
static basename(path: string | File | Directory, ext?: string): string;
/**
* Returns the extension of a path.
* @param path - The path to get the extension from.
* @returns A string representing the extension.
*/
static extname(path: string | File | Directory): string;
/**
* Parses a path into its components.
* @param path - The path to parse.
* @returns An object containing the parsed path components.
*/
static parse(path: string | File | Directory): {
root: string;
dir: string;
base: string;
ext: string;
name: string;
};
}
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pathUtilities/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAQrD,qBAAa,aAAa;IACxB;;;;OAIG;IACH,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,GAAG,MAAM;IAU5D;;;;;OAKG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM;IAevF;;;;OAIG;IACH,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO;IAQ3D;;;;OAIG;IACH,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM;IAUzD;;;;OAIG;IACH,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM;IAUvD;;;;;OAKG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM;IAStE;;;;OAIG;IACH,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM;IASvD;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG;QAC7C,IAAI,EAAE,MAAM,CAAC;QACb,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;KACd;CAQF"}

View File

@@ -0,0 +1,26 @@
export declare function format(sep: string, pathObject: {
dir?: string;
root?: string;
base?: string;
name?: string;
ext?: string;
}): string;
export declare function resolve(...args: string[]): string;
export declare function normalize(path: string): string;
export declare function isAbsolute(path: string): boolean;
export declare function join(...args: string[]): string;
export declare function relative(from: string, to: string): string;
export declare function toNamespacedPath(path: string): string;
export declare function dirname(path: string): string;
export declare function basename(path: string, suffix?: string): string;
export declare function extname(path: string): string;
export declare function parse(path: string): {
root: string;
dir: string;
base: string;
ext: string;
name: string;
};
export declare const sep = "/";
export declare const delimiter = ":";
//# sourceMappingURL=path.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/pathUtilities/path.ts"],"names":[],"mappings":"AA2FA,wBAAgB,MAAM,CACpB,GAAG,EAAE,MAAM,EACX,UAAU,EAAE;IACV,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,GACA,MAAM,CAOR;AAED,wBAAgB,OAAO,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,UA0BxC;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,UAgBrC;AACD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,WAEtC;AACD,wBAAgB,IAAI,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,UAcrC;AACD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,UA6DhD;AACD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,UAG5C;AACD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,UAoBnC;AACD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,UAiErD;AACD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,UA+CnC;AACD,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM;;;;;;EAwEjC;AACD,eAAO,MAAM,GAAG,MAAM,CAAC;AACvB,eAAO,MAAM,SAAS,MAAM,CAAC"}

View File

@@ -0,0 +1,4 @@
export declare function encodeURLChars(path: string): string;
export declare function isUrl(url: string): boolean;
export declare function asUrl(url: string | URL): URL | null;
//# sourceMappingURL=url.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/pathUtilities/url.ts"],"names":[],"mappings":"AA2CA,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,UAgB1C;AAED,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,WAMhC;AAED,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,cAQtC"}

17
node_modules/expo-file-system/build/streams.d.ts generated vendored Normal file
View File

@@ -0,0 +1,17 @@
import type { FileHandle } from './ExpoFileSystem.types';
export declare class FileSystemReadableStreamSource implements UnderlyingByteSource {
handle: FileHandle;
size: number;
type: "bytes";
constructor(handle: FileHandle);
cancel(): void;
pull(controller: ReadableByteStreamController): void;
}
export declare class FileSystemWritableSink implements UnderlyingSink {
handle: FileHandle;
constructor(handle: FileHandle);
abort(): void;
close(): void;
write(chunk: Uint8Array): void;
}
//# sourceMappingURL=streams.d.ts.map

1
node_modules/expo-file-system/build/streams.d.ts.map generated vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../src/streams.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,qBAAa,8BAA+B,YAAW,oBAAoB;IACzE,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAQ;IACpB,IAAI,EAAG,OAAO,CAAU;gBAEZ,MAAM,EAAE,UAAU;IAI9B,MAAM;IAIN,IAAI,CAAC,UAAU,EAAE,4BAA4B;CA6B9C;AAED,qBAAa,sBAAuB,YAAW,cAAc;IAC3D,MAAM,EAAE,UAAU,CAAC;gBAEP,MAAM,EAAE,UAAU;IAI9B,KAAK;IAIL,KAAK;IAIL,KAAK,CAAC,KAAK,EAAE,UAAU;CAGxB"}

19
node_modules/expo-file-system/expo-module.config.json generated vendored Normal file
View File

@@ -0,0 +1,19 @@
{
"platforms": ["apple", "android"],
"apple": {
"modules": ["FileSystemModule", "FileSystemLegacyModule"],
"appDelegateSubscribers": ["FileSystemBackgroundSessionHandler"]
},
"android": {
"modules": [
"expo.modules.filesystem.FileSystemModule",
"expo.modules.filesystem.legacy.FileSystemLegacyModule"
],
"publication": {
"groupId": "host.exp.exponent",
"artifactId": "expo.modules.filesystem",
"version": "55.0.10",
"repository": "local-maven-repo"
}
}
}

View File

@@ -0,0 +1,32 @@
require 'json'
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
Pod::Spec.new do |s|
s.name = 'ExpoFileSystem'
s.version = package['version']
s.summary = package['description']
s.description = package['description']
s.license = package['license']
s.author = package['author']
s.homepage = package['homepage']
s.platforms = {
:ios => '15.1',
:osx => '11.0',
:tvos => '15.1'
}
s.swift_version = '5.9'
s.source = { :git => 'https://github.com/expo/expo.git' }
s.static_framework = true
s.dependency 'ExpoModulesCore'
# Swift/Objective-C compatibility
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES'
}
s.resource_bundles = {'ExpoFileSystem_privacy' => ['PrivacyInfo.xcprivacy']}
s.source_files = "**/*.{h,m,swift}"
end

View File

@@ -0,0 +1,95 @@
// UIKit is unavailable on macOS, so platform checks are necessary.
// For macOS support, we should consider using NSOpenPanel: https://developer.apple.com/documentation/appkit/nsopenpanel
// UIDocumentPickerViewController is unavailable on tvOS
#if os(iOS)
import ExpoModulesCore
import UIKit
internal class FilePickingHandler: FilePickingResultHandler {
private weak var module: FileSystemModule?
internal var filePickingContext: FilePickingContext?
init(module: FileSystemModule) {
self.module = module
}
func presentDocumentPicker(
picker: UIDocumentPickerViewController,
isDirectory: Bool,
initialUri: URL?,
mimeType: String?,
promise: Promise
) {
guard let module = module else {
promise.reject(MissingViewControllerException())
return
}
if filePickingContext != nil {
promise.reject(PickingInProgressException())
return
}
guard let currentVc = module.appContext?.utilities?.currentViewController() else {
promise.reject(MissingViewControllerException())
return
}
let pickerDelegate = FilePickingDelegate(resultHandler: self, isDirectory: isDirectory)
filePickingContext = FilePickingContext(
promise: promise,
initialUri: initialUri,
mimeType: mimeType,
isDirectory: isDirectory,
delegate: pickerDelegate
)
picker.delegate = pickerDelegate
picker.presentationController?.delegate = pickerDelegate
picker.allowsMultipleSelection = false
if UIDevice.current.userInterfaceIdiom == .pad {
let viewFrame = currentVc.view.frame
picker.popoverPresentationController?.sourceRect = CGRect(
x: viewFrame.midX,
y: viewFrame.maxY,
width: 0,
height: 0
)
picker.popoverPresentationController?.sourceView = currentVc.view
picker.modalPresentationStyle = .pageSheet
}
currentVc.present(picker, animated: true)
}
func didPickFileAt(url: URL) {
handlePickingResult { context in
let file = FileSystemFile(url: url)
context.promise.resolve(file)
}
}
func didPickDirectoryAt(url: URL) {
handlePickingResult { context in
let directory = FileSystemDirectory(url: url)
context.promise.resolve(directory)
}
}
func didCancelPicking() {
handlePickingResult { context in
context.promise.reject(FilePickingCancelledException())
}
}
private func handlePickingResult(_ handler: (FilePickingContext) -> Void) {
guard let context = filePickingContext else {
return
}
filePickingContext = nil
handler(context)
}
}
#endif

View File

@@ -0,0 +1,167 @@
// UIDocumentPickerViewController is unavailable on tvOS
#if os(iOS)
import ExpoModulesCore
import MobileCoreServices
import UIKit
import UniformTypeIdentifiers
internal protocol FilePickingResultHandler {
func didPickFileAt(url: URL)
func didPickDirectoryAt(url: URL)
func didCancelPicking()
}
internal struct FilePickingContext {
let promise: Promise
let initialUri: URL?
let mimeType: String?
let isDirectory: Bool
let delegate: FilePickingDelegate
var pickedUrl: URL?
}
internal class FilePickingDelegate: NSObject, UIDocumentPickerDelegate, UIAdaptivePresentationControllerDelegate {
private let resultHandler: FilePickingResultHandler
private let isDirectory: Bool
private weak var pickingHandler: FilePickingHandler?
init(resultHandler: FilePickingResultHandler, isDirectory: Bool = false) {
self.resultHandler = resultHandler
self.isDirectory = isDirectory
self.pickingHandler = resultHandler as? FilePickingHandler
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let url = urls.first else {
self.resultHandler.didCancelPicking()
return
}
if isDirectory {
// For directory access, we need to start accessing the security-scoped resource
let didStartAccessing = url.startAccessingSecurityScopedResource()
if didStartAccessing {
// Store the picked URL for proper cleanup
if let pickingHandler = pickingHandler {
pickingHandler.filePickingContext?.pickedUrl = url
}
self.resultHandler.didPickDirectoryAt(url: url)
} else {
// If we can't access the directory, treat as cancellation
self.resultHandler.didCancelPicking()
}
} else {
self.resultHandler.didPickFileAt(url: url)
}
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
self.resultHandler.didCancelPicking()
}
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
self.resultHandler.didCancelPicking()
}
}
internal func createFilePicker(initialUri: URL?, mimeType: String?) -> UIDocumentPickerViewController {
if #available(iOS 14.0, *) {
let utTypes: [UTType]
if let mimeType = mimeType {
if let utType = UTType(mimeType: mimeType) {
utTypes = [utType]
} else {
utTypes = [UTType.item]
}
} else {
utTypes = [UTType.item]
}
let picker = UIDocumentPickerViewController(forOpeningContentTypes: utTypes, asCopy: true)
if let initialUri = initialUri {
picker.directoryURL = initialUri
}
return picker
}
let utiTypes: [String]
if let mimeType = mimeType {
utiTypes = [toUTI(mimeType: mimeType)]
} else {
utiTypes = [kUTTypeItem as String]
}
let picker = UIDocumentPickerViewController(documentTypes: utiTypes, in: .import)
if let initialUri = initialUri {
picker.directoryURL = initialUri
}
return picker
}
internal func createDirectoryPicker(initialUri: URL?) -> UIDocumentPickerViewController {
if #available(iOS 14.0, *) {
// Use UTType.folder for directory access as per Apple's documentation
let picker = UIDocumentPickerViewController(forOpeningContentTypes: [UTType.folder], asCopy: false)
if let initialUri = initialUri {
picker.directoryURL = initialUri
}
return picker
}
// For iOS 13 and earlier, use kUTTypeFolder
let picker = UIDocumentPickerViewController(documentTypes: [kUTTypeFolder as String], in: .open)
if let initialUri = initialUri {
picker.directoryURL = initialUri
}
return picker
}
@available(iOS 14.0, *)
private func toUTType(mimeType: String) -> UTType? {
switch mimeType {
case "*/*":
return UTType.item
case "image/*":
return UTType.image
case "video/*":
return UTType.movie
case "audio/*":
return UTType.audio
case "text/*":
return UTType.text
default:
return UTType(mimeType: mimeType)
}
}
private func toUTI(mimeType: String) -> String {
var uti: CFString
switch mimeType {
case "*/*":
uti = kUTTypeItem
case "image/*":
uti = kUTTypeImage
case "video/*":
uti = kUTTypeVideo
case "audio/*":
uti = kUTTypeAudio
case "text/*":
uti = kUTTypeText
default:
if let ref = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassMIMEType,
mimeType as CFString,
nil
)?.takeRetainedValue() {
uti = ref
} else {
uti = kUTTypeItem
}
}
return uti as String
}
#endif

View File

@@ -0,0 +1,112 @@
import Foundation
import ExpoModulesCore
internal final class FileSystemDirectory: FileSystemPath {
init(url: URL) {
super.init(url: url, isDirectory: true)
}
override func validateType() throws {
try withCorrectTypeAndScopedAccess(permission: .read) {
var isDirectory: ObjCBool = false
if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory) {
if !isDirectory.boolValue {
throw InvalidTypeDirectoryException()
}
}
}
}
func create(_ options: CreateOptions) throws {
try withCorrectTypeAndScopedAccess(permission: .write) {
guard try needsCreation(options) else {
return
}
try validateCanCreate(options)
do {
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: options.intermediates, attributes: nil)
} catch {
throw UnableToCreateException(error.localizedDescription)
}
}
}
var size: Int64 {
get throws {
try validatePermission(.read)
var size: Int64 = 0
guard let subpaths = try? FileManager.default.subpathsOfDirectory(atPath: url.path) else {
throw UnableToGetSizeException("attributes do not contain size")
}
for subpath in subpaths {
let strSubpath = url.appendingPathComponent(subpath).path
guard let attributes: [FileAttributeKey: Any] = try? FileManager.default.attributesOfItem(atPath: strSubpath), let subpathSize = attributes[.size] as? Int64 else {
continue
}
size += subpathSize
}
return size
}
}
override var exists: Bool {
guard checkPermission(.read) else {
return false
}
var isDirectory: ObjCBool = false
if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory) {
return isDirectory.boolValue
}
return false
}
// Internal only function
func listAsRecords() throws -> [[String: Any]] {
try withCorrectTypeAndScopedAccess(permission: .read) {
var contents: [[String: Any]] = []
let items = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
for item in items {
contents.append(["isDirectory": item.hasDirectoryPath, "uri": item.absoluteString])
}
return contents
}
}
func validatePath() throws {
guard url.isFileURL && url.hasDirectoryPath else {
throw Exception(name: "wrong type", description: "tried to create a directory with a file path")
}
}
func info() throws -> DirectoryInfo {
try withCorrectTypeAndScopedAccess(permission: .read) {
if !exists {
let result = DirectoryInfo()
result.exists = false
result.uri = url.absoluteString
return result
}
switch url.scheme {
case "file":
let result = DirectoryInfo()
result.exists = true
result.uri = url.absoluteString
result.size = try size
result.files = (try? FileManager.default.contentsOfDirectory(atPath: url.path)) ?? []
result.modificationTime = try modificationTime
result.creationTime = try creationTime
return result
default:
throw UnableToGetInfoException("url scheme \(String(describing: url.scheme)) is not supported")
}
}
}
func needsCreation(_ options: CreateOptions) throws -> Bool {
if !exists {
return true
}
return !options.idempotent
}
}

View File

@@ -0,0 +1,110 @@
import Foundation
import ExpoModulesCore
internal final class CopyOrMoveDirectoryToFileException: Exception {
override var reason: String {
"Unable to copy or move a directory to a file"
}
}
internal final class UnableToDownloadException: GenericException<String> {
override var reason: String {
"Unable to download a file: \(param)"
}
}
internal final class UnableToWriteBase64DataException: GenericException<String> {
override var reason: String {
"Unable to write base64 data to a file: \(param)"
}
}
internal final class InvalidTypeFileException: Exception {
override var reason: String {
"A folder with the same name already exists in the file location"
}
}
internal final class InvalidTypeDirectoryException: Exception {
override var reason: String {
"A file with the same name already exists in the directory location"
}
}
internal final class UnableToGetFileAttribute: GenericException<String> {
override var reason: String {
"Unable to get file attribute: \(param)"
}
}
internal final class UnableToGetSizeException: GenericException<String> {
override var reason: String {
"Unable to get file or directory size: \(param)"
}
}
internal final class UnableToDeleteException: GenericException<String> {
override var reason: String {
"Unable to delete file or directory: \(param)"
}
}
internal final class UnableToCreateException: GenericException<String> {
override var reason: String {
"Unable to create file or directory: \(param)"
}
}
internal final class UnableToReadHandleException: GenericException<String> {
override var reason: String {
"Unable to read from a file handle: \(param)"
}
}
internal final class UnableToGetInfoException: GenericException<String> {
override var reason: String {
"Unable to get info from a file: \(param)"
}
}
internal final class DestinationAlreadyExistsException: Exception {
override var reason: String {
"Destination already exists"
}
}
internal final class MissingPermissionException: GenericException<String> {
override var reason: String {
"Missing permission for uri: \(param)"
}
}
internal final class PickingInProgressException: Exception {
override var reason: String {
"File picking is already in progress"
}
}
internal final class MissingViewControllerException: Exception {
override var reason: String {
"No view controller available for presenting file picker"
}
}
internal final class FilePickingCancelledException: Exception {
override var reason: String {
"File picking was cancelled by the user"
}
}
internal final class NotImplementedException: Exception {
override var reason: String {
"Not implemented"
}
}
internal final class FeatureNotAvailableOnPlatformException: Exception {
override var reason: String {
"This feature is not available on this platform"
}
}

166
node_modules/expo-file-system/ios/FileSystemFile.swift generated vendored Normal file
View File

@@ -0,0 +1,166 @@
import Foundation
import ExpoModulesCore
import CryptoKit
import UniformTypeIdentifiers
internal final class FileSystemFile: FileSystemPath {
init(url: URL) {
super.init(url: url, isDirectory: false)
}
override func validateType() throws {
var isDirectory: ObjCBool = false
if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory) {
if isDirectory.boolValue {
throw InvalidTypeFileException()
}
}
}
func create(_ options: CreateOptions) throws {
try withCorrectTypeAndScopedAccess(permission: .write) {
try validateCanCreate(options)
do {
if options.intermediates {
try FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true)
}
try? FileManager.default.removeItem(atPath: url.path)
FileManager.default.createFile(atPath: url.path, contents: nil)
} catch {
throw UnableToCreateException(error.localizedDescription)
}
}
}
override var exists: Bool {
guard checkPermission(.read) else {
return false
}
var isDirectory: ObjCBool = false
if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory) {
return !isDirectory.boolValue
}
return false
}
// TODO: Move to the constructor once error is rethrowed
func validatePath() throws {
guard url.isFileURL && !url.hasDirectoryPath else {
throw Exception(name: "wrong type", description: "tried to create a file with a directory path")
}
}
var md5: String {
get throws {
return try withCorrectTypeAndScopedAccess(permission: .read) {
let fileData = try Data(contentsOf: url)
let hash = Insecure.MD5.hash(data: fileData)
return hash.map { String(format: "%02hhx", $0) }.joined()
}
}
}
var size: Int64 {
get throws {
return try getAttribute(.size, atPath: url.path)
}
}
var type: String? {
let pathExtension = url.pathExtension
if let utType = UTType(filenameExtension: pathExtension),
let mimeType = utType.preferredMIMEType {
return mimeType
}
return nil
}
func write(_ content: String, append: Bool = false) throws {
try withCorrectTypeAndScopedAccess(permission: .write) {
if append, let data = content.data(using: .utf8) {
try writeAppending(data)
} else {
try content.write(to: url, atomically: false, encoding: .utf8) // TODO: better error handling
}
}
}
func write(_ data: Data, append: Bool = false) throws {
try withCorrectTypeAndScopedAccess(permission: .write) {
if append {
try writeAppending(data)
} else {
try data.write(to: url)
}
}
}
// TODO: blob support
func write(_ content: TypedArray, append: Bool = false) throws {
try withCorrectTypeAndScopedAccess(permission: .write) {
let data = Data(bytes: content.rawPointer, count: content.byteLength)
if append {
try writeAppending(data)
} else {
try data.write(to: url)
}
}
}
private func writeAppending(_ data: Data) throws {
if !FileManager.default.fileExists(atPath: url.path) {
try data.write(to: url)
return
}
let fileHandle = try FileHandle(forWritingTo: url)
defer {
fileHandle.closeFile()
}
fileHandle.seekToEndOfFile()
fileHandle.write(data)
}
func text() throws -> String {
return try withCorrectTypeAndScopedAccess(permission: .write) {
return try String(contentsOf: url)
}
}
func bytes() throws -> Data {
return try withCorrectTypeAndScopedAccess(permission: .write) {
return try Data(contentsOf: url)
}
}
func base64() throws -> String {
return try withCorrectTypeAndScopedAccess(permission: .read) {
return try Data(contentsOf: url).base64EncodedString()
}
}
func info(options: InfoOptions) throws -> FileInfo {
return try withCorrectTypeAndScopedAccess(permission: .read) {
if !exists {
let result = FileInfo()
result.exists = false
result.uri = url.absoluteString
return result
}
switch url.scheme {
case "file":
let result = FileInfo()
result.exists = true
result.uri = url.absoluteString
result.size = try size
result.modificationTime = try modificationTime
result.creationTime = try creationTime
if options.md5 {
result.md5 = try md5
}
return result
default:
throw UnableToGetInfoException("url scheme \(String(describing: url.scheme)) is not supported")
}
}
}
}

View File

@@ -0,0 +1,54 @@
import Foundation
import ExpoModulesCore
@available(iOS 14, tvOS 14, *)
internal final class FileSystemFileHandle: SharedRef<FileHandle> {
let file: FileSystemFile
let handle: FileHandle
init(file: FileSystemFile) throws {
self.file = file
handle = try FileHandle(forUpdating: file.url)
super.init(handle)
}
func read(_ length: Int) throws -> Data {
do {
let data = try handle.read(upToCount: length)
return data ?? Data()
} catch {
throw UnableToReadHandleException(error.localizedDescription)
}
}
func write(_ bytes: Data) throws {
try handle.write(contentsOf: bytes)
}
func close() throws {
try handle.close()
}
var offset: UInt64? {
get {
try? handle.offset()
}
set(newOffset) {
guard let newOffset else {
return
}
handle.seek(toFileOffset: newOffset)
}
}
var size: UInt64? {
do {
let offset = try handle.offset()
let size = try handle.seekToEnd()
handle.seek(toFileOffset: offset)
return size
} catch {
return nil
}
}
}

View File

@@ -0,0 +1,373 @@
// Copyright 2024-present 650 Industries. All rights reserved.
import ExpoModulesCore
@available(iOS 14, tvOS 14, *)
public final class FileSystemModule: Module {
#if os(iOS)
private lazy var filePickingHandler = FilePickingHandler(module: self)
#endif
var documentDirectory: URL? {
return appContext?.config.documentDirectory
}
var cacheDirectory: URL? {
return appContext?.config.cacheDirectory
}
var totalDiskSpace: Int64? {
guard let path = documentDirectory?.path,
let attributes = try? FileManager.default.attributesOfFileSystem(forPath: path) else {
return nil
}
return attributes[.systemFreeSize] as? Int64
}
var availableDiskSpace: Int64? {
guard let path = documentDirectory?.path,
let attributes = try? FileManager.default.attributesOfFileSystem(forPath: path) else {
return nil
}
return attributes[.systemFreeSize] as? Int64
}
public func definition() -> ModuleDefinition {
Name("FileSystem")
Constant("documentDirectory") {
return documentDirectory?.absoluteString
}
Constant("cacheDirectory") {
return cacheDirectory?.absoluteString
}
Constant("bundleDirectory") {
return Bundle.main.bundlePath
}
Constant("appleSharedContainers") {
return getAppleSharedContainers()
}
Property("totalDiskSpace") {
return totalDiskSpace
}
Property("availableDiskSpace") {
return availableDiskSpace
}
// swiftlint:disable:next closure_body_length
AsyncFunction("downloadFileAsync") { (url: URL, to: FileSystemPath, options: DownloadOptions?, promise: Promise) in
try to.validatePermission(.write)
var request = URLRequest(url: url)
if let headers = options?.headers {
headers.forEach { key, value in
request.addValue(value, forHTTPHeaderField: key)
}
}
let downloadTask = URLSession.shared.downloadTask(with: request) { urlOrNil, responseOrNil, errorOrNil in
guard errorOrNil == nil else {
return promise.reject(UnableToDownloadException(errorOrNil?.localizedDescription ?? "unspecified error"))
}
guard let httpResponse = responseOrNil as? HTTPURLResponse else {
return promise.reject(UnableToDownloadException("no response"))
}
guard httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 else {
return promise.reject(UnableToDownloadException("response has status \(httpResponse.statusCode)"))
}
guard let fileURL = urlOrNil else {
return promise.reject(UnableToDownloadException("no file url"))
}
do {
let destination: URL
if let to = to as? FileSystemDirectory {
let filename = httpResponse.suggestedFilename ?? url.lastPathComponent
destination = to.url.appendingPathComponent(filename)
} else {
destination = to.url
}
if FileManager.default.fileExists(atPath: destination.path) {
if options?.idempotent == true {
try FileManager.default.removeItem(at: destination)
} else {
throw DestinationAlreadyExistsException()
}
}
try FileManager.default.moveItem(at: fileURL, to: destination)
// TODO: Remove .url.absoluteString once returning shared objects works
promise.resolve(destination.absoluteString)
} catch {
promise.reject(error)
}
}
downloadTask.resume()
}
AsyncFunction("pickDirectoryAsync") { (initialUri: URL?, promise: Promise) in
#if os(iOS)
filePickingHandler.presentDocumentPicker(
picker: createDirectoryPicker(initialUri: initialUri),
isDirectory: true,
initialUri: initialUri,
mimeType: nil,
promise: promise
)
#else
promise.reject(FeatureNotAvailableOnPlatformException())
#endif
}.runOnQueue(.main)
AsyncFunction("pickFileAsync") { (initialUri: URL?, mimeType: String?, promise: Promise) in
#if os(iOS)
filePickingHandler.presentDocumentPicker(
picker: createFilePicker(initialUri: initialUri, mimeType: mimeType),
isDirectory: false,
initialUri: initialUri,
mimeType: mimeType,
promise: promise
)
#else
promise.reject(FeatureNotAvailableOnPlatformException())
#endif
}.runOnQueue(.main)
Function("info") { (url: URL) in
let output = PathInfo()
output.exists = false
output.isDirectory = nil
guard let fileSystemManager = appContext?.fileSystem else {
return output
}
if fileSystemManager.getPathPermissions(url.path).contains(.read) {
var isDirectory: ObjCBool = false
if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory) {
output.exists = true
output.isDirectory = isDirectory.boolValue
return output
}
}
return output
}
// swiftlint:disable:next closure_body_length
Class(FileSystemFile.self) {
Constructor { (url: URL) in
return FileSystemFile(url: url.standardizedFileURL)
}
// we can't throw in a constructor, so this is a workaround
Function("validatePath") { file in
try file.validatePath()
}
// maybe asString, readAsString, readAsText, readText, ect.
AsyncFunction("text") { file in
return try file.text()
}
Function("textSync") { file in
return try file.text()
}
AsyncFunction("base64") { file in
return try file.base64()
}
Function("base64Sync") { file in
return try file.base64()
}
AsyncFunction("bytes") { file in
return try file.bytes()
}
Function("bytesSync") { file in
return try file.bytes()
}
Function("open") { file in
return try FileSystemFileHandle(file: file)
}
Function("info") { (file: FileSystemFile, options: InfoOptions?) in
return try file.info(options: options ?? InfoOptions())
}
Function("write") { (file: FileSystemFile, content: Either<String, TypedArray>, options: WriteOptions?) in
let append = options?.append ?? false
if let content: String = content.get() {
if options?.encoding == WriteEncoding.base64 {
guard let data = Data(base64Encoded: content, options: .ignoreUnknownCharacters) else {
throw UnableToWriteBase64DataException(file.url.absoluteString)
}
try file.write(data, append: append)
} else {
try file.write(content, append: append)
}
}
if let content: TypedArray = content.get() {
try file.write(content, append: append)
}
}
Property("size") { file in
try? file.size
}
Property("md5") { file in
try? file.md5
}
Property("modificationTime") { file in
try? file.modificationTime
}
Property("creationTime") { file in
try? file.creationTime
}
Property("type") { file in
file.type
}
Function("delete") { file in
try file.delete()
}
Property("exists") { file in
return file.exists
}
Function("create") { (file, options: CreateOptions?) in
try file.create(options ?? CreateOptions())
}
Function("copy") { (file, to: FileSystemPath) in
try file.copy(to: to)
}
Function("move") { (file, to: FileSystemPath) in
try file.move(to: to)
}
Function("rename") { (file, newName: String) in
try file.rename(newName)
}
Property("uri") { file in
return file.url.absoluteString
}
}
Class(FileSystemFileHandle.self) {
Function("readBytes") { (fileHandle, bytes: Int) in
try fileHandle.read(bytes)
}
Function("writeBytes") { (fileHandle, bytes: Data) in
try fileHandle.write(bytes)
}
Function("close") { fileHandle in
try fileHandle.close()
}
Property("offset") { fileHandle in
fileHandle.offset
}.set { (fileHandle, volume: UInt64) in
fileHandle.offset = volume
}
Property("size") { fileHandle in
fileHandle.size
}
}
// swiftlint:disable:next closure_body_length
Class(FileSystemDirectory.self) {
Constructor { (url: URL) in
return FileSystemDirectory(url: url.standardizedFileURL)
}
Function("info") { directory in
try directory.info()
}
// we can't throw in a constructor, so this is a workaround
Function("validatePath") { directory in
try directory.validatePath()
}
Function("delete") { directory in
try directory.delete()
}
Property("exists") { directory in
return directory.exists
}
Function("create") { (directory, options: CreateOptions?) in
try directory.create(options ?? CreateOptions())
}
Function("copy") { (directory, to: FileSystemPath) in
try directory.copy(to: to)
}
Function("move") { (directory, to: FileSystemPath) in
try directory.move(to: to)
}
Function("rename") { (directory, newName: String) in
try directory.rename(newName)
}
// this function is internal and will be removed in the future (when returning arrays of shared objects is supported)
Function("listAsRecords") { directory in
try directory.listAsRecords()
}
Function("createFile") { (directory, name: String, content: String?) in
let file = FileSystemFile(url: directory.url.appendingPathComponent(name))
try file.create(CreateOptions())
return file
}
Function("createDirectory") { (directory, name: String) in
let newDirectory = FileSystemDirectory(url: directory.url.appendingPathComponent(name))
try newDirectory.create(CreateOptions())
return newDirectory
}
Property("uri") { directory in
return directory.url.absoluteString
}
Property("size") { directory in
return try? directory.size
}
}
}
private func getAppleSharedContainers() -> [String: String] {
guard let appContext else {
return [:]
}
var result: [String: String] = [:]
for appGroup in appContext.appCodeSignEntitlements.appGroups ?? [] {
if let directory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) {
result[appGroup] = directory.standardizedFileURL.path
}
}
return result
}
}

135
node_modules/expo-file-system/ios/FileSystemPath.swift generated vendored Normal file
View File

@@ -0,0 +1,135 @@
import Foundation
import ExpoModulesCore
internal class FileSystemPath: SharedObject {
var url: URL
func validateType() throws {
throw NotImplementedException()
}
init(url: URL, isDirectory: Bool) {
let standardizedUrl = url.deletingLastPathComponent().appendingPathComponent(url.lastPathComponent, isDirectory: isDirectory)
self.url = standardizedUrl
}
func validatePermission(_ flag: FileSystemPermissionFlags) throws {
if !checkPermission(flag) {
throw MissingPermissionException(url.absoluteString)
}
}
func checkPermission(_ flag: FileSystemPermissionFlags) -> Bool {
return FileSystemUtilities.permissions(appContext, for: url).contains(flag)
}
func validateCanCreate(_ options: CreateOptions) throws {
if try !options.overwrite && exists {
throw FileAlreadyExistsException(url.absoluteString)
}
}
var exists: Bool {
get throws {
FileManager.default.fileExists(atPath: url.path)
}
}
func delete() throws {
try validatePermission(.write)
guard FileManager.default.fileExists(atPath: url.path) else {
throw UnableToDeleteException("path does not exist")
}
do {
try FileManager.default.removeItem(at: url)
} catch {
throw UnableToDeleteException(error.localizedDescription)
}
}
func getMoveOrCopyPath(to destination: FileSystemPath) throws -> URL {
if let destination = destination as? FileSystemDirectory {
if self is FileSystemFile {
return destination.url.appendingPathComponent(url.lastPathComponent)
}
// self if FileSystemDirectory
// we match unix behavior https://askubuntu.com/a/763915
if destination.exists {
return destination.url.appendingPathComponent(url.lastPathComponent, isDirectory: true)
}
return destination.url
}
// destination is FileSystemFile
guard self is FileSystemFile else {
throw CopyOrMoveDirectoryToFileException()
}
return destination.url
}
func copy(to destination: FileSystemPath) throws {
try validatePermission(.read)
try destination.validatePermission(.write)
try FileManager.default.copyItem(at: url, to: getMoveOrCopyPath(to: destination))
}
func move(to destination: FileSystemPath) throws {
try validatePermission(.write)
try destination.validatePermission(.write)
let destinationUrl = try getMoveOrCopyPath(to: destination)
try FileManager.default.moveItem(at: url, to: destinationUrl)
url = destinationUrl
}
func getRenamedUrl(newName: String) -> URL {
return url.deletingLastPathComponent().appendingPathComponent(newName)
}
func rename(_ newName: String) throws {
try validatePermission(.write)
let newUrl = getRenamedUrl(newName: newName)
try FileManager.default.moveItem(at: url, to: newUrl)
// Refetch the URL to ensure it has the correct trailing slash, which differs for directories and files.
let updatedUrl = getRenamedUrl(newName: newName)
url = updatedUrl
}
var modificationTime: Int64 {
get throws {
let modificationDate: Date = try getAttribute(.modificationDate, atPath: url.path)
return Int64(modificationDate.timeIntervalSince1970 * 1000)
}
}
var creationTime: Int64 {
get throws {
let creationDate: Date = try getAttribute(.creationDate, atPath: url.path)
return Int64(creationDate.timeIntervalSince1970 * 1000)
}
}
internal func getAttribute<T>(_ key: FileAttributeKey, atPath path: String) throws -> T {
try validatePermission(.read)
let attributes = try FileManager.default.attributesOfItem(atPath: path)
guard let attribute = attributes[key] else {
throw UnableToGetFileAttribute("attributes do not contain \(key)")
}
guard let attributeCasted = attribute as? T else {
throw UnableToGetFileAttribute("\(key) is not of expected type")
}
return attributeCasted
}
@discardableResult
func withCorrectTypeAndScopedAccess<T>(
permission: FileSystemPermissionFlags,
_ work: () throws -> T
) throws -> T {
let accessed = url.startAccessingSecurityScopedResource()
defer { if accessed { url.stopAccessingSecurityScopedResource() } }
try validatePermission(permission)
return try work()
}
}

View File

@@ -0,0 +1,47 @@
// Copyright 2024-present 650 Industries. All rights reserved.
import ExpoModulesCore
struct CreateOptions: Record {
@Field var intermediates: Bool = false
@Field var overwrite: Bool = false
@Field var idempotent: Bool = false
}
struct DownloadOptions: Record {
@Field var headers: [String: String]?
@Field var idempotent: Bool = false
}
struct FileInfo: Record {
@Field var exists: Bool
@Field var uri: String?
@Field var md5: String?
@Field var size: Int64?
@Field var modificationTime: Int64?
@Field var creationTime: Int64?
}
struct PathInfo: Record {
@Field var exists: Bool
@Field var isDirectory: Bool?
}
struct DirectoryInfo: Record {
@Field var exists: Bool
@Field var uri: String?
@Field var files: [String]?
@Field var size: Int64?
@Field var modificationTime: Int64?
@Field var creationTime: Int64?
}
enum WriteEncoding: String, Enumerable {
case utf8
case base64
}
struct WriteOptions: Record {
@Field var encoding: WriteEncoding?
@Field var append: Bool = false
}

View File

@@ -0,0 +1,6 @@
#import <ExpoFileSystem/EXFileSystemHandler.h>
@interface EXFileSystemAssetLibraryHandler : NSObject <EXFileSystemHandler>
@end

View File

@@ -0,0 +1,147 @@
#import <ExpoFileSystem/EXFileSystemAssetLibraryHandler.h>
#import <ExpoFileSystem/NSData+EXFileSystem.h>
#import <Photos/Photos.h>
@implementation EXFileSystemAssetLibraryHandler
+ (void)getInfoForFile:(NSURL *)fileUri
withOptions:(NSDictionary *)options
resolver:(EXPromiseResolveBlock)resolve
rejecter:(EXPromiseRejectBlock)reject
{
NSError *error;
PHFetchResult<PHAsset *> *fetchResult = [self fetchResultForUri:fileUri error:&error];
if (error) {
reject(@"E_UNSUPPORTED_ARG", error.description, error);
return;
}
if (fetchResult.count > 0) {
PHAsset *asset = fetchResult[0];
NSMutableDictionary *result = [NSMutableDictionary dictionary];
result[@"exists"] = @(YES);
result[@"isDirectory"] = @(NO);
result[@"uri"] = fileUri;
// Uses required reason API based on the following reason: 3B52.1
result[@"modificationTime"] = @(asset.modificationDate.timeIntervalSince1970);
if (options[@"md5"] || options[@"size"]) {
[[PHImageManager defaultManager] requestImageDataAndOrientationForAsset:asset options:nil resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, CGImagePropertyOrientation orientation, NSDictionary * _Nullable info) {
result[@"size"] = @(imageData.length);
if (options[@"md5"]) {
result[@"md5"] = [imageData md5String];
}
resolve(result);
}];
} else {
resolve(result);
}
} else {
resolve(@{@"exists": @(NO), @"isDirectory": @(NO)});
}
}
+ (void)copyFrom:(NSURL *)from
to:(NSURL *)to
resolver:(EXPromiseResolveBlock)resolve
rejecter:(EXPromiseRejectBlock)reject
{
NSString *toPath = [to.path stringByStandardizingPath];
// NOTE: The destination-delete and the copy should happen atomically, but we hope for the best for now
NSError *error;
if ([[NSFileManager defaultManager] fileExistsAtPath:toPath]) {
if (![[NSFileManager defaultManager] removeItemAtPath:toPath error:&error]) {
reject(@"E_FILE_NOT_COPIED",
[NSString stringWithFormat:@"File '%@' could not be copied to '%@' because a file already exists at "
"the destination and could not be deleted.", from, to],
error);
return;
}
}
PHFetchResult<PHAsset *> *fetchResult = [self fetchResultForUri:from error:&error];
if (error) {
reject(@"E_UNSUPPORTED_ARG", error.description, error);
return;
}
if (fetchResult.count > 0) {
PHAsset *asset = fetchResult[0];
if (asset.mediaType == PHAssetMediaTypeVideo) {
[[PHImageManager defaultManager] requestAVAssetForVideo:asset options:nil resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
if (![asset isKindOfClass:[AVURLAsset class]]) {
reject(@"ERR_INCORRECT_ASSET_TYPE",
[NSString stringWithFormat:@"File '%@' has incorrect asset type.", from],
nil);
return;
}
AVURLAsset* urlAsset = (AVURLAsset*)asset;
NSNumber *size;
[urlAsset.URL getResourceValue:&size forKey:NSURLFileSizeKey error:nil];
NSData *data = [NSData dataWithContentsOfURL:urlAsset.URL];
[EXFileSystemAssetLibraryHandler copyData:data toPath:toPath resolver:resolve rejecter:reject];
}];
} else {
[[PHImageManager defaultManager] requestImageDataAndOrientationForAsset:asset options:nil resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, CGImagePropertyOrientation orientation, NSDictionary * _Nullable info) {
[EXFileSystemAssetLibraryHandler copyData:imageData toPath:toPath resolver:resolve rejecter:reject];
}];
}
} else {
reject(@"E_FILE_NOT_COPIED",
[NSString stringWithFormat:@"File '%@' could not be found.", from],
error);
}
}
// adapted from RCTImageLoader.m
+ (PHFetchResult<PHAsset *> *)fetchResultForUri:(NSURL *)url error:(NSError **)error
{
if ([url.scheme caseInsensitiveCompare:@"ph"] == NSOrderedSame) {
// Fetch assets using PHAsset localIdentifier (recommended)
NSString *const localIdentifier = [url.absoluteString substringFromIndex:@"ph://".length];
return [PHAsset fetchAssetsWithLocalIdentifiers:@[localIdentifier] options:nil];
} else if ([url.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame) {
#if TARGET_OS_MACCATALYST
static BOOL hasWarned = NO;
if (!hasWarned) {
NSLog(@"assets-library:// URLs have been deprecated and cannot be accessed in macOS Catalyst. Returning nil (future warnings will be suppressed).");
hasWarned = YES;
}
return nil;
#elif TARGET_OS_IOS || TARGET_OS_TV
// This is the older, deprecated way of fetching assets from assets-library
// using the "assets-library://" protocol
return [PHAsset fetchAssetsWithALAssetURLs:@[url] options:nil];
#elif TARGET_OS_OSX
return nil;
#endif
}
NSString *description = [NSString stringWithFormat:@"Invalid URL provided, expected scheme to be either 'ph' or 'assets-library', was '%@'.", url.scheme];
if (error != NULL) {
*error = [[NSError alloc] initWithDomain:NSURLErrorDomain
code:NSURLErrorUnsupportedURL
userInfo:@{NSLocalizedDescriptionKey: description}];
}
return nil;
}
+ (void)copyData:(NSData *)data
toPath:(NSString *)path
resolver:(EXPromiseResolveBlock)resolve
rejecter:(EXPromiseRejectBlock)reject
{
if ([data writeToFile:path atomically:YES]) {
resolve(nil);
} else {
reject(@"E_FILE_NOT_COPIED",
[NSString stringWithFormat:@"File could not be copied to '%@'.", path],
nil);
}
}
@end

View File

@@ -0,0 +1,17 @@
// Copyright 2023-present 650 Industries. All rights reserved.
#import <ExpoModulesCore/ExpoModulesCore.h>
@protocol EXFileSystemHandler
+ (void)getInfoForFile:(NSURL *)fileUri
withOptions:(NSDictionary *)optionxs
resolver:(EXPromiseResolveBlock)resolve
rejecter:(EXPromiseRejectBlock)reject;
+ (void)copyFrom:(NSURL *)from
to:(NSURL *)to
resolver:(EXPromiseResolveBlock)resolve
rejecter:(EXPromiseRejectBlock)reject;
@end

View File

@@ -0,0 +1,7 @@
// Copyright 2023-present 650 Industries. All rights reserved.
#import <ExpoFileSystem/EXFileSystemHandler.h>
@interface EXFileSystemLocalFileHandler : NSObject <EXFileSystemHandler>
@end

View File

@@ -0,0 +1,80 @@
#import <ExpoFileSystem/EXFileSystemLocalFileHandler.h>
#import <ExpoFileSystem/NSData+EXFileSystem.h>
@implementation EXFileSystemLocalFileHandler
+ (void)getInfoForFile:(NSURL *)fileUri
withOptions:(NSDictionary *)options
resolver:(EXPromiseResolveBlock)resolve
rejecter:(EXPromiseRejectBlock)reject
{
NSString *path = fileUri.path;
BOOL isDirectory;
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory]) {
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
NSMutableDictionary *result = [NSMutableDictionary dictionary];
result[@"exists"] = @(YES);
result[@"isDirectory"] = @(isDirectory);
result[@"uri"] = [NSURL fileURLWithPath:path].absoluteString;
if ([options[@"md5"] boolValue]) {
result[@"md5"] = [[NSData dataWithContentsOfFile:path] md5String];
}
result[@"size"] = @([EXFileSystemLocalFileHandler getFileSize:path attributes:attributes]);
// Uses required reason API based on the following reason: 0A2A.1
result[@"modificationTime"] = @(attributes.fileModificationDate.timeIntervalSince1970);
resolve(result);
} else {
resolve(@{@"exists": @(NO), @"isDirectory": @(NO)});
}
}
+ (unsigned long long)getFileSize:(NSString *)path attributes:(NSDictionary<NSFileAttributeKey, id> *)attributes
{
if (attributes.fileType != NSFileTypeDirectory) {
return attributes.fileSize;
}
// The path is pointing to the folder
NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
NSEnumerator *contentsEnumurator = [contents objectEnumerator];
NSString *file;
unsigned long long folderSize = 0;
while (file = [contentsEnumurator nextObject]) {
NSString *filePath = [path stringByAppendingPathComponent:file];
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
folderSize += [EXFileSystemLocalFileHandler getFileSize:filePath attributes:fileAttributes];
}
return folderSize;
}
+ (void)copyFrom:(NSURL *)from
to:(NSURL *)to
resolver:(EXPromiseResolveBlock)resolve
rejecter:(EXPromiseRejectBlock)reject
{
NSString *fromPath = [from.path stringByStandardizingPath];
NSString *toPath = [to.path stringByStandardizingPath];
NSError *error;
if ([[NSFileManager defaultManager] fileExistsAtPath:toPath]) {
if (![[NSFileManager defaultManager] removeItemAtPath:toPath error:&error]) {
reject(@"E_FILE_NOT_COPIED",
[NSString stringWithFormat:@"File '%@' could not be copied to '%@' because a file already exists at "
"the destination and could not be deleted.", from, to],
error);
return;
}
}
if ([[NSFileManager defaultManager] copyItemAtPath:fromPath toPath:toPath error:&error]) {
resolve(nil);
} else {
reject(@"E_FILE_NOT_COPIED",
[NSString stringWithFormat:@"File '%@' could not be copied to '%@'.", from, to],
error);
}
}
@end

View File

@@ -0,0 +1,16 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <ExpoFileSystem/EXSessionUploadTaskDelegate.h>
#import <ExpoFileSystem/EXTaskHandlersManager.h>
typedef void (^EXUploadDelegateOnSendCallback)(NSURLSessionUploadTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend);
@interface EXSessionCancelableUploadTaskDelegate : EXSessionUploadTaskDelegate
- (nonnull instancetype)initWithResolve:(EXPromiseResolveBlock)resolve
reject:(EXPromiseRejectBlock)reject
onSendCallback:(EXUploadDelegateOnSendCallback)onSendCallback
resumableManager:(EXTaskHandlersManager *)manager
uuid:(NSString *)uuid;
@end

View File

@@ -0,0 +1,55 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <ExpoFileSystem/EXSessionCancelableUploadTaskDelegate.h>
@interface EXSessionCancelableUploadTaskDelegate ()
@property (strong, nonatomic, readonly) EXUploadDelegateOnSendCallback onSendCallback;
@property (weak, nonatomic) EXTaskHandlersManager *manager;
@property (strong, nonatomic) NSString *uuid;
@end
@implementation EXSessionCancelableUploadTaskDelegate
- (nonnull instancetype)initWithResolve:(EXPromiseResolveBlock)resolve
reject:(EXPromiseRejectBlock)reject
onSendCallback:(EXUploadDelegateOnSendCallback)onSendCallback
resumableManager:(EXTaskHandlersManager *)manager
uuid:(NSString *)uuid;
{
if (self = [super initWithResolve:resolve
reject:reject]) {
_onSendCallback = onSendCallback;
_manager = manager;
_uuid = uuid;
}
return self;
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error) {
// The task was paused by us. So, we shouldn't throw.
if (error.code == NSURLErrorCancelled) {
self.resolve([NSNull null]);
[_manager unregisterTask:_uuid];
return;
}
}
[super URLSession:session task:task didCompleteWithError:error];
[_manager unregisterTask:_uuid];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
if (_onSendCallback && bytesSent > 0) {
_onSendCallback(task, bytesSent, totalBytesSent, totalBytesExpectedToSend);
}
}
@end

View File

@@ -0,0 +1,13 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <ExpoFileSystem/EXSessionTaskDelegate.h>
@interface EXSessionDownloadTaskDelegate : EXSessionTaskDelegate
- (nonnull instancetype)initWithResolve:(EXPromiseResolveBlock)resolve
reject:(EXPromiseRejectBlock)reject
localUrl:(NSURL *)localUrl
shouldCalculateMd5:(BOOL)shouldCalculateMd5;
@end

View File

@@ -0,0 +1,64 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <ExpoFileSystem/EXSessionDownloadTaskDelegate.h>
#import <ExpoFileSystem/NSData+EXFileSystem.h>
@interface EXSessionDownloadTaskDelegate ()
@property (strong, nonatomic) NSURL *localUrl;
@property (nonatomic) BOOL shouldCalculateMd5;
@end
@implementation EXSessionDownloadTaskDelegate
- (nonnull instancetype)initWithResolve:(EXPromiseResolveBlock)resolve
reject:(EXPromiseRejectBlock)reject
localUrl:(NSURL *)localUrl
shouldCalculateMd5:(BOOL)shouldCalculateMd5
{
if (self = [super initWithResolve:resolve reject:reject])
{
_localUrl = localUrl;
_shouldCalculateMd5 = shouldCalculateMd5;
}
return self;
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:_localUrl.path]) {
[fileManager removeItemAtURL:_localUrl error:&error];
if (error) {
self.reject(@"ERR_FILESYSTEM_CANNOT_REMOVE",
[NSString stringWithFormat:@"Unable to remove file from local URI: '%@'", error.description],
error);
return;
}
}
[fileManager moveItemAtURL:location toURL:_localUrl error:&error];
if (error) {
self.reject(@"ERR_FILESYSTEM_CANNOT_SAVE",
[NSString stringWithFormat:@"Unable to save file to local URI: '%@'", error.description],
error);
return;
}
self.resolve([self parseServerResponse:downloadTask.response]);
}
- (NSDictionary *)parseServerResponse:(NSURLResponse *)response
{
NSMutableDictionary *result = [[super parseServerResponse:response] mutableCopy];
result[@"uri"] = _localUrl.absoluteString;
if (_shouldCalculateMd5) {
NSData *data = [NSData dataWithContentsOfURL:_localUrl];
result[@"md5"] = [data md5String];
}
return result;
}
@end

View File

@@ -0,0 +1,19 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoModulesCore/Platform.h>
#import <ExpoModulesCore/EXSingletonModule.h>
NS_ASSUME_NONNULL_BEGIN
@protocol EXSessionHandler
- (void)invokeCompletionHandlerForSessionIdentifier:(NSString *)identifier;
@end
@interface EXSessionHandler : EXSingletonModule <UIApplicationDelegate, EXSessionHandler>
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,49 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <ExpoFileSystem/EXSessionHandler.h>
#import <ExpoModulesCore/EXDefines.h>
@interface EXSessionHandler ()
@property (nonatomic, strong) NSMutableDictionary<NSString *, void (^)(void)> *completionHandlers;
@end
@implementation EXSessionHandler
EX_REGISTER_SINGLETON_MODULE(SessionHandler);
- (instancetype)init
{
if (self = [super init]) {
_completionHandlers = [NSMutableDictionary dictionary];
}
return self;
}
- (void)invokeCompletionHandlerForSessionIdentifier:(NSString *)identifier
{
if (!identifier) {
return;
}
void (^completionHandler)(void) = _completionHandlers[identifier];
if (completionHandler) {
// We need to run completionHandler explicite on the main thread because is's part of UIKit
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler();
});
[_completionHandlers removeObjectForKey:identifier];
}
}
#pragma mark - AppDelegate
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler
{
_completionHandlers[identifier] = completionHandler;
}
@end

View File

@@ -0,0 +1,18 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <ExpoFileSystem/EXSessionDownloadTaskDelegate.h>
#import <ExpoFileSystem/EXTaskHandlersManager.h>
typedef void (^EXDownloadDelegateOnWriteCallback)(NSURLSessionDownloadTask *task, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite);
@interface EXSessionResumableDownloadTaskDelegate : EXSessionDownloadTaskDelegate
- (nonnull instancetype)initWithResolve:(EXPromiseResolveBlock)resolve
reject:(EXPromiseRejectBlock)reject
localUrl:(NSURL *)localUrl
shouldCalculateMd5:(BOOL)shouldCalculateMd5
onWriteCallback:(EXDownloadDelegateOnWriteCallback)onWriteCallback
resumableManager:(EXTaskHandlersManager *)manager
uuid:(NSString *)uuid;
@end

View File

@@ -0,0 +1,66 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <ExpoFileSystem/EXSessionResumableDownloadTaskDelegate.h>
@interface EXSessionResumableDownloadTaskDelegate ()
@property (strong, nonatomic, readonly) EXDownloadDelegateOnWriteCallback onWriteCallback;
@property (weak, nonatomic) EXTaskHandlersManager *manager;
@property (strong, nonatomic) NSString *uuid;
@end
@implementation EXSessionResumableDownloadTaskDelegate
- (nonnull instancetype)initWithResolve:(EXPromiseResolveBlock)resolve
reject:(EXPromiseRejectBlock)reject
localUrl:(NSURL *)localUrl
shouldCalculateMd5:(BOOL)shouldCalculateMd5
onWriteCallback:(EXDownloadDelegateOnWriteCallback)onWriteCallback
resumableManager:(EXTaskHandlersManager *)manager
uuid:(NSString *)uuid;
{
if (self = [super initWithResolve:resolve
reject:reject
localUrl:localUrl
shouldCalculateMd5:shouldCalculateMd5]) {
_onWriteCallback = onWriteCallback;
_manager = manager;
_uuid = uuid;
}
return self;
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
[super URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
[_manager unregisterTask:_uuid];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error) {
// The task was paused by us. So, we shouldn't throw.
if (error.code == NSURLErrorCancelled) {
self.resolve([NSNull null]);
} else {
self.reject(@"ERR_FILESYSTEM_CANNOT_DOWNLOAD",
[NSString stringWithFormat:@"Unable to download file: %@", error.description],
error);
}
}
[_manager unregisterTask:_uuid];
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
if (_onWriteCallback && bytesWritten > 0) {
_onWriteCallback(downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}
}
@end

View File

@@ -0,0 +1,32 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoModulesCore/EXDefines.h>
@interface EXSessionTaskDelegate : NSObject
@property (nonatomic, strong, readonly) EXPromiseResolveBlock resolve;
@property (nonatomic, strong, readonly) EXPromiseRejectBlock reject;
- (nonnull instancetype)initWithResolve:(EXPromiseResolveBlock)resolve
reject:(EXPromiseRejectBlock)reject;
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;
- (NSDictionary *)parseServerResponse:(NSURLResponse *)response;
@end

View File

@@ -0,0 +1,58 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <ExpoFileSystem/EXSessionTaskDelegate.h>
@implementation EXSessionTaskDelegate
- (nonnull instancetype)initWithResolve:(EXPromiseResolveBlock)resolve
reject:(EXPromiseRejectBlock)reject
{
if (self = [super init]) {
_resolve = resolve;
_reject = reject;
}
return self;
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error) {
self.reject(@"ERR_FILESYSTEM_CANNOT_DOWNLOAD",
[NSString stringWithFormat:@"Unable to download file: %@", error.description],
error);
}
}
- (NSDictionary *)parseServerResponse:(NSURLResponse *)response
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
return @{
@"status": @([httpResponse statusCode]),
@"headers": [httpResponse allHeaderFields],
@"mimeType": EXNullIfNil([httpResponse MIMEType])
};
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
}
@end

View File

@@ -0,0 +1,19 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoFileSystem/EXSessionTaskDelegate.h>
#import <ExpoFileSystem/EXSessionHandler.h>
NS_ASSUME_NONNULL_BEGIN
@interface EXSessionTaskDispatcher : NSObject <NSURLSessionDelegate>
- (instancetype)initWithSessionHandler:(nullable id<EXSessionHandler>)sessionHandler;
- (void)registerTaskDelegate:(EXSessionTaskDelegate *)delegate forTask:(NSURLSessionTask *)task;
- (void)deactivate;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,96 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <ExpoFileSystem/EXSessionTaskDispatcher.h>
#import <ExpoFileSystem/EXSessionResumableDownloadTaskDelegate.h>
@interface EXSessionTaskDispatcher ()
@property (nonatomic, strong) NSMutableDictionary<NSURLSessionTask *, EXSessionTaskDelegate *> *tasks;
@property (nonatomic) BOOL isActive;
@property (nonatomic, weak, nullable) id<EXSessionHandler> sessionHandler;
@end
@implementation EXSessionTaskDispatcher
- (instancetype)initWithSessionHandler:(nullable id<EXSessionHandler>)sessionHandler;
{
if (self = [super init]) {
_tasks = [NSMutableDictionary dictionary];
_isActive = true;
_sessionHandler = sessionHandler;
}
return self;
}
#pragma mark - public methods
- (void)registerTaskDelegate:(EXSessionTaskDelegate *)delegate forTask:(NSURLSessionTask *)task
{
_tasks[task] = delegate;
}
- (void)deactivate
{
_isActive = false;
}
#pragma mark - dispatcher
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
if (_isActive) {
EXSessionTaskDelegate *exTask = _tasks[downloadTask];
if (exTask) {
[exTask URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
[_tasks removeObjectForKey:downloadTask];
}
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (_isActive) {
EXSessionTaskDelegate *exTask = _tasks[task];
if (exTask) {
[exTask URLSession:session task:task didCompleteWithError:error];
[_tasks removeObjectForKey:task];
}
}
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
if (_isActive) {
EXSessionTaskDelegate *exTask = _tasks[downloadTask];
[exTask URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite];
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
if (_isActive) {
EXSessionTaskDelegate *exTask = _tasks[dataTask];
[exTask URLSession:session dataTask:dataTask didReceiveData:data];
}
}
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
[_sessionHandler invokeCompletionHandlerForSessionIdentifier:session.configuration.identifier];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
if (_isActive) {
EXSessionTaskDelegate *exTask = _tasks[task];
[exTask URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalBytesExpectedToSend];
}
}
@end

View File

@@ -0,0 +1,8 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <ExpoFileSystem/EXSessionTaskDelegate.h>
@interface EXSessionUploadTaskDelegate : EXSessionTaskDelegate
@end

View File

@@ -0,0 +1,52 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <ExpoFileSystem/EXSessionUploadTaskDelegate.h>
@interface EXSessionUploadTaskDelegate ()
@property (strong, nonatomic) NSMutableData *responseData;
@end
@implementation EXSessionUploadTaskDelegate
- (instancetype)initWithResolve:(EXPromiseResolveBlock)resolve reject:(EXPromiseRejectBlock)reject
{
if (self = [super initWithResolve:resolve reject:reject]) {
_responseData = [NSMutableData new];
}
return self;
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
if (!data.length) {
return;
}
[_responseData appendData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error) {
self.reject(@"ERR_FILESYSTEM_CANNOT_UPLOAD",
[NSString stringWithFormat:@"Unable to upload the file: '%@'", error.description],
error);
return;
}
// We only set EXSessionUploadTaskDelegates as delegates of upload tasks
// so it should be safe to assume that this is what we will receive here.
NSURLSessionUploadTask *uploadTask = (NSURLSessionUploadTask *)task;
self.resolve([self parseServerResponse:uploadTask.response]);
}
- (NSDictionary *)parseServerResponse:(NSURLResponse *)response
{
NSMutableDictionary *result = [[super parseServerResponse:response] mutableCopy];
// TODO: add support for others response types (different encodings, files)
result[@"body"] = EXNullIfNil([[NSString alloc] initWithData:_responseData encoding:NSUTF8StringEncoding]);
return result;
}
@end

View File

@@ -0,0 +1,21 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface EXTaskHandlersManager : NSObject
- (NSURLSessionTask * _Nullable)taskForId:(NSString *)uuid;
- (NSURLSessionDownloadTask * _Nullable)downloadTaskForId:(NSString *)uuid;
- (NSURLSessionUploadTask * _Nullable)uploadTaskForId:(NSString *)uuid;
- (void)registerTask:(NSURLSessionTask *)task uuid:(NSString *)uuid;
- (void)unregisterTask:(NSString *)uuid;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,56 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <ExpoFileSystem/EXTaskHandlersManager.h>
@interface EXTaskHandlersManager ()
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSURLSessionTask *> *resumableDownloads;
@end
@implementation EXTaskHandlersManager
- (instancetype)init
{
if (self = [super init]) {
_resumableDownloads = [NSMutableDictionary dictionary];
}
return self;
}
- (void)registerTask:(NSURLSessionTask *)task uuid:(NSString *)uuid
{
_resumableDownloads[uuid] = task;
}
- (NSURLSessionTask * _Nullable)taskForId:(NSString *)uuid
{
return _resumableDownloads[uuid];
}
- (NSURLSessionDownloadTask * _Nullable)downloadTaskForId:(NSString *)uuid
{
NSURLSessionTask *task = [self taskForId:uuid];
if ([task isKindOfClass:[NSURLSessionDownloadTask class]]) {
return (NSURLSessionDownloadTask *)task;
}
return nil;
}
- (NSURLSessionUploadTask * _Nullable)uploadTaskForId:(NSString *)uuid
{
NSURLSessionTask *task = [self taskForId:uuid];
if ([task isKindOfClass:[NSURLSessionUploadTask class]]) {
return (NSURLSessionDownloadTask *)task;
}
return nil;
}
- (void)unregisterTask:(NSString *)uuid
{
[_resumableDownloads removeObjectForKey:uuid];
}
@end

View File

@@ -0,0 +1,88 @@
// Copyright 2023-present 650 Industries. All rights reserved.
import ExpoModulesCore
enum Encoding: String, Enumerable {
// Equivalents of String.Encoding
case ascii
case nextstep
case japaneseeuc
case utf8
case isolatin1
case symbol
case nonlossyascii
case shiftjis
case isolatin2
case unicode
case windowscp1251
case windowscp1252
case windowscp1253
case windowscp1254
case windowscp1250
case iso2022jp
case macosroman
case utf16
case utf16bigendian
case utf16littleendian
case utf32
case utf32bigendian
case utf32littleendian
// Without equivalents in String.Encoding
case base64
func toStringEncoding() -> String.Encoding? {
switch self {
case .ascii:
return .ascii
case .nextstep:
return .nextstep
case .japaneseeuc:
return .japaneseEUC
case .utf8:
return .utf8
case .isolatin1:
return .isoLatin1
case .symbol:
return .symbol
case .nonlossyascii:
return .nonLossyASCII
case .shiftjis:
return .shiftJIS
case .isolatin2:
return .isoLatin2
case .unicode:
return .unicode
case .windowscp1251:
return .windowsCP1251
case .windowscp1252:
return .windowsCP1252
case .windowscp1253:
return .windowsCP1253
case .windowscp1254:
return .windowsCP1254
case .windowscp1250:
return .windowsCP1250
case .iso2022jp:
return .iso2022JP
case .macosroman:
return .macOSRoman
case .utf16:
return .utf16
case .utf16bigendian:
return .utf16BigEndian
case .utf16littleendian:
return .utf16LittleEndian
case .utf32:
return .utf32
case .utf32bigendian:
return .utf32BigEndian
case .utf32littleendian:
return .utf32LittleEndian
// Cases that don't have their own equivalent in String.Encoding
case .base64:
return nil
}
}
}

View File

@@ -0,0 +1 @@
// Copyright 2023-present 650 Industries. All rights reserved.

View File

@@ -0,0 +1,27 @@
// Copyright 2023-present 650 Industries. All rights reserved.
import ExpoModulesCore
public final class FileSystemBackgroundSessionHandler: ExpoAppDelegateSubscriber, EXSessionHandlerProtocol {
public typealias BackgroundSessionCompletionHandler = () -> Void
private var completionHandlers: [String: BackgroundSessionCompletionHandler] = [:]
public func invokeCompletionHandler(forSessionIdentifier identifier: String) {
guard let completionHandler = completionHandlers[identifier] else {
return
}
DispatchQueue.main.async {
completionHandler()
}
completionHandlers.removeValue(forKey: identifier)
}
// MARK: - ExpoAppDelegateSubscriber
#if os(iOS) || os(tvOS)
public func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
completionHandlers[identifier] = completionHandler
}
#endif
}

View File

@@ -0,0 +1,110 @@
// Copyright 2023-present 650 Industries. All rights reserved.
import ExpoModulesCore
import Photos
private let assetIdentifier = "ph://"
internal func ensureFileDirectoryExists(_ fileUrl: URL) throws {
let directoryPath = fileUrl.deletingLastPathComponent()
if !FileManager.default.fileExists(atPath: directoryPath.path) {
throw DirectoryNotExistsException(directoryPath.path)
}
}
internal func readFileAsBase64(path: String, options: ReadingOptions) throws -> String {
let file = FileHandle(forReadingAtPath: path)
guard let file else {
throw FileNotExistsException(path)
}
if let position = options.position, position != 0 {
// TODO: Handle these errors?
try? file.seek(toOffset: UInt64(position))
}
if let length = options.length {
return file.readData(ofLength: length).base64EncodedString(options: .endLineWithLineFeed)
}
return file.readDataToEndOfFile().base64EncodedString(options: .endLineWithLineFeed)
}
internal func writeFileAsBase64(path: String, string: String) throws {
let data = Data(base64Encoded: string, options: .ignoreUnknownCharacters)
if !FileManager.default.createFile(atPath: path, contents: data) {
throw FileWriteFailedException(path)
}
}
internal func removeFile(path: String, idempotent: Bool = false) throws {
if FileManager.default.fileExists(atPath: path) {
do {
try FileManager.default.removeItem(atPath: path)
} catch {
throw FileCannotDeleteException(path)
.causedBy(error)
}
} else if !idempotent {
throw FileCannotDeleteException(path)
.causedBy(FileNotExistsException(path))
}
}
internal func getResourceValues(from directory: URL?, forKeys: Set<URLResourceKey>) throws -> URLResourceValues? {
do {
return try directory?.resourceValues(forKeys: forKeys)
} catch {
throw CannotDetermineDiskCapacity().causedBy(error)
}
}
internal func ensurePathPermission(_ appContext: AppContext?, path: String, flag: EXFileSystemPermissionFlags) throws {
guard let fileSystemManager = appContext?.fileSystem else {
throw Exceptions.PermissionsModuleNotFound()
}
guard fileSystemManager.getPathPermissions(path).contains(flag) else {
throw flag == .read ? FileNotReadableException(path) : FileNotWritableException(path)
}
}
internal func isPHAsset(path: String) -> Bool {
return path.contains(assetIdentifier)
}
internal func copyPHAsset(fromUrl: URL, toUrl: URL, with resourceManager: PHAssetResourceManager, promise: Promise) {
if isPhotoLibraryStatusAuthorized() {
if FileManager.default.fileExists(atPath: toUrl.path) {
promise.reject(FileAlreadyExistsException(toUrl.path))
return
}
let identifier = fromUrl.absoluteString.replacingOccurrences(of: assetIdentifier, with: "")
guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: nil).firstObject else {
promise.reject(FailedToFindAssetException(fromUrl.absoluteString))
return
}
let firstResource = PHAssetResource.assetResources(for: asset).first
if let firstResource {
resourceManager.writeData(for: firstResource, toFile: toUrl, options: nil) { error in
if error != nil {
promise.reject(FailedToCopyAssetException(fromUrl.absoluteString))
return
}
promise.resolve()
}
} else {
promise.reject(FailedToCopyAssetException(fromUrl.absoluteString))
}
}
}
internal func isPhotoLibraryStatusAuthorized() -> Bool {
if #available(iOS 14, tvOS 14, *) {
let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
return status == .authorized || status == .limited
}
return PHPhotoLibrary.authorizationStatus() == .authorized
}

View File

@@ -0,0 +1,99 @@
// Copyright 2023-present 650 Industries. All rights reserved.
import ExpoModulesCore
final class FileNotExistsException: GenericException<String> {
override var reason: String {
"File '\(param)' does not exist"
}
}
final class FileAlreadyExistsException: GenericException<String> {
override var reason: String {
"File '\(param)' already exists"
}
}
final class DirectoryNotExistsException: GenericException<String> {
override var reason: String {
"Directory '\(param)' does not exist"
}
}
final class FileNotReadableException: GenericException<String> {
override var reason: String {
"File '\(param)' is not readable"
}
}
final class FileNotWritableException: GenericException<String> {
override var reason: String {
"File '\(param)' is not writable"
}
}
final class FileWriteFailedException: GenericException<String> {
override var reason: String {
"Writing to '\(param)' file has failed"
}
}
final class FileCannotDeleteException: GenericException<String> {
override var reason: String {
"File '\(param)' could not be deleted"
}
}
final class InvalidFileUrlException: GenericException<URL> {
override var reason: String {
"'\(param.absoluteString)' is not a file URL"
}
}
final class UnsupportedSchemeException: GenericException<String?> {
override var reason: String {
"Unsupported URI scheme: '\(String(describing: param))'"
}
}
final class HeaderEncodingFailedException: GenericException<String> {
override var reason: String {
"Unable to encode headers for request '\(param)' to UTF8"
}
}
final class DownloadTaskNotFoundException: GenericException<String> {
override var reason: String {
"Cannot find a download task with id: '\(param)'"
}
}
final class CannotDetermineDiskCapacity: Exception {
override var reason: String {
"Unable to determine free disk storage capacity"
}
}
final class FailedToCreateBodyException: Exception {
override var reason: String {
"Unable to create multipart body"
}
}
final class FailedToAccessDirectoryException: Exception {
override var reason: String {
"Failed to access `Caches` directory"
}
}
final class FailedToCopyAssetException: GenericException<String> {
override var reason: String {
"Failed to copy photo library asset: \(param)"
}
}
final class FailedToFindAssetException: GenericException<String> {
override var reason: String {
"Failed to find photo library asset: \(param)"
}
}

View File

@@ -0,0 +1,316 @@
// Copyright 2023-present 650 Industries. All rights reserved.
import ExpoModulesCore
import Photos
private let EVENT_DOWNLOAD_PROGRESS = "expo-file-system.downloadProgress"
private let EVENT_UPLOAD_PROGRESS = "expo-file-system.uploadProgress"
public final class FileSystemLegacyModule: Module {
private var sessionTaskDispatcher: EXSessionTaskDispatcher!
private lazy var taskHandlersManager = EXTaskHandlersManager()
private lazy var resourceManager = PHAssetResourceManager()
private lazy var backgroundSession = createUrlSession(type: .background, delegate: sessionTaskDispatcher)
private lazy var foregroundSession = createUrlSession(type: .foreground, delegate: sessionTaskDispatcher)
private var documentDirectory: URL? {
return appContext?.config.documentDirectory
}
private var cacheDirectory: URL? {
return appContext?.config.cacheDirectory
}
public func definition() -> ModuleDefinition {
Name("ExponentFileSystem")
Constant("documentDirectory") {
return documentDirectory?.absoluteString
}
Constant("cacheDirectory") {
return cacheDirectory?.absoluteString
}
Constant("bundleDirectory") {
return Bundle.main.bundlePath
}
Events(EVENT_DOWNLOAD_PROGRESS, EVENT_UPLOAD_PROGRESS)
OnCreate {
Task { @MainActor in
sessionTaskDispatcher = EXSessionTaskDispatcher(
sessionHandler: ExpoAppDelegateSubscriberRepository.getSubscriberOfType(FileSystemBackgroundSessionHandler.self)
)
}
}
AsyncFunction("getInfoAsync") { (url: URL, options: InfoOptions, promise: Promise) in
let optionsDict = options.toDictionary(appContext: appContext)
switch url.scheme {
case "file":
EXFileSystemLocalFileHandler.getInfoForFile(url, withOptions: optionsDict, resolver: promise.resolver, rejecter: promise.legacyRejecter)
case "assets-library", "ph":
EXFileSystemAssetLibraryHandler.getInfoForFile(url, withOptions: optionsDict, resolver: promise.resolver, rejecter: promise.legacyRejecter)
default:
throw UnsupportedSchemeException(url.scheme)
}
}
AsyncFunction("readAsStringAsync") { (url: URL, options: ReadingOptions) -> String in
try ensurePathPermission(appContext, path: url.path, flag: .read)
if options.encoding == .base64 {
return try readFileAsBase64(path: url.path, options: options)
}
do {
return try String(contentsOfFile: url.path, encoding: options.encoding.toStringEncoding() ?? .utf8)
} catch {
throw FileNotReadableException(url.path)
}
}
AsyncFunction("writeAsStringAsync") { (url: URL, string: String, options: WritingOptions) in
try ensurePathPermission(appContext, path: url.path, flag: .write)
let data: Data?
if options.encoding == .base64 {
data = Data(base64Encoded: string, options: .ignoreUnknownCharacters)
} else {
data = string.data(using: options.encoding.toStringEncoding() ?? .utf8)
}
guard let data else {
throw FileNotWritableException(url.path)
}
do {
if options.append {
if !FileManager.default.fileExists(atPath: url.path) {
try data.write(to: url, options: .atomic)
} else {
let fileHandle = try FileHandle(forWritingTo: url)
defer {
fileHandle.closeFile()
}
fileHandle.seekToEndOfFile()
fileHandle.write(data)
}
} else {
try data.write(to: url, options: .atomic)
}
} catch {
throw FileNotWritableException(url.path)
.causedBy(error)
}
}
AsyncFunction("deleteAsync") { (url: URL, options: DeletingOptions) in
guard url.isFileURL else {
throw InvalidFileUrlException(url)
}
try ensurePathPermission(appContext, path: url.appendingPathComponent("..").path, flag: .write)
try removeFile(path: url.path, idempotent: options.idempotent)
}
AsyncFunction("moveAsync") { (options: RelocatingOptions) in
let (fromUrl, toUrl) = try options.asTuple()
guard fromUrl.isFileURL else {
throw InvalidFileUrlException(fromUrl)
}
guard toUrl.isFileURL else {
throw InvalidFileUrlException(toUrl)
}
try ensurePathPermission(appContext, path: fromUrl.appendingPathComponent("..").path, flag: .write)
try ensurePathPermission(appContext, path: toUrl.path, flag: .write)
try removeFile(path: toUrl.path, idempotent: true)
try FileManager.default.moveItem(atPath: fromUrl.path, toPath: toUrl.path)
}
AsyncFunction("copyAsync") { (options: RelocatingOptions, promise: Promise) in
let (fromUrl, toUrl) = try options.asTuple()
if isPHAsset(path: fromUrl.absoluteString) {
copyPHAsset(fromUrl: fromUrl, toUrl: toUrl, with: resourceManager, promise: promise)
return
}
try ensurePathPermission(appContext, path: fromUrl.path, flag: .read)
try ensurePathPermission(appContext, path: toUrl.path, flag: .write)
if fromUrl.scheme == "file" {
EXFileSystemLocalFileHandler.copy(from: fromUrl, to: toUrl, resolver: promise.resolver, rejecter: promise.legacyRejecter)
} else if ["ph", "assets-library"].contains(fromUrl.scheme) {
EXFileSystemAssetLibraryHandler.copy(from: fromUrl, to: toUrl, resolver: promise.resolver, rejecter: promise.legacyRejecter)
} else {
throw InvalidFileUrlException(fromUrl)
}
}
AsyncFunction("makeDirectoryAsync") { (url: URL, options: MakeDirectoryOptions) in
guard url.isFileURL else {
throw InvalidFileUrlException(url)
}
try ensurePathPermission(appContext, path: url.path, flag: .write)
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: options.intermediates, attributes: nil)
}
AsyncFunction("readDirectoryAsync") { (url: URL) -> [String] in
guard url.isFileURL else {
throw InvalidFileUrlException(url)
}
try ensurePathPermission(appContext, path: url.path, flag: .read)
return try FileManager.default.contentsOfDirectory(atPath: url.path)
}
AsyncFunction("downloadAsync") { (sourceUrl: URL, localUrl: URL, options: DownloadOptionsLegacy, promise: Promise) in
try ensureFileDirectoryExists(localUrl)
try ensurePathPermission(appContext, path: localUrl.path, flag: .write)
if sourceUrl.isFileURL {
try ensurePathPermission(appContext, path: sourceUrl.path, flag: .read)
EXFileSystemLocalFileHandler.copy(from: sourceUrl, to: localUrl, resolver: promise.resolver, rejecter: promise.legacyRejecter)
return
}
let session = options.sessionType == .background ? backgroundSession : foregroundSession
let request = createUrlRequest(url: sourceUrl, headers: options.headers)
let downloadTask = session.downloadTask(with: request)
let taskDelegate = EXSessionDownloadTaskDelegate(
resolve: promise.resolver,
reject: promise.legacyRejecter,
localUrl: localUrl,
shouldCalculateMd5: options.md5
)
sessionTaskDispatcher.register(taskDelegate, for: downloadTask)
downloadTask.resume()
}
AsyncFunction("uploadAsync") { (targetUrl: URL, localUrl: URL, options: UploadOptions, promise: Promise) in
guard localUrl.isFileURL else {
throw InvalidFileUrlException(localUrl)
}
guard FileManager.default.fileExists(atPath: localUrl.path) else {
throw FileNotExistsException(localUrl.path)
}
let session = options.sessionType == .background ? backgroundSession : foregroundSession
let task = try createUploadTask(session: session, targetUrl: targetUrl, sourceUrl: localUrl, options: options)
let taskDelegate = EXSessionUploadTaskDelegate(resolve: promise.resolver, reject: promise.legacyRejecter)
sessionTaskDispatcher.register(taskDelegate, for: task)
task.resume()
}
AsyncFunction("uploadTaskStartAsync") { (targetUrl: URL, localUrl: URL, uuid: String, options: UploadOptions, promise: Promise) in
let session = options.sessionType == .background ? backgroundSession : foregroundSession
let task = try createUploadTask(session: session, targetUrl: targetUrl, sourceUrl: localUrl, options: options)
let onSend: EXUploadDelegateOnSendCallback = { [weak self] _, _, totalBytesSent, totalBytesExpectedToSend in
self?.sendEvent(EVENT_UPLOAD_PROGRESS, [
"uuid": uuid,
"data": [
"totalBytesSent": totalBytesSent,
"totalBytesExpectedToSend": totalBytesExpectedToSend
]
])
}
let taskDelegate = EXSessionCancelableUploadTaskDelegate(
resolve: promise.resolver,
reject: promise.legacyRejecter,
onSendCallback: onSend,
resumableManager: taskHandlersManager,
uuid: uuid
)
sessionTaskDispatcher.register(taskDelegate, for: task)
taskHandlersManager.register(task, uuid: uuid)
task.resume()
}
// swiftlint:disable:next line_length closure_body_length
AsyncFunction("downloadResumableStartAsync") { (sourceUrl: URL, localUrl: URL, uuid: String, options: DownloadOptionsLegacy, resumeDataString: String?, promise: Promise) in
try ensureFileDirectoryExists(localUrl)
try ensurePathPermission(appContext, path: localUrl.path, flag: .write)
let session = options.sessionType == .background ? backgroundSession : foregroundSession
let onWrite: EXDownloadDelegateOnWriteCallback = { [weak self] _, _, totalBytesWritten, totalBytesExpectedToWrite in
self?.sendEvent(EVENT_DOWNLOAD_PROGRESS, [
"uuid": uuid,
"data": [
"totalBytesWritten": totalBytesWritten,
"totalBytesExpectedToWrite": totalBytesExpectedToWrite
]
])
}
let task: URLSessionDownloadTask
if let resumeDataString, let resumeData = Data(base64Encoded: resumeDataString) {
task = session.downloadTask(withResumeData: resumeData)
} else {
let request = createUrlRequest(url: sourceUrl, headers: options.headers)
task = session.downloadTask(with: request)
}
let taskDelegate = EXSessionResumableDownloadTaskDelegate(
resolve: promise.resolver,
reject: promise.legacyRejecter,
localUrl: localUrl,
shouldCalculateMd5: options.md5,
onWriteCallback: onWrite,
resumableManager: taskHandlersManager,
uuid: uuid
)
sessionTaskDispatcher.register(taskDelegate, for: task)
taskHandlersManager.register(task, uuid: uuid)
task.resume()
}
AsyncFunction("downloadResumablePauseAsync") { (id: String) -> [String: String?] in
guard let task = taskHandlersManager.downloadTask(forId: id) else {
throw DownloadTaskNotFoundException(id)
}
let resumeData = await task.cancelByProducingResumeData()
return [
"resumeData": resumeData?.base64EncodedString()
]
}
AsyncFunction("networkTaskCancelAsync") { (id: String) in
taskHandlersManager.task(forId: id)?.cancel()
}
AsyncFunction("getFreeDiskStorageAsync") { () -> Int64 in
// Uses required reason API based on the following reason: E174.1 85F4.1
#if !os(tvOS)
let resourceValues = try getResourceValues(from: documentDirectory, forKeys: [.volumeAvailableCapacityForImportantUsageKey])
guard let availableCapacity = resourceValues?.volumeAvailableCapacityForImportantUsage else {
throw CannotDetermineDiskCapacity()
}
return availableCapacity
#else
let resourceValues = try getResourceValues(from: cacheDirectory, forKeys: [.volumeAvailableCapacityKey])
guard let availableCapacity = resourceValues?.volumeAvailableCapacity else {
throw CannotDetermineDiskCapacity()
}
return Int64(availableCapacity)
#endif
}
AsyncFunction("getTotalDiskCapacityAsync") { () -> Int in
// Uses required reason API based on the following reason: E174.1 85F4.1
let resourceValues = try getResourceValues(from: documentDirectory, forKeys: [.volumeTotalCapacityKey])
guard let totalCapacity = resourceValues?.volumeTotalCapacity else {
throw CannotDetermineDiskCapacity()
}
return totalCapacity
}
}
}

View File

@@ -0,0 +1,74 @@
// Copyright 2023-present 650 Industries. All rights reserved.
import ExpoModulesCore
struct InfoOptions: Record {
@Field var md5: Bool = false
}
struct ReadingOptions: Record {
@Field var encoding: Encoding = .utf8
@Field var position: Int?
@Field var length: Int?
}
struct WritingOptions: Record {
@Field var encoding: Encoding = .utf8
@Field var append: Bool = false
}
struct DeletingOptions: Record {
@Field var idempotent: Bool = false
}
struct RelocatingOptions: Record {
@Field var from: URL?
@Field var to: URL?
func asTuple() throws -> (URL, URL) {
guard let from, let to else {
let missingOptionName = from == nil ? "from" : "to"
throw Exception(name: "MissingParameterException", description: "Missing option '\(missingOptionName)'")
}
return (from, to)
}
}
struct MakeDirectoryOptions: Record {
@Field var intermediates: Bool = false
}
struct DownloadOptionsLegacy: Record {
@Field var md5: Bool = false
@Field var cache: Bool = false
@Field var headers: [String: String]?
@Field var sessionType: SessionType = .background
}
struct UploadOptions: Record {
@Field var headers: [String: String]?
@Field var httpMethod: HttpMethod = .post
@Field var sessionType: SessionType = .background
@Field var uploadType: UploadType = .binaryContent
// Multipart
@Field var fieldName: String?
@Field var mimeType: String?
@Field var parameters: [String: String]?
}
enum SessionType: Int, Enumerable {
case background = 0
case foreground = 1
}
enum HttpMethod: String, Enumerable {
case post = "POST"
case put = "PUT"
case patch = "PATCH"
}
enum UploadType: Int, Enumerable {
case binaryContent = 0
case multipart = 1
}

View File

@@ -0,0 +1,9 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
@interface NSData (EXFileSystem)
- (NSString *)md5String;
@end

View File

@@ -0,0 +1,19 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <ExpoFileSystem/NSData+EXFileSystem.h>
#import <CommonCrypto/CommonDigest.h>
@implementation NSData (EXFileSystem)
- (NSString *)md5String
{
unsigned char digest[CC_MD5_DIGEST_LENGTH];
CC_MD5(self.bytes, (CC_LONG) self.length, digest);
NSMutableString *md5 = [NSMutableString stringWithCapacity:2 * CC_MD5_DIGEST_LENGTH];
for (unsigned int i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) {
[md5 appendFormat:@"%02x", digest[i]];
}
return md5;
}
@end

View File

@@ -0,0 +1,98 @@
// Copyright 2023-present 650 Industries. All rights reserved.
import CoreServices
import ExpoModulesCore
func findMimeType(forAttachment attachment: URL) -> String {
if let identifier = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, attachment.pathExtension as CFString, nil)?.takeRetainedValue() {
if let type = UTTypeCopyPreferredTagWithClass(identifier, kUTTagClassMIMEType)?.takeRetainedValue() {
return type as String
}
}
return "application/octet-stream"
}
func createUrlSession(type: SessionType, delegate: URLSessionDelegate) -> URLSession {
let configuration = type == .foreground ? URLSessionConfiguration.default : URLSessionConfiguration.background(withIdentifier: UUID().uuidString)
configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
configuration.urlCache = nil
return URLSession(configuration: configuration, delegate: delegate, delegateQueue: .main)
}
func createUrlRequest(url: URL, headers: [String: String]?) -> URLRequest {
var request = URLRequest(url: url)
if let headers {
for (key, value) in headers {
request.setValue(value, forHTTPHeaderField: key)
}
}
return request
}
func createUploadTask(session: URLSession, targetUrl: URL, sourceUrl: URL, options: UploadOptions) throws -> URLSessionUploadTask {
var request = createUrlRequest(url: targetUrl, headers: options.headers)
request.httpMethod = options.httpMethod.rawValue
switch options.uploadType {
case .binaryContent:
return session.uploadTask(with: request, fromFile: sourceUrl)
case .multipart:
let boundaryString = UUID().uuidString
guard let data = createMultipartBody(boundary: boundaryString, sourceUrl: sourceUrl, options: options) else {
throw FailedToCreateBodyException()
}
request.setValue("multipart/form-data; boundary=\(boundaryString)", forHTTPHeaderField: "Content-Type")
let localURL = try createLocalUrl(from: sourceUrl)
try? data.write(to: localURL)
return session.uploadTask(with: request, fromFile: localURL)
}
}
func createLocalUrl(from sourceUrl: URL) throws -> URL {
guard let cachesDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
throw FailedToAccessDirectoryException()
}
let tempDir = cachesDir.appendingPathComponent("uploads")
FileSystemUtilities.ensureDirExists(at: tempDir)
return tempDir.appendingPathComponent(sourceUrl.lastPathComponent)
}
func createMultipartBody(boundary: String, sourceUrl: URL, options: UploadOptions) -> Data? {
let fieldName = options.fieldName ?? sourceUrl.lastPathComponent
let mimeType = options.mimeType ?? findMimeType(forAttachment: sourceUrl)
guard let data = try? Data(contentsOf: sourceUrl) else {
return nil
}
var body = Data()
headersForMultipartParams(options.parameters, boundary: boundary, body: &body)
body.append("--\(boundary)\r\n".data)
body.append("Content-Disposition: form-data; name=\"\(fieldName)\"; filename=\"\(sourceUrl.lastPathComponent)\"\r\n".data)
body.append("Content-Type: \(mimeType)\r\n\r\n".data)
body.append(data)
body.append("\r\n".data)
body.append("--\(boundary)--\r\n".data)
return body
}
func headersForMultipartParams(_ params: [String: String]?, boundary: String, body: inout Data) {
if let params {
for (key, value) in params {
body.append("--\(boundary)\r\n".data)
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data)
body.append("\(value)\r\n".data)
}
}
}
// All swift strings are unicode correct.
// This avoids the optional created by string.data(using: .utf8)
private extension String {
var data: Data { Data(self.utf8) }
}

Some files were not shown because too many files have changed in this diff Show More