expo-file-system 18.2.0-canary-20250713-8f814f8 → 18.2.0-canary-20250729-d8899ae
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/android/build.gradle +3 -2
- package/android/src/main/java/expo/modules/filesystem/next/FileSystemDirectory.kt +51 -12
- package/android/src/main/java/expo/modules/filesystem/next/FileSystemFile.kt +48 -57
- package/android/src/main/java/expo/modules/filesystem/next/FileSystemFileHandle.kt +1 -1
- package/android/src/main/java/expo/modules/filesystem/next/FileSystemNextModule.kt +34 -10
- package/android/src/main/java/expo/modules/filesystem/next/FileSystemNextRecords.kt +10 -0
- package/android/src/main/java/expo/modules/filesystem/next/FileSystemPath.kt +71 -28
- package/android/src/main/java/expo/modules/filesystem/next/unifiedfile/JavaFile.kt +65 -0
- package/android/src/main/java/expo/modules/filesystem/next/unifiedfile/SAFDocumentFile.kt +79 -0
- package/android/src/main/java/expo/modules/filesystem/next/unifiedfile/UnifiedFileInterface.kt +23 -0
- package/build/next/ExpoFileSystem.types.d.ts +57 -8
- package/build/next/ExpoFileSystem.types.d.ts.map +1 -1
- package/build/next/FileSystem.d.ts +6 -24
- package/build/next/FileSystem.d.ts.map +1 -1
- package/build/next/index.d.ts +1 -1
- package/build/next/index.d.ts.map +1 -1
- package/build/next/pathUtilities/index.d.ts.map +1 -1
- package/build/next/pathUtilities/url.d.ts +3 -4
- package/build/next/pathUtilities/url.d.ts.map +1 -1
- package/expo-module.config.json +1 -1
- package/ios/Next/FileSystemDirectory.swift +24 -0
- package/ios/Next/FileSystemFile.swift +0 -27
- package/ios/Next/FileSystemNextModule.swift +19 -3
- package/ios/Next/FileSystemNextRecords.swift +9 -0
- package/ios/Next/FileSystemPath.swift +27 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/{18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8-sources.jar → 18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae-sources.jar} +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae-sources.jar.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae-sources.jar.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae-sources.jar.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae-sources.jar.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae.aar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae.aar.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae.aar.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae.aar.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae.aar.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/{18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8.module → 18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae.module} +36 -22
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae.module.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae.module.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae.module.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae.module.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/{18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8.pom → 18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae.pom} +7 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae.pom.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae.pom.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae.pom.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250729-d8899ae/expo.modules.filesystem-18.2.0-canary-20250729-d8899ae.pom.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/maven-metadata.xml +4 -4
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/maven-metadata.xml.md5 +1 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/maven-metadata.xml.sha1 +1 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/maven-metadata.xml.sha256 +1 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/maven-metadata.xml.sha512 +1 -1
- package/package.json +4 -4
- package/src/next/ExpoFileSystem.types.ts +64 -8
- package/src/next/FileSystem.ts +25 -62
- package/src/next/index.ts +1 -0
- package/src/next/pathUtilities/index.ts +29 -21
- package/src/next/pathUtilities/url.ts +17 -32
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8-sources.jar.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8-sources.jar.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8-sources.jar.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8-sources.jar.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8.aar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8.aar.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8.aar.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8.aar.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8.aar.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8.module.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8.module.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8.module.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8.module.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8.pom.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8.pom.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8.pom.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.filesystem/18.2.0-canary-20250713-8f814f8/expo.modules.filesystem-18.2.0-canary-20250713-8f814f8.pom.sha512 +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,9 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
### 🎉 New features
|
|
8
8
|
|
|
9
|
+
- Make file implement blob interface directly. ([#38160](https://github.com/expo/expo/pull/38160) by [@aleqsio](https://github.com/aleqsio))
|
|
10
|
+
- Add directory info function ([#37910](https://github.com/expo/expo/pull/37910) by [@Wenszel](https://github.com/Wenszel))
|
|
9
11
|
- Add total and available sizes, directory sizes. ([#37594](https://github.com/expo/expo/pull/37594) by [@aleqsio](https://github.com/aleqsio))
|
|
10
12
|
- 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))
|
|
11
13
|
- Add support for custom headers in downloadFileAsync ([#36108](https://github.com/expo/expo/pull/36108) by [@leonhh](https://github.com/leonhh))
|
|
14
|
+
- [next] Add limited support for SAF Uris. ([#38075](https://github.com/expo/expo/pull/38075) by [@aleqsio](https://github.com/aleqsio))
|
|
15
|
+
- [next] Add full support for SAF Uris. ([#38075](https://github.com/expo/expo/pull/38075) by [@aleqsio](https://github.com/aleqsio))
|
|
12
16
|
|
|
13
17
|
### 🐛 Bug fixes
|
|
14
18
|
|
package/android/build.gradle
CHANGED
|
@@ -4,13 +4,13 @@ plugins {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
group = 'host.exp.exponent'
|
|
7
|
-
version = '18.2.0-canary-
|
|
7
|
+
version = '18.2.0-canary-20250729-d8899ae'
|
|
8
8
|
|
|
9
9
|
android {
|
|
10
10
|
namespace "expo.modules.filesystem"
|
|
11
11
|
defaultConfig {
|
|
12
12
|
versionCode 30
|
|
13
|
-
versionName "18.2.0-canary-
|
|
13
|
+
versionName "18.2.0-canary-20250729-d8899ae"
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -21,4 +21,5 @@ dependencies {
|
|
|
21
21
|
api 'com.squareup.okhttp3:okhttp-urlconnection:4.9.2'
|
|
22
22
|
api 'com.squareup.okio:okio:2.9.0'
|
|
23
23
|
api "androidx.legacy:legacy-support-v4:1.0.0"
|
|
24
|
+
api "androidx.documentfile:documentfile:1.1.0"
|
|
24
25
|
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
package expo.modules.filesystem.next
|
|
2
2
|
|
|
3
3
|
import android.net.Uri
|
|
4
|
+
import expo.modules.filesystem.slashifyFilePath
|
|
4
5
|
import expo.modules.interfaces.filesystem.Permission
|
|
5
|
-
import java.io.File
|
|
6
6
|
|
|
7
|
-
class FileSystemDirectory(
|
|
7
|
+
class FileSystemDirectory(uri: Uri) : FileSystemPath(uri) {
|
|
8
8
|
fun validatePath() {
|
|
9
9
|
// Kept empty for now, but can be used to validate if the path is a valid directory path.
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
override fun validateType() {
|
|
13
|
-
if (file.exists() && !file.isDirectory) {
|
|
13
|
+
if (file.exists() && !file.isDirectory()) {
|
|
14
14
|
throw InvalidTypeFolderException()
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
val exists: Boolean get() {
|
|
19
19
|
return if (checkPermission(Permission.READ)) {
|
|
20
|
-
file.isDirectory
|
|
20
|
+
file.isDirectory()
|
|
21
21
|
} else {
|
|
22
22
|
false
|
|
23
23
|
}
|
|
@@ -26,41 +26,80 @@ class FileSystemDirectory(file: File) : FileSystemPath(file) {
|
|
|
26
26
|
val size: Long get() {
|
|
27
27
|
validatePermission(Permission.READ)
|
|
28
28
|
validateType()
|
|
29
|
-
return file.walkTopDown().filter { it.isFile }.map { it.length() }.sum()
|
|
29
|
+
return file.walkTopDown().filter { it.isFile() }.map { it.length() }.sum()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fun info(): DirectoryInfo {
|
|
33
|
+
validateType()
|
|
34
|
+
validatePermission(Permission.READ)
|
|
35
|
+
if (!file.exists()) {
|
|
36
|
+
val directoryInfo = DirectoryInfo(
|
|
37
|
+
exists = false,
|
|
38
|
+
uri = slashifyFilePath(file.uri.toString())
|
|
39
|
+
)
|
|
40
|
+
return directoryInfo
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
val directoryInfo = DirectoryInfo(
|
|
44
|
+
exists = true,
|
|
45
|
+
uri = slashifyFilePath(file.uri.toString()),
|
|
46
|
+
files = file.listFilesAsUnified().mapNotNull { i -> i.fileName },
|
|
47
|
+
modificationTime = modificationTime,
|
|
48
|
+
creationTime = creationTime,
|
|
49
|
+
size = size
|
|
50
|
+
)
|
|
51
|
+
return directoryInfo
|
|
30
52
|
}
|
|
31
53
|
|
|
32
54
|
fun create(options: CreateOptions = CreateOptions()) {
|
|
33
55
|
validateType()
|
|
34
56
|
validatePermission(Permission.WRITE)
|
|
35
57
|
validateCanCreate(options)
|
|
58
|
+
if (uri.isContentUri) {
|
|
59
|
+
throw UnableToCreateException("create function does not work with SAF Uris, use `createDirectory` and `createFile` instead")
|
|
60
|
+
}
|
|
36
61
|
if (options.overwrite && file.exists()) {
|
|
37
62
|
file.delete()
|
|
38
63
|
}
|
|
39
64
|
val created = if (options.intermediates) {
|
|
40
|
-
|
|
65
|
+
javaFile.mkdirs()
|
|
41
66
|
} else {
|
|
42
|
-
|
|
67
|
+
javaFile.mkdir()
|
|
43
68
|
}
|
|
44
69
|
if (!created) {
|
|
45
70
|
throw UnableToCreateException("directory already exists or could not be created")
|
|
46
71
|
}
|
|
47
72
|
}
|
|
48
73
|
|
|
74
|
+
fun createFile(mimeType: String?, fileName: String): FileSystemFile {
|
|
75
|
+
validateType()
|
|
76
|
+
validatePermission(Permission.WRITE)
|
|
77
|
+
val newFile = file.createFile(mimeType ?: "text/plain", fileName) ?: throw UnableToCreateException("file could not be created")
|
|
78
|
+
return FileSystemFile(newFile.uri)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
fun createDirectory(fileName: String): FileSystemDirectory {
|
|
82
|
+
validateType()
|
|
83
|
+
validatePermission(Permission.WRITE)
|
|
84
|
+
val newDirectory = file.createDirectory(fileName) ?: throw UnableToCreateException("directory could not be created")
|
|
85
|
+
return FileSystemDirectory(newDirectory.uri)
|
|
86
|
+
}
|
|
87
|
+
|
|
49
88
|
// this function is internal and will be removed in the future (when returning arrays of shared objects is supported)
|
|
50
89
|
fun listAsRecords(): List<Map<String, Any>> {
|
|
51
90
|
validateType()
|
|
52
91
|
validatePermission(Permission.READ)
|
|
53
|
-
return file.
|
|
54
|
-
val uriString =
|
|
92
|
+
return file.listFilesAsUnified().map {
|
|
93
|
+
val uriString = it.uri.toString()
|
|
55
94
|
mapOf(
|
|
56
|
-
"isDirectory" to it.isDirectory,
|
|
95
|
+
"isDirectory" to it.isDirectory(),
|
|
57
96
|
"uri" to if (uriString.endsWith("/")) uriString else "$uriString/"
|
|
58
97
|
)
|
|
59
|
-
}
|
|
98
|
+
}
|
|
60
99
|
}
|
|
61
100
|
|
|
62
101
|
fun asString(): String {
|
|
63
|
-
val uriString =
|
|
102
|
+
val uriString = file.uri.toString()
|
|
64
103
|
return if (uriString.endsWith("/")) uriString else "$uriString/"
|
|
65
104
|
}
|
|
66
105
|
}
|
|
@@ -1,24 +1,17 @@
|
|
|
1
1
|
package expo.modules.filesystem.next
|
|
2
2
|
|
|
3
3
|
import android.net.Uri
|
|
4
|
-
import android.os.Build
|
|
5
4
|
import android.util.Base64
|
|
6
|
-
import android.webkit.MimeTypeMap
|
|
7
5
|
import expo.modules.filesystem.InfoOptions
|
|
8
6
|
import expo.modules.filesystem.slashifyFilePath
|
|
9
7
|
import expo.modules.interfaces.filesystem.Permission
|
|
10
8
|
import expo.modules.kotlin.apifeatures.EitherType
|
|
11
9
|
import expo.modules.kotlin.typedarray.TypedArray
|
|
12
|
-
import java.io.File
|
|
13
10
|
import java.io.FileOutputStream
|
|
14
|
-
import java.nio.file.attribute.BasicFileAttributes
|
|
15
11
|
import java.security.MessageDigest
|
|
16
|
-
import kotlin.io.path.Path
|
|
17
|
-
import kotlin.io.path.readAttributes
|
|
18
|
-
import kotlin.time.Duration.Companion.milliseconds
|
|
19
12
|
|
|
20
13
|
@OptIn(EitherType::class)
|
|
21
|
-
class FileSystemFile(
|
|
14
|
+
class FileSystemFile(uri: Uri) : FileSystemPath(uri) {
|
|
22
15
|
// 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
|
|
23
16
|
fun validatePath() {
|
|
24
17
|
}
|
|
@@ -27,14 +20,14 @@ class FileSystemFile(file: File) : FileSystemPath(file) {
|
|
|
27
20
|
// After calling this function, we can use the `isDirectory` and `isFile` functions safely as they will match the shared class used.
|
|
28
21
|
override fun validateType() {
|
|
29
22
|
validatePermission(Permission.READ)
|
|
30
|
-
if (file.exists() && file.isDirectory) {
|
|
23
|
+
if (file.exists() && file.isDirectory()) {
|
|
31
24
|
throw InvalidTypeFileException()
|
|
32
25
|
}
|
|
33
26
|
}
|
|
34
27
|
|
|
35
28
|
val exists: Boolean get() {
|
|
36
29
|
return if (checkPermission(Permission.READ)) {
|
|
37
|
-
file.isFile
|
|
30
|
+
file.isFile()
|
|
38
31
|
} else {
|
|
39
32
|
false
|
|
40
33
|
}
|
|
@@ -44,13 +37,16 @@ class FileSystemFile(file: File) : FileSystemPath(file) {
|
|
|
44
37
|
validateType()
|
|
45
38
|
validatePermission(Permission.WRITE)
|
|
46
39
|
validateCanCreate(options)
|
|
47
|
-
if (
|
|
48
|
-
|
|
40
|
+
if (uri.isContentUri) {
|
|
41
|
+
throw UnableToCreateException("create function does not work with SAF Uris, use `createDirectory` and `createFile` instead")
|
|
42
|
+
}
|
|
43
|
+
if (options.overwrite && exists) {
|
|
44
|
+
javaFile.delete()
|
|
49
45
|
}
|
|
50
46
|
if (options.intermediates) {
|
|
51
|
-
|
|
47
|
+
javaFile.parentFile?.mkdirs()
|
|
52
48
|
}
|
|
53
|
-
val created =
|
|
49
|
+
val created = javaFile.createNewFile()
|
|
54
50
|
if (!created) {
|
|
55
51
|
throw UnableToCreateException("file already exists or could not be created")
|
|
56
52
|
}
|
|
@@ -62,8 +58,8 @@ class FileSystemFile(file: File) : FileSystemPath(file) {
|
|
|
62
58
|
if (!exists) {
|
|
63
59
|
create()
|
|
64
60
|
}
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
file.outputStream().use { outputStream ->
|
|
62
|
+
outputStream.write(content.toByteArray())
|
|
67
63
|
}
|
|
68
64
|
}
|
|
69
65
|
|
|
@@ -73,40 +69,56 @@ class FileSystemFile(file: File) : FileSystemPath(file) {
|
|
|
73
69
|
if (!exists) {
|
|
74
70
|
create()
|
|
75
71
|
}
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
if (uri.isContentUri) {
|
|
73
|
+
file.outputStream().use { outputStream ->
|
|
74
|
+
val array = ByteArray(content.length)
|
|
75
|
+
content.toDirectBuffer().get(array)
|
|
76
|
+
outputStream.write(array)
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
FileOutputStream(javaFile).use {
|
|
80
|
+
it.channel.write(content.toDirectBuffer())
|
|
81
|
+
}
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
fun asString(): String {
|
|
82
|
-
val uriString =
|
|
86
|
+
val uriString = file.uri.toString()
|
|
83
87
|
return if (uriString.endsWith("/")) uriString.dropLast(1) else uriString
|
|
84
88
|
}
|
|
85
89
|
|
|
86
90
|
fun text(): String {
|
|
87
91
|
validateType()
|
|
88
92
|
validatePermission(Permission.READ)
|
|
89
|
-
return file.
|
|
93
|
+
return file.inputStream().use { inputStream ->
|
|
94
|
+
inputStream.bufferedReader().use { it.readText() }
|
|
95
|
+
}
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
fun base64(): String {
|
|
93
99
|
validateType()
|
|
94
100
|
validatePermission(Permission.READ)
|
|
95
|
-
|
|
101
|
+
file.inputStream().use {
|
|
102
|
+
return Base64.encodeToString(it.readBytes(), Base64.NO_WRAP)
|
|
103
|
+
}
|
|
96
104
|
}
|
|
97
105
|
|
|
98
106
|
fun bytes(): ByteArray {
|
|
99
107
|
validateType()
|
|
100
108
|
validatePermission(Permission.READ)
|
|
101
|
-
|
|
109
|
+
file.inputStream().use {
|
|
110
|
+
return it.readBytes()
|
|
111
|
+
}
|
|
102
112
|
}
|
|
103
113
|
|
|
104
114
|
@OptIn(ExperimentalStdlibApi::class)
|
|
105
115
|
val md5: String get() {
|
|
106
116
|
validatePermission(Permission.READ)
|
|
107
117
|
val md = MessageDigest.getInstance("MD5")
|
|
108
|
-
|
|
109
|
-
|
|
118
|
+
file.inputStream().use {
|
|
119
|
+
val digest = md.digest(it.readBytes())
|
|
120
|
+
return digest.toHexString()
|
|
121
|
+
}
|
|
110
122
|
}
|
|
111
123
|
|
|
112
124
|
val size: Long? get() {
|
|
@@ -118,23 +130,7 @@ class FileSystemFile(file: File) : FileSystemPath(file) {
|
|
|
118
130
|
}
|
|
119
131
|
|
|
120
132
|
val type: String? get() {
|
|
121
|
-
return
|
|
122
|
-
?.run { MimeTypeMap.getSingleton().getMimeTypeFromExtension(lowercase()) }
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
val modificationTime: Long get() {
|
|
126
|
-
validateType()
|
|
127
|
-
return file.lastModified()
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
val creationTime: Long? get() {
|
|
131
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
132
|
-
validateType()
|
|
133
|
-
val attributes = Path(file.path).readAttributes<BasicFileAttributes>()
|
|
134
|
-
return attributes.creationTime().toMillis().milliseconds.inWholeMilliseconds
|
|
135
|
-
} else {
|
|
136
|
-
return null
|
|
137
|
-
}
|
|
133
|
+
return file.type
|
|
138
134
|
}
|
|
139
135
|
|
|
140
136
|
fun info(options: InfoOptions?): FileInfo {
|
|
@@ -143,25 +139,20 @@ class FileSystemFile(file: File) : FileSystemPath(file) {
|
|
|
143
139
|
if (!file.exists()) {
|
|
144
140
|
val fileInfo = FileInfo(
|
|
145
141
|
exists = false,
|
|
146
|
-
uri = slashifyFilePath(file.
|
|
142
|
+
uri = slashifyFilePath(file.uri.toString())
|
|
147
143
|
)
|
|
148
144
|
return fileInfo
|
|
149
145
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (options != null && options.md5 == true) {
|
|
160
|
-
fileInfo.md5 = md5
|
|
161
|
-
}
|
|
162
|
-
return fileInfo
|
|
163
|
-
}
|
|
164
|
-
else -> throw UnableToGetInfoException("file schema ${file.toURI().scheme} is not supported")
|
|
146
|
+
val fileInfo = FileInfo(
|
|
147
|
+
exists = true,
|
|
148
|
+
uri = slashifyFilePath(file.uri.toString()),
|
|
149
|
+
size = size,
|
|
150
|
+
modificationTime = modificationTime,
|
|
151
|
+
creationTime = creationTime
|
|
152
|
+
)
|
|
153
|
+
if (options != null && options.md5 == true) {
|
|
154
|
+
fileInfo.md5 = md5
|
|
165
155
|
}
|
|
156
|
+
return fileInfo
|
|
166
157
|
}
|
|
167
158
|
}
|
|
@@ -4,7 +4,7 @@ import expo.modules.kotlin.sharedobjects.SharedRef
|
|
|
4
4
|
import java.io.RandomAccessFile
|
|
5
5
|
import java.nio.ByteBuffer
|
|
6
6
|
import java.nio.channels.FileChannel
|
|
7
|
-
class FileSystemFileHandle(file: FileSystemFile) : SharedRef<FileChannel>(RandomAccessFile(file.
|
|
7
|
+
class FileSystemFileHandle(file: FileSystemFile) : SharedRef<FileChannel>(RandomAccessFile(file.javaFile, "rw").channel), AutoCloseable {
|
|
8
8
|
private val fileChannel: FileChannel = ref
|
|
9
9
|
|
|
10
10
|
private fun ensureIsOpen() {
|
|
@@ -63,9 +63,9 @@ class FileSystemNextModule : Module() {
|
|
|
63
63
|
val fileName = URLUtil.guessFileName(url.toString(), contentDisposition, contentType)
|
|
64
64
|
|
|
65
65
|
val destination = if (to is FileSystemDirectory) {
|
|
66
|
-
File(to.
|
|
66
|
+
File(to.javaFile, fileName)
|
|
67
67
|
} else {
|
|
68
|
-
to.
|
|
68
|
+
to.javaFile
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
if (destination.exists()) {
|
|
@@ -78,7 +78,7 @@ class FileSystemNextModule : Module() {
|
|
|
78
78
|
input.copyTo(output)
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
-
return@Coroutine destination.
|
|
81
|
+
return@Coroutine destination.toURI()
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
Function("info") { url: URI ->
|
|
@@ -93,8 +93,8 @@ class FileSystemNextModule : Module() {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
Class(FileSystemFile::class) {
|
|
96
|
-
Constructor { uri:
|
|
97
|
-
FileSystemFile(
|
|
96
|
+
Constructor { uri: Uri ->
|
|
97
|
+
FileSystemFile(uri)
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
Function("delete") { file: FileSystemFile ->
|
|
@@ -121,15 +121,27 @@ class FileSystemNextModule : Module() {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
AsyncFunction("text") { file: FileSystemFile ->
|
|
125
125
|
file.text()
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
Function("
|
|
128
|
+
Function("textSync") { file: FileSystemFile ->
|
|
129
|
+
file.text()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
AsyncFunction("base64") { file: FileSystemFile ->
|
|
133
|
+
file.base64()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
Function("base64Sync") { file: FileSystemFile ->
|
|
129
137
|
file.base64()
|
|
130
138
|
}
|
|
131
139
|
|
|
132
|
-
|
|
140
|
+
AsyncFunction("bytes") { file: FileSystemFile ->
|
|
141
|
+
file.bytes()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
Function("bytesSync") { file: FileSystemFile ->
|
|
133
145
|
file.bytes()
|
|
134
146
|
}
|
|
135
147
|
|
|
@@ -210,8 +222,12 @@ class FileSystemNextModule : Module() {
|
|
|
210
222
|
}
|
|
211
223
|
|
|
212
224
|
Class(FileSystemDirectory::class) {
|
|
213
|
-
Constructor { uri:
|
|
214
|
-
FileSystemDirectory(
|
|
225
|
+
Constructor { uri: Uri ->
|
|
226
|
+
FileSystemDirectory(uri)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
Function("info") { directory: FileSystemDirectory ->
|
|
230
|
+
directory.info()
|
|
215
231
|
}
|
|
216
232
|
|
|
217
233
|
Function("delete") { directory: FileSystemDirectory ->
|
|
@@ -222,6 +238,14 @@ class FileSystemNextModule : Module() {
|
|
|
222
238
|
directory.create(options ?: CreateOptions())
|
|
223
239
|
}
|
|
224
240
|
|
|
241
|
+
Function("createDirectory") { file: FileSystemDirectory, name: String ->
|
|
242
|
+
return@Function file.createDirectory(name)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
Function("createFile") { file: FileSystemDirectory, name: String, mimeType: String? ->
|
|
246
|
+
return@Function file.createFile(mimeType, name)
|
|
247
|
+
}
|
|
248
|
+
|
|
225
249
|
Property("exists") { directory: FileSystemDirectory ->
|
|
226
250
|
directory.exists
|
|
227
251
|
}
|
|
@@ -28,3 +28,13 @@ data class PathInfo(
|
|
|
28
28
|
@Field var exists: Boolean,
|
|
29
29
|
@Field var isDirectory: Boolean?
|
|
30
30
|
) : Record
|
|
31
|
+
|
|
32
|
+
data class DirectoryInfo(
|
|
33
|
+
@Field var exists: Boolean,
|
|
34
|
+
@Field var uri: String?,
|
|
35
|
+
@Field var files: List<String>? = null,
|
|
36
|
+
@Field var md5: String? = null,
|
|
37
|
+
@Field var size: Long? = null,
|
|
38
|
+
@Field var modificationTime: Long? = null,
|
|
39
|
+
@Field var creationTime: Long? = null
|
|
40
|
+
) : Record
|
|
@@ -1,35 +1,65 @@
|
|
|
1
1
|
package expo.modules.filesystem.next
|
|
2
2
|
|
|
3
|
+
import android.net.Uri
|
|
3
4
|
import android.os.Build
|
|
5
|
+
import androidx.core.net.toUri
|
|
6
|
+
import expo.modules.filesystem.next.unifiedfile.JavaFile
|
|
7
|
+
import expo.modules.filesystem.next.unifiedfile.SAFDocumentFile
|
|
8
|
+
import expo.modules.filesystem.next.unifiedfile.UnifiedFileInterface
|
|
4
9
|
import expo.modules.interfaces.filesystem.Permission
|
|
5
10
|
import expo.modules.kotlin.sharedobjects.SharedObject
|
|
6
11
|
import java.io.File
|
|
7
12
|
import java.util.EnumSet
|
|
8
13
|
import kotlin.io.path.moveTo
|
|
9
14
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
abstract class FileSystemPath(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
val Uri.isContentUri get(): Boolean {
|
|
16
|
+
return scheme == "content"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
abstract class FileSystemPath(var uri: Uri) : SharedObject() {
|
|
20
|
+
private var fileAdapter: UnifiedFileInterface? = null
|
|
21
|
+
val file: UnifiedFileInterface get() {
|
|
22
|
+
val currentAdapter = fileAdapter
|
|
23
|
+
if (currentAdapter?.uri == uri) {
|
|
24
|
+
return currentAdapter
|
|
25
|
+
}
|
|
26
|
+
val newAdapter = if (uri.isContentUri) {
|
|
27
|
+
SAFDocumentFile(appContext?.reactContext ?: throw Exception("No context"), uri)
|
|
28
|
+
} else {
|
|
29
|
+
JavaFile(uri)
|
|
30
|
+
}
|
|
31
|
+
fileAdapter = newAdapter
|
|
32
|
+
return newAdapter
|
|
33
|
+
}
|
|
34
|
+
val javaFile: File get() =
|
|
35
|
+
if (uri.isContentUri) {
|
|
36
|
+
throw Exception("This method cannot be used with content URIs: $uri")
|
|
37
|
+
} else {
|
|
38
|
+
(file as File)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fun delete() {
|
|
42
|
+
if (!file.exists()) {
|
|
43
|
+
throw UnableToDeleteException("uri '${file.uri}' does not exist")
|
|
18
44
|
}
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
if (child.isDirectory) {
|
|
45
|
+
if (file.isDirectory()) {
|
|
46
|
+
file.listFilesAsUnified().forEach { child ->
|
|
47
|
+
if (child.isDirectory()) {
|
|
22
48
|
// Recursively delete subdirectories
|
|
23
|
-
|
|
49
|
+
if (uri.isContentUri) {
|
|
50
|
+
SAFDocumentFile(appContext?.reactContext ?: throw Exception("No context"), child.uri).delete()
|
|
51
|
+
} else {
|
|
52
|
+
JavaFile(child.uri).delete()
|
|
53
|
+
}
|
|
24
54
|
} else {
|
|
25
55
|
if (!child.delete()) {
|
|
26
|
-
throw UnableToDeleteException("failed to delete '${child.
|
|
56
|
+
throw UnableToDeleteException("failed to delete '${child.uri}'")
|
|
27
57
|
}
|
|
28
58
|
}
|
|
29
59
|
}
|
|
30
60
|
}
|
|
31
|
-
if (!
|
|
32
|
-
throw UnableToDeleteException("failed to delete '${
|
|
61
|
+
if (!file.delete()) {
|
|
62
|
+
throw UnableToDeleteException("failed to delete '${file.uri}'")
|
|
33
63
|
}
|
|
34
64
|
}
|
|
35
65
|
|
|
@@ -41,26 +71,26 @@ abstract class FileSystemPath(public var file: File) : SharedObject() {
|
|
|
41
71
|
if (!destination.exists) {
|
|
42
72
|
throw DestinationDoesNotExistException()
|
|
43
73
|
}
|
|
44
|
-
return File(destination.
|
|
74
|
+
return File(destination.javaFile, javaFile.name)
|
|
45
75
|
}
|
|
46
76
|
// this if FileSystemDirectory
|
|
47
77
|
// we match unix behavior https://askubuntu.com/a/763915
|
|
48
78
|
if (destination.exists) {
|
|
49
|
-
return File(destination.
|
|
79
|
+
return File(destination.javaFile, javaFile.name)
|
|
50
80
|
}
|
|
51
|
-
if (destination.
|
|
81
|
+
if (destination.javaFile.parentFile?.exists() != true) {
|
|
52
82
|
throw DestinationDoesNotExistException()
|
|
53
83
|
}
|
|
54
|
-
return destination.
|
|
84
|
+
return destination.javaFile
|
|
55
85
|
}
|
|
56
86
|
// destination is FileSystemFile
|
|
57
87
|
if (this !is FileSystemFile) {
|
|
58
88
|
throw CopyOrMoveDirectoryToFileException()
|
|
59
89
|
}
|
|
60
|
-
if (destination.
|
|
90
|
+
if (destination.javaFile.parentFile?.exists() != true) {
|
|
61
91
|
throw DestinationDoesNotExistException()
|
|
62
92
|
}
|
|
63
|
-
return destination.
|
|
93
|
+
return destination.javaFile
|
|
64
94
|
}
|
|
65
95
|
|
|
66
96
|
fun validatePermission(permission: Permission) {
|
|
@@ -70,7 +100,11 @@ abstract class FileSystemPath(public var file: File) : SharedObject() {
|
|
|
70
100
|
}
|
|
71
101
|
|
|
72
102
|
fun checkPermission(permission: Permission): Boolean {
|
|
73
|
-
|
|
103
|
+
if (uri.isContentUri) {
|
|
104
|
+
// TODO: Consider adding a check for content URIs (not in legacy FS)
|
|
105
|
+
return true
|
|
106
|
+
}
|
|
107
|
+
val permissions = appContext?.filePermission?.getPathPermissions(appContext?.reactContext, javaFile.path) ?: EnumSet.noneOf(Permission::class.java)
|
|
74
108
|
return permissions.contains(permission)
|
|
75
109
|
}
|
|
76
110
|
|
|
@@ -86,7 +120,7 @@ abstract class FileSystemPath(public var file: File) : SharedObject() {
|
|
|
86
120
|
validatePermission(Permission.READ)
|
|
87
121
|
to.validatePermission(Permission.WRITE)
|
|
88
122
|
|
|
89
|
-
|
|
123
|
+
javaFile.copyRecursively(getMoveOrCopyPath(to))
|
|
90
124
|
}
|
|
91
125
|
|
|
92
126
|
fun move(to: FileSystemPath) {
|
|
@@ -97,12 +131,21 @@ abstract class FileSystemPath(public var file: File) : SharedObject() {
|
|
|
97
131
|
|
|
98
132
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
99
133
|
val destination = getMoveOrCopyPath(to)
|
|
100
|
-
|
|
101
|
-
|
|
134
|
+
javaFile.toPath().moveTo(destination.toPath())
|
|
135
|
+
uri = destination.toUri()
|
|
102
136
|
} else {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
137
|
+
javaFile.copyTo(getMoveOrCopyPath(to))
|
|
138
|
+
javaFile.delete()
|
|
139
|
+
uri = getMoveOrCopyPath(to).toUri()
|
|
106
140
|
}
|
|
107
141
|
}
|
|
142
|
+
|
|
143
|
+
val modificationTime: Long? get() {
|
|
144
|
+
validateType()
|
|
145
|
+
return file.lastModified()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
val creationTime: Long? get() {
|
|
149
|
+
return file.creationTime
|
|
150
|
+
}
|
|
108
151
|
}
|